@visualautomate/cli 0.6.1 → 0.7.0

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 +292 -243
  2. package/dist/index.js +2089 -427
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,247 +1,296 @@
1
- # @visualautomate/cli
2
-
3
- Build and test VisualAutomate plugins.
4
-
5
- ```bash
6
- npm install -g @visualautomate/cli
7
- vsa init my-step
8
- cd my-step
9
- vsa dev # the sandbox, and a run again on every save
10
- ```
11
-
12
- Nothing here signs in. Your plugin reaches the platform through its GitHub
13
- repository: push a commit, the tests run there, and Publish on the portal takes
14
- it from the commit that passed.
15
-
16
- `va` and `visualautomate` are the same command.
17
-
18
- That is the whole toolchain. There is nothing else to install: the CLI carries
19
- the SDK inside it, compiles your TypeScript itself, and writes the type
20
- declarations into the project. A plugin directory has no `node_modules`, no
21
- build step and no dependencies.
22
-
23
- ## What `init` gives you
24
-
25
- ```
26
- plugin.ts your code
27
- manifest.json what the canvas shows, with the rest of the options
28
- written out as commented examples
29
- plugin.json name, version, description, category
30
- test.json the input, config, storage and state `test` runs with
31
- visualautomate.d.ts the types, written in rather than installed
32
- tsconfig.json so your editor type-checks it
33
- ```
34
-
35
- Templates: `blank`, `http-request`, `connection`, `file`. Pass one with
36
- `--template`. Each runs unmodified.
37
-
38
- ## Types in your editor
39
-
40
- Your plugin's code imports nothing — the platform refuses the word `import`
41
- anywhere in the file, comments included, and the SDK is not a package a step may
42
- require. So the types are written into your project instead, and one line names
43
- them:
44
-
45
- ```js
46
- /** @type {PluginModule} */
47
- module.exports = {
48
- execute: async (input, config, context) => {
49
- // ^ completes, with the documentation
50
- },
51
- }
52
- ```
53
-
54
- `dev` and `test` write `visualautomate.d.ts` and a `jsconfig.json` beside your
55
- code and keep them current; `vsa types` does it on its own. In an app's
56
- repository they go at the root, where they cover every module. Nothing is
57
- installed, nothing of it exists while your plugin runs, and a file you wrote
58
- yourself is never written over — delete ours and it comes back.
59
-
60
- ## `add:property`
61
-
62
- A field on the node — what somebody fills in when they drop your module into a
63
- workflow — written into `manifest.json`:
64
-
65
- ```bash
66
- vsa add:property string apiKey "The key to call with"
67
- vsa add:property number retries "How many times to try again" 2 --required
68
- vsa add:property select mode "Which way round" fast --options "Fast:fast,Thorough:thorough"
69
- ```
70
-
71
- `<type> <name> [description] [default]`, where the name is what your code reads:
72
- `config.apiKey`. The label is made from the name unless `--label` says otherwise,
73
- and the default is stored as its type has it, so `2` is a number and `false` is a
74
- boolean.
75
-
76
- Types: `string`, `number`, `boolean`, `select`, `multiselect`, `json`, `color`,
77
- `date`, `connection`, `file`, `path`, `paths`. A `select` or `multiselect` needs
78
- `--options`, and a `connection` needs `--provider <name>`.
79
-
1
+ # @visualautomate/cli
2
+
3
+ Build and test VisualAutomate plugins.
4
+
5
+ ```bash
6
+ npm install -g @visualautomate/cli
7
+ vsa init my-step
8
+ cd my-step
9
+ vsa dev # the sandbox, and a run again on every save
10
+ ```
11
+
12
+ Nothing here signs in. Your plugin reaches the platform through its GitHub
13
+ repository: push a commit, the tests run there, and Publish on the portal takes
14
+ it from the commit that passed.
15
+
16
+ `va` and `visualautomate` are the same command.
17
+
18
+ That is the whole toolchain. There is nothing else to install: the CLI carries
19
+ the SDK inside it, compiles your TypeScript itself, and writes the type
20
+ declarations into the project. A plugin directory has no `node_modules`, no
21
+ build step and no dependencies.
22
+
23
+ ## What `init` gives you
24
+
25
+ ```
26
+ plugin.ts your code
27
+ manifest.json what the canvas shows, with the rest of the options
28
+ written out as commented examples
29
+ plugin.json name, version, description, category
30
+ test.json the input, config, storage and state `test` runs with
31
+ visualautomate.d.ts the types, written in rather than installed
32
+ tsconfig.json so your editor type-checks it
33
+ AGENTS.md, CLAUDE.md the platform's rules, for an AI assistant working here
34
+ ```
35
+
36
+ Templates: `blank`, `http-request`, `connection`, `file`. Pass one with
37
+ `--template`. Each runs unmodified.
38
+
39
+ ## Types in your editor
40
+
41
+ Your plugin's code imports nothing — the platform refuses the word `import`
42
+ anywhere in the file, comments included, and the SDK is not a package a step may
43
+ require. So the types are written into your project instead, and one line names
44
+ them:
45
+
46
+ ```js
47
+ /** @type {PluginModule} */
48
+ module.exports = {
49
+ execute: async (input, config, context) => {
50
+ // ^ completes, with the documentation
51
+ },
52
+ }
53
+ ```
54
+
55
+ `dev` and `test` write `visualautomate.d.ts` and a `jsconfig.json` beside your
56
+ code and keep them current; `vsa types` does it on its own. In an app's
57
+ repository they go at the root, where they cover every module. Nothing is
58
+ installed, nothing of it exists while your plugin runs, and a file you wrote
59
+ yourself is never written over — delete ours and it comes back.
60
+
61
+ ## `skill`
62
+
63
+ The guide an AI assistant reads to build steps for you: describe a step in a sentence ("a step that
64
+ creates an invoice in Moneybird") and the assistant writes the manifest, the code and the test file,
65
+ runs `vsa test`, and tells you how to publish it.
66
+
67
+ ```bash
68
+ vsa skill # .claude/skills/visualautomate-steps in this project, plus AGENTS.md and CLAUDE.md
69
+ vsa skill --global # ~/.claude/skills/visualautomate-steps, for every project
70
+ vsa skill --dir out # the skill's folder anywhere, e.g. to zip and upload to Claude on the web
71
+ ```
72
+
73
+ Claude Code finds the skill on its own. `AGENTS.md` is read by Cursor, Copilot, Codex and others;
74
+ `CLAUDE.md` is one line that pulls it in for Claude Code. Both carry the platform's rules: no `import`,
75
+ only allowed packages, never a credential, `{ output, data }`. `vsa init` writes them into every new
76
+ project as well.
77
+
78
+ Running it again brings the files up to date with this CLI. An `AGENTS.md` or `CLAUDE.md` you wrote
79
+ yourself is never written over: ours starts with a marker line, and a file without it is left alone.
80
+
81
+ ## `add:property`
82
+
83
+ A field on the node — what somebody fills in when they drop your module into a
84
+ workflow — written into `manifest.json`:
85
+
86
+ ```bash
87
+ vsa add:property string apiKey "The key to call with"
88
+ vsa add:property number retries "How many times to try again" 2 --required
89
+ vsa add:property select mode "Which way round" fast --options "Fast:fast,Thorough:thorough"
90
+ ```
91
+
92
+ `<type> <name> [description] [default]`, where the name is what your code reads:
93
+ `config.apiKey`. The label is made from the name unless `--label` says otherwise,
94
+ and the default is stored as its type has it, so `2` is a number and `false` is a
95
+ boolean.
96
+
97
+ Types: `string`, `number`, `boolean`, `select`, `multiselect`, `json`, `code`,
98
+ `secret`, `color`, `date`, `connection`, `file`, `path`, `paths`. A `select` or `multiselect` needs
99
+ `--options`, and a `connection` needs `--provider <name>`.
100
+
80
101
  The same field is added to `visualautomate.test.json` under `config`, with its
81
102
  default or a value of the right shape, so the next `vsa test` passes something
82
103
  for it instead of nothing. A value you have already put there is kept.
83
104
 
84
- At the root of an app's repository, `--module <name>` says which one — the same
85
- short names `test` and `dev` take. The manifest is edited where it stands: your
86
- comments, the examples underneath and your own formatting stay as they are.
87
-
88
- ## `test`
89
-
90
- Runs your plugin the way the platform would, in three steps:
91
-
92
- 1. **The platform's code checks.** The same rules the upload applies: only
93
- allowed packages in `require`, no dynamic `require`, none of the refused
94
- patterns (`eval`, `process.env`, `import`, …). Code that fails here would be
95
- refused on push, and says why.
96
- 2. **The sandbox's `require`.** A step gets the allowed npm packages and nothing
97
- else — no `fs`, no `http`. Those packages are installed in the sandbox; here
98
- they come from your project, and a missing one prints the `npm install`
99
- command for the version the sandbox has.
100
- 3. **A context that behaves.** `storage` and `state` are real, in memory, and
101
- the result shows what the step left behind:
102
-
103
- ```
104
- ✓ passed success, 3ms
105
- output success (expected success)
106
- data { "path": "notes/hello-shouted.txt", "size": 17 }
107
- files notes/hello.txt, notes/hello-shouted.txt
108
- state {"runs":1}
109
- ```
110
-
111
- A run passes when it succeeds and, if the test file names an `expectOutput`,
112
- leaves by that port — the same verdict as the GitHub check.
113
-
114
- The verdict is green when it passed and red when it did not, in the sandbox as
115
- well as out here. `NO_COLOR` turns that off, and output that is not a terminal —
116
- a pipe, a file, a CI log — is plain anyway; `FORCE_COLOR` turns it back on.
117
-
118
- ### In a GitHub repository
119
-
120
- `test` works wherever the platform tests your code on a push:
121
-
122
- ```
123
- my-plugin/ a plugin's repository
124
- manifest.json
125
- index.js
126
- visualautomate.test.json
127
-
128
- my-app/ an app's repository
129
- modules/
130
- send-message/
131
- manifest.json
132
- index.js
133
- visualautomate.test.json
134
- list-channels/
135
- …
136
- ```
137
-
138
- At the root of an app's repository every module runs, one after another. Name one
139
- to run only that: `vsa test send-message`, or any short name that picks it out —
140
- `vsa test send`. `--module <name>` is the same thing. `index.js` in a repository is run as written,
141
- unbundled, because that is the file the sandbox loads.
142
-
143
- ## `dev`
144
-
145
- ```bash
146
- vsa dev # every module in modules/
147
- vsa dev send-message # one of them; the shortest name that picks it will do
148
- vsa dev --tier boosted # a bigger machine
149
- vsa dev --local # on this machine's Node, without the sandbox
150
- ```
151
-
152
- Everything in one command: it starts Docker if it is not running, fetches the
153
- sandbox image the first time, runs every module, and runs them again on every
154
- save. Only the module whose folder changed runs again; a change anywhere else
155
- runs them all. Ctrl+C stops it.
156
-
157
- `context.connections` and `context.media` are **not** simulated. One is
158
- somebody's OAuth account and the other is an ffmpeg container; pretending to be
159
- either would produce a plugin that passes here and fails on a canvas. Asking for
160
- one fails with a sentence saying so.
161
-
162
- `visualautomate.test.json` (or `test.json`, or `--input <file>`) seeds the run:
163
-
164
- ```json
165
- {
166
- "config": { "source": "notes/hello.txt" },
167
- "input": {},
168
- "storage": { "notes/hello.txt": "hello from a file" },
169
- "state": { "runs": 0 },
170
- "expectOutput": "success",
171
- "timeoutMs": 30000,
172
- "allowedDomains": ["api.example.com"]
173
- }
174
- ```
175
-
176
- The platform's push check reads `config`, `input`, `expectOutput` and
177
- `timeoutMs`; `storage`, `state`, `secrets` and `allowedDomains` are for running
178
- locally. A file that is not valid JSON stops the run rather than running with
179
- nothing.
180
-
181
- ## Where `fetch` may go
182
-
183
- On the platform a step may contact the domains its plugin declares, plus any
184
- host in the configuration filled in for that step. A declared domain covers its
185
- subdomains; localhost, private networks and metadata addresses are never
186
- allowed.
187
-
188
- Put the domains in `allowedDomains` and `test` applies the same rule: a request
189
- anywhere else fails. Without `allowedDomains` nothing is blocked, but every host
190
- the platform would refuse is listed after the run.
191
-
192
- ## `--persist`
193
-
194
- Keeps storage and state between runs, in `.visualautomate/` next to the plugin:
195
-
196
- ```
197
- .visualautomate/storage/<path> files the step wrote
198
- .visualautomate/state.json the workflow state
199
- ```
200
-
201
- Once that folder exists it is used without the flag. Delete it to start over,
202
- and add it to `.gitignore`.
203
-
204
- ## The sandbox on your machine
205
-
206
- `dev` uses it by default; `test` does with `--docker`:
207
-
208
- ```bash
209
- vsa dev
210
- vsa test --docker
211
- vsa dev --tier boosted
212
- ```
213
-
214
- Your plugin runs inside the plugin sandbox image,
215
- `ghcr.io/visualautomate/plugin-sandbox`, at the version of this CLI:
216
-
217
- - the Node version the platform runs plugins on, with every allowed npm
218
- package installed — no `npm install` needed;
219
- - the CPU and memory of the sandbox tier: `standard` (¼ vCPU, 1 GB, the
220
- default), `boosted` (½ vCPU, 4 GB), `high` (1 vCPU, 6 GB), `max` (2 vCPU, 8 GB);
221
- - no Linux capabilities, a read-only filesystem apart from your project and a
222
- small `/tmp`, and nothing else from your machine.
223
-
224
- A step that runs out of memory or time here does the same on the platform.
225
-
226
- Docker does not have to be running: `dev` starts Docker Desktop and waits for it,
227
- then fetches the image if this machine does not have it. `--image` uses a
228
- different image, for example one you built yourself. The watcher stays on your
229
- machine, and each run starts a fresh container.
230
-
231
- `context.media` is not available locally, in or out of Docker.
232
-
233
- ## Publishing
234
-
235
- Not from here. Connect the repository to your app on the portal, push your
236
- commit, and the platform runs the same checks `vsa test` runs against every
237
- module it touched. Publish on the portal takes the code from a commit that
238
- passed, and a version snapshot is saved so you can roll back.
239
-
240
- Comments in `manifest.json` are a convenience of this tool: the file on your
241
- disk may have them, and they are stripped before the manifest is read.
242
-
243
- ## Where things are
244
-
245
- The runtime API your plugin is written against is documented in the types
246
- (`visualautomate.d.ts` in your project, `@visualautomate/plugin-sdk` if you
247
- prefer to install it) and on the platform's own docs page.
105
+ At the root of an app's repository, `--module <name>` says which one — the same
106
+ short names `test` and `dev` take. The manifest is edited where it stands: your
107
+ comments, the examples underneath and your own formatting stay as they are.
108
+
109
+ ## `test`
110
+
111
+ The developer portal's check on a push, run here. In order:
112
+
113
+ 0. **The manifest.** The rules the portal is known to refuse, and warnings for
114
+ what it would drop: an `auth` that names a host, an `action` with both a
115
+ connection and a `baseUrl`, a request path that is a full URL, a trigger
116
+ without `items` or without `"initial": "now"`. A declared step (an `action`
117
+ block) or a trigger stops here, checked: the platform does not run those on a
118
+ push either. A declared step is published from the portal, not GitHub: New
119
+ module → Call an API, paste its manifest.json, Try it, publish.
120
+ 1. **The platform's code checks.** The same rules the upload applies: only
121
+ allowed packages in `require`, no dynamic `require`, none of the refused
122
+ patterns (`eval`, `process.env`, `import`, …). Code that fails here would be
123
+ refused on push, and says why.
124
+ 2. **The sandbox's `require`.** A step gets the allowed npm packages and nothing
125
+ else — no `fs`, no `http`. Those packages are installed in the sandbox; here
126
+ they come from your project, and a missing one prints the `npm install`
127
+ command for the version the sandbox has.
128
+ 3. **The runner's way of loading code.** In a `vm` context of its own, with the
129
+ platform's globals and no others: `fetch`, `btoa`, `TextEncoder`, `Blob`,
130
+ `FormData` and the rest, but no `Buffer`, `process`, `globalThis` or
131
+ `structuredClone`, and a `console` with `log`, `error`, `warn` and `info`. A
132
+ step that leans on something Node has and the platform does not fails here.
133
+ 4. **A context that behaves.** `storage` and `state` are real, in memory, with
134
+ the store's limits (a value at most 128 KB, `ttlSeconds` at least 60), and
135
+ the result shows what the step left behind:
136
+
137
+ ```
138
+ ✓ passed success, 3ms
139
+ output success (expected success, the default)
140
+ data { "path": "notes/hello-shouted.txt", "size": 17 }
141
+ files notes/hello.txt, notes/hello-shouted.txt
142
+ state {"runs":1}
143
+ ```
144
+
145
+ A run passes when it succeeds and leaves by the test file's `expectOutput`, or by
146
+ `success` when it names none, within manifest.json's `timeoutMs` (30 seconds when
147
+ it names none): the same verdict as the GitHub check.
148
+
149
+ **Publishing needs a run on success.** The portal publishes only code whose push
150
+ test came back on `success`. A test that expects `error` passes, and `vsa test`
151
+ says it does not count. For a step that acts as an account, name one of your
152
+ connected accounts in the test file's `config`, under the property's name: the
153
+ push check then acts as it. The module's page on the portal lists the ids.
154
+
155
+ `vsa test` runs on this machine's Node and says so when that is not the
156
+ platform's (Node 24); `vsa test --docker` and `vsa dev` run on the platform's.
157
+
158
+ The verdict is green when it passed and red when it did not, in the sandbox as
159
+ well as out here. `NO_COLOR` turns that off, and output that is not a terminal —
160
+ a pipe, a file, a CI log — is plain anyway; `FORCE_COLOR` turns it back on.
161
+
162
+ ### In a GitHub repository
163
+
164
+ `test` works wherever the platform tests your code on a push:
165
+
166
+ ```
167
+ my-plugin/ a plugin's repository
168
+ manifest.json
169
+ index.js
170
+ visualautomate.test.json
171
+
172
+ my-app/ an app's repository
173
+ modules/
174
+ send-message/
175
+ manifest.json
176
+ index.js
177
+ visualautomate.test.json
178
+ list-channels/
179
+ …
180
+ ```
181
+
182
+ At the root of an app's repository every module runs, one after another. Name one
183
+ to run only that: `vsa test send-message`, or any short name that picks it out —
184
+ `vsa test send`. `--module <name>` is the same thing. `index.js` in a repository is run as written,
185
+ unbundled, because that is the file the sandbox loads.
186
+
187
+ ## `dev`
188
+
189
+ ```bash
190
+ vsa dev # every module in modules/
191
+ vsa dev send-message # one of them; the shortest name that picks it will do
192
+ vsa dev --tier boosted # a bigger machine
193
+ vsa dev --local # on this machine's Node, without the sandbox
194
+ ```
195
+
196
+ Everything in one command: it starts Docker if it is not running, fetches the
197
+ sandbox image the first time, runs every module, and runs them again on every
198
+ save. Only the module whose folder changed runs again; a change anywhere else
199
+ runs them all. Ctrl+C stops it.
200
+
201
+ `context.connections` and `context.media` are **not** simulated. One is
202
+ somebody's OAuth account and the other is an ffmpeg container; pretending to be
203
+ either would produce a plugin that passes here and fails on a canvas. There is
204
+ no account here, so `context.connections` is absent, as it is on the platform
205
+ when no account is chosen; `context.media` fails with a sentence saying so.
206
+
207
+ `visualautomate.test.json` (or `test.json`, or `--input <file>`) seeds the run:
208
+
209
+ ```json
210
+ {
211
+ "config": { "source": "notes/hello.txt" },
212
+ "input": {},
213
+ "storage": { "notes/hello.txt": "hello from a file" },
214
+ "state": { "runs": 0 },
215
+ "expectOutput": "success",
216
+ "allowedDomains": ["api.example.com"]
217
+ }
218
+ ```
219
+
220
+ The platform's push check reads `config`, `input` and `expectOutput`; `storage`,
221
+ `state`, `secrets` and `allowedDomains` are for running locally. The time limit
222
+ is manifest.json's `timeoutMs`, not the test file's: `vsa test` warns about a
223
+ `timeoutMs` here, because the push check ignores it. A file that is not valid JSON stops the run rather than running with
224
+ nothing.
225
+
226
+ ## Where `fetch` may go
227
+
228
+ On the platform a step may contact the hosts allowed for its plugin, which are
229
+ set on the platform rather than declared in the manifest, plus any host in the
230
+ configuration filled in for that step. A declared domain covers its
231
+ subdomains; localhost, private networks and metadata addresses are never
232
+ allowed.
233
+
234
+ Put the domains in `allowedDomains` and `test` applies the same rule: a request
235
+ anywhere else fails. Without `allowedDomains` nothing is blocked, but every host
236
+ the platform would refuse is listed after the run.
237
+
238
+ ## `--persist`
239
+
240
+ Keeps storage and state between runs, in `.visualautomate/` next to the plugin:
241
+
242
+ ```
243
+ .visualautomate/storage/<path> files the step wrote
244
+ .visualautomate/state.json the workflow state
245
+ ```
246
+
247
+ Once that folder exists it is used without the flag. Delete it to start over,
248
+ and add it to `.gitignore`.
249
+
250
+ ## The sandbox on your machine
251
+
252
+ `dev` uses it by default; `test` does with `--docker`:
253
+
254
+ ```bash
255
+ vsa dev
256
+ vsa test --docker
257
+ vsa dev --tier boosted
258
+ ```
259
+
260
+ Your plugin runs inside the plugin sandbox image,
261
+ `ghcr.io/visualautomate/plugin-sandbox`, at the version of this CLI:
262
+
263
+ - Node 24, the version the platform runs plugins on, with every allowed npm
264
+ package installed at the platform's versions — no `npm install` needed;
265
+ - the CPU and memory of the platform's machine: `standard` (½ vCPU, 4 GB, the
266
+ default), `boosted` (1 vCPU, 6 GB) or `high` (2 vCPU, 8 GB). `max` is no longer
267
+ offered and runs as `high`, as on the platform;
268
+ - no Linux capabilities, a read-only filesystem apart from your project and a
269
+ small `/tmp`, and nothing else from your machine.
270
+
271
+ A step that runs out of memory or time here does the same on the platform.
272
+
273
+ Docker does not have to be running: `dev` starts Docker Desktop and waits for it,
274
+ then fetches the image if this machine does not have it. `--image` uses a
275
+ different image, for example one you built yourself. The watcher stays on your
276
+ machine, and each run starts a fresh container.
277
+
278
+ `context.media` is not available locally, in or out of Docker.
279
+
280
+ ## Publishing
281
+
282
+ Not from here. Connect the repository to your app on the portal, push your
283
+ commit, and the platform runs the same checks `vsa test` runs against every
284
+ module it touched. Publish on the portal takes the code from a commit whose test
285
+ came back on `success`, and a version snapshot is saved so you can roll back. A
286
+ declared step is published once a Try it in the portal's editor has passed on
287
+ its saved version.
288
+
289
+ Comments in `manifest.json` are a convenience of this tool: the file on your
290
+ disk may have them, and they are stripped before the manifest is read.
291
+
292
+ ## Where things are
293
+
294
+ The runtime API your plugin is written against is documented in the types
295
+ (`visualautomate.d.ts` in your project, `@visualautomate/plugin-sdk` if you
296
+ prefer to install it) and on the platform's own docs page.