@jini-ai/cli 0.1.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.
Files changed (62) hide show
  1. package/LICENSE +201 -0
  2. package/dist/command-registry.d.ts +82 -0
  3. package/dist/command-registry.d.ts.map +1 -0
  4. package/dist/command-registry.js +88 -0
  5. package/dist/command-registry.js.map +1 -0
  6. package/dist/daemon-command.d.ts +42 -0
  7. package/dist/daemon-command.d.ts.map +1 -0
  8. package/dist/daemon-command.js +103 -0
  9. package/dist/daemon-command.js.map +1 -0
  10. package/dist/daemon-url.d.ts +68 -0
  11. package/dist/daemon-url.d.ts.map +1 -0
  12. package/dist/daemon-url.js +99 -0
  13. package/dist/daemon-url.js.map +1 -0
  14. package/dist/errors.d.ts +82 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +103 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/flags.d.ts +61 -0
  19. package/dist/flags.d.ts.map +1 -0
  20. package/dist/flags.js +111 -0
  21. package/dist/flags.js.map +1 -0
  22. package/dist/http.d.ts +63 -0
  23. package/dist/http.d.ts.map +1 -0
  24. package/dist/http.js +226 -0
  25. package/dist/http.js.map +1 -0
  26. package/dist/index.d.ts +24 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +30 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/local-daemon-discovery.d.ts +31 -0
  31. package/dist/local-daemon-discovery.d.ts.map +1 -0
  32. package/dist/local-daemon-discovery.js +53 -0
  33. package/dist/local-daemon-discovery.js.map +1 -0
  34. package/dist/main.d.ts +21 -0
  35. package/dist/main.d.ts.map +1 -0
  36. package/dist/main.js +231 -0
  37. package/dist/main.js.map +1 -0
  38. package/dist/prompt.d.ts +83 -0
  39. package/dist/prompt.d.ts.map +1 -0
  40. package/dist/prompt.js +198 -0
  41. package/dist/prompt.js.map +1 -0
  42. package/dist/redact.d.ts +48 -0
  43. package/dist/redact.d.ts.map +1 -0
  44. package/dist/redact.js +134 -0
  45. package/dist/redact.js.map +1 -0
  46. package/dist/run-command.d.ts +39 -0
  47. package/dist/run-command.d.ts.map +1 -0
  48. package/dist/run-command.js +291 -0
  49. package/dist/run-command.js.map +1 -0
  50. package/dist/tokens.d.ts +3 -0
  51. package/dist/tokens.d.ts.map +1 -0
  52. package/dist/tokens.js +12 -0
  53. package/dist/tokens.js.map +1 -0
  54. package/dist/usage.d.ts +24 -0
  55. package/dist/usage.d.ts.map +1 -0
  56. package/dist/usage.js +25 -0
  57. package/dist/usage.js.map +1 -0
  58. package/dist/version-command.d.ts +38 -0
  59. package/dist/version-command.d.ts.map +1 -0
  60. package/dist/version-command.js +78 -0
  61. package/dist/version-command.js.map +1 -0
  62. package/package.json +48 -0
package/dist/main.js ADDED
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @module main
4
+ *
5
+ * The bootable `jini` binary (`package.json`'s `"bin": { "jini": "./dist/main.js" }`). Every
6
+ * other module in this package is a library building block — `run-command.ts`,
7
+ * `daemon-command.ts`, and `version-command.ts` each export a `register*Commands(registry,
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
10
+ * `run-command.ts`'s original addition said as much — "no wiring of this file's commands into
11
+ * an actual bootable CLI entrypoint." This closes that gap.
12
+ *
13
+ * Deliberately **not** re-exported from `index.ts`. `index.ts` is this package's library
14
+ * barrel — importing it must be side-effect-free. This file's whole purpose is a
15
+ * process.argv-parsing, stdout/stderr-writing, potentially process.exit-calling side effect;
16
+ * bundling that into the library barrel would mean simply `import`ing `@jini-ai/cli` as a
17
+ * dependency could start acting like a CLI invocation. The guarded top-level block at the
18
+ * bottom of this file (only runs `main()` for real when this module is the actual process
19
+ * entrypoint, not merely imported) exists for the same reason, one level down: importing the
20
+ * named `main` export for testing (or for a consumer that wants to build its own bin wrapper
21
+ * around it) must not itself trigger a real run.
22
+ *
23
+ * **Daemon-URL resolution.** `resolveDaemonUrl` (`daemon-url.ts`) has no baked-in default —
24
+ * this package has never had a locked default daemon port to fall back to (see
25
+ * `source-map.md`: `@jini-ai/node-host`'s `createLocalNodeDaemon` binds an ephemeral port, not a
26
+ * fixed one). This binary wires the three real, already-built pieces together instead of
27
+ * inventing a new one: an explicit `--daemon-url <url>` flag (highest precedence), a
28
+ * `JINI_DAEMON_URL` env var, and — when `--data-dir <path>` or `--registry-path <path>` is
29
+ * given — `local-daemon-discovery.ts`'s `createLocalDaemonDiscovery`, the CLI-side reader for
30
+ * a `@jini-ai/node-host`-written on-disk daemon registry record. That discovery module's own doc
31
+ * already flagged it as built-but-unconsumed ("no pack has registered against
32
+ * `CommandRegistry` yet"); this is that consumer. If none of the three resolve, there is still
33
+ * no silent fallback — `resolveDaemonUrl` throws, and this file's own catch boundary (below)
34
+ * turns that into a clean structured-error exit instead of a raw stack trace.
35
+ *
36
+ * **Global flags are stripped before dispatch, not left for `CommandRegistry`'s `valueFlags`
37
+ * skip-ahead.** `CommandRegistry.dispatch`'s `valueFlags` option (see `command-registry.ts`)
38
+ * correctly *finds* the command-name token when a global flag with a value precedes it (e.g.
39
+ * `--daemon-url http://x run` picks `run`, not the URL) — but the `rest` array it then hands
40
+ * the matched handler still *contains* that leading global flag and its value, ahead of
41
+ * whatever the handler expects as its own first token. That's harmless for a flat command, but
42
+ * `run-command.ts`/`daemon-command.ts`'s own registered handlers immediately do a *second*
43
+ * `const [sub, ...rest] = args` dispatch of their own, assuming `args[0]` is their immediate
44
+ * subcommand (`start`/`status`/etc.) with no tolerance for a stray leading flag — exactly what
45
+ * a global flag ahead of the command name would produce. Rather than change that (well-tested,
46
+ * out-of-scope) nested-dispatch assumption, {@link partitionGlobalArgv} below removes this
47
+ * binary's own three global flags (and their values) from `argv` in a single pass *before*
48
+ * dispatch, so `run`/`daemon`/`version` always land as a clean first token no matter where on
49
+ * the command line `--daemon-url`/`--data-dir`/`--registry-path` were typed.
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,
53
+ * `jini --version`/`jini -v` (recognized only as the very first token — see {@link main}) is a
54
+ * plain alias for `jini version` (`version-command.ts`): it prints the *running daemon's* real
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
57
+ * implementation to keep correct, not two.
58
+ *
59
+ * **Error boundary.** `parseFlags` (`flags.ts`) throws a plain `Error` for an unrecognized
60
+ * `--flag` or a string flag with no value — by design (see that module's own doc: "a
61
+ * hallucinated flag from an LLM-driven caller should error immediately"), but every existing
62
+ * command handler (`run-command.ts`, `daemon-command.ts`, `version-command.ts`) calls it
63
+ * un-wrapped, so that throw would otherwise surface as an unhandled rejection with a raw stack
64
+ * trace instead of the clean `{ error: { code, message } }` envelope every *other* failure
65
+ * path in this package produces. {@link main} wraps its dispatch in a boundary that converts
66
+ * any such raw throw into the same structured-error contract. Distinguishing "a raw,
67
+ * unexpected throw" from "a nested command already exited cleanly through
68
+ * `exitWithStructuredError`" (the latter must never be re-wrapped — that would double-report
69
+ * the same failure, or worse, mask the original exit code with a generic one) uses a local
70
+ * flag set by this file's own `exit` wrapper: if the wrapped `exit` was ever invoked before the
71
+ * catch fires, the original error is re-thrown untouched instead of reformatted. In real
72
+ * operation this distinction is moot — `process.exit` never returns, so nothing downstream of
73
+ * a real exit call ever reaches the catch — but it matters for this file's own tests, which
74
+ * (like every other test in this package) inject a throwing `exit` double to make
75
+ * `process.exit`'s real "never returns" behavior observable without ending the test process.
76
+ */
77
+ import { fileURLToPath } from 'node:url';
78
+ import { CommandRegistry } from './command-registry.js';
79
+ import { registerDaemonCommands } from './daemon-command.js';
80
+ import { resolveDaemonUrl } from './daemon-url.js';
81
+ import { exitWithStructuredError } from './errors.js';
82
+ import { createLocalDaemonDiscovery } from './local-daemon-discovery.js';
83
+ import { registerRunCommands } from './run-command.js';
84
+ import { renderUsage } from './usage.js';
85
+ import { registerVersionCommand } from './version-command.js';
86
+ function defaultWrite(text) {
87
+ process.stdout.write(text);
88
+ }
89
+ function defaultWriteErr(text) {
90
+ process.stderr.write(text);
91
+ }
92
+ /** Name of the environment variable {@link resolveDaemonUrl} checks when `--daemon-url` is absent. */
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: [
98
+ { flag: '--daemon-url <url>', description: 'Explicit daemon base URL. Takes precedence over JINI_DAEMON_URL and local discovery.' },
99
+ { flag: '--data-dir <path>', description: "Resolve a locally running daemon's URL from this data directory's on-disk registry (see @jini-ai/node-host's createLocalNodeDaemon)." },
100
+ { flag: '--registry-path <path>', description: "Exact registry file path, overriding --data-dir's derived default." },
101
+ { flag: '--help, -h', description: 'Show this help.' },
102
+ { flag: '--version, -v', description: 'Print the running daemon\'s version (alias for "jini version"; recognized only as the first argument).' },
103
+ ],
104
+ });
105
+ const GLOBAL_FLAG_NAMES = ['daemon-url', 'data-dir', 'registry-path'];
106
+ /**
107
+ * Single-pass extraction of this binary's own global `--daemon-url`/`--data-dir`/
108
+ * `--registry-path` flags out of the full `argv`, returning both their values and every other
109
+ * token untouched (including the command name and all of its own subcommand-specific flags) as
110
+ * `rest` — see this file's own module doc, "Global flags are stripped before dispatch," for why
111
+ * a dedicated single-pass scanner is used here instead of `flags.ts`'s `parseFlags` (whose
112
+ * strict mode would throw on an unrelated subcommand flag, and whose permissive/heuristic mode
113
+ * doesn't reliably agree with "always consume the very next token" for a value-taking flag
114
+ * immediately followed by another `--flag`).
115
+ *
116
+ * @throws A plain `Error` when a recognized global flag is the last token with no value
117
+ * following it — caught by {@link main}'s own error boundary, same as any other malformed-input
118
+ * failure.
119
+ */
120
+ function partitionGlobalArgv(argv) {
121
+ const names = new Set(GLOBAL_FLAG_NAMES);
122
+ const globals = {};
123
+ const rest = [];
124
+ for (let i = 0; i < argv.length; i++) {
125
+ // Loop is bounded by argv.length, so this index is always in range —
126
+ // noUncheckedIndexedAccess types it as possibly-undefined regardless.
127
+ const token = argv[i];
128
+ if (token.startsWith('--')) {
129
+ const eq = token.indexOf('=');
130
+ const key = eq >= 0 ? token.slice(2, eq) : token.slice(2);
131
+ if (names.has(key)) {
132
+ if (eq >= 0) {
133
+ globals[key] = token.slice(eq + 1);
134
+ }
135
+ else {
136
+ const value = argv[i + 1];
137
+ if (value === undefined)
138
+ throw new Error(`flag --${key} requires a value`);
139
+ globals[key] = value;
140
+ i++;
141
+ }
142
+ continue;
143
+ }
144
+ }
145
+ rest.push(token);
146
+ }
147
+ return { globals, rest };
148
+ }
149
+ /** Builds a `resolveDaemonUrl`-backed `resolveBaseUrl` closure from this binary's own parsed global flags. */
150
+ function buildResolveBaseUrl(globals, env, warn) {
151
+ const { 'daemon-url': daemonUrl, 'data-dir': dataDir, 'registry-path': registryPath } = globals;
152
+ const discover = registryPath !== undefined
153
+ ? createLocalDaemonDiscovery({ registryPath })
154
+ : dataDir !== undefined
155
+ ? createLocalDaemonDiscovery({ dataDir })
156
+ : undefined;
157
+ return () => resolveDaemonUrl({
158
+ flagUrl: daemonUrl ?? null,
159
+ env,
160
+ envVarName: DAEMON_URL_ENV_VAR,
161
+ ...(discover !== undefined ? { discover } : {}),
162
+ warn,
163
+ });
164
+ }
165
+ /**
166
+ * Parse `argv` (already stripped of `node`/script-path, i.e. `process.argv.slice(2)`) and
167
+ * dispatch to the registered `run`/`daemon`/`version` commands. Never throws a raw error to its
168
+ * caller under normal use — usage errors and daemon failures both exit through this package's
169
+ * structured-error contract (`errors.ts`), via the injected (or default, real) `exit`.
170
+ */
171
+ export async function main(argv, deps = {}) {
172
+ const write = deps.write ?? defaultWrite;
173
+ const writeErr = deps.writeErr ?? defaultWriteErr;
174
+ const rawExit = deps.exit ?? ((code) => process.exit(code));
175
+ const env = deps.env ?? process.env;
176
+ if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
177
+ write(`${ROOT_USAGE}\n`);
178
+ return;
179
+ }
180
+ // `--version`/`-v` as the very first token is a plain alias for `jini version` — see this
181
+ // file's own module doc for why there is no separate "this package's own version" surface.
182
+ const effectiveArgv = argv[0] === '--version' || argv[0] === '-v' ? ['version', ...argv.slice(1)] : argv;
183
+ // Tracks whether `exit` was ever invoked, so the catch block below can tell "a nested command
184
+ // already exited cleanly" (re-throw untouched) apart from "a genuinely raw, unhandled throw"
185
+ // (reformat into a structured error) — see this file's own module doc, "Error boundary."
186
+ let exitCalled = false;
187
+ const exit = (code) => {
188
+ exitCalled = true;
189
+ return rawExit(code);
190
+ };
191
+ let result;
192
+ try {
193
+ 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);
207
+ }
208
+ catch (error) {
209
+ if (exitCalled)
210
+ throw error;
211
+ const message = error instanceof Error ? error.message : String(error);
212
+ exitWithStructuredError({ code: 'invalid-flag', message }, { write: writeErr, exit });
213
+ return;
214
+ }
215
+ if (result.kind === 'empty') {
216
+ write(`${ROOT_USAGE}\n`);
217
+ return;
218
+ }
219
+ if (result.kind === 'not-found') {
220
+ exitWithStructuredError({ code: 'invalid-flag', message: `unknown command: "${result.name}". Run "jini --help" for usage.` }, { write: writeErr, exit });
221
+ }
222
+ }
223
+ // Only run for real when this module is the actual process entrypoint (`node dist/main.js
224
+ // ...`, or the `jini` bin shim pnpm/npm links to it) — not merely imported, e.g. by this
225
+ // file's own tests importing the named `main` export above. Mirrors Node's CommonJS
226
+ // `require.main === module` idiom for ESM; see this file's own module doc.
227
+ const isMainModule = process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1];
228
+ if (isMainModule) {
229
+ await main(process.argv.slice(2));
230
+ }
231
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module prompt
3
+ *
4
+ * Long-form text intake shared across CLI domains: two sibling conventions
5
+ * for reading a large piece of text from an inline flag, a `--*-file <path>`
6
+ * flag, or stdin (`-`).
7
+ *
8
+ * `readPromptFromFlags` is the `--prompt <text>` / `--prompt-file <path>` /
9
+ * `--prompt-file -` convention, ported from OD's `apps/daemon/src/cli.ts`
10
+ * `readPromptFromFlags` (see `source-map.md`). This exact pattern recurred
11
+ * verbatim across many of OD's product commands (`brand create`, `run
12
+ * start`, `files version-create`, `automation source ingest`, …) with zero
13
+ * product nouns in the reading logic itself, which is what makes it a
14
+ * genuine reusable primitive rather than a one-off helper worth leaving in
15
+ * each caller.
16
+ *
17
+ * `readBodyFromFlags` is the sibling `--body`/`--body-file` convention,
18
+ * ported from the `cli-capability-barrels` branch's `cli/core/io.ts`
19
+ * `readMemoryBodyFromFlags` (see `source-map.md`'s "Barrel branch
20
+ * reconciliation" section). Despite the origin name, it has no memory-domain
21
+ * coupling — that module's own docblock confirms it is reused verbatim by
22
+ * both the memory and automation domains — so it is ported here under a
23
+ * de-branded name alongside its prompt sibling.
24
+ *
25
+ * Per CR-004/SEC-RB-009
26
+ * (`ADS-memory/reports/code-review/CR-remaining-backend-audit-2026-07-21.md`,
27
+ * `ADS-memory/reports/security/SEC-remaining-backend-audit-2026-07-21.md`),
28
+ * the default file/stdin readers below cap how many bytes they will read
29
+ * (rejecting past the cap rather than truncating silently or reading
30
+ * unbounded input into memory) and accept an optional `AbortSignal` so a
31
+ * caller can cancel an in-progress read. Both knobs only apply to the
32
+ * default `readFile`/`readStdin` implementations — they have no effect if a
33
+ * caller injects their own.
34
+ */
35
+ /** Thrown by the default readers when a file or stdin stream exceeds its configured byte cap. */
36
+ export declare class PayloadTooLargeError extends Error {
37
+ constructor(message: string);
38
+ }
39
+ export interface PromptFlags {
40
+ prompt?: string;
41
+ 'prompt-file'?: string;
42
+ }
43
+ export interface ReadPromptFromFlagsOptions {
44
+ /** Defaults to a bounded, cancellable `node:fs` stream read; inject for tests. */
45
+ readFile?: (path: string) => Promise<string>;
46
+ /** Defaults to a bounded, cancellable read of `process.stdin` to EOF as utf8; inject for tests. */
47
+ readStdin?: () => Promise<string>;
48
+ /** Maximum bytes the default readFile/readStdin will read before rejecting. Defaults to 10 MiB. No effect on an injected reader. */
49
+ maxBytes?: number;
50
+ /** Cancels an in-progress default readFile/readStdin call. No effect on an injected reader. */
51
+ signal?: AbortSignal;
52
+ }
53
+ /**
54
+ * Resolve prompt text from `--prompt`, or `--prompt-file <path>` /
55
+ * `--prompt-file -` (stdin). Returns `null` when neither flag is set so the
56
+ * caller can fall back to its own default.
57
+ */
58
+ export declare function readPromptFromFlags(flags: PromptFlags, options?: ReadPromptFromFlagsOptions): Promise<string | null>;
59
+ export interface BodyFlags {
60
+ body?: string;
61
+ 'body-file'?: string;
62
+ }
63
+ export interface ReadBodyFromFlagsOptions {
64
+ /** Defaults to a bounded, cancellable `node:fs` stream read; inject for tests. */
65
+ readFile?: (path: string) => Promise<string>;
66
+ /** Defaults to a bounded, cancellable drain of `process.stdin`'s async iterator as utf8; inject for tests. */
67
+ readStdin?: () => Promise<string>;
68
+ /** Maximum bytes the default readFile/readStdin will read before rejecting. Defaults to 10 MiB. No effect on an injected reader. */
69
+ maxBytes?: number;
70
+ /** Cancels an in-progress default readFile/readStdin call. No effect on an injected reader. */
71
+ signal?: AbortSignal;
72
+ }
73
+ /**
74
+ * Resolve long-form body text from `--body` (inline — an empty string counts
75
+ * as provided) or `--body-file <path>` / `--body-file -` (stdin). Returns
76
+ * `undefined` when neither flag is present, so callers can distinguish "not
77
+ * provided" from an intentionally empty body — unlike {@link
78
+ * readPromptFromFlags}, which treats an empty `--prompt` as unset, this
79
+ * primitive's origin caller (`od memory tree edit`) needs to allow clearing
80
+ * a body to an empty string.
81
+ */
82
+ export declare function readBodyFromFlags(flags: BodyFlags, options?: ReadBodyFromFlagsOptions): Promise<string | undefined>;
83
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,iGAAiG;AACjG,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAKD,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oIAAoI;IACpI,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AA8FD,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,8GAA8G;IAC9G,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,oIAAoI;IACpI,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,SAAS,EAChB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW7B"}
package/dist/prompt.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @module prompt
3
+ *
4
+ * Long-form text intake shared across CLI domains: two sibling conventions
5
+ * for reading a large piece of text from an inline flag, a `--*-file <path>`
6
+ * flag, or stdin (`-`).
7
+ *
8
+ * `readPromptFromFlags` is the `--prompt <text>` / `--prompt-file <path>` /
9
+ * `--prompt-file -` convention, ported from OD's `apps/daemon/src/cli.ts`
10
+ * `readPromptFromFlags` (see `source-map.md`). This exact pattern recurred
11
+ * verbatim across many of OD's product commands (`brand create`, `run
12
+ * start`, `files version-create`, `automation source ingest`, …) with zero
13
+ * product nouns in the reading logic itself, which is what makes it a
14
+ * genuine reusable primitive rather than a one-off helper worth leaving in
15
+ * each caller.
16
+ *
17
+ * `readBodyFromFlags` is the sibling `--body`/`--body-file` convention,
18
+ * ported from the `cli-capability-barrels` branch's `cli/core/io.ts`
19
+ * `readMemoryBodyFromFlags` (see `source-map.md`'s "Barrel branch
20
+ * reconciliation" section). Despite the origin name, it has no memory-domain
21
+ * coupling — that module's own docblock confirms it is reused verbatim by
22
+ * both the memory and automation domains — so it is ported here under a
23
+ * de-branded name alongside its prompt sibling.
24
+ *
25
+ * Per CR-004/SEC-RB-009
26
+ * (`ADS-memory/reports/code-review/CR-remaining-backend-audit-2026-07-21.md`,
27
+ * `ADS-memory/reports/security/SEC-remaining-backend-audit-2026-07-21.md`),
28
+ * the default file/stdin readers below cap how many bytes they will read
29
+ * (rejecting past the cap rather than truncating silently or reading
30
+ * unbounded input into memory) and accept an optional `AbortSignal` so a
31
+ * caller can cancel an in-progress read. Both knobs only apply to the
32
+ * default `readFile`/`readStdin` implementations — they have no effect if a
33
+ * caller injects their own.
34
+ */
35
+ /** Thrown by the default readers when a file or stdin stream exceeds its configured byte cap. */
36
+ export class PayloadTooLargeError extends Error {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = 'PayloadTooLargeError';
40
+ }
41
+ }
42
+ /** Default cap for the default file/stdin readers: generous for a prompt/body payload, small enough to bound worst-case memory. */
43
+ const DEFAULT_MAX_READ_BYTES = 10 * 1024 * 1024;
44
+ /**
45
+ * Resolve prompt text from `--prompt`, or `--prompt-file <path>` /
46
+ * `--prompt-file -` (stdin). Returns `null` when neither flag is set so the
47
+ * caller can fall back to its own default.
48
+ */
49
+ export async function readPromptFromFlags(flags, options = {}) {
50
+ if (typeof flags.prompt === 'string' && flags.prompt.length > 0) {
51
+ return flags.prompt;
52
+ }
53
+ const promptFile = flags['prompt-file'];
54
+ if (typeof promptFile !== 'string' || promptFile.length === 0) {
55
+ return null;
56
+ }
57
+ const readLimits = { maxBytes: options.maxBytes ?? DEFAULT_MAX_READ_BYTES, ...(options.signal !== undefined ? { signal: options.signal } : {}) };
58
+ if (promptFile === '-') {
59
+ const readStdin = options.readStdin ?? (() => defaultReadStdin(readLimits));
60
+ return await readStdin();
61
+ }
62
+ const readFile = options.readFile ?? ((path) => defaultReadFile(path, readLimits));
63
+ return await readFile(promptFile);
64
+ }
65
+ async function defaultReadFile(path, limits) {
66
+ const { createReadStream } = await import('node:fs');
67
+ return await new Promise((resolve, reject) => {
68
+ const stream = createReadStream(path, limits.signal !== undefined ? { signal: limits.signal } : {});
69
+ const chunks = [];
70
+ let total = 0;
71
+ let settled = false;
72
+ // `settled`-guarded: defensive against Node's own documented Readable
73
+ // contract ever being violated (a stream is only ever supposed to emit
74
+ // one of 'end'/'error', never both, and `stream.destroy()` with no
75
+ // error argument — the only way this module calls it — is documented to
76
+ // suppress any further 'error' from the destroy path itself). Kept as
77
+ // real protection against a future/third-party stream implementation
78
+ // bug, not something this module's own real `createReadStream` usage
79
+ // can trigger — see `source-map.md`'s dated entry for the full
80
+ // re-verification record (a single terminal event is all this file's
81
+ // own usage can ever produce).
82
+ const finish = (fn) => {
83
+ if (settled)
84
+ return;
85
+ settled = true;
86
+ fn();
87
+ };
88
+ // `@types/node` types a Readable's 'data' listener as `(chunk: string |
89
+ // Buffer) => void` unconditionally (accommodating a stream some other
90
+ // caller configured with an `encoding`), but this stream never sets one
91
+ // (`limits` has no such field — see `ReadLimits`), so `chunk` is always
92
+ // really a `Buffer` here — a plain assertion, not a runtime `typeof`
93
+ // branch (a prior `typeof chunk === 'string' ? ... : ...` ternary here
94
+ // was dead code given this file's only call site; removed rather than
95
+ // left uncovered).
96
+ stream.on('data', (rawChunk) => {
97
+ const chunk = rawChunk;
98
+ total += chunk.length;
99
+ if (total > limits.maxBytes) {
100
+ stream.destroy();
101
+ finish(() => reject(new PayloadTooLargeError(`file exceeded the ${limits.maxBytes}-byte limit: ${path}`)));
102
+ return;
103
+ }
104
+ chunks.push(chunk);
105
+ });
106
+ stream.on('error', (err) => finish(() => reject(err)));
107
+ stream.on('end', () => finish(() => resolve(Buffer.concat(chunks).toString('utf8'))));
108
+ });
109
+ }
110
+ async function defaultReadStdin(limits) {
111
+ return await new Promise((resolve, reject) => {
112
+ if (limits.signal?.aborted === true) {
113
+ reject(limits.signal.reason ?? new Error('stdin read aborted'));
114
+ return;
115
+ }
116
+ let buffer = '';
117
+ let total = 0;
118
+ process.stdin.setEncoding('utf8');
119
+ const cleanup = () => {
120
+ process.stdin.off('data', onData);
121
+ process.stdin.off('end', onEnd);
122
+ process.stdin.off('error', onError);
123
+ limits.signal?.removeEventListener('abort', onAbort);
124
+ };
125
+ const pauseStdin = () => {
126
+ process.stdin.pause?.();
127
+ };
128
+ const onData = (chunk) => {
129
+ total += Buffer.byteLength(chunk, 'utf8');
130
+ if (total > limits.maxBytes) {
131
+ cleanup();
132
+ pauseStdin();
133
+ reject(new PayloadTooLargeError(`stdin exceeded the ${limits.maxBytes}-byte limit`));
134
+ return;
135
+ }
136
+ buffer += chunk;
137
+ };
138
+ const onEnd = () => { cleanup(); resolve(buffer); };
139
+ const onError = (err) => { cleanup(); reject(err); };
140
+ const onAbort = () => {
141
+ cleanup();
142
+ pauseStdin();
143
+ reject(limits.signal?.reason ?? new Error('stdin read aborted'));
144
+ };
145
+ process.stdin.on('data', onData);
146
+ process.stdin.on('end', onEnd);
147
+ process.stdin.on('error', onError);
148
+ limits.signal?.addEventListener('abort', onAbort, { once: true });
149
+ });
150
+ }
151
+ /**
152
+ * Resolve long-form body text from `--body` (inline — an empty string counts
153
+ * as provided) or `--body-file <path>` / `--body-file -` (stdin). Returns
154
+ * `undefined` when neither flag is present, so callers can distinguish "not
155
+ * provided" from an intentionally empty body — unlike {@link
156
+ * readPromptFromFlags}, which treats an empty `--prompt` as unset, this
157
+ * primitive's origin caller (`od memory tree edit`) needs to allow clearing
158
+ * a body to an empty string.
159
+ */
160
+ export async function readBodyFromFlags(flags, options = {}) {
161
+ if (typeof flags.body === 'string')
162
+ return flags.body;
163
+ const bodyFile = flags['body-file'];
164
+ if (typeof bodyFile !== 'string')
165
+ return undefined;
166
+ const readLimits = { maxBytes: options.maxBytes ?? DEFAULT_MAX_READ_BYTES, ...(options.signal !== undefined ? { signal: options.signal } : {}) };
167
+ if (bodyFile === '-') {
168
+ const readStdin = options.readStdin ?? (() => defaultReadBodyStdin(readLimits));
169
+ return await readStdin();
170
+ }
171
+ const readFile = options.readFile ?? ((path) => defaultReadFile(path, readLimits));
172
+ return await readFile(bodyFile);
173
+ }
174
+ async function defaultReadBodyStdin(limits) {
175
+ limits.signal?.throwIfAborted();
176
+ const stdin = process.stdin;
177
+ const onAbort = () => { stdin.destroy?.(new Error('stdin read aborted')); };
178
+ limits.signal?.addEventListener('abort', onAbort, { once: true });
179
+ try {
180
+ let body = '';
181
+ let total = 0;
182
+ for await (const chunk of stdin) {
183
+ limits.signal?.throwIfAborted();
184
+ const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
185
+ total += Buffer.byteLength(text, 'utf8');
186
+ if (total > limits.maxBytes) {
187
+ stdin.destroy?.();
188
+ throw new PayloadTooLargeError(`stdin exceeded the ${limits.maxBytes}-byte limit`);
189
+ }
190
+ body += text;
191
+ }
192
+ return body;
193
+ }
194
+ finally {
195
+ limits.signal?.removeEventListener('abort', onAbort);
196
+ }
197
+ }
198
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @module redact
3
+ *
4
+ * Bounded, redacted rendering of text that ultimately traces back to an
5
+ * untrusted network peer — a daemon's error body, a `fetch()` rejection's
6
+ * cause message — rather than to this process's own code. Three problems
7
+ * are addressed together because a hostile or merely buggy daemon can
8
+ * combine them: an unbounded body can be enormous, an arbitrary body can
9
+ * contain terminal control/ANSI escape sequences that manipulate the
10
+ * user's terminal when printed verbatim, and it can echo back secrets (an
11
+ * `Authorization` header, an API key, a bearer/refresh token) that were
12
+ * present in the request or in an upstream provider's own error.
13
+ *
14
+ * Added per CR-004 / SEC-RB-009 (`ADS-memory/reports/code-review/CR-remaining-backend-audit-2026-07-21.md`,
15
+ * `ADS-memory/reports/security/SEC-remaining-backend-audit-2026-07-21.md`):
16
+ * `http.ts` and `errors.ts` previously wrote raw fetch-cause messages,
17
+ * arbitrary daemon JSON, or complete non-JSON response bodies straight to
18
+ * the terminal.
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
+ /**
40
+ * Recursively sanitize an arbitrary parsed-JSON value (the shape a daemon error envelope's
41
+ * `data`/`details` field can legitimately be): every string leaf is passed through
42
+ * {@link sanitizeUntrustedText}, and both array length and object key count are capped per
43
+ * level so a maliciously wide or deep payload can't blow up the work done here. Depth beyond
44
+ * the cap is replaced with a placeholder rather than silently dropped, so truncation is visible
45
+ * instead of looking like an empty/missing value.
46
+ */
47
+ export declare function sanitizeUnknownDeep(value: unknown, depth?: number): unknown;
48
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA2BH;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+B1D;AASD,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED,MAAM,WAAW,mBAAmB;IAClC,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAM7F;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAetE"}