open-claude-p 1.1.2 → 1.2.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.
@@ -26,15 +26,20 @@ import { OPTION_SPEC, getOption } from './spec.js';
26
26
  * @property {string[]} positional
27
27
  * @property {string[]} unknown Tokens that look like flags but were not in the spec.
28
28
  * @property {string[]} errors Human-readable error strings.
29
+ * @property {Set<string>} supplied Canonical names actually present in argv.
30
+ * Defaults are pre-seeded into `options`, so a value alone cannot tell
31
+ * "user passed `--flag=false`" apart from "user passed nothing" — this
32
+ * set is the only way to distinguish them.
29
33
  */
30
34
 
31
35
  /**
32
36
  * @param {string[]} argv
37
+ * @param {Record<string, string|undefined>} [env] defaults to `process.env`
33
38
  * @returns {ParseResult}
34
39
  */
35
- export function parseArgv(argv) {
40
+ export function parseArgv(argv, env = process.env) {
36
41
  /** @type {ParseResult} */
37
- const out = { options: {}, positional: [], unknown: [], errors: [] };
42
+ const out = { options: {}, positional: [], unknown: [], errors: [], supplied: new Set() };
38
43
 
39
44
  // Pre-seed defaults so the validator sees them.
40
45
  for (const spec of OPTION_SPEC) {
@@ -43,6 +48,14 @@ export function parseArgv(argv) {
43
48
  }
44
49
  }
45
50
 
51
+ // Canonical names supplied on the command line. Used only by the
52
+ // env-var pass at the end: argv always wins over the environment, and
53
+ // that has to include an explicit opt-out like `--print-mode=false`,
54
+ // whose value is indistinguishable from the default if you compare
55
+ // values instead of tracking what was actually seen.
56
+ /** @type {Set<string>} */
57
+ const supplied = out.supplied;
58
+
46
59
  let i = 0;
47
60
  while (i < argv.length) {
48
61
  const tok = argv[i];
@@ -63,6 +76,7 @@ export function parseArgv(argv) {
63
76
  i++;
64
77
  continue;
65
78
  }
79
+ supplied.add(spec.name);
66
80
  i = consumeOption(spec, argv, i, inline, out);
67
81
  continue;
68
82
  }
@@ -93,11 +107,13 @@ export function parseArgv(argv) {
93
107
  );
94
108
  break;
95
109
  }
110
+ supplied.add(spec.name);
96
111
  i = consumeOption(spec, argv, i, undefined, out);
97
112
  consumedByValue = true;
98
113
  break;
99
114
  }
100
115
  // Boolean short: just set.
116
+ supplied.add(spec.name);
101
117
  out.options[spec.name] = true;
102
118
  }
103
119
  if (!consumedByValue) i++;
@@ -109,9 +125,64 @@ export function parseArgv(argv) {
109
125
  i++;
110
126
  }
111
127
 
128
+ applyEnvFallbacks(out, supplied, env);
129
+
112
130
  return out;
113
131
  }
114
132
 
133
+ /**
134
+ * Fill in options that were not supplied on the command line from their
135
+ * declared `env:` variable.
136
+ *
137
+ * OPTION_SPEC has always documented an `env` field, but nothing read it —
138
+ * the two entries that used it (`--print-mode`, `--no-session-persistence`)
139
+ * were wired up by hand at their call sites instead, so any new entry that
140
+ * declared `env` silently did nothing. This makes the declaration real, and
141
+ * keeps the precedence the hand-rolled versions already used:
142
+ *
143
+ * explicit argv > environment variable > spec default
144
+ *
145
+ * Booleans accept `1` / `true` / `yes` / `on` (case-insensitive); anything
146
+ * else, including the empty string, leaves the option at its default so
147
+ * `VAR=` and `VAR=0` both read as "off". Value-taking options are coerced
148
+ * through the same `coerce()` path as argv, and a malformed value produces
149
+ * the same style of error rather than being silently ignored.
150
+ *
151
+ * @param {ParseResult} out
152
+ * @param {Set<string>} supplied canonical names seen on the command line
153
+ * @param {Record<string, string|undefined>} env
154
+ */
155
+ function applyEnvFallbacks(out, supplied, env) {
156
+ for (const spec of OPTION_SPEC) {
157
+ if (!spec.env) continue;
158
+ const raw = Object.prototype.hasOwnProperty.call(env ?? {}, spec.env)
159
+ ? env[spec.env]
160
+ : undefined;
161
+ // Library callers can pass any object as `env`; only honour real
162
+ // string values so a stray number/object cannot throw in `.trim()`.
163
+ if (typeof raw !== 'string' || raw === '') continue;
164
+ // Supplied on the command line — argv always wins.
165
+ if (supplied.has(spec.name)) continue;
166
+
167
+ if (spec.kind === 'boolean') {
168
+ const v = raw.trim().toLowerCase();
169
+ if (v === '1' || v === 'true' || v === 'yes' || v === 'on') {
170
+ out.options[spec.name] = true;
171
+ }
172
+ continue;
173
+ }
174
+ if (spec.kind === 'array') {
175
+ // Comma-separated, so a single env var can carry a repeatable flag.
176
+ const items = raw.split(',').map((x) => x.trim()).filter(Boolean);
177
+ if (items.length > 0) out.options[spec.name] = items;
178
+ continue;
179
+ }
180
+ const { value, error } = coerce(spec, raw);
181
+ if (error) out.errors.push(`${spec.env}: ${error}`);
182
+ else out.options[spec.name] = value;
183
+ }
184
+ }
185
+
115
186
  /**
116
187
  * Consume the value(s) for `spec` starting at argv index `i` (where argv[i]
117
188
  * is the flag token itself). `inline` is the post-`=` value when present.
@@ -17,6 +17,9 @@
17
17
  // times (occurrences accumulate); array kind also collects
18
18
  // variadic values until the next flag-looking token.
19
19
  // env: Optional environment-variable fallback.
20
+ // printModeOnly: Only forward this flag when running under
21
+ // `--print-mode`. For upstream flags that require
22
+ // `--print` and abort the process without it.
20
23
  // description: One-line help text.
21
24
  // forward: How this option reaches the upstream `claude` process.
22
25
  // { type: 'argv', flag: '--name' } pass to spawn argv
@@ -72,7 +75,11 @@ export const OPTION_SPEC = [
72
75
  {
73
76
  name: 'model',
74
77
  kind: 'string',
75
- description: 'Model alias or full name (e.g. `sonnet`, `claude-sonnet-4-6`).',
78
+ description:
79
+ 'Model alias or full name. Aliases resolve to the latest model in ' +
80
+ 'that family (e.g. `fable`, `opus`, `sonnet`, `haiku`); full names ' +
81
+ 'are passed through verbatim (e.g. `claude-opus-5`, ' +
82
+ '`claude-fable-5-1`, `claude-sonnet-5`).',
76
83
  forward: { type: 'argv', flag: '--model' },
77
84
  },
78
85
  {
@@ -225,6 +232,24 @@ export const OPTION_SPEC = [
225
232
  '(stream-json input + output only).',
226
233
  forward: { type: 'shim' },
227
234
  },
235
+ {
236
+ name: 'forward-subagent-text',
237
+ kind: 'boolean',
238
+ default: false,
239
+ description:
240
+ 'Forward subagent text and thinking blocks as assistant/user messages ' +
241
+ 'with `parent_tool_use_id` set. Requires `--output-format=stream-json`.',
242
+ forward: { type: 'argv', flag: '--forward-subagent-text' },
243
+ },
244
+ {
245
+ name: 'prompt-suggestions',
246
+ kind: 'boolean',
247
+ default: false,
248
+ description:
249
+ 'Emit a `prompt_suggestion` message after each turn carrying a ' +
250
+ 'predicted next user prompt (stream-json output).',
251
+ forward: { type: 'argv', flag: '--prompt-suggestions' },
252
+ },
228
253
  {
229
254
  name: 'include-hook-events',
230
255
  kind: 'boolean',
@@ -236,7 +261,18 @@ export const OPTION_SPEC = [
236
261
  name: 'include-partial-messages',
237
262
  kind: 'boolean',
238
263
  default: false,
239
- description: 'Include partial assistant message chunks in stream-json output.',
264
+ // Upstream refuses this flag unless it is also given `--print`
265
+ // ("Error: --include-partial-messages requires --print and
266
+ // --output-format=stream-json") and exits immediately. The default
267
+ // PTY path drives the interactive TUI, so forwarding it there kills
268
+ // the spawn. ocp already synthesises `assistant-partial` events from
269
+ // the TUI frames on that path, so the flag has nothing to add there
270
+ // anyway — it is forwarded only under `--print-mode`.
271
+ printModeOnly: true,
272
+ description:
273
+ 'Include partial assistant message chunks in stream-json output. ' +
274
+ 'Forwarded upstream only under `--print-mode`; on the default PTY ' +
275
+ 'path ocp emits `assistant-partial` events itself.',
240
276
  forward: { type: 'argv', flag: '--include-partial-messages' },
241
277
  },
242
278
 
@@ -244,8 +280,11 @@ export const OPTION_SPEC = [
244
280
  {
245
281
  name: 'effort',
246
282
  kind: 'enum',
247
- choices: ['low', 'medium', 'high', 'max'],
248
- description: 'Effort level the model should apply.',
283
+ choices: ['low', 'medium', 'high', 'xhigh', 'max'],
284
+ description:
285
+ 'Effort level the model should apply (`low` | `medium` | `high` | ' +
286
+ '`xhigh` | `max`). `xhigh` sits between `high` and `max` and is the ' +
287
+ 'upstream default for coding and agentic work.',
249
288
  forward: { type: 'argv', flag: '--effort' },
250
289
  },
251
290
  {
@@ -281,6 +320,14 @@ export const OPTION_SPEC = [
281
320
  description: 'API-side task budget in tokens. Shim-enforced.',
282
321
  forward: { type: 'shim' },
283
322
  },
323
+ {
324
+ name: 'autocompact',
325
+ kind: 'string',
326
+ description:
327
+ 'Auto-compact window size: `auto`, or an explicit token budget ' +
328
+ 'between 100k and 1M (e.g. `200000`, `500k`).',
329
+ forward: { type: 'argv', flag: '--autocompact' },
330
+ },
284
331
  {
285
332
  name: 'fallback-model',
286
333
  kind: 'string',
@@ -293,8 +340,10 @@ export const OPTION_SPEC = [
293
340
  name: 'permission-mode',
294
341
  kind: 'string',
295
342
  description:
296
- 'Permission mode (`default` | `plan` | `acceptEdits` | ' +
297
- '`bypassPermissions` | `dontAsk` | `auto`).',
343
+ 'Permission mode (`acceptEdits` | `auto` | `bypassPermissions` | ' +
344
+ '`manual` | `dontAsk` | `plan`). `manual` is the current name for ' +
345
+ "the interactive prompt-on-every-tool mode; upstream still accepts " +
346
+ 'the older `default` spelling, so ocp keeps accepting it too.',
298
347
  forward: { type: 'argv', flag: '--permission-mode' },
299
348
  },
300
349
  {
@@ -305,6 +354,35 @@ export const OPTION_SPEC = [
305
354
  'Allow `--dangerously-skip-permissions` as a choice without enabling it.',
306
355
  forward: { type: 'argv', flag: '--allow-dangerously-skip-permissions' },
307
356
  },
357
+ {
358
+ name: 'permission-prompts',
359
+ kind: 'enum',
360
+ choices: ['host', 'none'],
361
+ description:
362
+ 'Who answers permission prompts: `host` (the SDK host, or the tool ' +
363
+ 'named by `--permission-prompt-tool`) or `none` (nobody — anything ' +
364
+ 'that would prompt is denied automatically; the permission mode ' +
365
+ 'still decides everything else). `none` suits PTY automation, which ' +
366
+ 'has no way to answer a prompt, when denying is preferable to ' +
367
+ 'bypassing checks outright.',
368
+ forward: { type: 'argv', flag: '--permission-prompts' },
369
+ },
370
+ {
371
+ name: 'restricted',
372
+ kind: 'boolean',
373
+ default: false,
374
+ description:
375
+ 'Restricted mode: drop the built-in tools that run commands or code ' +
376
+ '(Bash, PowerShell, REPL, …) and WebFetch unless `--tools` names ' +
377
+ 'them, ignore user/project/local settings files (managed settings ' +
378
+ 'and `--settings` still apply), confine the file tools to the ' +
379
+ 'working directories, and refuse `bypassPermissions`. Pair with ' +
380
+ '`--strict-mcp-config` to skip MCP servers too. Note that ocp does ' +
381
+ 'not get its usual clean transcript in this mode — no session ' +
382
+ 'JSONL is written for the run, so the reply is reconstructed from ' +
383
+ 'the TUI frames and may carry leftover box-border chrome.',
384
+ forward: { type: 'argv', flag: '--restricted' },
385
+ },
308
386
  {
309
387
  name: 'tools',
310
388
  kind: 'string',
@@ -353,6 +431,29 @@ export const OPTION_SPEC = [
353
431
  description: 'Append to the system prompt from a file path.',
354
432
  forward: { type: 'argv', flag: '--append-system-prompt-file' },
355
433
  },
434
+ {
435
+ name: 'system-prompt-snapshot',
436
+ kind: 'enum',
437
+ choices: ['on', 'off'],
438
+ description:
439
+ 'Record the system prompt once per conversation and reuse it ' +
440
+ 'verbatim on every later request and resume (`on`, the upstream ' +
441
+ 'default), or render it fresh every request (`off`, for iterating ' +
442
+ 'on prompt text). With `on`, a later run passing different prompt ' +
443
+ 'text is ignored until the conversation is compacted.',
444
+ forward: { type: 'argv', flag: '--system-prompt-snapshot' },
445
+ },
446
+ {
447
+ name: 'exclude-dynamic-system-prompt-sections',
448
+ kind: 'boolean',
449
+ default: false,
450
+ description:
451
+ 'Move per-machine sections (cwd, env info, memory paths, git status) ' +
452
+ 'out of the system prompt and into the first user message, so the ' +
453
+ 'cached prompt prefix is identical across machines and users. Ignored ' +
454
+ 'when `--system-prompt` replaces the default prompt.',
455
+ forward: { type: 'argv', flag: '--exclude-dynamic-system-prompt-sections' },
456
+ },
356
457
  {
357
458
  name: 'add-dir',
358
459
  kind: 'array',
@@ -387,6 +488,13 @@ export const OPTION_SPEC = [
387
488
  description: 'Load plugins from directory (repeatable).',
388
489
  forward: { type: 'argv', flag: '--plugin-dir' },
389
490
  },
491
+ {
492
+ name: 'plugin-url',
493
+ kind: 'array',
494
+ repeatable: true,
495
+ description: 'Fetch a plugin `.zip` from a URL for this session only (repeatable).',
496
+ forward: { type: 'argv', flag: '--plugin-url' },
497
+ },
390
498
  {
391
499
  name: 'disable-slash-commands',
392
500
  kind: 'boolean',
@@ -455,6 +563,38 @@ export const OPTION_SPEC = [
455
563
  description: 'Run Setup hooks with the `maintenance` trigger, then continue.',
456
564
  forward: { type: 'argv', flag: '--maintenance' },
457
565
  },
566
+ {
567
+ name: 'safe-mode',
568
+ kind: 'boolean',
569
+ default: false,
570
+ description:
571
+ 'Start with all customizations disabled (CLAUDE.md, skills, plugins, ' +
572
+ 'hooks, MCP servers, custom commands and agents, output styles, ' +
573
+ 'workflows, themes, keybindings). Admin policy settings still apply. ' +
574
+ 'Sets `CLAUDE_CODE_SAFE_MODE=1`. Useful for reproducing a failure ' +
575
+ 'without local configuration in the way.',
576
+ forward: { type: 'argv', flag: '--safe-mode' },
577
+ },
578
+ {
579
+ name: 'ax-screen-reader',
580
+ kind: 'boolean',
581
+ default: false,
582
+ env: 'OCP_AX_SCREEN_READER',
583
+ description:
584
+ 'Ask the upstream TUI for screen-reader friendly output: flat text, ' +
585
+ 'no decorative borders, no animations. Because ocp reconstructs its ' +
586
+ 'answer by scraping TUI frames, this markedly reduces the amount of ' +
587
+ 'chrome the parsers have to strip. Opt-in — it changes the captured ' +
588
+ 'frame shape, so it is never enabled by default.',
589
+ forward: { type: 'argv', flag: '--ax-screen-reader' },
590
+ },
591
+ {
592
+ name: 'brief',
593
+ kind: 'boolean',
594
+ default: false,
595
+ description: 'Enable the SendUserMessage tool for agent-to-user communication.',
596
+ forward: { type: 'argv', flag: '--brief' },
597
+ },
458
598
  {
459
599
  name: 'debug-file',
460
600
  kind: 'string',
@@ -8,8 +8,14 @@
8
8
  // Rules are appended to `CROSS_RULES`; the runner returns the concatenated
9
9
  // list of human-readable errors.
10
10
 
11
+ // Upstream (`claude --permission-mode`) advertises: acceptEdits, auto,
12
+ // bypassPermissions, manual, dontAsk, plan. `manual` is the current name
13
+ // for the prompt-on-every-tool mode that used to be spelled `default`;
14
+ // upstream still accepts `default`, so we keep it here rather than
15
+ // breaking callers that pass it.
11
16
  const PERMISSION_MODES = new Set([
12
- 'default', 'plan', 'acceptEdits', 'bypassPermissions', 'dontAsk', 'auto',
17
+ 'acceptEdits', 'auto', 'bypassPermissions', 'manual', 'dontAsk', 'plan',
18
+ 'default',
13
19
  ]);
14
20
 
15
21
  /** @type {Array<(options: Record<string, unknown>) => string | undefined>} */
@@ -85,6 +91,40 @@ export const CROSS_RULES = [
85
91
  return '`--fork-session` requires `--resume <id>` or `--continue`.';
86
92
  }
87
93
  },
94
+ // R11. forward-subagent-text => stream-json output. Upstream only emits
95
+ // the extra assistant/user messages on the stream-json channel.
96
+ (o) => {
97
+ if (o['forward-subagent-text'] === true && o['output-format'] !== 'stream-json') {
98
+ return '`--forward-subagent-text` requires `--output-format=stream-json`.';
99
+ }
100
+ },
101
+ // R12. prompt-suggestions => stream-json output. The suggestion arrives
102
+ // as its own `prompt_suggestion` event; the text and json adapters
103
+ // have nowhere to put it.
104
+ (o) => {
105
+ if (o['prompt-suggestions'] === true && o['output-format'] !== 'stream-json') {
106
+ return '`--prompt-suggestions` requires `--output-format=stream-json`.';
107
+ }
108
+ },
109
+ // R13. autocompact takes `auto` or a token budget in the 100k–1M range.
110
+ // Accepts a bare integer or a `k`/`m` suffix.
111
+ (o) => {
112
+ const v = o.autocompact;
113
+ if (v === undefined) return undefined;
114
+ if (typeof v !== 'string' || v === '') {
115
+ return '`--autocompact` must be `auto` or a token budget between 100k and 1M.';
116
+ }
117
+ if (v === 'auto') return undefined;
118
+ const m = /^(\d+(?:\.\d+)?)([km])?$/i.exec(v.trim());
119
+ if (!m) {
120
+ return `\`--autocompact\` must be \`auto\` or a token budget between 100k and 1M; got ${JSON.stringify(v)}.`;
121
+ }
122
+ const mult = m[2] ? { k: 1e3, m: 1e6 }[m[2].toLowerCase()] : 1;
123
+ const tokens = Number(m[1]) * mult;
124
+ if (!(tokens >= 100_000 && tokens <= 1_000_000)) {
125
+ return `\`--autocompact\` token budget must be between 100k and 1M; got ${JSON.stringify(v)}.`;
126
+ }
127
+ },
88
128
  ];
89
129
 
90
130
  /**