@4pm/cli 1.5.22 → 1.5.25

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 (2) hide show
  1. package/dist/index.js +75 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33013,6 +33013,15 @@ var dispatchCommandRequestSchema = external_exports.object({
33013
33013
  * agent run (the Console tab + Git merge, which legitimately use tools).
33014
33014
  */
33015
33015
  aiOneShot: external_exports.boolean().optional(),
33016
+ /**
33017
+ * Read-only agent AI mode (ADR-0265): the prompt is a task that must **read + inspect the repo
33018
+ * but write nothing** (the project-template "Analyze impact"). Unlike `aiOneShot` it keeps the
33019
+ * read/search tools (`Read`/`Glob`/`Grep`/`Bash`) and runs multi-turn, but the cli blocks the
33020
+ * write/orchestration tools and runs claude under `--permission-mode plan` (codex `--sandbox
33021
+ * read-only`) so it can diff files without editing them. Only meaningful with `ai:true`;
33022
+ * **mutually exclusive with `aiOneShot`** (a run is one-shot | read-only | full-agent).
33023
+ */
33024
+ aiReadOnly: external_exports.boolean().optional(),
33016
33025
  /**
33017
33026
  * Per-run AI execution overrides (ADR-0261): model/thinking/temperature chosen in the web
33018
33027
  * "AI settings" modal (per-user localStorage), layered over the profile default by the cli.
@@ -33027,6 +33036,13 @@ var dispatchCommandRequestSchema = external_exports.object({
33027
33036
  message: "Provide exactly one of machineLinkId or pick."
33028
33037
  });
33029
33038
  }
33039
+ if (v.aiOneShot && v.aiReadOnly) {
33040
+ ctx.addIssue({
33041
+ code: external_exports.ZodIssueCode.custom,
33042
+ path: ["aiReadOnly"],
33043
+ message: "aiOneShot and aiReadOnly are mutually exclusive."
33044
+ });
33045
+ }
33030
33046
  if (!v.ai && v.command.length > COMMAND_MAX_LEN) {
33031
33047
  ctx.addIssue({
33032
33048
  code: external_exports.ZodIssueCode.too_big,
@@ -33318,16 +33334,6 @@ var MEMO_MAX_LENGTH = 65536;
33318
33334
  var putMemoRequestSchema = external_exports.object({
33319
33335
  content: external_exports.string().max(MEMO_MAX_LENGTH)
33320
33336
  });
33321
- var MEMO_ITEM_MAX_LENGTH = 2e3;
33322
- var createMemoItemSchema = external_exports.object({
33323
- content: external_exports.string().trim().min(1).max(MEMO_ITEM_MAX_LENGTH)
33324
- });
33325
- var updateMemoItemSchema = external_exports.object({
33326
- content: external_exports.string().trim().min(1).max(MEMO_ITEM_MAX_LENGTH).optional(),
33327
- done: external_exports.boolean().optional()
33328
- }).refine((v) => v.content !== void 0 || v.done !== void 0, {
33329
- message: "content or done required"
33330
- });
33331
33337
  var HTTP_JSON_BODY_LIMIT_BYTES = 2 * 1048576;
33332
33338
  var putSpecDraftRequestSchema = external_exports.object({
33333
33339
  spec: external_exports.record(external_exports.unknown()),
@@ -34843,6 +34849,48 @@ function oneShotArgs(cmd) {
34843
34849
  if (!cmd.includes("claude")) return [];
34844
34850
  return ["--max-turns=1", "--disallowedTools", ONE_SHOT_DISALLOWED_CLAUDE_TOOLS, "--permission-mode", "default"];
34845
34851
  }
34852
+ var READ_ONLY_DISALLOWED_CLAUDE_TOOLS = [
34853
+ "Edit",
34854
+ "Write",
34855
+ "NotebookEdit",
34856
+ "Task",
34857
+ "SlashCommand",
34858
+ "Skill",
34859
+ "WebSearch",
34860
+ "WebFetch",
34861
+ "TaskCreate",
34862
+ "TaskGet",
34863
+ "TaskList",
34864
+ "TaskOutput",
34865
+ "TaskStop",
34866
+ "TaskUpdate",
34867
+ "Monitor",
34868
+ "DesignSync",
34869
+ "CronCreate",
34870
+ "CronDelete",
34871
+ "CronList",
34872
+ "EnterWorktree",
34873
+ "ExitWorktree",
34874
+ "RemoteTrigger",
34875
+ "ScheduleWakeup",
34876
+ "SendMessage",
34877
+ "PushNotification"
34878
+ ].join(",");
34879
+ var READ_ONLY_MAX_TURNS = 40;
34880
+ function readOnlyArgs(cmd, profileMaxTurns) {
34881
+ if (cmd.includes("claude")) {
34882
+ const turns = typeof profileMaxTurns === "number" && profileMaxTurns > 0 ? Math.floor(profileMaxTurns) : READ_ONLY_MAX_TURNS;
34883
+ return [
34884
+ `--max-turns=${turns}`,
34885
+ "--disallowedTools",
34886
+ READ_ONLY_DISALLOWED_CLAUDE_TOOLS,
34887
+ "--permission-mode",
34888
+ "plan"
34889
+ ];
34890
+ }
34891
+ if (cmd.includes("codex")) return ["--sandbox", "read-only"];
34892
+ return [];
34893
+ }
34846
34894
  function requiredArgs(cmd) {
34847
34895
  if (cmd.includes("claude")) return REQUIRED_AI_ARGS.claude;
34848
34896
  if (cmd.includes("codex")) return REQUIRED_AI_ARGS.codex;
@@ -34916,21 +34964,21 @@ function overrideEnv(cmd, o) {
34916
34964
  const budget = THINKING_TOKENS[o.thinking];
34917
34965
  return budget ? { MAX_THINKING_TOKENS: String(budget) } : {};
34918
34966
  }
34919
- function buildRunArgs(profile, cmd, resumeId, oneShot = false, override) {
34967
+ function buildRunArgs(profile, cmd, resumeId, oneShot = false, override, readOnly = false) {
34920
34968
  const extras = profile.args ?? [];
34921
34969
  const model = override?.model?.trim() || profile.model?.trim();
34922
34970
  const modelArgs = model ? ["--model", model] : [];
34923
34971
  const resumeArgs = resumeId && cmd.includes("claude") ? ["--resume", resumeId] : [];
34924
34972
  const turns = profile.maxTurns;
34925
- const perProfileTurns = !oneShot && cmd.includes("claude") && typeof turns === "number" && turns > 0 ? [`--max-turns=${Math.floor(turns)}`] : [];
34926
- const oneShotExtra = oneShot ? oneShotArgs(cmd) : perProfileTurns;
34973
+ const perProfileTurns = !oneShot && !readOnly && cmd.includes("claude") && typeof turns === "number" && turns > 0 ? [`--max-turns=${Math.floor(turns)}`] : [];
34974
+ const modeExtra = oneShot ? oneShotArgs(cmd) : readOnly ? readOnlyArgs(cmd, turns) : perProfileTurns;
34927
34975
  return [
34928
34976
  ...requiredArgs(cmd),
34929
34977
  ...resumeArgs,
34930
34978
  ...extras,
34931
34979
  ...modelArgs,
34932
34980
  ...overrideArgs(cmd, override),
34933
- ...oneShotExtra
34981
+ ...modeExtra
34934
34982
  ];
34935
34983
  }
34936
34984
  function claudeHomeDirs(config) {
@@ -34970,10 +35018,10 @@ function resolveClaudeProfiles(config, workingDir) {
34970
35018
  if (idx > 0) profiles.unshift(profiles.splice(idx, 1)[0]);
34971
35019
  return profiles;
34972
35020
  }
34973
- function planAiRun(prompt, config, hint = {}, resume = /* @__PURE__ */ new Map(), oneShot = false, override) {
34974
- return isUnifiedConfig(config) ? planUnifiedRun(prompt, config, hint.credential ?? null, resume, oneShot, override) : planLegacyRun(prompt, config, hint.dir ?? null, resume, oneShot, override);
35021
+ function planAiRun(prompt, config, hint = {}, resume = /* @__PURE__ */ new Map(), oneShot = false, override, readOnly = false) {
35022
+ return isUnifiedConfig(config) ? planUnifiedRun(prompt, config, hint.credential ?? null, resume, oneShot, override, readOnly) : planLegacyRun(prompt, config, hint.dir ?? null, resume, oneShot, override, readOnly);
34975
35023
  }
34976
- function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, override) {
35024
+ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, override, readOnly = false) {
34977
35025
  const baseEnv = config.aiEnv ?? {};
34978
35026
  const active = activeProvider(config);
34979
35027
  const usable = config.aiProfiles.filter(isUsableCredential);
@@ -34982,7 +35030,7 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
34982
35030
  return {
34983
35031
  cmd: active,
34984
35032
  attempts: [
34985
- { cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot, override), stdin: prompt, env: { ...baseEnv, ...overrideEnv(active, override) } }
35033
+ { cmd: active, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, active, void 0, oneShot, override, readOnly), stdin: prompt, env: { ...baseEnv, ...overrideEnv(active, override) } }
34986
35034
  ]
34987
35035
  };
34988
35036
  }
@@ -35000,20 +35048,20 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
35000
35048
  label: cred.label?.trim() || profileDisplayLabel(dir2),
35001
35049
  dir: dir2,
35002
35050
  key,
35003
- args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override),
35051
+ args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly),
35004
35052
  stdin: prompt,
35005
35053
  env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) }
35006
35054
  }));
35007
35055
  return { cmd: attempts[0]?.cmd ?? DEFAULT_AI_CLI, attempts };
35008
35056
  }
35009
- function planLegacyRun(prompt, config, workingDir, resume, oneShot, override) {
35057
+ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, readOnly = false) {
35010
35058
  const cmd = config.aiCli || DEFAULT_AI_CLI;
35011
35059
  const baseEnv = config.aiEnv ?? {};
35012
35060
  const envVar = profileEnvVar(cmd);
35013
35061
  const defaultPlan = {
35014
35062
  cmd,
35015
35063
  attempts: [
35016
- { cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot, override), stdin: prompt, env: { ...baseEnv, ...overrideEnv(cmd, override) } }
35064
+ { cmd, label: "default", dir: null, key: null, args: buildRunArgs({ profile: "" }, cmd, void 0, oneShot, override, readOnly), stdin: prompt, env: { ...baseEnv, ...overrideEnv(cmd, override) } }
35017
35065
  ]
35018
35066
  };
35019
35067
  if (!envVar) return defaultPlan;
@@ -35030,7 +35078,7 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override) {
35030
35078
  label: profileDisplayLabel(dir2),
35031
35079
  dir: dir2,
35032
35080
  key,
35033
- args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override),
35081
+ args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly),
35034
35082
  stdin: prompt,
35035
35083
  env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) }
35036
35084
  };
@@ -38443,7 +38491,8 @@ var WsClient = class {
38443
38491
  "server",
38444
38492
  dispatch.aiOneShot ?? false,
38445
38493
  dispatch.images,
38446
- dispatch.aiConfig
38494
+ dispatch.aiConfig,
38495
+ dispatch.aiReadOnly ?? false
38447
38496
  );
38448
38497
  }
38449
38498
  break;
@@ -38940,7 +38989,7 @@ var WsClient = class {
38940
38989
  * exactly like typing the prompt in the cli (tries profiles, meters real tokens) instead
38941
38990
  * of spawning the text as a raw command. Streams output to the transcript AND the server.
38942
38991
  */
38943
- async runAiPrompt(prompt, commandId, origin, oneShot = false, images, aiConfig) {
38992
+ async runAiPrompt(prompt, commandId, origin, oneShot = false, images, aiConfig, readOnly = false) {
38944
38993
  const config = readProfileConfig(this.context.profileDir);
38945
38994
  const projectTimeoutSec = config.projectAiRunTimeoutSec ?? 0;
38946
38995
  const machineTimeoutSec = config.aiRunTimeoutSec ?? 0;
@@ -39012,7 +39061,7 @@ var WsClient = class {
39012
39061
  const guardedPrompt = this.restrictToFolder && this.physicRoot ? folderScopeGuard(this.physicRoot, aiBodyPrompt) : aiBodyPrompt;
39013
39062
  const hint = unified ? { credential: startCred ?? workingCred } : { dir: startDir ?? workingDir };
39014
39063
  const memCfg = resolveMemoryConfig(config);
39015
- const firstAttempt = planAiRun(guardedPrompt, config, hint, /* @__PURE__ */ new Map(), oneShot, aiConfig).attempts[0];
39064
+ const firstAttempt = planAiRun(guardedPrompt, config, hint, /* @__PURE__ */ new Map(), oneShot, aiConfig, readOnly).attempts[0];
39016
39065
  const resumeId = memCfg.enabled && firstAttempt?.key && firstAttempt.cmd === "claude" ? this.sessionIdByKey.get(firstAttempt.key) : void 0;
39017
39066
  let effectivePrompt = guardedPrompt;
39018
39067
  let resume = /* @__PURE__ */ new Map();
@@ -39024,7 +39073,7 @@ ${this.aiMemory}
39024
39073
 
39025
39074
  ${guardedPrompt}`;
39026
39075
  }
39027
- const plan = planAiRun(effectivePrompt, config, hint, resume, oneShot, aiConfig);
39076
+ const plan = planAiRun(effectivePrompt, config, hint, resume, oneShot, aiConfig, readOnly);
39028
39077
  const markerArgs = plan.attempts[0]?.args ?? [];
39029
39078
  const startStamp = formatTimestampInZone(/* @__PURE__ */ new Date(), this.orgTimezone);
39030
39079
  this.bus.push({ source: origin, kind: "aireq", text: `${startStamp} ${plan.cmd} \u2039 ${prompt}` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.5.22",
3
+ "version": "1.5.25",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "license": "LicenseRef-4PM-Source-Available",