@4pm/cli 1.31.0-b → 1.32.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 (2) hide show
  1. package/dist/index.js +109 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28708,6 +28708,13 @@ var ErrorCode = {
28708
28708
  WORKER_POOL_NAME_TAKEN: "WORKER_POOL_NAME_TAKEN",
28709
28709
  WORKER_POOL_ATTACHED: "WORKER_POOL_ATTACHED",
28710
28710
  WORKER_POOL_ALREADY_ATTACHED: "WORKER_POOL_ALREADY_ATTACHED",
28711
+ // A pool can't be both a project pool and the org AI pool (ADR-0376 mutual exclusivity).
28712
+ AI_POOL_CONFLICT: "AI_POOL_CONFLICT",
28713
+ // No org AI pool configured (Settings) for a project-less org AI task (ADR-0376).
28714
+ AI_POOL_NOT_CONFIGURED: "AI_POOL_NOT_CONFIGURED",
28715
+ // Change-request (ADR-0379) not found / already resolved.
28716
+ CHANGE_REQUEST_NOT_FOUND: "CHANGE_REQUEST_NOT_FOUND",
28717
+ CHANGE_REQUEST_ALREADY_RESOLVED: "CHANGE_REQUEST_ALREADY_RESOLVED",
28711
28718
  MACHINE_ALREADY_IN_POOL: "MACHINE_ALREADY_IN_POOL",
28712
28719
  NOT_MACHINE_USER: "NOT_MACHINE_USER",
28713
28720
  // worker · physic project
@@ -39010,6 +39017,11 @@ var WsChannels = {
39010
39017
  // entries, commits, pushes a branch, opens a PR, then wipes the token/clone, and replies with the
39011
39018
  // PR URL. The write token never persists on the worker (leak-safety on release/reassign).
39012
39019
  FAQ_COMPOSE: "faq.compose",
39020
+ // Org-level "AI author checklist items" (ADR-0376): server (picks an idle cli of the org AI pool) →
39021
+ // cli request–reply. The server forwards a ready-built prompt; the worker runs claude one-shot (no
39022
+ // tools, empty dir — like support-answer) and replies with the raw output for the web to parse. No
39023
+ // project is involved (checklists are org-scoped).
39024
+ CHECKLIST_AUTHOR: "checklist.author",
39013
39025
  // Shared AI memory (ADR-0245): cli → server one-way write-back of the compacted rolling memory for
39014
39026
  // the project it serves (persisted per project × machine-user link). The read direction rides
39015
39027
  // `ws_token` (seeds the cli cache on connect); this channel only carries updates.
@@ -39961,7 +39973,8 @@ function planUnifiedRun(prompt, config, workingCredential, resume, oneShot, over
39961
39973
  key,
39962
39974
  args: buildRunArgs(cred, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
39963
39975
  stdin: prompt,
39964
- env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) }
39976
+ env: envVar ? { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) } : { ...baseEnv, ...overrideEnv(cmd, override) },
39977
+ ...cred.apiKey ? { apiKey: cred.apiKey } : {}
39965
39978
  }));
39966
39979
  return { cmd: attempts[0]?.cmd ?? DEFAULT_AI_CLI, attempts };
39967
39980
  }
@@ -39991,7 +40004,8 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
39991
40004
  key,
39992
40005
  args: buildRunArgs(profile, cmd, resume.get(key), oneShot, override, readOnly, bypass, [...allAiProfileDirs(config), dir2]),
39993
40006
  stdin: prompt,
39994
- env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) }
40007
+ env: { ...baseEnv, [envVar]: dir2, ...overrideEnv(cmd, override) },
40008
+ ...profile.apiKey ? { apiKey: profile.apiKey } : {}
39995
40009
  };
39996
40010
  });
39997
40011
  return { cmd, attempts };
@@ -40807,6 +40821,16 @@ async function runClaudeWithFailover(prompt, ai2, imageDir, timeoutMs = ANSWER_T
40807
40821
  }
40808
40822
  throw new Error(`claude ${lastReason}`);
40809
40823
  }
40824
+ async function runChecklistAuthor(req, ai2) {
40825
+ try {
40826
+ const { text } = await runClaudeWithFailover(req.prompt, ai2);
40827
+ if (!text) return { error: "empty output" };
40828
+ return { output: text };
40829
+ } catch (err) {
40830
+ logger.warn("checklist.author.failed", { error: String(err) });
40831
+ return { error: String(err) };
40832
+ }
40833
+ }
40810
40834
  async function runSupportAnswer(req, ai2, profileDir2) {
40811
40835
  let imageDir;
40812
40836
  let question = req.question;
@@ -42211,6 +42235,58 @@ async function runCommand(dispatch, emitRaw, opts) {
42211
42235
  });
42212
42236
  }
42213
42237
 
42238
+ // src/core/resolve-api-key.ts
42239
+ var cache = /* @__PURE__ */ new Map();
42240
+ async function loadAws(pkg) {
42241
+ const spec = pkg;
42242
+ return await import(spec);
42243
+ }
42244
+ async function fetchSsm(d) {
42245
+ const aws = await loadAws("@aws-sdk/client-ssm");
42246
+ const SSMClient = aws.SSMClient;
42247
+ const GetParameterCommand = aws.GetParameterCommand;
42248
+ const client = new SSMClient(d.region ? { region: d.region } : {});
42249
+ const res = await client.send(new GetParameterCommand({ Name: d.name, WithDecryption: true }));
42250
+ return res.Parameter?.Value?.trim() || null;
42251
+ }
42252
+ async function fetchSecretsManager(d) {
42253
+ const aws = await loadAws("@aws-sdk/client-secrets-manager");
42254
+ const SecretsManagerClient = aws.SecretsManagerClient;
42255
+ const GetSecretValueCommand = aws.GetSecretValueCommand;
42256
+ const client = new SecretsManagerClient(d.region ? { region: d.region } : {});
42257
+ const res = await client.send(new GetSecretValueCommand({ SecretId: d.secretId }));
42258
+ const raw = (res.SecretString ?? "").trim();
42259
+ if (!raw) return null;
42260
+ if (d.jsonKey) {
42261
+ try {
42262
+ const obj = JSON.parse(raw);
42263
+ const v2 = obj[d.jsonKey];
42264
+ return typeof v2 === "string" && v2.trim() ? v2.trim() : null;
42265
+ } catch {
42266
+ return null;
42267
+ }
42268
+ }
42269
+ return raw;
42270
+ }
42271
+ async function resolveApiKey(desc) {
42272
+ if (!desc || !desc.source) return null;
42273
+ if (desc.source === "inline") return desc.value?.trim() || null;
42274
+ const cacheKey = JSON.stringify(desc);
42275
+ const hit = cache.get(cacheKey);
42276
+ if (hit) return hit;
42277
+ try {
42278
+ const val = desc.source === "ssm" ? await fetchSsm(desc) : desc.source === "secretsManager" ? await fetchSecretsManager(desc) : null;
42279
+ if (val) {
42280
+ cache.set(cacheKey, val);
42281
+ return val;
42282
+ }
42283
+ return null;
42284
+ } catch (err) {
42285
+ logger.warn("ai.apiKey.resolve.failed", { source: desc.source, error: String(err) });
42286
+ return null;
42287
+ }
42288
+ }
42289
+
42214
42290
  // src/core/ai-runner.ts
42215
42291
  var TIMEOUT_EXIT = 124;
42216
42292
  function isAuthFailure(text) {
@@ -42259,7 +42335,9 @@ async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0, sig
42259
42335
  handlers.onChunk(text);
42260
42336
  }
42261
42337
  };
42262
- finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attempt.env, emit, timeoutMs, signal);
42338
+ const resolvedKey = attempt.apiKey ? await resolveApiKey(attempt.apiKey) : null;
42339
+ const attemptEnv = resolvedKey ? { ...attempt.env, ANTHROPIC_API_KEY: resolvedKey } : attempt.env;
42340
+ finalExit = await runAttempt(commandId, attempt.cmd, attempt.args, attempt.stdin, cwd2, attemptEnv, emit, timeoutMs, signal);
42263
42341
  const tail = parser.flush();
42264
42342
  if (tail) {
42265
42343
  captured += tail;
@@ -45837,6 +45915,28 @@ function handleSupportChannels(ctx, message, payload) {
45837
45915
  }).finally(() => ctx.bus.endBusy("faq-compose"));
45838
45916
  return true;
45839
45917
  }
45918
+ case WsChannels.CHECKLIST_AUTHOR: {
45919
+ const creq = payload;
45920
+ const ccfg = readProfileConfig(ctx.profileDir);
45921
+ const ccmd = ccfg.aiCli || "claude";
45922
+ const cai = {
45923
+ cmd: ccmd,
45924
+ profiles: resolveClaudeProfiles(ccfg, getWorkingProfile(ctx.profileDir, ccmd)),
45925
+ env: ccfg.aiEnv
45926
+ };
45927
+ ctx.bus.push({ source: "server", kind: "aireq", text: `${ccmd} \u2039 author checklist items` });
45928
+ ctx.bus.startBusy("checklist-author");
45929
+ void runChecklistAuthor(creq, cai).then((reply) => {
45930
+ if (reply.error) {
45931
+ ctx.bus.push({ source: "server", kind: "log", text: `checklist-author failed: ${reply.error}`, level: "warn" });
45932
+ } else {
45933
+ ctx.bus.push({ source: "server", kind: "aires", text: `${ccmd} \u203A` });
45934
+ }
45935
+ reportToolResult(ccmd, !reply.error, reply.error);
45936
+ ctx.send(WsChannels.CHECKLIST_AUTHOR, reply, message.id);
45937
+ }).finally(() => ctx.bus.endBusy("checklist-author"));
45938
+ return true;
45939
+ }
45840
45940
  default:
45841
45941
  return false;
45842
45942
  }
@@ -50527,7 +50627,7 @@ function widestLine(string) {
50527
50627
  }
50528
50628
 
50529
50629
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/measure-text.js
50530
- var cache = {};
50630
+ var cache2 = {};
50531
50631
  var measureText = (text) => {
50532
50632
  if (text.length === 0) {
50533
50633
  return {
@@ -50535,13 +50635,13 @@ var measureText = (text) => {
50535
50635
  height: 0
50536
50636
  };
50537
50637
  }
50538
- const cachedDimensions = cache[text];
50638
+ const cachedDimensions = cache2[text];
50539
50639
  if (cachedDimensions) {
50540
50640
  return cachedDimensions;
50541
50641
  }
50542
50642
  const width = widestLine(text);
50543
50643
  const height = text.split("\n").length;
50544
- cache[text] = { width, height };
50644
+ cache2[text] = { width, height };
50545
50645
  return { width, height };
50546
50646
  };
50547
50647
  var measure_text_default = measureText;
@@ -51066,10 +51166,10 @@ function cliTruncate(text, columns, options = {}) {
51066
51166
  }
51067
51167
 
51068
51168
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/wrap-text.js
51069
- var cache2 = {};
51169
+ var cache3 = {};
51070
51170
  var wrapText = (text, maxWidth, wrapType) => {
51071
51171
  const cacheKey = text + String(maxWidth) + String(wrapType);
51072
- const cachedText = cache2[cacheKey];
51172
+ const cachedText = cache3[cacheKey];
51073
51173
  if (cachedText) {
51074
51174
  return cachedText;
51075
51175
  }
@@ -51090,7 +51190,7 @@ var wrapText = (text, maxWidth, wrapType) => {
51090
51190
  }
51091
51191
  wrappedText = cliTruncate(text, maxWidth, { position });
51092
51192
  }
51093
- cache2[cacheKey] = wrappedText;
51193
+ cache3[cacheKey] = wrappedText;
51094
51194
  return wrappedText;
51095
51195
  };
51096
51196
  var wrap_text_default = wrapText;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.31.0-b",
3
+ "version": "1.32.0",
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",