Metadata-Version: 2.4
Name: salpa-cli
Version: 0.3.3
Summary: Salpa node authoring CLI — scaffold, validate, and push node packages into your running Salpa app.
Project-URL: Homepage, https://bocores.com/salpa-cli
Project-URL: Documentation, https://salpa.app/docs/custom-nodes
Author: Salpa
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: cli,computational-science,node-authoring,salpa,scaffold,workflow
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Code Generators
Requires-Python: >=3.9
Requires-Dist: rich>=13
Requires-Dist: tomli>=2.0; python_version < '3.11'
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# salpa-cli

The Salpa node authoring CLI. Scaffolds node packages that match the shipped
template contract — the deterministic path to a new node — checks them against
that contract, and puts them into the Salpa app on your machine.

```bash
python3 -m pip install salpa-cli
salpa new my-analyzer          # interactive
salpa new my-suite -t multi-node-package --yes
salpa validate my_suite        # will it install and register?
salpa push my_suite            # into your running Salpa app
salpa unpush my_suite          # and back out again
```

Installing adds the `salpa` command. If your shell can't find `salpa` afterward,
your Python scripts directory isn't on your `PATH` —
installing into a virtual environment is the simplest fix (it puts `salpa` on `PATH`).

## Requirements

**Python 3.9 or newer.** `pip install salpa-cli` reports a clear error below that.

- **macOS** — the system `python3` is 3.9, which is enough. For a newer one:
  `brew install python`, or [python.org](https://www.python.org/downloads/).
- **Linux** — most distributions ship 3.9+. Otherwise use your package manager
  (`apt install python3`, `dnf install python3`, …) or [pyenv](https://github.com/pyenv/pyenv).
- **Windows** — often has no `python3` at all; install from
  [python.org](https://www.python.org/downloads/) or the Microsoft Store, and tick
  "Add python.exe to PATH".

**pixi** — needed only for `pixi install` / `pixi run test` (building a node's
environment and running its tests). You do **not** have to install it separately if
you run Salpa: the app keeps a pixi under `~/.bocoflow`, and `salpa new` finds it
and prints its path. To have one on your own `PATH`, install it from
[pixi.sh](https://pixi.sh). Stdlib-only nodes need no pixi at all.

`salpa new` creates `./<under_scored_name>/` from a bundled template, with every
placeholder substituted and the directory underscored (the dir name becomes the
Python package name, so a hyphen would break `from .core import ...`; meta.toml's
`name` stays kebab-case).

## Templates

Templates ship inside the wheel and are the single source of truth for the node
contract, so there is no second copy to drift.

| Template | For |
|----------|-----|
| `individual-node` | one node with its own pixi env (default) |
| `multi-node-package` | several related nodes sharing one pixi env, chained via `predecessor_data` |

## Options

```
salpa new [NAME]
  -t, --template     individual-node | multi-node-package
  -d, --description  one-line description
      --author       author name
      --category     UI category (e.g. "Cheminformatics")
  -o, --output       directory to create the package in (default: .)
  -y, --yes          non-interactive: accept defaults, no prompts
      --install      run `pixi install` in the new package afterwards
```

## Validating

`salpa validate [PATH]` checks a package against the contract the app enforces at
install and registration time. Every check maps to a real failure — a node missing
from `[package.nodes]`, a shared-environment name that drifted between three files,
a stray per-node `pixi.toml` that silently opts a node out of the shared env. It is
not a linter: style is not checked.

```
salpa validate [PATH]           # default: the current directory
      --strict                  # warnings become errors (CI)
      --ignore CODE             # suppress one check by code, repeatable
      --json                    # machine-readable report
      --no-import               # skip the import checks
      --python PATH             # override the auto-detected import-check interpreter
```

Exit codes: `0` clean · `1` at least one error · `2` the path is not a package.

Findings are `ERROR` (will not install or will not register) or `WARN` (installs,
but is probably not what you meant). Each carries a stable code (`E3`, `S2`, …) so
it can be quoted, grepped, and suppressed.

**Import checks.** `I1`-`I3` import your `node.py` in a subprocess to confirm the
app can read its `OPTIONS`. They need the authoring SDK (`bocoflow_core`), and the
interpreter that has it is found for you — your package already declares the SDK in
its own `pixi.toml`, so its solved `test` environment is the right one to judge the
package against:

```bash
pixi install -e test    # solve the test env — the one your pixi.toml puts the SDK in
salpa validate          # the import checks now run, no flags
```

The search order, first hit wins: `.pixi/envs/test` → `.pixi/envs/default` → the
interpreter running `salpa`. A candidate that cannot import `bocoflow_core` is
passed over, and the report names the one that answered. `pip install
bocoflow-core-sdk` beside `salpa` works too, and `--python PATH` overrides the
search entirely (a `--python` that lacks the SDK skips rather than falling back —
naming an interpreter is an instruction, not a hint).

With no interpreter at all, the checks are **skipped**, and both the status line and
the summary say so — `No findings (import checks skipped)` is a smaller claim than
`No findings. This package should install and register.`, and reads as one.

**Suppressing a check.** Some warnings describe a shape that is occasionally
correct — `E3` (a per-node `pixi.toml`) is a deliberate opt-out when a node's
dependencies conflict with its package's shared environment. `--ignore` lets such a
package pass `--strict` without silencing everything:

```bash
salpa validate --strict --ignore E3
```

Suppressed findings are still reported (and appear under `suppressed` in `--json`)
— they are hidden from the exit code, not from you.

`salpa new` runs a validation pass over its own output and prints a one-line
summary. That pass covers structure, naming and environment only; the import
checks are left to `salpa validate`.

## Pushing it into Salpa

`salpa push [PATH]` makes a package appear in your running Salpa app's
**Marketplace ▸ Browse**, ready to install.

**It is not publishing.** It targets the app on *your* machine; nothing is uploaded
anywhere, and there is no public hub involved.

```
salpa push [PATH]
      --copy           copy into the app instead of linking your directory
      --source-id      id for this source in the app
      --source-name    display name for this source
      --port           port the app is listening on (default 18000, or $SALPA_PORT)
      --force          push despite `salpa validate` errors; with --copy, also
                       replace a package copy that this push did not create
  -y, --yes            accept the layout change without asking
```

Exit codes: `0` ok · `1` refused or failed · `2` the path is not a package.

`push` validates, arranges your package into the layout a package source uses
(`registry.json` + `packages/<pkg>/`, asking before it moves anything), generates
the catalog file, and asks the app to sync it and put the package on the shelf.

**The install itself stays in the app.** `push` stops at the shelf — installing is
user-consented in the UI, and runs the same installer every other package goes
through. There is no second installer in this CLI, and no assumption anywhere about
where the app keeps its files: the app is asked, over the API its own UI uses.
Salpa must therefore be running; if it isn't, `push` says so and stops.

By default your directory *is* the source — nothing is copied, so re-running after
an edit refreshes what the app offers:

```bash
salpa push packages/my_suite      # edit, push, repeat
```

`--copy` copies the package into the app instead. Self-contained and survives
moving your working copy, at the cost of needing another `--copy` push per edit.
It lands in the directory the app keeps its own packages in, so it will replace an
earlier copy of yours but refuses to overwrite anything else — your `[package].name`
decides the directory name, and a collision there is someone else's package.

## Taking it back out

`salpa unpush [PATH]` is the reverse: it removes the package from the app's shelf
and deregisters the source, so the app stops offering it.

```
salpa unpush [PATH]
      --source-id      id of the source to remove (default: whichever the push created)
      --port           port the app is listening on (default 18000, or $SALPA_PORT)
      --force          remove it even though the package is installed
  -y, --yes            remove without asking
```

Exit codes: `0` ok (including nothing to remove) · `1` refused or failed · `2` the
path is not a package.

**Your own directory is never touched** — the `packages/` layout and `registry.json`
that `push` wrote stay put, and `salpa push` registers them again whenever you want
the package back. You do not have to remember whether you used `--copy`: `unpush`
reads which mode was used off what is actually in the app, and cleans up the copy
in the app's catalog when there is one.

Unpushing something that was never pushed reports `Nothing to remove` and exits `0`,
so it is safe to run twice.

`unpush` refuses while the package is **installed**, and points you at
Marketplace ▸ Installed to uninstall it first — removing the source out from under
an installed copy would leave it pointing at a source that no longer exists.
`--force` removes it anyway. As with `push` stopping at the shelf, `unpush` never
uninstalls anything itself; that stays in the app.

Full walkthrough: `salpa docs publishing-to-your-app`.

## Authoring guide

`salpa docs` prints the bundled node-authoring guide. Full documentation:
https://salpa.app/docs/custom-nodes

## Reserved

`salpa publish` / `install` / `run` are reserved for later and not implemented
yet. The current surface is `salpa new`, `salpa validate`, `salpa push`,
`salpa unpush` and `salpa docs`.
