@junoflow/cli 0.15.0 → 0.17.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 +273 -95
- package/package.json +2 -2
- package/src/api.js +154 -15
- package/src/authoring-skills.js +2 -6
- package/src/commands/app-init.js +1 -1
- package/src/commands/approval-wait.js +215 -0
- package/src/commands/flow-init.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/permissions.js +47 -0
- package/src/commands/run-follow.js +68 -0
- package/src/commands/session-approve.js +22 -3
- package/src/commands/session.js +103 -0
- package/src/commands/skill.js +28 -19
- package/src/commands/tool.js +23 -5
- package/src/commands/whoami.js +38 -0
- package/src/exit.js +5 -1
- package/src/help.js +143 -22
- package/src/index.js +115 -48
- package/src/prompt.js +9 -0
- package/src/verbs.js +6 -1
- package/verbs.json +391 -6
- package/src/commands/agent-skill-install.js +0 -64
- package/src/installed-skill.js +0 -55
package/src/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
AGENT_SKILL_INSTALL_USAGE,
|
|
5
|
-
agentSkillInstall,
|
|
6
|
-
} from './commands/agent-skill-install.js';
|
|
7
3
|
import { APP_CHECK_USAGE, appCheck } from './commands/app-check.js';
|
|
8
4
|
import { APP_DEPLOY_USAGE, appDeploy } from './commands/app-deploy.js';
|
|
9
5
|
import { APP_DEV_USAGE, appDev } from './commands/app-dev.js';
|
|
10
6
|
import { APP_GEN_TYPES_USAGE, appGenTypes } from './commands/app-gen-types.js';
|
|
11
7
|
import { APP_INIT_USAGE, appInit } from './commands/app-init.js';
|
|
12
8
|
import { APP_PULL_USAGE, appPull } from './commands/app-pull.js';
|
|
9
|
+
import { APPROVAL_WAIT_USAGE, approvalWait } from './commands/approval-wait.js';
|
|
13
10
|
import { ASK_USAGE, ask } from './commands/ask.js';
|
|
14
11
|
import { CHECK_USAGE, check } from './commands/check.js';
|
|
15
12
|
import { CONFIG_SET_USAGE, configSet } from './commands/config.js';
|
|
@@ -36,13 +33,33 @@ import {
|
|
|
36
33
|
taskRun,
|
|
37
34
|
} from './commands/task-run.js';
|
|
38
35
|
import { PULL_USAGE, pull } from './commands/pull.js';
|
|
36
|
+
import { PERMISSIONS_USAGE, permissions } from './commands/permissions.js';
|
|
39
37
|
import { SKILL_USAGE, skill } from './commands/skill.js';
|
|
40
38
|
import {
|
|
41
39
|
SESSION_APPROVE_USAGE,
|
|
42
40
|
sessionApprove,
|
|
43
41
|
} from './commands/session-approve.js';
|
|
42
|
+
import { runFollow } from './commands/run-follow.js';
|
|
43
|
+
import {
|
|
44
|
+
SESSION_CANCEL_USAGE,
|
|
45
|
+
SESSION_EXPORT_USAGE,
|
|
46
|
+
SESSION_GET_USAGE,
|
|
47
|
+
SESSION_LIST_USAGE,
|
|
48
|
+
SESSION_RATE_USAGE,
|
|
49
|
+
sessionCancel,
|
|
50
|
+
sessionExport,
|
|
51
|
+
sessionGet,
|
|
52
|
+
sessionList,
|
|
53
|
+
sessionRate,
|
|
54
|
+
} from './commands/session.js';
|
|
44
55
|
import { runTool } from './commands/tool.js';
|
|
45
56
|
import { VERIFY_TYPES_USAGE, verifyTypes } from './commands/verify-types.js';
|
|
57
|
+
import {
|
|
58
|
+
VERSION_USAGE,
|
|
59
|
+
WHOAMI_USAGE,
|
|
60
|
+
version,
|
|
61
|
+
whoami,
|
|
62
|
+
} from './commands/whoami.js';
|
|
46
63
|
import {
|
|
47
64
|
ConflictError,
|
|
48
65
|
DiagnosticsError,
|
|
@@ -58,15 +75,15 @@ import {
|
|
|
58
75
|
EXIT_UNREACHABLE,
|
|
59
76
|
EXIT_USAGE,
|
|
60
77
|
} from './exit.js';
|
|
61
|
-
import {
|
|
62
|
-
groupHelp,
|
|
63
|
-
nonToolHelp,
|
|
64
|
-
verbHelp,
|
|
65
|
-
WORKING_COPY_HEADING,
|
|
66
|
-
workingCopyListing,
|
|
67
|
-
} from './help.js';
|
|
78
|
+
import { groupHelp, nonToolHelp, rootHelp, verbHelp } from './help.js';
|
|
68
79
|
import { closest, closestByVerb, levenshtein } from './suggest.js';
|
|
69
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
groupOf,
|
|
82
|
+
groupSummaries,
|
|
83
|
+
normalizeVerb,
|
|
84
|
+
rowFor,
|
|
85
|
+
rows,
|
|
86
|
+
} from './verbs.js';
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
89
|
* The two flags every command that reaches an instance takes, and the two a
|
|
@@ -94,11 +111,58 @@ const NON_TOOL = {
|
|
|
94
111
|
},
|
|
95
112
|
usage: ASK_USAGE,
|
|
96
113
|
},
|
|
114
|
+
whoami: { run: whoami, options: URL_OPTION, usage: WHOAMI_USAGE },
|
|
115
|
+
version: { run: version, options: URL_OPTION, usage: VERSION_USAGE },
|
|
116
|
+
permissions: {
|
|
117
|
+
run: permissions,
|
|
118
|
+
options: {
|
|
119
|
+
...URL_OPTION,
|
|
120
|
+
'allow-change': { type: 'boolean', default: false },
|
|
121
|
+
'working-copy': { type: 'boolean', default: false },
|
|
122
|
+
json: { type: 'boolean', default: false },
|
|
123
|
+
},
|
|
124
|
+
usage: PERMISSIONS_USAGE,
|
|
125
|
+
},
|
|
126
|
+
'session list': {
|
|
127
|
+
run: sessionList,
|
|
128
|
+
options: {
|
|
129
|
+
...URL_OPTION,
|
|
130
|
+
rating: { type: 'string' },
|
|
131
|
+
profile: { type: 'string' },
|
|
132
|
+
limit: { type: 'string' },
|
|
133
|
+
},
|
|
134
|
+
usage: SESSION_LIST_USAGE,
|
|
135
|
+
},
|
|
136
|
+
'session get': {
|
|
137
|
+
run: sessionGet,
|
|
138
|
+
options: { ...URL_OPTION, transcript: { type: 'boolean', default: false } },
|
|
139
|
+
usage: SESSION_GET_USAGE,
|
|
140
|
+
},
|
|
141
|
+
'session export': {
|
|
142
|
+
run: sessionExport,
|
|
143
|
+
options: URL_OPTION,
|
|
144
|
+
usage: SESSION_EXPORT_USAGE,
|
|
145
|
+
},
|
|
146
|
+
'session rate': {
|
|
147
|
+
run: sessionRate,
|
|
148
|
+
options: { ...URL_OPTION, rating: { type: 'string' } },
|
|
149
|
+
usage: SESSION_RATE_USAGE,
|
|
150
|
+
},
|
|
151
|
+
'session cancel': {
|
|
152
|
+
run: sessionCancel,
|
|
153
|
+
options: URL_OPTION,
|
|
154
|
+
usage: SESSION_CANCEL_USAGE,
|
|
155
|
+
},
|
|
97
156
|
'session approve': {
|
|
98
157
|
run: sessionApprove,
|
|
99
158
|
options: { ...URL_OPTION, wait: { type: 'string' } },
|
|
100
159
|
usage: SESSION_APPROVE_USAGE,
|
|
101
160
|
},
|
|
161
|
+
'approval wait': {
|
|
162
|
+
run: approvalWait,
|
|
163
|
+
options: { ...URL_OPTION, wait: { type: 'string' } },
|
|
164
|
+
usage: APPROVAL_WAIT_USAGE,
|
|
165
|
+
},
|
|
102
166
|
'credential set': {
|
|
103
167
|
run: credentialSet,
|
|
104
168
|
options: {
|
|
@@ -135,18 +199,6 @@ const NON_TOOL = {
|
|
|
135
199
|
usage: TASK_PING_USAGE,
|
|
136
200
|
},
|
|
137
201
|
'config set': { run: configSet, options: {}, usage: CONFIG_SET_USAGE },
|
|
138
|
-
'agent-skill install': {
|
|
139
|
-
run: agentSkillInstall,
|
|
140
|
-
options: {
|
|
141
|
-
...URL_OPTION,
|
|
142
|
-
dir: { type: 'string' },
|
|
143
|
-
'allow-change': { type: 'boolean', default: false },
|
|
144
|
-
'working-copy': { type: 'boolean', default: false },
|
|
145
|
-
'agents-md': { type: 'boolean', default: false },
|
|
146
|
-
json: { type: 'boolean', default: false },
|
|
147
|
-
},
|
|
148
|
-
usage: AGENT_SKILL_INSTALL_USAGE,
|
|
149
|
-
},
|
|
150
202
|
};
|
|
151
203
|
|
|
152
204
|
/**
|
|
@@ -301,6 +353,21 @@ const WORKING_COPY = {
|
|
|
301
353
|
},
|
|
302
354
|
};
|
|
303
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Flags a tool row takes beyond its verbs.json ones, which name no field of
|
|
358
|
+
* the tool's input and so cannot be in the table: what the CLI does around
|
|
359
|
+
* the call. `run get --follow` streams the run's steps to stderr until it is
|
|
360
|
+
* done, then prints get_run as without it.
|
|
361
|
+
*/
|
|
362
|
+
const TOOL_EXTRAS = {
|
|
363
|
+
'run get': {
|
|
364
|
+
options: { follow: { type: 'boolean', default: false } },
|
|
365
|
+
help: "flag: --follow → no input field: stream the run's steps to stderr until it ends, then print the run",
|
|
366
|
+
run: (row, args, io) =>
|
|
367
|
+
args.follow ? runFollow(row, args, io) : runTool(row, args, io),
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
304
371
|
/** The handler of a non-tool row, working-copy or not, or undefined. */
|
|
305
372
|
function handlerFor(row) {
|
|
306
373
|
if (row.tool) return undefined;
|
|
@@ -320,26 +387,6 @@ function dispatchable(command) {
|
|
|
320
387
|
return row && DISPATCHABLE.includes(row) ? row : undefined;
|
|
321
388
|
}
|
|
322
389
|
|
|
323
|
-
const USAGE = `juno — operate a Juno instance, and develop flows, integrations and apps for it
|
|
324
|
-
|
|
325
|
-
Usage: juno <command> [options]
|
|
326
|
-
juno <group> <verb> [id] [--input <json>|-|@file] [--jq <expr>]
|
|
327
|
-
|
|
328
|
-
Groups: ${[...GROUPS].join(', ')}
|
|
329
|
-
Commands: ${DISPATCHABLE.filter((r) => !groupOf(r))
|
|
330
|
-
.map((r) => r.command)
|
|
331
|
-
.join(', ')}
|
|
332
|
-
Run \`juno <group> --help\` for a group's commands.
|
|
333
|
-
|
|
334
|
-
${WORKING_COPY_HEADING}:
|
|
335
|
-
${workingCopyListing(DISPATCHABLE, handlerFor)}
|
|
336
|
-
|
|
337
|
-
--url <url> picks the instance; else $JUNO_URL, package.json juno.target,
|
|
338
|
-
then the user config (juno config set url <url>).
|
|
339
|
-
--token <key> is the API key to send; else $JUNO_TOKEN, then the user config
|
|
340
|
-
(juno config set token <key>). Create one under Settings → API keys.
|
|
341
|
-
`;
|
|
342
|
-
|
|
343
390
|
/**
|
|
344
391
|
* run is the whole CLI, injectable so the tests drive it without spawning a
|
|
345
392
|
* process: io carries cwd, env, the TTY bit, stdin and the three output
|
|
@@ -387,9 +434,11 @@ async function dispatch(argv, io) {
|
|
|
387
434
|
const { leading, rest } = leadingFlags(argv);
|
|
388
435
|
const [first, ...tail] = rest;
|
|
389
436
|
|
|
437
|
+
// Bare `juno` is `juno --help`, exit 0: it is where an agent with nothing
|
|
438
|
+
// installed starts, and the primer is the answer it came for.
|
|
390
439
|
if (!first || first === '--help' || first === '-h' || first === 'help') {
|
|
391
|
-
|
|
392
|
-
return
|
|
440
|
+
const show = (args) => showRootHelp(args, io);
|
|
441
|
+
return runCommand({ options: URL_OPTION }, tail, leading, io, show, show);
|
|
393
442
|
}
|
|
394
443
|
|
|
395
444
|
const top = dispatchable(first);
|
|
@@ -428,6 +477,11 @@ async function dispatch(argv, io) {
|
|
|
428
477
|
throw unknownTopLevel(first, first, [first]);
|
|
429
478
|
}
|
|
430
479
|
|
|
480
|
+
async function showRootHelp(args, io) {
|
|
481
|
+
io.log(await rootHelp(DISPATCHABLE, handlerFor, groupSummaries, args, io));
|
|
482
|
+
return EXIT_OK;
|
|
483
|
+
}
|
|
484
|
+
|
|
431
485
|
async function showGroupHelp(group, args, io, code) {
|
|
432
486
|
io.log(await groupHelp(group, DISPATCHABLE, handlerFor, args, io));
|
|
433
487
|
return code;
|
|
@@ -468,21 +522,28 @@ function runRow(row, argv, leading, io) {
|
|
|
468
522
|
);
|
|
469
523
|
}
|
|
470
524
|
|
|
525
|
+
// --wait is how long a destructive tool's approval is waited for; every
|
|
526
|
+
// tool row takes it, since which tools are destructive is the server's.
|
|
471
527
|
const options = {
|
|
472
528
|
...URL_OPTION,
|
|
473
529
|
input: { type: 'string' },
|
|
474
530
|
jq: { type: 'string' },
|
|
531
|
+
wait: { type: 'string' },
|
|
475
532
|
};
|
|
476
533
|
for (const [flag, spec] of Object.entries(row.flags ?? {})) {
|
|
477
534
|
options[flag] = { type: 'string', multiple: spec.multiple === true };
|
|
478
535
|
}
|
|
536
|
+
const extras = TOOL_EXTRAS[row.command];
|
|
537
|
+
Object.assign(options, extras?.options);
|
|
538
|
+
const run = extras?.run ?? runTool;
|
|
479
539
|
return runCommand(
|
|
480
|
-
{ run: (args) =>
|
|
540
|
+
{ run: (args) => run(row, args, io), options },
|
|
481
541
|
argv,
|
|
482
542
|
leading,
|
|
483
543
|
io,
|
|
484
544
|
async (args) => {
|
|
485
|
-
|
|
545
|
+
const help = await verbHelp(row, args, io);
|
|
546
|
+
io.log(extras?.help ? `${help}\n${extras.help}` : help);
|
|
486
547
|
return EXIT_OK;
|
|
487
548
|
}
|
|
488
549
|
);
|
|
@@ -611,6 +672,12 @@ export function defaultIO() {
|
|
|
611
672
|
write: (text) => process.stdout.write(text),
|
|
612
673
|
warn: (msg) => process.stderr.write(`${msg}\n`),
|
|
613
674
|
error: (msg) => process.stderr.write(`${msg}\n`),
|
|
675
|
+
// Ctrl-C while waiting for an approval: the handler says how to resume,
|
|
676
|
+
// and the wait returns rather than the process dying mid-line.
|
|
677
|
+
onInterrupt: (fn) => {
|
|
678
|
+
process.once('SIGINT', fn);
|
|
679
|
+
return () => process.off('SIGINT', fn);
|
|
680
|
+
},
|
|
614
681
|
};
|
|
615
682
|
}
|
|
616
683
|
|
package/src/prompt.js
CHANGED
|
@@ -182,6 +182,15 @@ function nextStep(event, id) {
|
|
|
182
182
|
return `still running; keep waiting with: juno ask --session ${id}`;
|
|
183
183
|
}
|
|
184
184
|
if (event.type !== 'awaiting_input') return null;
|
|
185
|
+
const approvalID = event.pending_input?.approval_id;
|
|
186
|
+
if (approvalID) {
|
|
187
|
+
// A destructive call's approval record: only a person in Juno answers it.
|
|
188
|
+
const url = event.pending_input.approval_url ?? `/approvals/${approvalID}`;
|
|
189
|
+
return (
|
|
190
|
+
`waiting for a person to approve it in Juno: ${url}\n` +
|
|
191
|
+
`wait for the outcome with: juno ask --session ${id}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
185
194
|
if (event.pending_input?.kind === 'approval') {
|
|
186
195
|
return (
|
|
187
196
|
`awaiting approval; approve with: juno session approve ${id}\n` +
|
package/src/verbs.js
CHANGED
|
@@ -2,7 +2,9 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
|
|
3
3
|
// createRequire rather than an import attribute: `with { type: 'json' }` needs
|
|
4
4
|
// Node 20.10+, and engines says >=20.
|
|
5
|
-
const { commands: ROWS } = createRequire(import.meta.url)(
|
|
5
|
+
const { commands: ROWS, groups: GROUPS } = createRequire(import.meta.url)(
|
|
6
|
+
'../verbs.json'
|
|
7
|
+
);
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* verbs.json is the command table: every `juno <group> <verb>` the CLI
|
|
@@ -12,6 +14,9 @@ const { commands: ROWS } = createRequire(import.meta.url)('../verbs.json');
|
|
|
12
14
|
*/
|
|
13
15
|
export const rows = ROWS;
|
|
14
16
|
|
|
17
|
+
/** The one line `juno --help` lists each group by, keyed by group. */
|
|
18
|
+
export const groupSummaries = GROUPS;
|
|
19
|
+
|
|
15
20
|
const byCommand = new Map(ROWS.map((row) => [row.command, row]));
|
|
16
21
|
|
|
17
22
|
/** The row for a command string ("state get"), or undefined. */
|