@softr/cli 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +206 -2
  2. package/dist/cli.mjs +16622 -0
  3. package/package.json +49 -4
package/README.md CHANGED
@@ -1,3 +1,207 @@
1
- # Temporary Holding Version
1
+ # @softr/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `softr` builds, tests and publishes [Softr Workflows](https://docs.softr.io/workflows/workflows) from
4
+ files on disk. It exists so that a coding agent (or a developer) can treat a workflow like a small project: the
5
+ workflow definition is a `softr-workflows.jsonc` file, every **Run custom code** node is a real `.js` or `.py`
6
+ file next to it, and the CLI moves both to and from the Softr workflows service. Nothing runs locally; every test
7
+ and every execution happens in the workflows service.
8
+
9
+ Status: proof of concept. Versions below 1.0 may change between releases; a single-binary build is planned.
10
+
11
+ ## Install and run
12
+
13
+ ```bash
14
+ npm install -g @softr/cli # then: softr --help
15
+ npx @softr/cli --help # without installing
16
+ npm run dev -- --help # from a checkout
17
+ ```
18
+
19
+ Node 22 or newer. `npm update -g @softr/cli` updates a global install; `npx` fetches the latest release on
20
+ its own.
21
+
22
+ ## Agent skill
23
+
24
+ The agent's guide to this tool is [`skills/softr-workflows/SKILL.md`](skills/softr-workflows/SKILL.md), in the
25
+ [Agent Skills](https://agentskills.io) format, with the code-file rules in
26
+ [`references/custom-code.md`](skills/softr-workflows/references/custom-code.md). It says what the CLI is for, when not
27
+ to use it, what it needs, how to install and update it, and how to work with it, so adding the skill is the only step
28
+ a user takes.
29
+
30
+ Users install it from the public skills repository, `softr-io/softr-io-skills`: as a plugin in Claude Code, which
31
+ keeps it updated, or with `npx skills add softr-io/softr-io-skills --skill softr-workflows` in every other agent. The
32
+ exact steps are on [docs.softr.io/skills/workflows-cli](https://docs.softr.io/skills/workflows-cli).
33
+
34
+ The skill is edited here and published from here: a merge to `master` that changes `skills/` runs
35
+ `.github/workflows/sync-skills.yml`, a call of the shared `sync-skills` workflow in `softr-io/github-workflows`, which
36
+ asks `softr-io/softr-io-skills` to open a sync pull request with the new copy (its `docs/sync-skills.md` is the
37
+ guide). Never edit the copy in the skills repository. `npm run check:skill` validates the format; CI runs it too.
38
+
39
+ ## Log in
40
+
41
+ Two ways, both checked with the studio before the token is stored in `~/.softr/credentials.json` (mode 0600, keyed by
42
+ the API host). In a terminal, `login` asks which one; the browser is the recommended default:
43
+
44
+ ```bash
45
+ softr login # asks: 1. browser (recommended, OAuth + PKCE) or 2. paste a personal API token
46
+ softr login --no-browser # browser login, but prints the URL to open elsewhere; the CLI waits for the answer
47
+ softr login --token pat_... # a personal API token created in the studio (API tokens), no questions asked
48
+ softr whoami # who the stored token belongs to and what it grants
49
+ softr logout
50
+ ```
51
+
52
+ The browser login grants the scopes registered for the CLI in the studio, on every workspace you can reach; the consent
53
+ page lists them. The access token renews itself, and a login is needed again after 30 days without use. A personal API
54
+ token needs a `workflows:*` action in its statements; `login` warns when it has none. `SOFTR_WORKFLOWS_TOKEN` overrides the
55
+ stored token for scripts and CI. The studio session cookie is not accepted.
56
+
57
+ The CLI targets Softr production. For development, `SOFTR_WORKFLOWS_API_URL`, `SOFTR_WORKFLOWS_STUDIO_API_URL` and
58
+ `SOFTR_WORKFLOWS_STUDIO_URL` point it at another workflows service, studio API and studio, for example locally running
59
+ ones. A workflow directory remembers the target it was pulled from.
60
+
61
+ ## Project layout
62
+
63
+ One directory holds exactly one workflow. A parent directory may group several.
64
+
65
+ ```
66
+ my-project/
67
+ enrich-leads/
68
+ softr-workflows.jsonc # the workflow (comments allowed)
69
+ actions/
70
+ enrich-record.js # body of the CUSTOM_CODE node "enrich-record"
71
+ score.py
72
+ .softr-workflows/ # CLI state: remote version/draft, cached sample outputs (gitignore it)
73
+ weekly-digest/
74
+ softr-workflows.jsonc
75
+ ```
76
+
77
+ ### `softr-workflows.jsonc`
78
+
79
+ The file has the shape of the workflows API write model (`title`, `workspaceId`, `triggers`, `actions`, `paths`,
80
+ `configuration`) plus the server `id`. Two CLI conventions apply:
81
+
82
+ - **File references.** Any string value of the form `"@@/relative/path"` is replaced by that file's content on
83
+ `push`. The path is relative to the config file. `pull` uses it for every `CUSTOM_CODE` node's `inputs.code`.
84
+ - **Readable node ids.** Ids such as `enrich-record` are fine; the service only requires uniqueness and no `:::`.
85
+ Placeholders then read `{outputs.enrich-record:::$.body}`.
86
+ - **Schema.** `$schema` points at `.softr-workflows/schema.json`, generated from the same catalog that drives `add`
87
+ and `spec`: known node types get their inputs, enums and the path condition grammar; node types outside the
88
+ catalog are accepted with free inputs, and drafts may hold nulls. The service remains the authority at publish.
89
+
90
+ Validation stays in the service: `push` saves a draft, `publish` (planned) runs the full validation.
91
+
92
+ ### Code files
93
+
94
+ The service stores the _body_ of a function and runs it with a single variable, `inputData`, holding the node's
95
+ "Data to use in the code" map (`inputs.inputData` in the config). Locally the body lives inside a real function so
96
+ that editors, linters and agents see valid code:
97
+
98
+ ```js
99
+ export default function (inputData) {
100
+ return inputData.email.endsWith('@softr.io');
101
+ }
102
+ ```
103
+
104
+ ```python
105
+ def main(inputData):
106
+ return {"ok": inputData["email"].endswith("@softr.io")}
107
+ ```
108
+
109
+ `pull` writes a generated header above the function: the rules of the runtime and one typed line per `inputData`
110
+ entry. The types come from the saved test outputs of the referenced nodes (trusted), then from their declared output
111
+ metadata (marked as not seen in test data), and are `unknown` with a hint when the upstream node was never tested.
112
+ JavaScript gets a JSDoc `@typedef` with `@property` lines, so editors type-check `inputData`; Python gets the same as
113
+ comments. The header is regenerated on every `pull` and is never uploaded.
114
+
115
+ `push` uploads only that function body. Anything outside it (imports, helpers, constants) is rejected with a
116
+ message. On CUSTOM_CODE 1.2.0 both languages call HTTP APIs with `fetch` and the JavaScript function is `async`; a
117
+ node still on 1.0.0 or 1.1.0 keeps its synchronous runtime without `fetch`, and its header says so until you set
118
+ `"version": "1.2.0"` on it. JavaScript has no imports; Python imports from an allowlist of standard modules inside
119
+ `main`. The return value becomes the node output at `$.body`. Never write workflow placeholders such as
120
+ `{outputs.x:::$.y}` inside code; map them in `inputData` instead. See the
121
+ [Run Custom Code](https://docs.softr.io/workflows/actions/run-custom-code) docs.
122
+
123
+ ## Commands
124
+
125
+ | Command | What it does |
126
+ | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
+ | `login [--token <pat_...>] [--no-browser]` / `whoami` / `logout` | Log in through the browser (OAuth with PKCE), or store a personal API token after confirming it with the studio; show the stored identity and its grants; forget the token. |
128
+ | `list [--workspace <id>]` | List the workflows you can access, one table per workspace. A workspace whose listing fails is reported at the end and skipped. |
129
+ | `integrations [--workspace <id>] [--type <TYPE>]` | List the workspace's connected integrations (id, name, type, account, auth kind) from the studio API; the id goes into a node's `integrationId` input. Inside a workflow directory the workspace is the workflow's; `--type` filters. Credentials are never shown. |
130
+ | `pull [<workflowId>] [--dir <path>] [--force]` | Download a workflow into a directory (new `./<title-slug>/` by default). Without an id, list what you can pull. |
131
+ | `push [--dir <path>] [--dry-run] [--force]` | Inline `@@` references and code files, save as a new draft. Sends nothing when the files match the last sync, and refuses when the server copy changed since then (`--force` overrides both). Warns about placeholders that no saved test data backs. |
132
+ | `init <title> [--workspace <id>] [--trigger <TYPE>] [--dir <path>]` | Create a workflow on the server with one trigger (default `WEBHOOK`) and the local directory for it. |
133
+ | `add <TYPE> [--id <id>] [--after <nodeId>] [--in-loop <loopId>] [--to <actionId>] [--lang JAVASCRIPT\|PYTHON] [--title <t>]` | Append an action after a node (default: the only node without a successor) and the path to it, or append a trigger (`--to` connects it). Custom code gets `actions/<id>.js\|py` with an empty function. Comments in the config survive. |
134
+ | `replace <nodeId> <TYPE>` | Give a node another type of the same kind: id, title and paths stay, inputs start from the new template. |
135
+ | `remove <nodeId>` | Remove a node and the paths to and from it; a loop goes with its body. Code files stay on disk. |
136
+ | `spec [<TYPE>]` | The node types this CLI offers; with a type: inputs, output, notes, an example node and the docs URL. |
137
+ | `schema [--write]` | Print the JSON schema of `softr-workflows.jsonc`, generated from the node catalog, or write it into the workflow directory. `pull` and `init` write it and set `$schema` in the config, so editors validate and complete the file. |
138
+ | `status` | Local edits vs the last sync, the server's version and draft, the live version. |
139
+ | `publish [--force]` / `unpublish` | Enable the latest version (the service validates the whole workflow) or disable the workflow. `publish` fetches the server copy first and refuses unpushed local edits and a server copy that changed since the last sync; then it lists nodes that are untested, failed, tested before the last save, or tested against outdated upstream data: a terminal asks for confirmation, a script or agent needs `--force`. |
140
+ | `test <nodeId> [--mock] [--iteration <n>] [--sample <n>]` | Run one node in the workflows service and save its sample output. Triggers propose samples; `--sample` picks one (default the first). |
141
+ | `test --all` | Run the trigger and then every action in execution order, predecessors first across parallel paths and loop bodies. Branches, filters and loops are skipped. Failures are reported at the end; the exit code is 1 when any test failed. |
142
+ | `outputs` | One line per node: tested, failed, untested, with the field count. |
143
+ | `outputs <nodeId>` | The node's referenceable variables with types and examples, ready to paste. |
144
+ | `outputs --refresh` | Fetch the saved samples from the service and regenerate the code headers. |
145
+ | `verify [--test-missing]` | One readiness report: code files parse, local files match the server, the service accepts the definition for publishing, every node tested on current data, every placeholder backed by test data. `--test-missing` first runs the tests that are missing, in execution order. Exit code 1 while something needs attention. |
146
+ | `placeholder <nodeId> [keys...]` | Build the token for a node's output field from keys (`*` for every element, digits for an index, or one dotted path), checked against the saved test data: type and example when it exists, the keys that do exist when it does not. A loop id gives the current item. |
147
+
148
+ Every command accepts `--json`. After a `pull`, the target is remembered in `.softr-workflows/state.json`.
149
+
150
+ Every `test` and `outputs --refresh` updates `.softr-workflows/outputs.json` and rewrites the code headers, so the
151
+ types of `inputData` follow the latest test data. Tests run against the server's copy of the workflow. `test` compares
152
+ the local files with that copy and names the nodes whose edits are not pushed yet, so a green result is never mistaken
153
+ for a verdict on local code.
154
+
155
+ `verify`, `status` and the publish guard read the service's readiness report
156
+ (`GET /v1/workflows/{id}/readiness`): the publish validation as a dry run with every message, and per node the test
157
+ state, staleness against the last save, outdated references and placeholders no saved sample backs. On a deployment
158
+ without that endpoint the CLI runs the same node checks itself and says so; the validation dry run is then missing.
159
+
160
+ `.softr-workflows/state.json` keeps the server version and draft of the last sync and a content hash of what was
161
+ synced. That is how `push` knows there is nothing to send, how it notices that the server copy moved on, and how
162
+ `test` tells edited from unedited without any server-side support.
163
+
164
+ ### The node types on offer
165
+
166
+ `spec` lists them. The set is small on purpose: the generic triggers (webhook, schedules, Softr Tables and Softr Apps
167
+ events, incoming email), the control nodes (branch, filter, loop, wait), `RESPONDED_TO_WEBHOOK`, `CALL_API`, and
168
+ `CUSTOM_CODE` for everything else. Other integrations stay in the studio. Each entry points to its public docs page.
169
+
170
+ ### A typical session
171
+
172
+ ```bash
173
+ softr init "Lead scoring" # server + ./lead-scoring/
174
+ cd lead-scoring
175
+ softr add CUSTOM_CODE --id score --lang python # actions/score.py + node + path
176
+ # edit actions/score.py, set inputs.inputData in softr-workflows.jsonc
177
+ softr push
178
+ softr test --all # trigger first (send it one request), then score
179
+ softr outputs score # variables the next node can use
180
+ softr publish
181
+ ```
182
+
183
+ ## Development
184
+
185
+ ```bash
186
+ npm ci
187
+ npm run check # typecheck, prettier, tests, build
188
+ node dist/cli.mjs --help
189
+ ```
190
+
191
+ The build is one self-contained ESM file (`dist/cli.mjs`), which keeps a later `bun build --compile` possible: no
192
+ native addons, no runtime reads relative to `__dirname`.
193
+
194
+ CI (`.github/workflows/ci.yml`) runs `npm run check` on every pull request and every push to `master`. A pull request
195
+ gets an AI review on request: add the `ai-review` label or comment `/review` (`.github/workflows/ai-review.yml`;
196
+ [`AGENTS.md`](AGENTS.md) is what the reviewer reads first).
197
+
198
+ ### Releasing
199
+
200
+ A release is a version bump merged to `master`; [docs/releasing.md](docs/releasing.md) has the mechanics.
201
+
202
+ ## Further reading
203
+
204
+ - Triggers: <https://docs.softr.io/workflows/trigger-types>
205
+ - Advanced concepts (Branch, Loop, Run Custom Code, Call API): <https://docs.softr.io/workflows/advanced-concepts>
206
+ - For agents: the docs index <https://docs.softr.io/llms.txt>; every page is also served as Markdown by appending `.md`
207
+ to its URL, which is what `spec` and the skill link to.