@jini-ai/cli 0.3.1 → 0.4.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.
@@ -2,13 +2,13 @@
2
2
  * @module local-daemon-discovery
3
3
  *
4
4
  * The real `discover` callback `resolveDaemonUrl` (`daemon-url.ts`) has always accepted but this
5
- * package never implemented — see `source-map.md`'s "Deferred" item 1 and its own 2026-07-21
5
+ * package never implemented — see `archived provenance ledger`'s "Deferred" item 1 and its own 2026-07-21
6
6
  * investigation, which found `@jini-ai/server`'s `createLocalNodeDaemon` wrote nothing anywhere a
7
7
  * separate CLI process could read. That daemon side is now built (`@jini-ai/server`'s
8
8
  * `discoveryFile` config, backed by `@jini-ai/sidecar`'s `daemon-registry.ts`); this module is the
9
9
  * matching CLI-side reader: `createLocalDaemonDiscovery(...)` builds a probe over that same
10
10
  * on-disk record, verified live (not just present — see `readLiveDaemonRegistryRecord`'s own
11
- * doc), that plugs directly into `resolveDaemonUrl({ discover })`.
11
+ * doc), that plugs directly into `resolveDaemonUrl({}, { discover })`.
12
12
  *
13
13
  * Deliberately not wired to a hardcoded default `dataDir`: exactly like `resolveDaemonUrl` itself
14
14
  * has no baked-in env var name or default URL, this package has no opinion on where a given
@@ -23,14 +23,14 @@ import { readLiveDaemonRegistryRecord, resolveDaemonRegistryPath } from '@jini-a
23
23
  * `registryPath`/`dataDir` was supplied.
24
24
  */
25
25
  function resolveRegistryPathFromOptions(options) {
26
- if (options.registryPath !== undefined)
26
+ if ("registryPath" in options)
27
27
  return options.registryPath;
28
- if (options.dataDir !== undefined)
29
- return resolveDaemonRegistryPath(options.dataDir);
28
+ if ("dataDir" in options)
29
+ return resolveDaemonRegistryPath({ dataDir: options.dataDir });
30
30
  throw new Error('createLocalDaemonDiscovery requires either dataDir or registryPath');
31
31
  }
32
32
  /**
33
- * Build a `resolveDaemonUrl({ discover })`-compatible probe backed by a local, on-disk daemon
33
+ * Build a `resolveDaemonUrl({}, { discover })`-compatible probe backed by a local, on-disk daemon
34
34
  * registry record: reads the record at the resolved registry path, confirms the recording
35
35
  * process's pid is still alive (a stale record left behind by a daemon that crashed rather than
36
36
  * shut down cleanly is never trusted — see `readLiveDaemonRegistryRecord`), and returns its
@@ -46,7 +46,7 @@ function resolveRegistryPathFromOptions(options) {
46
46
  export function createLocalDaemonDiscovery(options) {
47
47
  const registryPath = resolveRegistryPathFromOptions(options);
48
48
  return async () => {
49
- const record = await readLiveDaemonRegistryRecord(registryPath);
49
+ const record = await readLiveDaemonRegistryRecord({ registryPath });
50
50
  return record?.url ?? null;
51
51
  };
52
52
  }
package/dist/main.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  export interface MainDeps {
3
3
  /** Defaults to `process.stdout.write`; inject for tests. */
4
- write?: (text: string) => void;
4
+ write?: (requiredArgs: {
5
+ text: string;
6
+ }) => void;
5
7
  /** Defaults to `process.stderr.write`; inject for tests. */
6
- writeErr?: (text: string) => void;
8
+ writeErr?: (requiredArgs: {
9
+ text: string;
10
+ }) => void;
7
11
  /** Defaults to `process.exit`; inject for tests (must not return). */
8
- exit?: (code: number) => never;
12
+ exit?: (requiredArgs: {
13
+ code: number;
14
+ }) => never;
9
15
  /** Defaults to the global `fetch`; inject for tests. */
10
16
  fetchImpl?: typeof fetch;
11
17
  /** Defaults to `process.env`; inject for tests. */
@@ -17,5 +23,7 @@ export interface MainDeps {
17
23
  * caller under normal use — usage errors and daemon failures both exit through this package's
18
24
  * structured-error contract (`errors.ts`), via the injected (or default, real) `exit`.
19
25
  */
20
- export declare function main(argv: readonly string[], deps?: MainDeps): Promise<void>;
26
+ export declare function main({ argv }: {
27
+ argv: readonly string[];
28
+ }, deps?: MainDeps): Promise<void>;
21
29
  //# sourceMappingURL=main.d.ts.map
package/dist/main.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * other module in this package is a library building block — `run-command.ts`,
7
7
  * `daemon-command.ts`, and `version-command.ts` each export a `register*Commands(registry,
8
8
  * deps)` function ready for a pack to call, but until this file, nothing actually parsed
9
- * `process.argv` and called them: `packages/cli/source-map.md`'s "Not built" note on
9
+ * `process.argv` and called them: `packages/cli/archived provenance ledger`'s "Not built" note on
10
10
  * `run-command.ts`'s original addition said as much — "no wiring of this file's commands into
11
11
  * an actual bootable CLI entrypoint." This closes that gap.
12
12
  *
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * **Daemon-URL resolution.** `resolveDaemonUrl` (`daemon-url.ts`) has no baked-in default —
24
24
  * this package has never had a locked default daemon port to fall back to (see
25
- * `source-map.md`: `@jini-ai/server`'s `createLocalNodeDaemon` binds an ephemeral port, not a
25
+ * `archived provenance ledger`: `@jini-ai/server`'s `createLocalNodeDaemon` binds an ephemeral port, not a
26
26
  * fixed one). This binary wires the three real, already-built pieces together instead of
27
27
  * inventing a new one: an explicit `--daemon-url <url>` flag (highest precedence), a
28
28
  * `JINI_DAEMON_URL` env var, and — when `--data-dir <path>` or `--registry-path <path>` is
@@ -48,12 +48,12 @@
48
48
  * dispatch, so `run`/`daemon`/`version` always land as a clean first token no matter where on
49
49
  * the command line `--daemon-url`/`--data-dir`/`--registry-path` were typed.
50
50
  *
51
- * **`--version`/`-v`.** This package's `package.json` is `"private": true` with a placeholder
52
- * `"0.0.0"` version — not a real, published semver worth printing. Rather than invent one,
51
+ * **`--version`/`-v`.** This package has its own published semver, but this transport command reports
52
+ * the daemon it is controlling so operators can identify the running service. Accordingly,
53
53
  * `jini --version`/`jini -v` (recognized only as the very first token — see {@link main}) is a
54
54
  * plain alias for `jini version` (`version-command.ts`): it prints the *running daemon's* real
55
55
  * version. That is arguably more useful to a user of this transport-only CLI than this
56
- * package's own unpublished build number would be, and it means `--version` has exactly one
56
+ * package's own transport version would be, and it means `--version` has exactly one
57
57
  * implementation to keep correct, not two.
58
58
  *
59
59
  * **Error boundary.** `parseFlags` (`flags.ts`) throws a plain `Error` for an unrecognized
@@ -83,25 +83,21 @@ import { createLocalDaemonDiscovery } from './local-daemon-discovery.js';
83
83
  import { registerRunCommands } from './run-command.js';
84
84
  import { renderUsage } from './usage.js';
85
85
  import { registerVersionCommand } from './version-command.js';
86
- function defaultWrite(text) {
86
+ function defaultWrite({ text }) {
87
87
  process.stdout.write(text);
88
88
  }
89
- function defaultWriteErr(text) {
89
+ function defaultWriteErr({ text }) {
90
90
  process.stderr.write(text);
91
91
  }
92
92
  /** Name of the environment variable {@link resolveDaemonUrl} checks when `--daemon-url` is absent. */
93
93
  const DAEMON_URL_ENV_VAR = 'JINI_DAEMON_URL';
94
- const ROOT_USAGE = renderUsage({
95
- usage: ['jini <command> [...args]'],
96
- description: 'CLI transport for a Jini daemon over HTTP. Commands: run, daemon, version.',
97
- options: [
94
+ const ROOT_USAGE = renderUsage({ usage: ['jini <command> [...args]'] }, { description: 'CLI transport for a Jini daemon over HTTP. Commands: run, daemon, version.', options: [
98
95
  { flag: '--daemon-url <url>', description: 'Explicit daemon base URL. Takes precedence over JINI_DAEMON_URL and local discovery.' },
99
96
  { flag: '--data-dir <path>', description: "Resolve a locally running daemon's URL from this data directory's on-disk registry (see @jini-ai/server's createLocalNodeDaemon)." },
100
97
  { flag: '--registry-path <path>', description: "Exact registry file path, overriding --data-dir's derived default." },
101
98
  { flag: '--help, -h', description: 'Show this help.' },
102
99
  { flag: '--version, -v', description: 'Print the running daemon\'s version (alias for "jini version"; recognized only as the first argument).' },
103
- ],
104
- });
100
+ ] });
105
101
  const GLOBAL_FLAG_NAMES = ['daemon-url', 'data-dir', 'registry-path'];
106
102
  /**
107
103
  * Single-pass extraction of this binary's own global `--daemon-url`/`--data-dir`/
@@ -154,7 +150,7 @@ function buildResolveBaseUrl(globals, env, warn) {
154
150
  : dataDir !== undefined
155
151
  ? createLocalDaemonDiscovery({ dataDir })
156
152
  : undefined;
157
- return () => resolveDaemonUrl({
153
+ return () => resolveDaemonUrl({}, {
158
154
  flagUrl: daemonUrl ?? null,
159
155
  env,
160
156
  envVarName: DAEMON_URL_ENV_VAR,
@@ -168,13 +164,13 @@ function buildResolveBaseUrl(globals, env, warn) {
168
164
  * caller under normal use — usage errors and daemon failures both exit through this package's
169
165
  * structured-error contract (`errors.ts`), via the injected (or default, real) `exit`.
170
166
  */
171
- export async function main(argv, deps = {}) {
167
+ export async function main({ argv }, deps = {}) {
172
168
  const write = deps.write ?? defaultWrite;
173
169
  const writeErr = deps.writeErr ?? defaultWriteErr;
174
- const rawExit = deps.exit ?? ((code) => process.exit(code));
170
+ const rawExit = deps.exit ?? (({ code }) => process.exit(code));
175
171
  const env = deps.env ?? process.env;
176
172
  if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
177
- write(`${ROOT_USAGE}\n`);
173
+ write({ text: `${ROOT_USAGE}\n` });
178
174
  return;
179
175
  }
180
176
  // `--version`/`-v` as the very first token is a plain alias for `jini version` — see this
@@ -184,26 +180,28 @@ export async function main(argv, deps = {}) {
184
180
  // already exited cleanly" (re-throw untouched) apart from "a genuinely raw, unhandled throw"
185
181
  // (reformat into a structured error) — see this file's own module doc, "Error boundary."
186
182
  let exitCalled = false;
187
- const exit = (code) => {
183
+ const exit = ({ code }) => {
188
184
  exitCalled = true;
189
- return rawExit(code);
185
+ return rawExit({ code });
190
186
  };
191
187
  let result;
192
188
  try {
193
189
  const { globals, rest } = partitionGlobalArgv(effectiveArgv);
194
- const resolveBaseUrl = buildResolveBaseUrl(globals, env, writeErr);
195
- const commandDeps = {
196
- resolveBaseUrl,
197
- write,
198
- writeErr,
199
- exit,
200
- ...(deps.fetchImpl !== undefined ? { fetchImpl: deps.fetchImpl } : {}),
201
- };
202
- const registry = new CommandRegistry();
203
- registerRunCommands(registry, commandDeps);
204
- registerDaemonCommands(registry, commandDeps);
205
- registerVersionCommand(registry, commandDeps);
206
- result = await registry.dispatch(rest);
190
+ const resolveBaseUrl = buildResolveBaseUrl(globals, env, ({ message }) => writeErr({ text: message }));
191
+ const registry = new CommandRegistry({});
192
+ registerRunCommands({ registry, resolveBaseUrl }, {
193
+ write, writeErr, exit,
194
+ ...(deps.fetchImpl === undefined ? {} : { fetchImpl: deps.fetchImpl }),
195
+ });
196
+ registerDaemonCommands({ registry, resolveBaseUrl }, {
197
+ write, writeErr, exit,
198
+ ...(deps.fetchImpl === undefined ? {} : { fetchImpl: deps.fetchImpl }),
199
+ });
200
+ registerVersionCommand({ registry, resolveBaseUrl }, {
201
+ write, writeErr, exit,
202
+ ...(deps.fetchImpl === undefined ? {} : { fetchImpl: deps.fetchImpl }),
203
+ });
204
+ result = await registry.dispatch({ argv: rest });
207
205
  }
208
206
  catch (error) {
209
207
  if (exitCalled)
@@ -213,7 +211,7 @@ export async function main(argv, deps = {}) {
213
211
  return;
214
212
  }
215
213
  if (result.kind === 'empty') {
216
- write(`${ROOT_USAGE}\n`);
214
+ write({ text: `${ROOT_USAGE}\n` });
217
215
  return;
218
216
  }
219
217
  if (result.kind === 'not-found') {
@@ -226,6 +224,6 @@ export async function main(argv, deps = {}) {
226
224
  // `require.main === module` idiom for ESM; see this file's own module doc.
227
225
  const isMainModule = process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1];
228
226
  if (isMainModule) {
229
- await main(process.argv.slice(2));
227
+ await main({ argv: process.argv.slice(2) });
230
228
  }
231
229
  //# sourceMappingURL=main.js.map
package/dist/prompt.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * `readPromptFromFlags` is the `--prompt <text>` / `--prompt-file <path>` /
9
9
  * `--prompt-file -` convention, ported from OD's `apps/daemon/src/cli.ts`
10
- * `readPromptFromFlags` (see `source-map.md`). This exact pattern recurred
10
+ * `readPromptFromFlags` (see `archived provenance ledger`). This exact pattern recurred
11
11
  * verbatim across many of OD's product commands (`brand create`, `run
12
12
  * start`, `files version-create`, `automation source ingest`, …) with zero
13
13
  * product nouns in the reading logic itself, which is what makes it a
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * `readBodyFromFlags` is the sibling `--body`/`--body-file` convention,
18
18
  * ported from the `cli-capability-barrels` branch's `cli/core/io.ts`
19
- * `readMemoryBodyFromFlags` (see `source-map.md`'s "Barrel branch
19
+ * `readMemoryBodyFromFlags` (see `archived provenance ledger`'s "Barrel branch
20
20
  * reconciliation" section). Despite the origin name, it has no memory-domain
21
21
  * coupling — that module's own docblock confirms it is reused verbatim by
22
22
  * both the memory and automation domains — so it is ported here under a
@@ -34,7 +34,9 @@
34
34
  */
35
35
  /** Thrown by the default readers when a file or stdin stream exceeds its configured byte cap. */
36
36
  export declare class PayloadTooLargeError extends Error {
37
- constructor(message: string);
37
+ constructor({ message }: {
38
+ message: string;
39
+ });
38
40
  }
39
41
  export interface PromptFlags {
40
42
  prompt?: string;
@@ -42,7 +44,9 @@ export interface PromptFlags {
42
44
  }
43
45
  export interface ReadPromptFromFlagsOptions {
44
46
  /** Defaults to a bounded, cancellable `node:fs` stream read; inject for tests. */
45
- readFile?: (path: string) => Promise<string>;
47
+ readFile?: (requiredArgs: {
48
+ path: string;
49
+ }) => Promise<string>;
46
50
  /** Defaults to a bounded, cancellable read of `process.stdin` to EOF as utf8; inject for tests. */
47
51
  readStdin?: () => Promise<string>;
48
52
  /** Maximum bytes the default readFile/readStdin will read before rejecting. Defaults to 10 MiB. No effect on an injected reader. */
@@ -55,14 +59,18 @@ export interface ReadPromptFromFlagsOptions {
55
59
  * `--prompt-file -` (stdin). Returns `null` when neither flag is set so the
56
60
  * caller can fall back to its own default.
57
61
  */
58
- export declare function readPromptFromFlags(flags: PromptFlags, options?: ReadPromptFromFlagsOptions): Promise<string | null>;
62
+ export declare function readPromptFromFlags({ flags }: {
63
+ flags: PromptFlags;
64
+ }, options?: ReadPromptFromFlagsOptions): Promise<string | null>;
59
65
  export interface BodyFlags {
60
66
  body?: string;
61
67
  'body-file'?: string;
62
68
  }
63
69
  export interface ReadBodyFromFlagsOptions {
64
70
  /** Defaults to a bounded, cancellable `node:fs` stream read; inject for tests. */
65
- readFile?: (path: string) => Promise<string>;
71
+ readFile?: (requiredArgs: {
72
+ path: string;
73
+ }) => Promise<string>;
66
74
  /** Defaults to a bounded, cancellable drain of `process.stdin`'s async iterator as utf8; inject for tests. */
67
75
  readStdin?: () => Promise<string>;
68
76
  /** Maximum bytes the default readFile/readStdin will read before rejecting. Defaults to 10 MiB. No effect on an injected reader. */
@@ -79,5 +87,7 @@ export interface ReadBodyFromFlagsOptions {
79
87
  * primitive's origin caller (`od memory tree edit`) needs to allow clearing
80
88
  * a body to an empty string.
81
89
  */
82
- export declare function readBodyFromFlags(flags: BodyFlags, options?: ReadBodyFromFlagsOptions): Promise<string | undefined>;
90
+ export declare function readBodyFromFlags({ flags }: {
91
+ flags: BodyFlags;
92
+ }, options?: ReadBodyFromFlagsOptions): Promise<string | undefined>;
83
93
  //# sourceMappingURL=prompt.d.ts.map
package/dist/prompt.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * `readPromptFromFlags` is the `--prompt <text>` / `--prompt-file <path>` /
9
9
  * `--prompt-file -` convention, ported from OD's `apps/daemon/src/cli.ts`
10
- * `readPromptFromFlags` (see `source-map.md`). This exact pattern recurred
10
+ * `readPromptFromFlags` (see `archived provenance ledger`). This exact pattern recurred
11
11
  * verbatim across many of OD's product commands (`brand create`, `run
12
12
  * start`, `files version-create`, `automation source ingest`, …) with zero
13
13
  * product nouns in the reading logic itself, which is what makes it a
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * `readBodyFromFlags` is the sibling `--body`/`--body-file` convention,
18
18
  * ported from the `cli-capability-barrels` branch's `cli/core/io.ts`
19
- * `readMemoryBodyFromFlags` (see `source-map.md`'s "Barrel branch
19
+ * `readMemoryBodyFromFlags` (see `archived provenance ledger`'s "Barrel branch
20
20
  * reconciliation" section). Despite the origin name, it has no memory-domain
21
21
  * coupling — that module's own docblock confirms it is reused verbatim by
22
22
  * both the memory and automation domains — so it is ported here under a
@@ -34,7 +34,7 @@
34
34
  */
35
35
  /** Thrown by the default readers when a file or stdin stream exceeds its configured byte cap. */
36
36
  export class PayloadTooLargeError extends Error {
37
- constructor(message) {
37
+ constructor({ message }) {
38
38
  super(message);
39
39
  this.name = 'PayloadTooLargeError';
40
40
  }
@@ -46,7 +46,7 @@ const DEFAULT_MAX_READ_BYTES = 10 * 1024 * 1024;
46
46
  * `--prompt-file -` (stdin). Returns `null` when neither flag is set so the
47
47
  * caller can fall back to its own default.
48
48
  */
49
- export async function readPromptFromFlags(flags, options = {}) {
49
+ export async function readPromptFromFlags({ flags }, options = {}) {
50
50
  if (typeof flags.prompt === 'string' && flags.prompt.length > 0) {
51
51
  return flags.prompt;
52
52
  }
@@ -59,8 +59,8 @@ export async function readPromptFromFlags(flags, options = {}) {
59
59
  const readStdin = options.readStdin ?? (() => defaultReadStdin(readLimits));
60
60
  return await readStdin();
61
61
  }
62
- const readFile = options.readFile ?? ((path) => defaultReadFile(path, readLimits));
63
- return await readFile(promptFile);
62
+ const readFile = options.readFile ?? (({ path }) => defaultReadFile(path, readLimits));
63
+ return await readFile({ path: promptFile });
64
64
  }
65
65
  async function defaultReadFile(path, limits) {
66
66
  const { createReadStream } = await import('node:fs');
@@ -76,7 +76,7 @@ async function defaultReadFile(path, limits) {
76
76
  // suppress any further 'error' from the destroy path itself). Kept as
77
77
  // real protection against a future/third-party stream implementation
78
78
  // bug, not something this module's own real `createReadStream` usage
79
- // can trigger — see `source-map.md`'s dated entry for the full
79
+ // can trigger — see `archived provenance ledger`'s dated entry for the full
80
80
  // re-verification record (a single terminal event is all this file's
81
81
  // own usage can ever produce).
82
82
  const finish = (fn) => {
@@ -98,7 +98,7 @@ async function defaultReadFile(path, limits) {
98
98
  total += chunk.length;
99
99
  if (total > limits.maxBytes) {
100
100
  stream.destroy();
101
- finish(() => reject(new PayloadTooLargeError(`file exceeded the ${limits.maxBytes}-byte limit: ${path}`)));
101
+ finish(() => reject(new PayloadTooLargeError({ message: `file exceeded the ${limits.maxBytes}-byte limit: ${path}` })));
102
102
  return;
103
103
  }
104
104
  chunks.push(chunk);
@@ -130,7 +130,7 @@ async function defaultReadStdin(limits) {
130
130
  if (total > limits.maxBytes) {
131
131
  cleanup();
132
132
  pauseStdin();
133
- reject(new PayloadTooLargeError(`stdin exceeded the ${limits.maxBytes}-byte limit`));
133
+ reject(new PayloadTooLargeError({ message: `stdin exceeded the ${limits.maxBytes}-byte limit` }));
134
134
  return;
135
135
  }
136
136
  buffer += chunk;
@@ -157,7 +157,7 @@ async function defaultReadStdin(limits) {
157
157
  * primitive's origin caller (`od memory tree edit`) needs to allow clearing
158
158
  * a body to an empty string.
159
159
  */
160
- export async function readBodyFromFlags(flags, options = {}) {
160
+ export async function readBodyFromFlags({ flags }, options = {}) {
161
161
  if (typeof flags.body === 'string')
162
162
  return flags.body;
163
163
  const bodyFile = flags['body-file'];
@@ -168,8 +168,8 @@ export async function readBodyFromFlags(flags, options = {}) {
168
168
  const readStdin = options.readStdin ?? (() => defaultReadBodyStdin(readLimits));
169
169
  return await readStdin();
170
170
  }
171
- const readFile = options.readFile ?? ((path) => defaultReadFile(path, readLimits));
172
- return await readFile(bodyFile);
171
+ const readFile = options.readFile ?? (({ path }) => defaultReadFile(path, readLimits));
172
+ return await readFile({ path: bodyFile });
173
173
  }
174
174
  async function defaultReadBodyStdin(limits) {
175
175
  limits.signal?.throwIfAborted();
@@ -185,7 +185,7 @@ async function defaultReadBodyStdin(limits) {
185
185
  total += Buffer.byteLength(text, 'utf8');
186
186
  if (total > limits.maxBytes) {
187
187
  stdin.destroy?.();
188
- throw new PayloadTooLargeError(`stdin exceeded the ${limits.maxBytes}-byte limit`);
188
+ throw new PayloadTooLargeError({ message: `stdin exceeded the ${limits.maxBytes}-byte limit` });
189
189
  }
190
190
  body += text;
191
191
  }
package/dist/redact.d.ts CHANGED
@@ -17,25 +17,6 @@
17
17
  * arbitrary daemon JSON, or complete non-JSON response bodies straight to
18
18
  * the terminal.
19
19
  */
20
- /**
21
- * Strip ANSI/terminal escape sequences (CSI `ESC [ ... final-byte`, OSC `ESC ] ... BEL-or-ST`,
22
- * and bare ESC-prefixed bytes) plus other C0/C1 control characters from `text`. Written as an
23
- * explicit char-code scanner rather than a regex literal with hex escapes, so the pattern is
24
- * plain ASCII source with no embedded control bytes.
25
- */
26
- export declare function stripControlSequences(text: string): string;
27
- /** Redact labeled credentials and long opaque-token-looking substrings from `text`. */
28
- export declare function redactSecretLike(text: string): string;
29
- export interface SanitizeTextOptions {
30
- /** Maximum output length; longer input is truncated with a trailing marker. Defaults to 500. */
31
- maxLength?: number;
32
- }
33
- /**
34
- * Strip terminal control sequences, redact anything that looks like a secret, and cap the
35
- * length of `text` — the one function every boundary that surfaces daemon/network-derived text
36
- * to stderr/stdout should route through first.
37
- */
38
- export declare function sanitizeUntrustedText(text: string, options?: SanitizeTextOptions): string;
39
20
  /**
40
21
  * Recursively sanitize an arbitrary parsed-JSON value (the shape a daemon error envelope's
41
22
  * `data`/`details` field can legitimately be): every string leaf is passed through
@@ -44,5 +25,9 @@ export declare function sanitizeUntrustedText(text: string, options?: SanitizeTe
44
25
  * the cap is replaced with a placeholder rather than silently dropped, so truncation is visible
45
26
  * instead of looking like an empty/missing value.
46
27
  */
47
- export declare function sanitizeUnknownDeep(value: unknown, depth?: number): unknown;
28
+ export declare function sanitizeUnknownDeep({ value }: {
29
+ value: unknown;
30
+ }, { depth }?: {
31
+ depth?: number;
32
+ }): unknown;
48
33
  //# sourceMappingURL=redact.d.ts.map
package/dist/redact.js CHANGED
@@ -17,102 +17,10 @@
17
17
  * arbitrary daemon JSON, or complete non-JSON response bodies straight to
18
18
  * the terminal.
19
19
  */
20
- /** Hard cap on how much untrusted text {@link sanitizeUntrustedText} will ever return, absent an override. */
21
- const DEFAULT_MAX_EXCERPT_LENGTH = 500;
20
+ import { sanitizeUntrustedText } from '@jini-ai/core/text';
22
21
  /** Recursion/branching caps for {@link sanitizeUnknownDeep} — bounds the work done on an arbitrary parsed-JSON tree. */
23
22
  const MAX_SANITIZE_DEPTH = 4;
24
23
  const MAX_SANITIZE_ENTRIES = 50;
25
- const ESC = 0x1b;
26
- const BEL = 0x07;
27
- const BACKSLASH = 0x5c;
28
- const OSC_OPEN = 0x5d; // ']'
29
- const CSI_OPEN = 0x5b; // '['
30
- function isCsiFinalByte(code) {
31
- return code >= 0x40 && code <= 0x7e; // '@'..'~'
32
- }
33
- function isOtherControlCode(code) {
34
- if (code === 0x09 || code === 0x0a || code === 0x0d)
35
- return false; // tab / LF / CR pass through
36
- if (code <= 0x1f)
37
- return true; // remaining C0 controls (ESC itself is handled separately)
38
- if (code === 0x7f)
39
- return true; // DEL
40
- if (code >= 0x80 && code <= 0x9f)
41
- return true; // C1 controls
42
- return false;
43
- }
44
- /**
45
- * Strip ANSI/terminal escape sequences (CSI `ESC [ ... final-byte`, OSC `ESC ] ... BEL-or-ST`,
46
- * and bare ESC-prefixed bytes) plus other C0/C1 control characters from `text`. Written as an
47
- * explicit char-code scanner rather than a regex literal with hex escapes, so the pattern is
48
- * plain ASCII source with no embedded control bytes.
49
- */
50
- export function stripControlSequences(text) {
51
- let out = '';
52
- for (let i = 0; i < text.length; i++) {
53
- const code = text.charCodeAt(i);
54
- if (code === ESC) {
55
- const next = text.charCodeAt(i + 1);
56
- if (next === CSI_OPEN) {
57
- let j = i + 2;
58
- while (j < text.length && !isCsiFinalByte(text.charCodeAt(j)))
59
- j++;
60
- i = j; // land on the final byte; the outer loop's i++ steps past it
61
- continue;
62
- }
63
- if (next === OSC_OPEN) {
64
- let j = i + 2;
65
- while (j < text.length &&
66
- text.charCodeAt(j) !== BEL &&
67
- !(text.charCodeAt(j) === ESC && text.charCodeAt(j + 1) === BACKSLASH)) {
68
- j++;
69
- }
70
- if (j < text.length && text.charCodeAt(j) === BEL)
71
- i = j;
72
- else
73
- i = Math.min(j + 1, text.length - 1);
74
- continue;
75
- }
76
- continue; // bare ESC (or an unrecognized ESC-prefixed byte): drop just the ESC byte
77
- }
78
- if (isOtherControlCode(code))
79
- continue;
80
- out += text[i];
81
- }
82
- return out;
83
- }
84
- // A labeled credential (`Authorization: ...`, `api-key=...`) has everything after the label
85
- // redacted. A bare run of 20+ base64url/hex-alphabet characters is treated as a possible
86
- // opaque token/secret and redacted outright, even with no label.
87
- const LABELED_SECRET_RE = /\b(authorization|bearer|api[-_]?key|access[-_]?token|refresh[-_]?token|client[-_]?secret|password|secret|cookie)\b\s*[:=]\s*\S+/gi;
88
- const OPAQUE_TOKEN_RE = /[A-Za-z0-9_-]{20,}/g;
89
- /**
90
- * Whole opaque-token runs that are known not to be secrets, kept verbatim. Matched against the
91
- * ENTIRE run, so a secret glued onto one of these is still one longer run and still redacted:
92
- * - a host's tool-failure correlation id (`ERR-XXXX-XXXX-XXXX-XXXX`, upper-case hex — Tovu's
93
- * `mintToolErrorId`), which exists to be shown to the model and quoted back to an operator;
94
- * - `delegated-tool-calls`, the daemon route segment every delegated tool failure names in its URL.
95
- */
96
- const NON_SECRET_TOKEN_RE = /^(?:ERR-[0-9A-F]{4}(?:-[0-9A-F]{4}){3}|delegated-tool-calls)$/;
97
- /** Redact labeled credentials and long opaque-token-looking substrings from `text`. */
98
- export function redactSecretLike(text) {
99
- return text
100
- .replace(LABELED_SECRET_RE, (match) => match.replace(/\S+$/, '[redacted]'))
101
- .replace(OPAQUE_TOKEN_RE, (match) => (NON_SECRET_TOKEN_RE.test(match) ? match : '[redacted]'));
102
- }
103
- /**
104
- * Strip terminal control sequences, redact anything that looks like a secret, and cap the
105
- * length of `text` — the one function every boundary that surfaces daemon/network-derived text
106
- * to stderr/stdout should route through first.
107
- */
108
- export function sanitizeUntrustedText(text, options = {}) {
109
- const maxLength = options.maxLength ?? DEFAULT_MAX_EXCERPT_LENGTH;
110
- const sanitized = redactSecretLike(stripControlSequences(text));
111
- if (sanitized.length <= maxLength)
112
- return sanitized;
113
- const omitted = sanitized.length - maxLength;
114
- return `${sanitized.slice(0, maxLength)}… [truncated ${omitted} more characters]`;
115
- }
116
24
  /**
117
25
  * Recursively sanitize an arbitrary parsed-JSON value (the shape a daemon error envelope's
118
26
  * `data`/`details` field can legitimately be): every string leaf is passed through
@@ -121,18 +29,18 @@ export function sanitizeUntrustedText(text, options = {}) {
121
29
  * the cap is replaced with a placeholder rather than silently dropped, so truncation is visible
122
30
  * instead of looking like an empty/missing value.
123
31
  */
124
- export function sanitizeUnknownDeep(value, depth = 0) {
32
+ export function sanitizeUnknownDeep({ value }, { depth = 0 } = {}) {
125
33
  if (depth > MAX_SANITIZE_DEPTH)
126
34
  return '[omitted: nested too deeply]';
127
35
  if (typeof value === 'string')
128
- return sanitizeUntrustedText(value);
36
+ return sanitizeUntrustedText({ text: value });
129
37
  if (Array.isArray(value)) {
130
- return value.slice(0, MAX_SANITIZE_ENTRIES).map((item) => sanitizeUnknownDeep(item, depth + 1));
38
+ return value.slice(0, MAX_SANITIZE_ENTRIES).map((item) => sanitizeUnknownDeep({ value: item }, { depth: depth + 1 }));
131
39
  }
132
40
  if (value !== null && typeof value === 'object') {
133
41
  const out = {};
134
42
  for (const [key, val] of Object.entries(value).slice(0, MAX_SANITIZE_ENTRIES)) {
135
- out[sanitizeUntrustedText(key, { maxLength: 100 })] = sanitizeUnknownDeep(val, depth + 1);
43
+ out[sanitizeUntrustedText({ text: key }, { maxLength: 100 })] = sanitizeUnknownDeep({ value: val }, { depth: depth + 1 });
136
44
  }
137
45
  return out;
138
46
  }
@@ -4,24 +4,42 @@ export interface RunCommandDeps {
4
4
  /** Resolves the daemon HTTP base URL once per command invocation (e.g. wraps `resolveDaemonUrl`). */
5
5
  resolveBaseUrl: () => Promise<string> | string;
6
6
  /** Defaults to `process.stdout.write`; inject for tests. Used for successful command output. */
7
- write?: (text: string) => void;
7
+ write?: (requiredArgs: {
8
+ text: string;
9
+ }) => void;
8
10
  /** Defaults to `process.stderr.write`; inject for tests. Used for usage/validation errors. */
9
- writeErr?: (text: string) => void;
11
+ writeErr?: (requiredArgs: {
12
+ text: string;
13
+ }) => void;
10
14
  /** Defaults to the global `fetch`; inject for tests. */
11
15
  fetchImpl?: typeof fetch;
12
16
  /** Defaults to `process.exit`; inject for tests (must not return). */
13
- exit?: (code: number) => never;
17
+ exit?: (requiredArgs: {
18
+ code: number;
19
+ }) => never;
14
20
  /** Extra/overriding `code -> exitCode` entries layered on the package default table. */
15
21
  exitCodes?: ExitCodeTable;
16
22
  }
17
23
  /** `run start --context-ref <ref> [--agent-id <id>] [--idempotency-key <key>]` */
18
- export declare function runStartCommand(args: readonly string[], deps: RunCommandDeps): Promise<void>;
24
+ export declare function runStartCommand({ args, resolveBaseUrl: resolveBaseUrlPort }: {
25
+ args: readonly string[];
26
+ resolveBaseUrl: RunCommandDeps["resolveBaseUrl"];
27
+ }, optionalArgs?: Omit<RunCommandDeps, "resolveBaseUrl">): Promise<void>;
19
28
  /** `run list [--context-ref <ref>]` */
20
- export declare function runListCommand(args: readonly string[], deps: RunCommandDeps): Promise<void>;
29
+ export declare function runListCommand({ args, resolveBaseUrl: resolveBaseUrlPort }: {
30
+ args: readonly string[];
31
+ resolveBaseUrl: RunCommandDeps["resolveBaseUrl"];
32
+ }, optionalArgs?: Omit<RunCommandDeps, "resolveBaseUrl">): Promise<void>;
21
33
  /** `run get <runId>` */
22
- export declare function runGetCommand(args: readonly string[], deps: RunCommandDeps): Promise<void>;
34
+ export declare function runGetCommand({ args, resolveBaseUrl: resolveBaseUrlPort }: {
35
+ args: readonly string[];
36
+ resolveBaseUrl: RunCommandDeps["resolveBaseUrl"];
37
+ }, optionalArgs?: Omit<RunCommandDeps, "resolveBaseUrl">): Promise<void>;
23
38
  /** `run cancel <runId> [--reason <text>]` */
24
- export declare function runCancelCommand(args: readonly string[], deps: RunCommandDeps): Promise<void>;
39
+ export declare function runCancelCommand({ args, resolveBaseUrl: resolveBaseUrlPort }: {
40
+ args: readonly string[];
41
+ resolveBaseUrl: RunCommandDeps["resolveBaseUrl"];
42
+ }, optionalArgs?: Omit<RunCommandDeps, "resolveBaseUrl">): Promise<void>;
25
43
  /**
26
44
  * Streams `GET /api/runs/:runId/events` and writes one NDJSON line per event to `deps.write`,
27
45
  * stopping once a terminal `'end'` event arrives (or the stream closes on its own). Each frame's
@@ -31,9 +49,19 @@ export declare function runCancelCommand(args: readonly string[], deps: RunComma
31
49
  * the way a diagnostic error message is, since it is the run's actual event payload, not incidental
32
50
  * error detail.
33
51
  */
34
- export declare function watchRunEvents(baseUrl: string, runId: string, args: readonly string[], deps: RunCommandDeps): Promise<void>;
52
+ export declare function watchRunEvents({ baseUrl, runId, args }: {
53
+ baseUrl: string;
54
+ runId: string;
55
+ args: readonly string[];
56
+ }, deps?: Omit<RunCommandDeps, "resolveBaseUrl">): Promise<void>;
35
57
  /** `run watch <runId> [--after-cursor <cursor>]` */
36
- export declare function runWatchCommand(args: readonly string[], deps: RunCommandDeps): Promise<void>;
58
+ export declare function runWatchCommand({ args, resolveBaseUrl: resolveBaseUrlPort }: {
59
+ args: readonly string[];
60
+ resolveBaseUrl: RunCommandDeps["resolveBaseUrl"];
61
+ }, optionalArgs?: Omit<RunCommandDeps, "resolveBaseUrl">): Promise<void>;
37
62
  /** Registers `run` (dispatching `start`/`list`/`cancel`/`watch` on the first remaining token) against `registry`. */
38
- export declare function registerRunCommands(registry: CommandRegistry, deps: RunCommandDeps): void;
63
+ export declare function registerRunCommands({ registry, resolveBaseUrl: resolveBaseUrlPort }: {
64
+ registry: CommandRegistry;
65
+ resolveBaseUrl: RunCommandDeps["resolveBaseUrl"];
66
+ }, optionalArgs?: Omit<RunCommandDeps, "resolveBaseUrl">): void;
39
67
  //# sourceMappingURL=run-command.d.ts.map