@jini-ai/cli 0.3.1 → 0.4.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.
- package/CHANGELOG.md +101 -0
- package/README.md +27 -10
- package/dist/command-registry.d.ts +19 -7
- package/dist/command-registry.js +8 -7
- package/dist/daemon-command.d.ts +22 -7
- package/dist/daemon-command.js +34 -40
- package/dist/daemon-url.d.ts +16 -6
- package/dist/daemon-url.js +9 -24
- package/dist/errors.d.ts +16 -6
- package/dist/errors.js +17 -16
- package/dist/flags.d.ts +11 -5
- package/dist/flags.js +5 -5
- package/dist/http.d.ts +25 -9
- package/dist/http.js +27 -24
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/introspection.d.ts +75 -0
- package/dist/introspection.js +63 -0
- package/dist/local-daemon-discovery.d.ts +7 -15
- package/dist/local-daemon-discovery.js +7 -7
- package/dist/main.d.ts +12 -4
- package/dist/main.js +32 -34
- package/dist/prompt.d.ts +17 -7
- package/dist/prompt.js +13 -13
- package/dist/redact.d.ts +5 -20
- package/dist/redact.js +5 -97
- package/dist/run-command.d.ts +38 -10
- package/dist/run-command.js +85 -119
- package/dist/tokens.js +1 -1
- package/dist/usage.d.ts +4 -2
- package/dist/usage.js +8 -8
- package/dist/version-command.d.ts +18 -6
- package/dist/version-command.js +13 -14
- package/package.json +32 -3
package/dist/run-command.js
CHANGED
|
@@ -14,59 +14,38 @@
|
|
|
14
14
|
* Every network call goes through this package's already-hardened
|
|
15
15
|
* `postJsonToDaemon`/`getJsonFromDaemon` (bounded timeout, response-size cap,
|
|
16
16
|
* structured-error mapping, redaction) — nothing here re-implements that.
|
|
17
|
-
* `run watch`'s SSE consumption is new (this package's own `
|
|
17
|
+
* `run watch`'s SSE consumption is new (this package's own `archived provenance ledger`
|
|
18
18
|
* calls the mechanics — GET an SSE endpoint, translate frames to one NDJSON
|
|
19
19
|
* line per event on stdout, stop at a terminal `'end'` event — a clean
|
|
20
20
|
* pattern that had no route to call until now).
|
|
21
21
|
*/
|
|
22
|
+
import { decodeSseFrames } from '@jini-ai/core/primitives';
|
|
22
23
|
import { parseFlags, positionalArgs } from './flags.js';
|
|
23
24
|
import { exitWithStructuredError, structuredHttpFailure } from './errors.js';
|
|
24
25
|
import { getJsonFromDaemon, postJsonToDaemon } from './http.js';
|
|
25
|
-
import { sanitizeUntrustedText, stripControlSequences } from '
|
|
26
|
+
import { sanitizeUntrustedText, stripControlSequences } from '@jini-ai/core/text';
|
|
26
27
|
import { renderUsage } from './usage.js';
|
|
27
|
-
function defaultWrite(text) {
|
|
28
|
+
function defaultWrite({ text }) {
|
|
28
29
|
process.stdout.write(text);
|
|
29
30
|
}
|
|
30
|
-
function defaultWriteErr(text) {
|
|
31
|
+
function defaultWriteErr({ text }) {
|
|
31
32
|
process.stderr.write(text);
|
|
32
33
|
}
|
|
33
|
-
const RUN_START_USAGE = renderUsage({
|
|
34
|
-
usage: ['run start --context-ref <ref> [--agent-id <id>] [--idempotency-key <key>]'],
|
|
35
|
-
description: 'Starts a new run for the given contextRef.',
|
|
36
|
-
options: [
|
|
34
|
+
const RUN_START_USAGE = renderUsage({ usage: ['run start --context-ref <ref> [--agent-id <id>] [--idempotency-key <key>]'] }, { description: 'Starts a new run for the given contextRef.', options: [
|
|
37
35
|
{ flag: '--context-ref', description: 'Opaque caller-supplied identity the run belongs to (required).' },
|
|
38
36
|
{ flag: '--agent-id', description: 'Which registered agent should drive this run.' },
|
|
39
37
|
{ flag: '--idempotency-key', description: 'Starting twice with the same key returns the original run.' },
|
|
40
|
-
]
|
|
41
|
-
});
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
const RUN_GET_USAGE = renderUsage({
|
|
48
|
-
usage: ['run get <runId>'],
|
|
49
|
-
description: 'Fetches a single run by id.',
|
|
50
|
-
});
|
|
51
|
-
const RUN_CANCEL_USAGE = renderUsage({
|
|
52
|
-
usage: ['run cancel <runId> [--reason <text>]'],
|
|
53
|
-
description: 'Requests cancellation of an in-progress run. A no-op on an already-terminal run.',
|
|
54
|
-
options: [{ flag: '--reason', description: 'Optional human-readable cancellation reason.' }],
|
|
55
|
-
});
|
|
56
|
-
const RUN_WATCH_USAGE = renderUsage({
|
|
57
|
-
usage: ['run watch <runId> [--after-cursor <cursor>]'],
|
|
58
|
-
description: 'Streams a run\'s protocol events as one NDJSON line per event on stdout until a terminal event arrives.',
|
|
59
|
-
options: [{ flag: '--after-cursor', description: 'Resume streaming after this event cursor (reconnect support).' }],
|
|
60
|
-
});
|
|
61
|
-
const RUN_USAGE = renderUsage({
|
|
62
|
-
usage: ['run <start|list|get|cancel|watch> ...'],
|
|
63
|
-
description: 'Manage runs against a Jini daemon over HTTP.',
|
|
64
|
-
});
|
|
38
|
+
] });
|
|
39
|
+
const RUN_LIST_USAGE = renderUsage({ usage: ['run list [--context-ref <ref>]'] }, { description: 'Lists runs, optionally scoped to a contextRef.', options: [{ flag: '--context-ref', description: 'Only list runs belonging to this contextRef.' }] });
|
|
40
|
+
const RUN_GET_USAGE = renderUsage({ usage: ['run get <runId>'] }, { description: 'Fetches a single run by id.' });
|
|
41
|
+
const RUN_CANCEL_USAGE = renderUsage({ usage: ['run cancel <runId> [--reason <text>]'] }, { description: 'Requests cancellation of an in-progress run. A no-op on an already-terminal run.', options: [{ flag: '--reason', description: 'Optional human-readable cancellation reason.' }] });
|
|
42
|
+
const RUN_WATCH_USAGE = renderUsage({ usage: ['run watch <runId> [--after-cursor <cursor>]'] }, { description: 'Streams a run\'s protocol events as one NDJSON line per event on stdout until a terminal event arrives.', options: [{ flag: '--after-cursor', description: 'Resume streaming after this event cursor (reconnect support).' }] });
|
|
43
|
+
const RUN_USAGE = renderUsage({ usage: ['run <start|list|get|cancel|watch> ...'] }, { description: 'Manage runs against a Jini daemon over HTTP.' });
|
|
65
44
|
/** Builds `{write, exit, exitCodes?}` for `errors.ts`'s structured-error helpers, never assigning `exitCodes` when unset (required under `exactOptionalPropertyTypes`). */
|
|
66
45
|
function errorOptions(deps) {
|
|
67
46
|
return {
|
|
68
47
|
write: deps.writeErr ?? defaultWriteErr,
|
|
69
|
-
exit: deps.exit ?? ((code) => process.exit(code)),
|
|
48
|
+
exit: deps.exit ?? (({ code }) => process.exit(code)),
|
|
70
49
|
...(deps.exitCodes !== undefined ? { exitCodes: deps.exitCodes } : {}),
|
|
71
50
|
};
|
|
72
51
|
}
|
|
@@ -86,18 +65,19 @@ function invalidFlag(deps, message) {
|
|
|
86
65
|
return exitWithStructuredError({ code: 'invalid-flag', message }, errorOptions(deps));
|
|
87
66
|
}
|
|
88
67
|
function printJsonResult(deps, value) {
|
|
89
|
-
(deps.write ?? defaultWrite)(`${JSON.stringify(value)}\n`);
|
|
68
|
+
(deps.write ?? defaultWrite)({ text: `${JSON.stringify(value)}\n` });
|
|
90
69
|
}
|
|
91
70
|
async function resolveBaseUrl(deps) {
|
|
92
71
|
return deps.resolveBaseUrl();
|
|
93
72
|
}
|
|
94
73
|
/** `run start --context-ref <ref> [--agent-id <id>] [--idempotency-key <key>]` */
|
|
95
|
-
export async function runStartCommand(args,
|
|
74
|
+
export async function runStartCommand({ args, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs = {}) {
|
|
75
|
+
const deps = { resolveBaseUrl: resolveBaseUrlPort, ...optionalArgs };
|
|
96
76
|
if (args.includes('--help') || args.includes('-h')) {
|
|
97
|
-
(deps.write ?? defaultWrite)(`${RUN_START_USAGE}\n`);
|
|
77
|
+
(deps.write ?? defaultWrite)({ text: `${RUN_START_USAGE}\n` });
|
|
98
78
|
return;
|
|
99
79
|
}
|
|
100
|
-
const flags = parseFlags(args, { string: new Set(['context-ref', 'agent-id', 'idempotency-key']) });
|
|
80
|
+
const flags = parseFlags({ argv: args }, { string: new Set(['context-ref', 'agent-id', 'idempotency-key']) });
|
|
101
81
|
const contextRef = flags['context-ref'];
|
|
102
82
|
if (typeof contextRef !== 'string' || contextRef.length === 0) {
|
|
103
83
|
missingInput(deps, '--context-ref is required');
|
|
@@ -108,46 +88,49 @@ export async function runStartCommand(args, deps) {
|
|
|
108
88
|
if (typeof flags['idempotency-key'] === 'string')
|
|
109
89
|
body.idempotencyKey = flags['idempotency-key'];
|
|
110
90
|
const baseUrl = await resolveBaseUrl(deps);
|
|
111
|
-
const result = await postJsonToDaemon(baseUrl, '/api/runs', body, transportOptions(deps));
|
|
91
|
+
const result = await postJsonToDaemon({ base: baseUrl, route: '/api/runs', body }, transportOptions(deps));
|
|
112
92
|
printJsonResult(deps, result);
|
|
113
93
|
}
|
|
114
94
|
/** `run list [--context-ref <ref>]` */
|
|
115
|
-
export async function runListCommand(args,
|
|
95
|
+
export async function runListCommand({ args, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs = {}) {
|
|
96
|
+
const deps = { resolveBaseUrl: resolveBaseUrlPort, ...optionalArgs };
|
|
116
97
|
if (args.includes('--help') || args.includes('-h')) {
|
|
117
|
-
(deps.write ?? defaultWrite)(`${RUN_LIST_USAGE}\n`);
|
|
98
|
+
(deps.write ?? defaultWrite)({ text: `${RUN_LIST_USAGE}\n` });
|
|
118
99
|
return;
|
|
119
100
|
}
|
|
120
|
-
const flags = parseFlags(args, { string: new Set(['context-ref']) });
|
|
101
|
+
const flags = parseFlags({ argv: args }, { string: new Set(['context-ref']) });
|
|
121
102
|
const contextRef = flags['context-ref'];
|
|
122
103
|
const route = typeof contextRef === 'string' && contextRef.length > 0
|
|
123
104
|
? `/api/runs?contextRef=${encodeURIComponent(contextRef)}`
|
|
124
105
|
: '/api/runs';
|
|
125
106
|
const baseUrl = await resolveBaseUrl(deps);
|
|
126
|
-
const result = await getJsonFromDaemon(baseUrl, route, transportOptions(deps));
|
|
107
|
+
const result = await getJsonFromDaemon({ base: baseUrl, route }, transportOptions(deps));
|
|
127
108
|
printJsonResult(deps, result);
|
|
128
109
|
}
|
|
129
110
|
/** `run get <runId>` */
|
|
130
|
-
export async function runGetCommand(args,
|
|
111
|
+
export async function runGetCommand({ args, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs = {}) {
|
|
112
|
+
const deps = { resolveBaseUrl: resolveBaseUrlPort, ...optionalArgs };
|
|
131
113
|
if (args.includes('--help') || args.includes('-h')) {
|
|
132
|
-
(deps.write ?? defaultWrite)(`${RUN_GET_USAGE}\n`);
|
|
114
|
+
(deps.write ?? defaultWrite)({ text: `${RUN_GET_USAGE}\n` });
|
|
133
115
|
return;
|
|
134
116
|
}
|
|
135
|
-
const runId = positionalArgs(args)[0];
|
|
117
|
+
const runId = positionalArgs({ argv: args })[0];
|
|
136
118
|
if (typeof runId !== 'string' || runId.length === 0) {
|
|
137
119
|
missingInput(deps, 'runId is required: run get <runId>');
|
|
138
120
|
}
|
|
139
121
|
const baseUrl = await resolveBaseUrl(deps);
|
|
140
|
-
const result = await getJsonFromDaemon(baseUrl, `/api/runs/${encodeURIComponent(runId)}
|
|
122
|
+
const result = await getJsonFromDaemon({ base: baseUrl, route: `/api/runs/${encodeURIComponent(runId)}` }, transportOptions(deps));
|
|
141
123
|
printJsonResult(deps, result);
|
|
142
124
|
}
|
|
143
125
|
/** `run cancel <runId> [--reason <text>]` */
|
|
144
|
-
export async function runCancelCommand(args,
|
|
126
|
+
export async function runCancelCommand({ args, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs = {}) {
|
|
127
|
+
const deps = { resolveBaseUrl: resolveBaseUrlPort, ...optionalArgs };
|
|
145
128
|
if (args.includes('--help') || args.includes('-h')) {
|
|
146
|
-
(deps.write ?? defaultWrite)(`${RUN_CANCEL_USAGE}\n`);
|
|
129
|
+
(deps.write ?? defaultWrite)({ text: `${RUN_CANCEL_USAGE}\n` });
|
|
147
130
|
return;
|
|
148
131
|
}
|
|
149
|
-
const flags = parseFlags(args, { string: new Set(['reason']) });
|
|
150
|
-
const runId = positionalArgs(args, { string: new Set(['reason']) })[0];
|
|
132
|
+
const flags = parseFlags({ argv: args }, { string: new Set(['reason']) });
|
|
133
|
+
const runId = positionalArgs({ argv: args }, { string: new Set(['reason']) })[0];
|
|
151
134
|
if (typeof runId !== 'string' || runId.length === 0) {
|
|
152
135
|
missingInput(deps, 'runId is required: run cancel <runId>');
|
|
153
136
|
}
|
|
@@ -155,50 +138,31 @@ export async function runCancelCommand(args, deps) {
|
|
|
155
138
|
if (typeof flags.reason === 'string')
|
|
156
139
|
body.reason = flags.reason;
|
|
157
140
|
const baseUrl = await resolveBaseUrl(deps);
|
|
158
|
-
const result = await postJsonToDaemon(baseUrl, `/api/runs/${encodeURIComponent(runId)}/cancel`, body, transportOptions(deps));
|
|
141
|
+
const result = await postJsonToDaemon({ base: baseUrl, route: `/api/runs/${encodeURIComponent(runId)}/cancel`, body }, transportOptions(deps));
|
|
159
142
|
printJsonResult(deps, result);
|
|
160
143
|
}
|
|
161
|
-
/** Cap on total buffered-but-not-yet-frame-terminated SSE
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
let id;
|
|
165
|
-
let eventName;
|
|
166
|
-
const dataLines = [];
|
|
167
|
-
for (const line of rawFrame.split('\n')) {
|
|
168
|
-
if (line.startsWith('id: '))
|
|
169
|
-
id = line.slice(4);
|
|
170
|
-
else if (line.startsWith('event: '))
|
|
171
|
-
eventName = line.slice(7);
|
|
172
|
-
else if (line.startsWith('data: '))
|
|
173
|
-
dataLines.push(line.slice(6));
|
|
174
|
-
}
|
|
175
|
-
if (dataLines.length === 0)
|
|
176
|
-
return null;
|
|
177
|
-
return { ...(id !== undefined ? { id } : {}), ...(eventName !== undefined ? { event: eventName } : {}), data: dataLines.join('\n') };
|
|
178
|
-
}
|
|
179
|
-
/** Reads `body` as an SSE byte stream and yields one {@link SseFrame} per blank-line-terminated frame. */
|
|
144
|
+
/** Cap on total buffered-but-not-yet-frame-terminated SSE text — a daemon that never sends a blank-line frame terminator must not grow this without bound (retains the historical 10 MiB-sized text cap; TextDecoder output is measured in UTF-16 units). */
|
|
145
|
+
const MAX_SSE_BUFFER_CHARS = 10 * 1024 * 1024;
|
|
146
|
+
/** Reads complete records; this adapter releases the lock without canceling the host's body. */
|
|
180
147
|
async function* readSseFrames(body) {
|
|
181
148
|
const reader = body.getReader();
|
|
182
|
-
|
|
183
|
-
let buffer = '';
|
|
184
|
-
try {
|
|
149
|
+
async function* chunks() {
|
|
185
150
|
for (;;) {
|
|
186
151
|
const { done, value } = await reader.read();
|
|
187
152
|
if (done)
|
|
188
|
-
|
|
153
|
+
return;
|
|
189
154
|
if (value !== undefined)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
155
|
+
yield value;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
for await (const frame of decodeSseFrames({ source: chunks() }, {
|
|
160
|
+
flushFinalFrame: false, maxBufferChars: MAX_SSE_BUFFER_CHARS,
|
|
161
|
+
})) {
|
|
162
|
+
if (frame.dataLines.length === 0)
|
|
163
|
+
continue;
|
|
164
|
+
yield { ...(frame.id !== undefined ? { id: frame.id } : {}),
|
|
165
|
+
...(frame.event !== null ? { event: frame.event } : {}), data: frame.data };
|
|
202
166
|
}
|
|
203
167
|
}
|
|
204
168
|
finally {
|
|
@@ -224,8 +188,8 @@ function isTerminalEventFrame(frame) {
|
|
|
224
188
|
* the way a diagnostic error message is, since it is the run's actual event payload, not incidental
|
|
225
189
|
* error detail.
|
|
226
190
|
*/
|
|
227
|
-
export async function watchRunEvents(baseUrl, runId, args, deps) {
|
|
228
|
-
const flags = parseFlags(args, { string: new Set(['after-cursor']) });
|
|
191
|
+
export async function watchRunEvents({ baseUrl, runId, args }, deps = {}) {
|
|
192
|
+
const flags = parseFlags({ argv: args }, { string: new Set(['after-cursor']) });
|
|
229
193
|
const afterCursor = flags['after-cursor'];
|
|
230
194
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
231
195
|
const write = deps.write ?? defaultWrite;
|
|
@@ -234,11 +198,11 @@ export async function watchRunEvents(baseUrl, runId, args, deps) {
|
|
|
234
198
|
headers['last-event-id'] = afterCursor;
|
|
235
199
|
const resp = await fetchImpl(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events`, { headers });
|
|
236
200
|
if (!resp.ok || resp.body === null) {
|
|
237
|
-
return structuredHttpFailure(resp, 'daemon-not-running', errorOptions(deps));
|
|
201
|
+
return structuredHttpFailure({ resp }, { fallbackCode: 'daemon-not-running', ...errorOptions(deps) });
|
|
238
202
|
}
|
|
239
203
|
try {
|
|
240
204
|
for await (const frame of readSseFrames(resp.body)) {
|
|
241
|
-
write(`${stripControlSequences(frame.data)}\n`);
|
|
205
|
+
write({ text: `${stripControlSequences({ text: frame.data })}\n` });
|
|
242
206
|
if (isTerminalEventFrame(frame))
|
|
243
207
|
return;
|
|
244
208
|
}
|
|
@@ -247,45 +211,47 @@ export async function watchRunEvents(baseUrl, runId, args, deps) {
|
|
|
247
211
|
// A broken/hostile stream (e.g. the unbounded-buffer guard above) must exit through the same
|
|
248
212
|
// structured contract as every other failure here, not surface as an unhandled rejection.
|
|
249
213
|
const message = error instanceof Error ? error.message : String(error);
|
|
250
|
-
return exitWithStructuredError({ code: 'daemon-not-running', message: sanitizeUntrustedText(message) }, errorOptions(deps));
|
|
214
|
+
return exitWithStructuredError({ code: 'daemon-not-running', message: sanitizeUntrustedText({ text: message }) }, errorOptions(deps));
|
|
251
215
|
}
|
|
252
216
|
}
|
|
253
217
|
/** `run watch <runId> [--after-cursor <cursor>]` */
|
|
254
|
-
export async function runWatchCommand(args,
|
|
218
|
+
export async function runWatchCommand({ args, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs = {}) {
|
|
219
|
+
const deps = { resolveBaseUrl: resolveBaseUrlPort, ...optionalArgs };
|
|
255
220
|
if (args.includes('--help') || args.includes('-h')) {
|
|
256
|
-
(deps.write ?? defaultWrite)(`${RUN_WATCH_USAGE}\n`);
|
|
221
|
+
(deps.write ?? defaultWrite)({ text: `${RUN_WATCH_USAGE}\n` });
|
|
257
222
|
return;
|
|
258
223
|
}
|
|
259
|
-
const runId = positionalArgs(args, { string: new Set(['after-cursor']) })[0];
|
|
224
|
+
const runId = positionalArgs({ argv: args }, { string: new Set(['after-cursor']) })[0];
|
|
260
225
|
if (typeof runId !== 'string' || runId.length === 0) {
|
|
261
226
|
missingInput(deps, 'runId is required: run watch <runId>');
|
|
262
227
|
}
|
|
263
228
|
const baseUrl = await resolveBaseUrl(deps);
|
|
264
|
-
await watchRunEvents(baseUrl, runId, args, deps);
|
|
229
|
+
await watchRunEvents({ baseUrl, runId, args }, deps);
|
|
265
230
|
}
|
|
266
231
|
/** Registers `run` (dispatching `start`/`list`/`cancel`/`watch` on the first remaining token) against `registry`. */
|
|
267
|
-
export function registerRunCommands(registry,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
232
|
+
export function registerRunCommands({ registry, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs = {}) {
|
|
233
|
+
const deps = { resolveBaseUrl: resolveBaseUrlPort, ...optionalArgs };
|
|
234
|
+
registry.add({ name: 'run', handler: async ({ args }) => {
|
|
235
|
+
const [sub, ...rest] = args;
|
|
236
|
+
switch (sub) {
|
|
237
|
+
case 'start':
|
|
238
|
+
return runStartCommand({ args: rest, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs);
|
|
239
|
+
case 'list':
|
|
240
|
+
return runListCommand({ args: rest, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs);
|
|
241
|
+
case 'get':
|
|
242
|
+
return runGetCommand({ args: rest, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs);
|
|
243
|
+
case 'cancel':
|
|
244
|
+
return runCancelCommand({ args: rest, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs);
|
|
245
|
+
case 'watch':
|
|
246
|
+
return runWatchCommand({ args: rest, resolveBaseUrl: resolveBaseUrlPort }, optionalArgs);
|
|
247
|
+
case undefined:
|
|
248
|
+
case '--help':
|
|
249
|
+
case '-h':
|
|
250
|
+
(deps.write ?? defaultWrite)({ text: `${RUN_USAGE}\n` });
|
|
251
|
+
return;
|
|
252
|
+
default:
|
|
253
|
+
invalidFlag(deps, `unknown "run" subcommand: ${sub}`);
|
|
254
|
+
}
|
|
255
|
+
} }, { usage: RUN_USAGE });
|
|
290
256
|
}
|
|
291
257
|
//# sourceMappingURL=run-command.js.map
|
package/dist/tokens.js
CHANGED
package/dist/usage.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module usage
|
|
3
3
|
*
|
|
4
4
|
* The generic *shape* of every OD `print*Help` function, extracted from
|
|
5
|
-
* `apps/daemon/src/cli.ts` (see `
|
|
5
|
+
* `apps/daemon/src/cli.ts` (see `archived provenance ledger`): one or more usage
|
|
6
6
|
* invocation lines, an optional prose description, and an optional flat
|
|
7
7
|
* list of `--option` descriptions. OD's actual help text is 100% product
|
|
8
8
|
* content and was not ported — only the rendering convention.
|
|
@@ -14,11 +14,13 @@ export interface UsageOption {
|
|
|
14
14
|
export interface UsageSpec {
|
|
15
15
|
/** One or more `cmd sub <arg>` invocation lines. */
|
|
16
16
|
usage: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export interface UsageOptions {
|
|
17
19
|
/** Optional prose shown after the usage lines. */
|
|
18
20
|
description?: string;
|
|
19
21
|
/** Optional flat `--flag` reference list. */
|
|
20
22
|
options?: readonly UsageOption[];
|
|
21
23
|
}
|
|
22
24
|
/** Render a {@link UsageSpec} into the "Usage:\n ...\n\nOptions:\n ..." text every OD help command used. */
|
|
23
|
-
export declare function renderUsage(
|
|
25
|
+
export declare function renderUsage({ usage }: UsageSpec, options?: UsageOptions): string;
|
|
24
26
|
//# sourceMappingURL=usage.d.ts.map
|
package/dist/usage.js
CHANGED
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
* @module usage
|
|
3
3
|
*
|
|
4
4
|
* The generic *shape* of every OD `print*Help` function, extracted from
|
|
5
|
-
* `apps/daemon/src/cli.ts` (see `
|
|
5
|
+
* `apps/daemon/src/cli.ts` (see `archived provenance ledger`): one or more usage
|
|
6
6
|
* invocation lines, an optional prose description, and an optional flat
|
|
7
7
|
* list of `--option` descriptions. OD's actual help text is 100% product
|
|
8
8
|
* content and was not ported — only the rendering convention.
|
|
9
9
|
*/
|
|
10
10
|
/** Render a {@link UsageSpec} into the "Usage:\n ...\n\nOptions:\n ..." text every OD help command used. */
|
|
11
|
-
export function renderUsage(
|
|
12
|
-
const lines = ['Usage:', ...
|
|
13
|
-
if (
|
|
14
|
-
lines.push('',
|
|
11
|
+
export function renderUsage({ usage }, options = {}) {
|
|
12
|
+
const lines = ['Usage:', ...usage.map((line) => ` ${line}`)];
|
|
13
|
+
if (options.description !== undefined && options.description.length > 0) {
|
|
14
|
+
lines.push('', options.description);
|
|
15
15
|
}
|
|
16
|
-
if (
|
|
17
|
-
const longest = Math.max(...
|
|
16
|
+
if (options.options !== undefined && options.options.length > 0) {
|
|
17
|
+
const longest = Math.max(...options.options.map((option) => option.flag.length));
|
|
18
18
|
lines.push('', 'Options:');
|
|
19
|
-
for (const option of
|
|
19
|
+
for (const option of options.options) {
|
|
20
20
|
lines.push(` ${option.flag.padEnd(longest)} ${option.description}`);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `version` — prints just the running daemon's version string. Sourced from
|
|
5
5
|
* `GET /api/daemon/status`'s `version` field (`packages/http/src/daemon-status.ts`,
|
|
6
6
|
* `DaemonStatusResponse.version`) — the same route `daemon-command.ts`'s `daemon status`
|
|
7
|
-
* already calls. `packages/cli/
|
|
7
|
+
* already calls. `packages/cli/archived provenance ledger`'s original UNCLEAR verdict on a standalone
|
|
8
8
|
* `version` command noted "@jini-ai/http-kit has no version route yet ... nothing to call"; that
|
|
9
9
|
* blocker never actually required a *separate* route — `daemonStatusRoute`'s response body
|
|
10
10
|
* already carries a `version` field, so no new HTTP route was added for this command. It just
|
|
@@ -21,18 +21,30 @@ export interface VersionCommandDeps {
|
|
|
21
21
|
/** Resolves the daemon HTTP base URL once per command invocation (e.g. wraps `resolveDaemonUrl`). */
|
|
22
22
|
resolveBaseUrl: () => Promise<string> | string;
|
|
23
23
|
/** Defaults to `process.stdout.write`; inject for tests. Used for the printed version string. */
|
|
24
|
-
write?: (
|
|
24
|
+
write?: (requiredArgs: {
|
|
25
|
+
text: string;
|
|
26
|
+
}) => void;
|
|
25
27
|
/** Defaults to `process.stderr.write`; inject for tests. Used for usage/validation errors. */
|
|
26
|
-
writeErr?: (
|
|
28
|
+
writeErr?: (requiredArgs: {
|
|
29
|
+
text: string;
|
|
30
|
+
}) => void;
|
|
27
31
|
/** Defaults to the global `fetch`; inject for tests. */
|
|
28
32
|
fetchImpl?: typeof fetch;
|
|
29
33
|
/** Defaults to `process.exit`; inject for tests (must not return). */
|
|
30
|
-
exit?: (
|
|
34
|
+
exit?: (requiredArgs: {
|
|
35
|
+
code: number;
|
|
36
|
+
}) => never;
|
|
31
37
|
/** Extra/overriding `code -> exitCode` entries layered on the package default table. */
|
|
32
38
|
exitCodes?: ExitCodeTable;
|
|
33
39
|
}
|
|
34
40
|
/** `version` */
|
|
35
|
-
export declare function versionCommand(args
|
|
41
|
+
export declare function versionCommand({ args, resolveBaseUrl }: {
|
|
42
|
+
args: readonly string[];
|
|
43
|
+
resolveBaseUrl: VersionCommandDeps["resolveBaseUrl"];
|
|
44
|
+
}, optionalArgs?: Omit<VersionCommandDeps, "resolveBaseUrl">): Promise<void>;
|
|
36
45
|
/** Registers `version` against `registry`. */
|
|
37
|
-
export declare function registerVersionCommand(registry
|
|
46
|
+
export declare function registerVersionCommand({ registry, resolveBaseUrl }: {
|
|
47
|
+
registry: CommandRegistry;
|
|
48
|
+
resolveBaseUrl: VersionCommandDeps["resolveBaseUrl"];
|
|
49
|
+
}, optionalArgs?: Omit<VersionCommandDeps, "resolveBaseUrl">): void;
|
|
38
50
|
//# sourceMappingURL=version-command.d.ts.map
|
package/dist/version-command.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `version` — prints just the running daemon's version string. Sourced from
|
|
5
5
|
* `GET /api/daemon/status`'s `version` field (`packages/http/src/daemon-status.ts`,
|
|
6
6
|
* `DaemonStatusResponse.version`) — the same route `daemon-command.ts`'s `daemon status`
|
|
7
|
-
* already calls. `packages/cli/
|
|
7
|
+
* already calls. `packages/cli/archived provenance ledger`'s original UNCLEAR verdict on a standalone
|
|
8
8
|
* `version` command noted "@jini-ai/http-kit has no version route yet ... nothing to call"; that
|
|
9
9
|
* blocker never actually required a *separate* route — `daemonStatusRoute`'s response body
|
|
10
10
|
* already carries a `version` field, so no new HTTP route was added for this command. It just
|
|
@@ -18,21 +18,18 @@
|
|
|
18
18
|
import { exitWithStructuredError } from './errors.js';
|
|
19
19
|
import { getJsonFromDaemon } from './http.js';
|
|
20
20
|
import { renderUsage } from './usage.js';
|
|
21
|
-
function defaultWrite(text) {
|
|
21
|
+
function defaultWrite({ text }) {
|
|
22
22
|
process.stdout.write(text);
|
|
23
23
|
}
|
|
24
|
-
function defaultWriteErr(text) {
|
|
24
|
+
function defaultWriteErr({ text }) {
|
|
25
25
|
process.stderr.write(text);
|
|
26
26
|
}
|
|
27
|
-
const VERSION_USAGE = renderUsage({
|
|
28
|
-
usage: ['version'],
|
|
29
|
-
description: 'Prints the running daemon\'s version string (nothing else — see "daemon status" for the full status envelope).',
|
|
30
|
-
});
|
|
27
|
+
const VERSION_USAGE = renderUsage({ usage: ['version'] }, { description: 'Prints the running daemon\'s version string (nothing else — see "daemon status" for the full status envelope).' });
|
|
31
28
|
/** Builds `{write, exit, exitCodes?}` for `errors.ts`'s structured-error helpers, never assigning `exitCodes` when unset (required under `exactOptionalPropertyTypes`). */
|
|
32
29
|
function errorOptions(deps) {
|
|
33
30
|
return {
|
|
34
31
|
write: deps.writeErr ?? defaultWriteErr,
|
|
35
|
-
exit: deps.exit ?? ((code) => process.exit(code)),
|
|
32
|
+
exit: deps.exit ?? (({ code }) => process.exit(code)),
|
|
36
33
|
...(deps.exitCodes !== undefined ? { exitCodes: deps.exitCodes } : {}),
|
|
37
34
|
};
|
|
38
35
|
}
|
|
@@ -61,18 +58,20 @@ function extractVersion(deps, body) {
|
|
|
61
58
|
return exitWithStructuredError({ code: 'daemon-not-running', message: '/api/daemon/status response did not include a version string' }, errorOptions(deps));
|
|
62
59
|
}
|
|
63
60
|
/** `version` */
|
|
64
|
-
export async function versionCommand(args,
|
|
61
|
+
export async function versionCommand({ args, resolveBaseUrl }, optionalArgs = {}) {
|
|
62
|
+
const deps = { resolveBaseUrl, ...optionalArgs };
|
|
65
63
|
if (args.includes('--help') || args.includes('-h')) {
|
|
66
|
-
(deps.write ?? defaultWrite)(`${VERSION_USAGE}\n`);
|
|
64
|
+
(deps.write ?? defaultWrite)({ text: `${VERSION_USAGE}\n` });
|
|
67
65
|
return;
|
|
68
66
|
}
|
|
69
67
|
const baseUrl = await deps.resolveBaseUrl();
|
|
70
|
-
const result = await getJsonFromDaemon(baseUrl, '/api/daemon/status', transportOptions(deps));
|
|
68
|
+
const result = await getJsonFromDaemon({ base: baseUrl, route: '/api/daemon/status' }, transportOptions(deps));
|
|
71
69
|
const version = extractVersion(deps, result);
|
|
72
|
-
(deps.write ?? defaultWrite)(`${version}\n`);
|
|
70
|
+
(deps.write ?? defaultWrite)({ text: `${version}\n` });
|
|
73
71
|
}
|
|
74
72
|
/** Registers `version` against `registry`. */
|
|
75
|
-
export function registerVersionCommand(registry,
|
|
76
|
-
|
|
73
|
+
export function registerVersionCommand({ registry, resolveBaseUrl }, optionalArgs = {}) {
|
|
74
|
+
const deps = { resolveBaseUrl, ...optionalArgs };
|
|
75
|
+
registry.add({ name: 'version', handler: ({ args }) => versionCommand({ args, resolveBaseUrl: resolveBaseUrl }, optionalArgs) }, { usage: VERSION_USAGE });
|
|
77
76
|
}
|
|
78
77
|
//# sourceMappingURL=version-command.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "CLI transport shell for driving a Jini daemon over HTTP: flag parsing, daemon-URL resolution, local daemon discovery, and a pluggable command registry.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"jini": {
|
|
12
12
|
"domain": "server",
|
|
13
13
|
"kind": "cli",
|
|
14
|
+
"entries": {
|
|
15
|
+
".": "node",
|
|
16
|
+
"./introspection": "universal"
|
|
17
|
+
},
|
|
14
18
|
"runtime": "node"
|
|
15
19
|
},
|
|
16
20
|
"type": "module",
|
|
@@ -22,6 +26,11 @@
|
|
|
22
26
|
"types": "./dist/index.d.ts",
|
|
23
27
|
"import": "./dist/index.js",
|
|
24
28
|
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./introspection": {
|
|
31
|
+
"types": "./dist/introspection.d.ts",
|
|
32
|
+
"import": "./dist/introspection.js",
|
|
33
|
+
"default": "./dist/introspection.js"
|
|
25
34
|
}
|
|
26
35
|
},
|
|
27
36
|
"bin": {
|
|
@@ -29,6 +38,9 @@
|
|
|
29
38
|
},
|
|
30
39
|
"files": [
|
|
31
40
|
"dist",
|
|
41
|
+
"README.md",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"LICENSE",
|
|
32
44
|
"!dist/**/*.test.js",
|
|
33
45
|
"!dist/**/*.test.d.ts",
|
|
34
46
|
"!dist/**/*.test.js.map",
|
|
@@ -40,9 +52,26 @@
|
|
|
40
52
|
"access": "public",
|
|
41
53
|
"registry": "https://registry.npmjs.org"
|
|
42
54
|
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"commander": "^15.0.0",
|
|
57
|
+
"@jini-ai/core": "0.4.2",
|
|
58
|
+
"@jini-ai/sidecar": "0.4.1",
|
|
59
|
+
"@jini-ai/platform": "0.5.2"
|
|
60
|
+
},
|
|
43
61
|
"dependencies": {
|
|
44
|
-
"@jini-ai/core": "0.
|
|
45
|
-
|
|
62
|
+
"@jini-ai/core": "^0.4.2"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@jini-ai/platform": "^0.5.1",
|
|
66
|
+
"@jini-ai/sidecar": "^0.4.0"
|
|
67
|
+
},
|
|
68
|
+
"peerDependenciesMeta": {
|
|
69
|
+
"@jini-ai/platform": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"@jini-ai/sidecar": {
|
|
73
|
+
"optional": true
|
|
74
|
+
}
|
|
46
75
|
},
|
|
47
76
|
"scripts": {
|
|
48
77
|
"build": "tsc -p tsconfig.json && chmod +x dist/main.js",
|