@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.
@@ -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 `source-map.md`
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 './redact.js';
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 RUN_LIST_USAGE = renderUsage({
43
- usage: ['run list [--context-ref <ref>]'],
44
- description: 'Lists runs, optionally scoped to a contextRef.',
45
- options: [{ flag: '--context-ref', description: 'Only list runs belonging to this contextRef.' }],
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, deps) {
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, deps) {
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, deps) {
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)}`, transportOptions(deps));
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, deps) {
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 bytes — a daemon that never sends a blank-line frame terminator must not grow this without bound (mirrors `http.ts`'s `readJsonWithLimit` byte cap). */
162
- const MAX_SSE_BUFFER_BYTES = 10 * 1024 * 1024;
163
- function parseSseFrame(rawFrame) {
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
- const decoder = new TextDecoder();
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
- break;
153
+ return;
189
154
  if (value !== undefined)
190
- buffer += decoder.decode(value, { stream: true });
191
- if (buffer.length > MAX_SSE_BUFFER_BYTES) {
192
- throw new Error(`SSE stream exceeded the ${MAX_SSE_BUFFER_BYTES}-byte unterminated-frame buffer limit`);
193
- }
194
- let sepIndex;
195
- while ((sepIndex = buffer.indexOf('\n\n')) >= 0) {
196
- const rawFrame = buffer.slice(0, sepIndex);
197
- buffer = buffer.slice(sepIndex + 2);
198
- const frame = parseSseFrame(rawFrame);
199
- if (frame !== null)
200
- yield frame;
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, deps) {
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, deps) {
268
- registry.add('run', async (args) => {
269
- const [sub, ...rest] = args;
270
- switch (sub) {
271
- case 'start':
272
- return runStartCommand(rest, deps);
273
- case 'list':
274
- return runListCommand(rest, deps);
275
- case 'get':
276
- return runGetCommand(rest, deps);
277
- case 'cancel':
278
- return runCancelCommand(rest, deps);
279
- case 'watch':
280
- return runWatchCommand(rest, deps);
281
- case undefined:
282
- case '--help':
283
- case '-h':
284
- (deps.write ?? defaultWrite)(`${RUN_USAGE}\n`);
285
- return;
286
- default:
287
- invalidFlag(deps, `unknown "run" subcommand: ${sub}`);
288
- }
289
- }, { usage: RUN_USAGE });
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
@@ -8,5 +8,5 @@
8
8
  * subcommands against.
9
9
  */
10
10
  import { token } from '@jini-ai/core';
11
- export const CommandRegistryToken = token('jini.cli.commandRegistry');
11
+ export const CommandRegistryToken = token({ id: 'jini.cli.commandRegistry' });
12
12
  //# sourceMappingURL=tokens.js.map
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 `source-map.md`): one or more usage
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(spec: UsageSpec): string;
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 `source-map.md`): one or more usage
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(spec) {
12
- const lines = ['Usage:', ...spec.usage.map((line) => ` ${line}`)];
13
- if (spec.description !== undefined && spec.description.length > 0) {
14
- lines.push('', spec.description);
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 (spec.options !== undefined && spec.options.length > 0) {
17
- const longest = Math.max(...spec.options.map((option) => option.flag.length));
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 spec.options) {
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/source-map.md`'s original UNCLEAR verdict on a standalone
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?: (text: string) => void;
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?: (text: string) => void;
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?: (code: number) => never;
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: readonly string[], deps: VersionCommandDeps): Promise<void>;
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: CommandRegistry, deps: VersionCommandDeps): void;
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
@@ -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/source-map.md`'s original UNCLEAR verdict on a standalone
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, deps) {
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, deps) {
76
- registry.add('version', (args) => versionCommand(args, deps), { usage: VERSION_USAGE });
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.1",
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.3.1",
45
- "@jini-ai/sidecar": "0.3.0"
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",