@gondoai/cli 0.1.0 → 0.2.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.
- package/README.md +24 -94
- package/package.json +1 -1
- package/src/cli.mjs +114 -83
package/README.md
CHANGED
|
@@ -1,118 +1,48 @@
|
|
|
1
1
|
# Gondo CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use Gondo Admin capabilities from a coding agent or terminal. Requires Node.js 22.12+, a Gondo account with Pro access, and an API key created by an account admin.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Start
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Save `GONDO_API_KEY=…` in a private `gondo.env` file outside source control, then run:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
2.
|
|
11
|
-
|
|
12
|
-
```dotenv
|
|
13
|
-
GONDO_API_KEY=your-api-key
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
3. From any directory, read the guide:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npx --yes @gondoai/cli --env-file /absolute/path/to/gondo.env guide
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
No Gondo app checkout is needed. To install a persistent `gondo` command instead:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
npm install --global @gondoai/cli
|
|
26
|
-
gondo --env-file /absolute/path/to/gondo.env guide
|
|
9
|
+
```sh
|
|
10
|
+
npx --yes @gondoai/cli@0.2.0 --env-file /absolute/path/gondo.env guide
|
|
27
11
|
```
|
|
28
12
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Keep the key out of prompts, source control, and shared logs. Give your agent the local env-file path, not the key text. Connect required integrations using the setup commands below or in Gondo; enable Public browsing for public websites.
|
|
32
|
-
|
|
33
|
-
## Give this to your agent
|
|
13
|
+
The key identifies its account automatically. Optional `GONDO_API_URL` and `GONDO_ACCOUNT_ID` overrides support development. Environment variables override the explicitly selected env file. Never paste credentials into prompts or command arguments.
|
|
34
14
|
|
|
35
|
-
|
|
15
|
+
## Shared capabilities
|
|
36
16
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
gondo --env-file ./gondo.env guide --topic workflows
|
|
47
|
-
gondo --env-file ./gondo.env guide --topic nodes
|
|
48
|
-
gondo --env-file ./gondo.env list /integrations
|
|
49
|
-
gondo --env-file ./gondo.env employees list
|
|
50
|
-
gondo --env-file ./gondo.env workflows list
|
|
51
|
-
gondo --help
|
|
52
|
-
gondo --version
|
|
17
|
+
```sh
|
|
18
|
+
npx --yes @gondoai/cli@0.2.0 --env-file /absolute/path/gondo.env tools list
|
|
19
|
+
npx --yes @gondoai/cli@0.2.0 --env-file /absolute/path/gondo.env tools describe gondo_upsert_workflow_node
|
|
20
|
+
npx --yes @gondoai/cli@0.2.0 --env-file /absolute/path/gondo.env call gondo_upsert_workflow_node --file arguments.json
|
|
21
|
+
npx --yes @gondoai/cli@0.2.0 --env-file /absolute/path/gondo.env call load_skill --file skill.json --session os_existing
|
|
22
|
+
npx --yes @gondoai/cli@0.2.0 --env-file /absolute/path/gondo.env workflows get <id> --source active --format json
|
|
23
|
+
npx --yes @gondoai/cli@0.2.0 --help
|
|
53
24
|
```
|
|
54
25
|
|
|
55
|
-
The
|
|
26
|
+
The server supplies Admin's existing guides, tool names, descriptions and schemas. The CLI contains no separate authoring manual. `guide --topic workflows` returns the authoritative workflow bundle. Convenience commands adapt to the same operations. Workflow reads return one definition; use `--editor-state` explicitly for the complete editor state.
|
|
56
27
|
|
|
57
|
-
|
|
28
|
+
Use the host's native web search, delegation and authorization. The catalog preserves Admin's `requireConfirmation` flag; the host applies its authorization policy before invocation, including authorization already given by the user. Required workflow human reviews and login stay in signed-in Gondo; interactive calls return action links. File tools return session artifacts; download inspection images and view them in the host before making visual claims.
|
|
58
29
|
|
|
59
|
-
|
|
30
|
+
Integration management requires `integrations:manage`. Creation follows Admin defaults. Change availability explicitly with `integrations enable|disable <id>`. Submit credentials using `integrations credentials <id> --file <private-file>` or stdin. Credential replacement never implicitly enables a connection. Custom API, custom-tool and browser management are available through the shared catalog.
|
|
60
31
|
|
|
61
|
-
|
|
32
|
+
Responses are JSON. Exit codes: **0** success, **1** failure, **2** action required. Mutations are never automatically retried. After an uncertain write, inspect its run or execution before proceeding. Existing session, binary transfer, private webhook output and attempt download commands remain available through `--help`.
|
|
62
33
|
|
|
63
|
-
|
|
34
|
+
## Development and release
|
|
64
35
|
|
|
65
|
-
```
|
|
36
|
+
```sh
|
|
66
37
|
npm ci
|
|
67
38
|
npm test
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Tests exercise the packed npm artifact installed in a separate temporary directory, including its executable, env-file loading, HTTP authentication, and YAML requests. No Gondo credentials or live account are required.
|
|
71
|
-
|
|
72
|
-
To publish the prepared `@gondoai/cli@0.1.0` release, sign in with an npm account that can publish to the `gondoai` organisation:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npm login
|
|
76
|
-
npm whoami
|
|
77
39
|
npm pack --dry-run
|
|
78
|
-
npm publish --access public
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Publishing runs the tests again. Complete npm's authentication/2FA prompt when requested. Then verify the registry install from any other directory:
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
npx --yes @gondoai/cli@0.1.0 --version
|
|
85
|
-
npx --yes @gondoai/cli@0.1.0 --env-file /absolute/path/to/gondo.env guide
|
|
86
40
|
```
|
|
87
41
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## Connection setup and API-key scopes
|
|
91
|
-
|
|
92
|
-
All keys have `operator` access. In Account → API keys, admins can additionally select **Manage integrations** (`integrations:manage`) when creating a key. Existing keys do not gain this permission automatically; create a replacement and revoke the old key to change permissions. Ordinary keys can already use connected APIs, including writes; this additional scope controls connection setup, not provider API permissions. Keys cannot grant scopes or approve human reviews.
|
|
93
|
-
|
|
94
|
-
```sh
|
|
95
|
-
gondo integrations providers
|
|
96
|
-
gondo integrations provider clio
|
|
97
|
-
gondo integrations create --provider clio --name "Demo Clio"
|
|
98
|
-
gondo integrations credentials <id> --file ./private-credentials.json --variant <variant>
|
|
99
|
-
gondo integrations test <id>
|
|
100
|
-
gondo integrations enable <id>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Only prebuilt providers with supplied credentials are supported. Credential JSON/YAML must match the displayed schema; `--file -` reads stdin. Do not pass secret values as arguments. Connections start disabled, and enablement is explicit. Replacement credentials are validated separately before an atomic switch; failed checks leave live credentials untouched. Concurrent replacements return a conflict rather than overwriting each other. `checked: false` means no provider check was available. Use `integrations list|get <id>`, `disable <id>`, or `update <id> --file settings.json` (fields: `label`, `maxScope`, `enabledToolsets`, `disabledTools`). Provider metadata lists valid tool names; `maxScope` accepts `read`, `write`, `admin`. These settings govern named tools, not direct API code.
|
|
104
|
-
|
|
105
|
-
## Webhooks and workflow documents
|
|
106
|
-
|
|
107
|
-
```sh
|
|
108
|
-
gondo workflows webhook get <workflow-id>
|
|
109
|
-
gondo workflows webhook configure <workflow-id> --output ./private-webhook.json
|
|
110
|
-
gondo attempts files list <attempt-id>
|
|
111
|
-
gondo attempts files download <attempt-id> <artifact-id> --output-dir ./outputs
|
|
112
|
-
```
|
|
42
|
+
Tests include an installed packed artifact, authenticated HTTP transport, server capability-version checks, private credential files and binary downloads.
|
|
113
43
|
|
|
114
|
-
|
|
44
|
+
**Deploy the paired app release exposing capability protocol version 2 before publishing CLI 0.2.0.** This release requires `/operator/tools` and the shared `/operator/guide`; discovery on an older server fails clearly without falling back to stale instructions. This is the initial supported CLI contract; older CLI releases are not supported.
|
|
115
45
|
|
|
116
|
-
|
|
46
|
+
The [shared implementation brief](https://github.com/commandable/commandable-app-v1/blob/main/docs/unified-authoring.md) documents the paired app change and verification.
|
|
117
47
|
|
|
118
|
-
|
|
48
|
+
After that deployment, an authorized npm maintainer can publish with `npm publish --access public`. Creating or merging these PRs does not itself publish the npm package.
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { Readable, Transform } from 'node:stream'
|
|
|
6
6
|
import { pipeline } from 'node:stream/promises'
|
|
7
7
|
import { parseArgs, parseEnv } from 'node:util'
|
|
8
8
|
import packageJson from '../package.json' with { type: 'json' }
|
|
9
|
-
import { parseDocument } from 'yaml'
|
|
9
|
+
import { parseDocument, stringify } from 'yaml'
|
|
10
10
|
|
|
11
11
|
export const HELP = `Gondo account operator CLI
|
|
12
12
|
|
|
@@ -16,11 +16,14 @@ Load a local env file with: gondo --env-file ./gondo.env guide
|
|
|
16
16
|
Use gondo --version to show the installed CLI version.
|
|
17
17
|
Account admins create keys in Account Settings. Never put a key in source code.
|
|
18
18
|
|
|
19
|
-
gondo guide [--topic overview|workflows
|
|
19
|
+
gondo guide [--topic overview|workflows]
|
|
20
|
+
gondo tools list|describe <existing-admin-tool-name>
|
|
21
|
+
gondo call <existing-admin-tool-name> --file arguments.json [--session <id>] [--output-dir <dir>]
|
|
22
|
+
Tool names, descriptions and argument schemas come from the server. Host and interactive tools return required actions.
|
|
20
23
|
gondo list [namespace-path] [--limit 25]
|
|
21
24
|
gondo read <namespace-path>
|
|
22
25
|
gondo employees list|get <id>|create --file employee.yaml|update <id> --file changes.json|delete <id>
|
|
23
|
-
gondo workflows list|get <id
|
|
26
|
+
gondo workflows list|get <id> [--source active|draft|published] [--format json|yaml] [--editor-state]|create --employee <id> --name <name>
|
|
24
27
|
gondo workflows save-draft <id> --file workflow.yaml
|
|
25
28
|
gondo workflows validate <id> [--file workflow.yaml] [--source draft|published]
|
|
26
29
|
gondo workflows export <id> [--source active|draft|published] [--output workflow.yaml]
|
|
@@ -29,7 +32,7 @@ gondo workflows rename <id> --name <name>
|
|
|
29
32
|
gondo runs list|get|attempts|events|definition|cancel <run-id>
|
|
30
33
|
gondo attempts get|events|definition|cancel <attempt-id>
|
|
31
34
|
gondo runs test|start <workflow-id> [--input input.json]
|
|
32
|
-
gondo exec --file investigate.js --integrations ref_one,ref_two [--session <id>] [--attach <path> ...] [--output-dir <dir>]
|
|
35
|
+
gondo exec --file investigate.js --integrations ref_one,ref_two [--channels ref_one,ref_two] [--session <id>] [--attach <path> ...] [--output-dir <dir>]
|
|
33
36
|
gondo sessions create|list|get <id>|close <id>
|
|
34
37
|
gondo files list <session-id>|upload <session-id> --file <path>
|
|
35
38
|
gondo files download <file-id> --session <id> --output-dir <dir>
|
|
@@ -45,7 +48,7 @@ gondo attempts files list <attempt-id>
|
|
|
45
48
|
gondo attempts files download <attempt-id> <artifact-id> --output-dir <dir>
|
|
46
49
|
|
|
47
50
|
Keys have operator access; integration changes also require integrations:manage.
|
|
48
|
-
New integrations
|
|
51
|
+
New integrations follow Admin defaults (enabled). Saving credentials never re-enables an existing connection.
|
|
49
52
|
Webhook configure/rotate-secret require --output; secrets never appear on stdout. Configure never rotates an existing secret.
|
|
50
53
|
|
|
51
54
|
Requires Pro, including active Pro trials. Responses are JSON. Exit 0: success; 1: failure; 2: user action required.
|
|
@@ -72,6 +75,9 @@ const options = {
|
|
|
72
75
|
'name': { type: 'string' },
|
|
73
76
|
'integrations': { type: 'string' },
|
|
74
77
|
'source': { type: 'string' },
|
|
78
|
+
'format': { type: 'string' },
|
|
79
|
+
'editor-state': { type: 'boolean' },
|
|
80
|
+
'channels': { type: 'string' },
|
|
75
81
|
'topic': { type: 'string' },
|
|
76
82
|
'limit': { type: 'string' },
|
|
77
83
|
'help': { type: 'boolean', short: 'h' },
|
|
@@ -93,6 +99,15 @@ function segment(value, label = 'ID') {
|
|
|
93
99
|
return encodeURIComponent(result)
|
|
94
100
|
}
|
|
95
101
|
|
|
102
|
+
function positiveInteger(value, label) {
|
|
103
|
+
const text = value.trim()
|
|
104
|
+
const number = Number(text)
|
|
105
|
+
if (!/^\d+$/.test(text) || !Number.isSafeInteger(number) || number <= 0)
|
|
106
|
+
throw new Error(`${label} must be a positive safe integer`)
|
|
107
|
+
|
|
108
|
+
return number
|
|
109
|
+
}
|
|
110
|
+
|
|
96
111
|
async function readStructured(file, stdin = process.stdin) {
|
|
97
112
|
required(file, '--file or --input')
|
|
98
113
|
let source
|
|
@@ -140,18 +155,37 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
140
155
|
|
|
141
156
|
const request = { method: 'GET', path: '', query: {}, body: undefined, output: values.output, ...(values['env-file'] ? { envFile: values['env-file'] } : {}) }
|
|
142
157
|
|
|
158
|
+
// Convenience commands are argument adapters to the shared Admin operations.
|
|
159
|
+
const call = (name, args, presentation) => {
|
|
160
|
+
request.path = `/operator/tools/${name}/call`
|
|
161
|
+
request.method = 'POST'
|
|
162
|
+
request.query = {}
|
|
163
|
+
request.body = { arguments: args, ...(values.session ? { sessionId: values.session } : {}), ...(presentation ? { presentation } : {}) }
|
|
164
|
+
request.requiresCapabilities = 2
|
|
165
|
+
}
|
|
166
|
+
|
|
143
167
|
if (command === 'guide') {
|
|
144
168
|
request.path = '/operator/guide'
|
|
145
169
|
|
|
146
170
|
request.query.topic = values.topic ?? 'overview'
|
|
171
|
+
request.requiresCapabilities = 2
|
|
172
|
+
}
|
|
173
|
+
else if (command === 'tools') {
|
|
174
|
+
if (action === 'list')
|
|
175
|
+
request.path = '/operator/tools'
|
|
176
|
+
else if (action === 'describe')
|
|
177
|
+
request.path = `/operator/tools/${segment(id, 'tool name')}`
|
|
178
|
+
request.requiresCapabilities = 2
|
|
179
|
+
}
|
|
180
|
+
else if (command === 'call') {
|
|
181
|
+
call(segment(action, 'tool name'), await readStructured(values.file, stdin))
|
|
182
|
+
request.outputDir = values['output-dir']
|
|
147
183
|
}
|
|
148
184
|
else if (command === 'list' || command === 'read') {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (values.limit)
|
|
154
|
-
request.query.limit = values.limit
|
|
185
|
+
call(command === 'list' ? 'gondo_list' : 'gondo_read', {
|
|
186
|
+
path: command === 'read' ? required(action, 'namespace path') : action ?? '/',
|
|
187
|
+
...(command === 'list' && values.limit !== undefined ? { limit: positiveInteger(values.limit, '--limit') } : {}),
|
|
188
|
+
})
|
|
155
189
|
}
|
|
156
190
|
else if (command === 'exec') {
|
|
157
191
|
request.path = '/code/execute'
|
|
@@ -160,6 +194,7 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
160
194
|
|
|
161
195
|
request.body = {
|
|
162
196
|
code: await readFile(required(values.file, '--file'), 'utf8'),
|
|
197
|
+
channels: values.channels?.split(',').map(ref => ref.trim()).filter(Boolean) ?? [],
|
|
163
198
|
integrations: values.integrations?.split(',').map(ref => ref.trim()).filter(Boolean) ?? [],
|
|
164
199
|
...(values.session ? { sessionId: values.session } : {}),
|
|
165
200
|
}
|
|
@@ -205,23 +240,19 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
205
240
|
request.body = { integration: required(values.integration, '--integration') }
|
|
206
241
|
}
|
|
207
242
|
else if (command === 'employees') {
|
|
208
|
-
if (action === 'list')
|
|
243
|
+
if (action === 'list')
|
|
209
244
|
request.path = '/employees'
|
|
210
|
-
|
|
211
|
-
else if (action === 'create') {
|
|
212
|
-
request.path = '/employees'
|
|
213
|
-
|
|
214
|
-
request.method = 'POST'
|
|
215
|
-
|
|
216
|
-
request.body = await readStructured(values.file)
|
|
217
|
-
}
|
|
218
|
-
else if (['get', 'update', 'delete'].includes(action)) {
|
|
245
|
+
else if (action === 'get')
|
|
219
246
|
request.path = `/employees/${segment(id)}`
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
247
|
+
else if (['create', 'update', 'delete'].includes(action)) {
|
|
248
|
+
const body = action === 'delete' ? {} : await readStructured(values.file)
|
|
249
|
+
const { allowedIntegrationRefs, allowedChannelRefs, ...rest } = body
|
|
250
|
+
call(`${action}_employee`, {
|
|
251
|
+
...rest,
|
|
252
|
+
...(action !== 'create' ? { employee_id: required(id, 'employee ID') } : {}),
|
|
253
|
+
...(allowedIntegrationRefs !== undefined ? { allowed_integration_refs: allowedIntegrationRefs ?? [] } : {}),
|
|
254
|
+
...(allowedChannelRefs !== undefined ? { allowed_channel_refs: allowedChannelRefs ?? [] } : {}),
|
|
255
|
+
})
|
|
225
256
|
}
|
|
226
257
|
}
|
|
227
258
|
else if (command === 'integrations') {
|
|
@@ -233,10 +264,13 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
233
264
|
else if (action === 'list')
|
|
234
265
|
request.path = base
|
|
235
266
|
else if (action === 'create') {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
267
|
+
call('gondo_add_prebuilt_api_integration', {
|
|
268
|
+
type: required(values.provider, '--provider'), label: required(values.name, '--name'),
|
|
269
|
+
...(values.reference ? { reference_id: values.reference } : {}),
|
|
270
|
+
})
|
|
239
271
|
}
|
|
272
|
+
else if (action === 'enable' || action === 'disable')
|
|
273
|
+
call('gondo_set_integration_enabled', { integration_id: required(id, 'integration ID'), enabled: action === 'enable' })
|
|
240
274
|
else if (action === 'get')
|
|
241
275
|
request.path = `${base}/${segment(id)}`
|
|
242
276
|
else if (action === 'update') {
|
|
@@ -244,7 +278,7 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
244
278
|
request.method = 'PATCH'
|
|
245
279
|
request.body = await readStructured(values.file)
|
|
246
280
|
}
|
|
247
|
-
else if (['credentials', 'test'
|
|
281
|
+
else if (['credentials', 'test'].includes(action)) {
|
|
248
282
|
request.path = `${base}/${segment(id)}/${action}`
|
|
249
283
|
request.method = 'POST'
|
|
250
284
|
if (action === 'credentials') {
|
|
@@ -280,56 +314,45 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
280
314
|
}
|
|
281
315
|
}
|
|
282
316
|
else if (command === 'workflows') {
|
|
283
|
-
if (action === 'list')
|
|
284
|
-
request.path = '/workflows'
|
|
285
|
-
}
|
|
286
|
-
else if (action === 'create') {
|
|
317
|
+
if (action === 'list')
|
|
287
318
|
request.path = '/workflows'
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
request.body = { employeeId: required(values.employee, '--employee'), name: required(values.name, '--name') }
|
|
292
|
-
}
|
|
319
|
+
else if (action === 'create')
|
|
320
|
+
call('create_new_workflow', { employee_id: required(values.employee, '--employee'), suggested_name: required(values.name, '--name') })
|
|
293
321
|
else {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
'
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
'
|
|
301
|
-
'export': ['POST', '/yaml'],
|
|
302
|
-
'publish': ['POST', '/publish'],
|
|
303
|
-
'enable': ['PATCH', ''],
|
|
304
|
-
'disable': ['PATCH', ''],
|
|
305
|
-
'rename': ['PATCH', ''],
|
|
306
|
-
'delete': ['DELETE', ''],
|
|
322
|
+
const workflowId = required(id, 'workflow ID')
|
|
323
|
+
const base = `/workflows/${segment(workflowId)}`
|
|
324
|
+
if (action === 'get')
|
|
325
|
+
call('gondo_read', { path: base }, { source: values.source ?? 'active', format: values.format ?? 'json', editorState: values['editor-state'] ?? false })
|
|
326
|
+
else if (action === 'save-draft') {
|
|
327
|
+
const body = await definitionBody(values.file)
|
|
328
|
+
call('gondo_put_workflow', { op: 'replace', workflow_id: workflowId, definition_yaml: body.definitionYaml ?? stringify(body.definition) })
|
|
307
329
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
request.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
else if (action === 'publish')
|
|
331
|
+
call('gondo_publish_workflow', { workflow_id: workflowId })
|
|
332
|
+
else if (action === 'rename')
|
|
333
|
+
call('gondo_update_workflow_settings', { workflow_id: workflowId, name: required(values.name, '--name') })
|
|
334
|
+
else if (action === 'validate' && !values.file && !values.source)
|
|
335
|
+
call('gondo_validate_workflow', { workflow_id: workflowId })
|
|
336
|
+
else {
|
|
337
|
+
const routes = {
|
|
338
|
+
'discard-draft': ['DELETE', '/draft'],
|
|
339
|
+
'validate': ['POST', '/validate'],
|
|
340
|
+
'export': ['POST', '/yaml'],
|
|
341
|
+
'enable': ['PATCH', ''],
|
|
342
|
+
'disable': ['PATCH', ''],
|
|
343
|
+
'delete': ['DELETE', ''],
|
|
344
|
+
}
|
|
345
|
+
const route = routes[action]
|
|
346
|
+
if (route) {
|
|
347
|
+
request.method = route[0]
|
|
348
|
+
request.path = `${base}${route[1]}`
|
|
349
|
+
if (action === 'validate')
|
|
350
|
+
request.body = { source: values.source ?? 'draft', ...(values.file ? await definitionBody(values.file) : {}) }
|
|
351
|
+
if (action === 'export')
|
|
352
|
+
request.body = { source: values.source ?? 'active' }
|
|
353
|
+
if (action === 'enable' || action === 'disable')
|
|
354
|
+
request.body = { enabled: action === 'enable' }
|
|
355
|
+
}
|
|
333
356
|
}
|
|
334
357
|
}
|
|
335
358
|
}
|
|
@@ -337,8 +360,8 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
337
360
|
if (action === 'list') {
|
|
338
361
|
request.path = '/runs'
|
|
339
362
|
|
|
340
|
-
if (values.limit)
|
|
341
|
-
request.query.limit = values.limit
|
|
363
|
+
if (values.limit !== undefined)
|
|
364
|
+
request.query.limit = positiveInteger(values.limit, '--limit')
|
|
342
365
|
}
|
|
343
366
|
else if (action === 'test' || action === 'start') {
|
|
344
367
|
request.path = '/runs'
|
|
@@ -371,7 +394,7 @@ export async function buildRequest(argv, { stdin = process.stdin } = {}) {
|
|
|
371
394
|
if (!request.path)
|
|
372
395
|
throw new Error('Unknown command. Use --help for supported commands.')
|
|
373
396
|
|
|
374
|
-
const maxPositionals = command === 'integrations' && ['providers', 'list', 'create'].includes(action) ? 2 : command === 'workflows' && action === 'webhook' ? 4 : command === 'attempts' && action === 'files' ? (id === 'download' ? 5 : 4) : ['guide', 'exec'].includes(command) ? 1 : ['list', 'read'].includes(command) ? 2 : 3
|
|
397
|
+
const maxPositionals = command === 'call' ? 2 : command === 'tools' && action === 'list' ? 2 : command === 'integrations' && ['providers', 'list', 'create'].includes(action) ? 2 : command === 'workflows' && action === 'webhook' ? 4 : command === 'attempts' && action === 'files' ? (id === 'download' ? 5 : 4) : ['guide', 'exec'].includes(command) ? 1 : ['list', 'read'].includes(command) ? 2 : 3
|
|
375
398
|
|
|
376
399
|
if (positionals.length > maxPositionals)
|
|
377
400
|
throw new Error('Unexpected positional argument. Use --help for command syntax.')
|
|
@@ -426,6 +449,7 @@ export async function runCli(argv, { env = process.env, fetchImpl = fetch, stdou
|
|
|
426
449
|
let request
|
|
427
450
|
let secretHandle
|
|
428
451
|
let secretWritten = false
|
|
452
|
+
let toolCallStarted = false
|
|
429
453
|
try {
|
|
430
454
|
request = await buildRequest(argv, { stdin })
|
|
431
455
|
|
|
@@ -480,12 +504,14 @@ export async function runCli(argv, { env = process.env, fetchImpl = fetch, stdou
|
|
|
480
504
|
let response
|
|
481
505
|
|
|
482
506
|
try {
|
|
507
|
+
if (next.path.startsWith('/operator/tools/') && next.path.endsWith('/call'))
|
|
508
|
+
toolCallStarted = true
|
|
483
509
|
response = await fetchImpl(url, {
|
|
484
510
|
method: next.method,
|
|
485
511
|
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json', ...uploadHeaders },
|
|
486
512
|
...(uploadStream ? { body: uploadStream, duplex: 'half' } : next.body !== undefined ? { body: JSON.stringify(next.body) } : {}),
|
|
487
513
|
redirect: 'error',
|
|
488
|
-
signal: AbortSignal.timeout(120000),
|
|
514
|
+
signal: AbortSignal.timeout(next.path === '/code/execute' || next.path?.endsWith('/call') ? 420000 : 120000),
|
|
489
515
|
})
|
|
490
516
|
}
|
|
491
517
|
finally {
|
|
@@ -495,6 +521,9 @@ export async function runCli(argv, { env = process.env, fetchImpl = fetch, stdou
|
|
|
495
521
|
if (next.binary && response.ok)
|
|
496
522
|
return { response }
|
|
497
523
|
|
|
524
|
+
if (next.requiresCapabilities && [404, 405].includes(response.status))
|
|
525
|
+
throw new Error('This Gondo server does not support CLI 0.2.0 shared capabilities. Deploy the matching app release before using this command.')
|
|
526
|
+
|
|
498
527
|
const text = await response.text()
|
|
499
528
|
let result
|
|
500
529
|
|
|
@@ -505,6 +534,8 @@ export async function runCli(argv, { env = process.env, fetchImpl = fetch, stdou
|
|
|
505
534
|
throw new Error(`Runtime returned non-JSON (HTTP ${response.status}). Check GONDO_API_URL points to the runtime.`)
|
|
506
535
|
}
|
|
507
536
|
|
|
537
|
+
if (next.requiresCapabilities && response.ok && result?.version !== next.requiresCapabilities)
|
|
538
|
+
throw new Error('This Gondo server has an incompatible capability version. Deploy the app release supporting CLI 0.2.0.')
|
|
508
539
|
return { response, result }
|
|
509
540
|
}
|
|
510
541
|
|
|
@@ -621,7 +652,7 @@ export async function runCli(argv, { env = process.env, fetchImpl = fetch, stdou
|
|
|
621
652
|
}
|
|
622
653
|
|
|
623
654
|
let received = await send(request)
|
|
624
|
-
if (request.path === '/code/execute')
|
|
655
|
+
if (request.path === '/code/execute' || received.result?.executionId)
|
|
625
656
|
recovery = { sessionId: received.result?.sessionId, executionId: received.result?.executionId }
|
|
626
657
|
|
|
627
658
|
if (request.outputDir && received.result?.files?.length) {
|
|
@@ -717,7 +748,7 @@ export async function runCli(argv, { env = process.env, fetchImpl = fetch, stdou
|
|
|
717
748
|
if (request?.secretOutput && secretHandle && !secretWritten)
|
|
718
749
|
message += ' Webhook state may have changed; inspect it before explicitly rotating again. No automatic retry was performed.'
|
|
719
750
|
|
|
720
|
-
stderr.write(`${JSON.stringify({ error: message, ...(request?.path === '/code/execute' ? { sessionId: request.body?.sessionId, ...recovery, recovery: 'Inspect the session and execution before continuing; do not repeat this execution automatically.' } : {}) })}\n`)
|
|
751
|
+
stderr.write(`${JSON.stringify({ error: message, ...(toolCallStarted || request?.path === '/code/execute' || request?.body?.sessionId || recovery ? { sessionId: request.body?.sessionId, ...recovery, recovery: toolCallStarted ? 'The tool call may have changed state. Inspect the affected resources and any session or execution before continuing; do not repeat this call automatically.' : 'Inspect the session and execution before continuing; do not repeat this execution automatically.' } : {}) })}\n`)
|
|
721
752
|
|
|
722
753
|
return 1
|
|
723
754
|
}
|