@4pm/cli 1.25.0 → 1.27.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 +142 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28557,11 +28557,12 @@ function fetchWhoami(serverUrl, hashcode3) {
28557
28557
  function selfUnlink(serverUrl, hashcode3) {
28558
28558
  return post(serverUrl, "/machine-links/self-unlink", { hashcode3 });
28559
28559
  }
28560
- async function fetchCliVersion(serverUrl) {
28560
+ async function fetchCliVersion(serverUrl, version) {
28561
28561
  const ctrl = new AbortController();
28562
28562
  const timer = setTimeout(() => ctrl.abort(), 2e4);
28563
28563
  try {
28564
- const res = await fetch(`${serverUrl}/meta/cli-version`, { signal: ctrl.signal });
28564
+ const query = version ? `?version=${encodeURIComponent(version)}` : "";
28565
+ const res = await fetch(`${serverUrl}/meta/cli-version${query}`, { signal: ctrl.signal });
28565
28566
  return unwrap(res);
28566
28567
  } finally {
28567
28568
  clearTimeout(timer);
@@ -28798,6 +28799,8 @@ var en_default = {
28798
28799
  "update.updating": "Updating {from} \u2192 {to}\u2026",
28799
28800
  "update.checkVersionFailed": "Could not check cli version: {error}",
28800
28801
  "update.newVersionAutoUpdateOff": "A new version {latest} is available (running {current}) \u2014 autoUpdate is off.",
28802
+ "update.unsupportedSoon": "cli {current} stops being supported on {date} \u2014 update before then (4pm update).",
28803
+ "update.policyNote": "Note from 4PM: {note}",
28801
28804
  "update.applying": "{kind} cli update {from} \u2192 {to}\u2026",
28802
28805
  "update.kindMandatory": "Mandatory",
28803
28806
  "update.kindAutomatic": "Automatic",
@@ -28981,6 +28984,8 @@ var vi_default = {
28981
28984
  "update.updating": "\u0110ang c\u1EADp nh\u1EADt {from} \u2192 {to}\u2026",
28982
28985
  "update.checkVersionFailed": "Kh\xF4ng th\u1EC3 ki\u1EC3m tra phi\xEAn b\u1EA3n cli: {error}",
28983
28986
  "update.newVersionAutoUpdateOff": "C\xF3 phi\xEAn b\u1EA3n m\u1EDBi {latest} (\u0111ang ch\u1EA1y {current}) \u2014 autoUpdate \u0111ang t\u1EAFt.",
28987
+ "update.unsupportedSoon": "cli {current} s\u1EBD ng\u1EEBng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 t\u1EEB {date} \u2014 h\xE3y c\u1EADp nh\u1EADt tr\u01B0\u1EDBc ng\xE0y \u0111\xF3 (4pm update).",
28988
+ "update.policyNote": "Ghi ch\xFA t\u1EEB 4PM: {note}",
28984
28989
  "update.applying": "C\u1EADp nh\u1EADt cli {kind} {from} \u2192 {to}\u2026",
28985
28990
  "update.kindMandatory": "b\u1EAFt bu\u1ED9c",
28986
28991
  "update.kindAutomatic": "t\u1EF1 \u0111\u1ED9ng",
@@ -29164,6 +29169,8 @@ var ja_default = {
29164
29169
  "update.updating": "\u66F4\u65B0\u4E2D {from} \u2192 {to}\u2026",
29165
29170
  "update.checkVersionFailed": "cli \u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F: {error}",
29166
29171
  "update.newVersionAutoUpdateOff": "\u65B0\u3057\u3044\u30D0\u30FC\u30B8\u30E7\u30F3 {latest} \u304C\u5229\u7528\u53EF\u80FD\u3067\u3059\uFF08\u5B9F\u884C\u4E2D {current}\uFF09\u2014 autoUpdate \u306F\u30AA\u30D5\u3067\u3059\u3002",
29172
+ "update.unsupportedSoon": "cli {current} \u306F {date} \u306B\u30B5\u30DD\u30FC\u30C8\u304C\u7D42\u4E86\u3057\u307E\u3059 \u2014 \u305D\u308C\u307E\u3067\u306B\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\uFF084pm update\uFF09\u3002",
29173
+ "update.policyNote": "4PM \u304B\u3089\u306E\u304A\u77E5\u3089\u305B: {note}",
29167
29174
  "update.applying": "{kind} \u306E cli \u66F4\u65B0 {from} \u2192 {to}\u2026",
29168
29175
  "update.kindMandatory": "\u5FC5\u9808",
29169
29176
  "update.kindAutomatic": "\u81EA\u52D5",
@@ -29347,6 +29354,8 @@ var zh_default = {
29347
29354
  "update.updating": "\u6B63\u5728\u66F4\u65B0 {from} \u2192 {to}\u2026",
29348
29355
  "update.checkVersionFailed": "\u65E0\u6CD5\u68C0\u67E5 cli \u7248\u672C\uFF1A{error}",
29349
29356
  "update.newVersionAutoUpdateOff": "\u6709\u65B0\u7248\u672C {latest} \u53EF\u7528\uFF08\u5F53\u524D\u8FD0\u884C {current}\uFF09\u2014 autoUpdate \u5DF2\u5173\u95ED\u3002",
29357
+ "update.unsupportedSoon": "cli {current} \u5C06\u4E8E {date} \u505C\u6B62\u652F\u6301 \u2014 \u8BF7\u5728\u6B64\u4E4B\u524D\u66F4\u65B0\uFF084pm update\uFF09\u3002",
29358
+ "update.policyNote": "\u6765\u81EA 4PM \u7684\u8BF4\u660E\uFF1A{note}",
29350
29359
  "update.applying": "{kind} cli \u66F4\u65B0 {from} \u2192 {to}\u2026",
29351
29360
  "update.kindMandatory": "\u5F3A\u5236",
29352
29361
  "update.kindAutomatic": "\u81EA\u52A8",
@@ -32701,12 +32710,18 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
32701
32710
  if (CLI_VERSION.startsWith("0.0.0")) return { action: "none" };
32702
32711
  let meta;
32703
32712
  try {
32704
- meta = await fetchCliVersion(serverUrl);
32713
+ meta = await fetchCliVersion(serverUrl, CLI_VERSION);
32705
32714
  } catch (err) {
32706
32715
  console.warn(t("update.checkVersionFailed", { error: String(err) }));
32707
32716
  return { action: "none" };
32708
32717
  }
32709
- const mandatory = compareSemver(CLI_VERSION, meta.minSupported) < 0;
32718
+ const mandatory = compareSemver(CLI_VERSION, meta.minSupported) < 0 || meta.status === "unsupported";
32719
+ if (meta.status === "warning" && meta.unsupportedFrom) {
32720
+ console.warn(t("update.unsupportedSoon", { current: CLI_VERSION, date: meta.unsupportedFrom.slice(0, 10) }));
32721
+ if (meta.note) console.warn(t("update.policyNote", { note: meta.note }));
32722
+ } else if (meta.status === "unsupported" && meta.note) {
32723
+ console.warn(t("update.policyNote", { note: meta.note }));
32724
+ }
32710
32725
  const outdated = shouldUpdateTo(CLI_VERSION, meta.latest);
32711
32726
  if (!outdated) return { action: "none" };
32712
32727
  if (!mandatory && !autoUpdate) {
@@ -37255,6 +37270,67 @@ var agentToolsWriteRequestSchema = external_exports.object({
37255
37270
  permissions: agentToolsPermissionsSchema
37256
37271
  });
37257
37272
 
37273
+ // vendor/@4pm/dto/meta.ts
37274
+ var cliVersionString = external_exports.string().trim().regex(/^\d+\.\d+\.\d+([-+][0-9A-Za-z.-]+)?$/);
37275
+ var setCliMinVersionRequestSchema = external_exports.object({
37276
+ minSupported: cliVersionString.nullable(),
37277
+ disconnect: external_exports.boolean().optional()
37278
+ });
37279
+ var setCliVersionOverrideRequestSchema = external_exports.object({
37280
+ status: external_exports.enum(["supported", "warning", "unsupported"]),
37281
+ unsupportedFrom: external_exports.string().datetime({ offset: true }).nullable().optional(),
37282
+ note: external_exports.string().max(500).optional(),
37283
+ disconnect: external_exports.boolean().optional()
37284
+ });
37285
+
37286
+ // vendor/@4pm/dto/storage.ts
37287
+ var STORAGE_KINDS = [
37288
+ "messageText",
37289
+ "postText",
37290
+ "commentText",
37291
+ "memoText",
37292
+ "messageImages",
37293
+ "messageFiles",
37294
+ "postImages",
37295
+ "postFiles",
37296
+ "commentImages",
37297
+ "commentFiles",
37298
+ "memoImages",
37299
+ "memoFiles",
37300
+ "templates",
37301
+ "aiDocImages",
37302
+ "artifacts",
37303
+ "commandHistory"
37304
+ ];
37305
+ var COMMUNITY_STORAGE_KINDS = [
37306
+ "messageText",
37307
+ "postText",
37308
+ "commentText",
37309
+ "messageImages",
37310
+ "messageFiles",
37311
+ "postImages",
37312
+ "postFiles",
37313
+ "commentImages",
37314
+ "commentFiles"
37315
+ ];
37316
+ var SERVER_CLEARABLE_STORAGE_KINDS = [
37317
+ "memoText",
37318
+ "memoImages",
37319
+ "memoFiles",
37320
+ "templates",
37321
+ "commandHistory"
37322
+ ];
37323
+ var CLEARABLE_STORAGE_KINDS = [
37324
+ ...COMMUNITY_STORAGE_KINDS,
37325
+ ...SERVER_CLEARABLE_STORAGE_KINDS
37326
+ ];
37327
+ var storageClearRequestSchema = external_exports.object({
37328
+ kinds: external_exports.array(external_exports.enum(STORAGE_KINDS)).min(1).max(STORAGE_KINDS.length),
37329
+ before: external_exports.string().datetime({ offset: true }),
37330
+ /** org-0009 / community-0015 only — must equal the org name. */
37331
+ confirm: external_exports.string().max(200).optional()
37332
+ });
37333
+
37258
37334
  // vendor/@4pm/dto/org.ts
37259
37335
  var CLI_SETTINGS_BOUNDS = {
37260
37336
  reconnectMaxBackoffSec: { min: 5, max: 60 },
@@ -37274,6 +37350,8 @@ var TEMPLATE_KINDS = ["report", "est", "wbs", "proposal", "feasibility"];
37274
37350
  // vendor/@4pm/dto/project.ts
37275
37351
  var listProjectsQuerySchema = baseRequestSchema.extend({
37276
37352
  pinned: pinnedFilterSchema,
37353
+ /** Only projects this team is attached to (project-0001; the public v1 list — ADR-0364). */
37354
+ teamId: external_exports.string().uuid().optional(),
37277
37355
  /** ADMIN-only Trash: list soft-deleted projects (ADR-0109). */
37278
37356
  deleted: deletedFilterSchema
37279
37357
  });
@@ -38370,6 +38448,9 @@ var WsChannels = {
38370
38448
  QUOTA_CHECK: "quota.check",
38371
38449
  // cli → server (reply): per-org concurrent AI-run slot — acquire / renew / release (ADR-0359)
38372
38450
  RUN_SLOT: "run.slot",
38451
+ // cli → server (reply): read / add the org's monthly autonomous-book counters — USER_TODO / USER_QA /
38452
+ // AI_TODO rows vs `autonomousRequestsPerMonth` (ADR-0365)
38453
+ AUTONOMOUS_BOOK_USAGE: "autonomous.bookUsage",
38373
38454
  USAGE_REPORT: "usage.report",
38374
38455
  // cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
38375
38456
  MACHINE_USAGE: "machine.usage",
@@ -40447,6 +40528,14 @@ function tableRowsById(md) {
40447
40528
  }
40448
40529
  return rows;
40449
40530
  }
40531
+ var CAPPED_BOOK_FILES = { USER_TODO: "USER_TODO.md", USER_QA: "USER_QA.md", AI_TODO: "AI_TODO.md" };
40532
+ async function cappedBookIds(root) {
40533
+ const out = {};
40534
+ for (const [book, file] of Object.entries(CAPPED_BOOK_FILES)) {
40535
+ out[book] = new Set(tableRowsById(await readText(join17(root, file))).keys());
40536
+ }
40537
+ return out;
40538
+ }
40450
40539
  function tickScript(root) {
40451
40540
  return join17(root, TICK_REL);
40452
40541
  }
@@ -40538,6 +40627,7 @@ async function uninstallCron(root) {
40538
40627
  await crontabSet(kept.join("\n"));
40539
40628
  }
40540
40629
  async function writeAutonomous(root, profileDir2, req, by) {
40630
+ let added;
40541
40631
  try {
40542
40632
  switch (req.kind) {
40543
40633
  case "settings": {
@@ -40582,6 +40672,7 @@ async function writeAutonomous(root, profileDir2, req, by) {
40582
40672
  const row = `| ${id} | | | ${tableCell(req.content)} |
40583
40673
  `;
40584
40674
  await writeFile4(join17(root, BOOK_FILES.userTodo), cur.replace(/\s*$/, "\n") + row, "utf8");
40675
+ added = 1;
40585
40676
  const authors = await readJsonMap(join17(root, AUTHORS_REL));
40586
40677
  authors[id] = { by, byLabel: req.byLabel ?? by, at: (/* @__PURE__ */ new Date()).toISOString() };
40587
40678
  await writeFile4(join17(root, AUTHORS_REL), JSON.stringify(authors, null, 2) + "\n", "utf8");
@@ -40594,8 +40685,10 @@ async function writeAutonomous(root, profileDir2, req, by) {
40594
40685
  const authors = await readJsonMap(join17(root, AUTHORS_REL));
40595
40686
  const at2 = (/* @__PURE__ */ new Date()).toISOString();
40596
40687
  const byLabel = req.byLabel ?? by;
40688
+ added = 0;
40597
40689
  for (const [id, cells] of next) {
40598
40690
  if (prev.get(id) !== cells) authors[id] = { by, byLabel, at: at2 };
40691
+ if (!prev.has(id)) added += 1;
40599
40692
  }
40600
40693
  await writeFile4(join17(root, file), req.content, "utf8");
40601
40694
  await writeFile4(join17(root, AUTHORS_REL), JSON.stringify(authors, null, 2) + "\n", "utf8");
@@ -40607,7 +40700,7 @@ async function writeAutonomous(root, profileDir2, req, by) {
40607
40700
  break;
40608
40701
  }
40609
40702
  }
40610
- return { ok: true, status: await getAutonomousStatus(root, profileDir2) };
40703
+ return { ok: true, status: await getAutonomousStatus(root, profileDir2), ...added !== void 0 ? { added } : {} };
40611
40704
  } catch (err) {
40612
40705
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
40613
40706
  }
@@ -41682,6 +41775,7 @@ async function acquireRunSlot(ctx, onQueued, signal) {
41682
41775
  }
41683
41776
  return { kind: "granted", handle, queued: ticket !== void 0 };
41684
41777
  }
41778
+ if ("denied" in reply) return { kind: "denied", reason: reply.denied };
41685
41779
  ticket = reply.ticket;
41686
41780
  limit = reply.limit;
41687
41781
  if (reply.position !== lastPosition) {
@@ -41996,6 +42090,13 @@ ${guardedPrompt}`;
41996
42090
  const text = `RUN_QUEUE_TIMEOUT: waited 30 min for a free run slot (plan limit ${slot.limit} parallel runs) \u2014 not run`;
41997
42091
  ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
41998
42092
  ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
42093
+ `, log: true });
42094
+ result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
42095
+ } else if (slot.kind === "denied") {
42096
+ neverStarted = true;
42097
+ const text = "STORAGE_QUOTA_EXCEEDED: the organization's hosted storage is full \u2014 clear storage in Settings \u203A Storage, then retry";
42098
+ ctx.bus.push({ source: origin, kind: "log", text, level: "warn" });
42099
+ ctx.send(WsChannels.COMMAND_OUTPUT, { commandId, seq: seq++, chunk: `${text}
41999
42100
  `, log: true });
42000
42101
  result = { exitCode: 1, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 }, sessionId: "" };
42001
42102
  } else if (slot.kind === "cancelled") {
@@ -42320,12 +42421,17 @@ function rejectByReview(ctx, commandId, origin, prompt, reasons) {
42320
42421
  import { randomUUID } from "crypto";
42321
42422
  import { appendFile as appendFile2, mkdir as mkdir3, readFile as readFile5, readdir as readdir2, rm as rm2, stat } from "fs/promises";
42322
42423
  import { join as join20 } from "path";
42323
- function buildAutonomousCyclePrompt() {
42424
+ function buildAutonomousCyclePrompt(opts = {}) {
42425
+ const capNote = opts.intakeBlocked ? `
42426
+
42427
+ **Step 2 is DISABLED this cycle** \u2014 the organization reached its monthly plan limit for new \`AI_TODO\` /
42428
+ \`USER_QA\` rows. Do NOT add any row to \`AI_TODO.md\` or \`USER_QA.md\`; skip Step 2 entirely and continue
42429
+ with the already-approved work.` : "";
42324
42430
  return `You are running UNATTENDED as the 4PM autonomous cycle (headless, one tick). Do EXACTLY ONE small
42325
42431
  work cycle with the steps below, then STOP. Keep each cycle to ONE small task so you never run out of
42326
42432
  tokens mid-way. If a step fails, append a row to the **Incidents** table of \`AI_DONE.md\` and STOP \u2014
42327
42433
  do not push on. Write all books/docs/code in the language the project's \`CLAUDE.md\` specifies (default
42328
- English).
42434
+ English).${capNote}
42329
42435
 
42330
42436
  ## Books & approval (MUST read the approvals file)
42331
42437
  The five "book" files at the project root \u2014 \`USER_TODO.md\`, \`USER_QA.md\`, \`AI_TODO.md\`,
@@ -42459,8 +42565,16 @@ async function runAutonomousCycle(ctx) {
42459
42565
  await log(`[run] cycle (tick ${hist.ticks.count}/${today})`);
42460
42566
  const commandId = randomUUID();
42461
42567
  const override = cfg.model ? { model: cfg.model } : void 0;
42568
+ const usage = await readBookUsage(ctx);
42569
+ const intakeBlocked = ["AI_TODO", "USER_QA"].some((b2) => {
42570
+ const c = usage?.books[b2];
42571
+ return !!c && c.limit !== null && c.used >= c.limit;
42572
+ });
42573
+ if (intakeBlocked) await log("[cap] monthly AI_TODO/USER_QA limit reached \u2014 intake skipped this cycle");
42574
+ const booksBefore = await cappedBookIds(root);
42462
42575
  try {
42463
- await runAiPrompt(ctx, buildAutonomousCyclePrompt(), commandId, "local", false, void 0, override, false, true);
42576
+ await runAiPrompt(ctx, buildAutonomousCyclePrompt({ intakeBlocked }), commandId, "local", false, void 0, override, false, true);
42577
+ await reportNewBookRows(ctx, root, booksBefore);
42464
42578
  hist = await readHistories(root);
42465
42579
  hist.consecutiveFails = 0;
42466
42580
  await recordRun(root, hist, "success", "cycle complete", today);
@@ -42468,6 +42582,7 @@ async function runAutonomousCycle(ctx) {
42468
42582
  ctx.bus.autonomousDone(true);
42469
42583
  } catch (err) {
42470
42584
  const msg = err instanceof Error ? err.message : String(err);
42585
+ await reportNewBookRows(ctx, root, booksBefore);
42471
42586
  hist = await readHistories(root);
42472
42587
  hist.consecutiveFails += 1;
42473
42588
  await recordRun(root, hist, "failure", msg, today);
@@ -42484,6 +42599,25 @@ async function runAutonomousCycle(ctx) {
42484
42599
  running.delete(root);
42485
42600
  }
42486
42601
  }
42602
+ async function readBookUsage(ctx) {
42603
+ try {
42604
+ return await ctx.request(WsChannels.AUTONOMOUS_BOOK_USAGE, {});
42605
+ } catch {
42606
+ return null;
42607
+ }
42608
+ }
42609
+ async function reportNewBookRows(ctx, root, before) {
42610
+ try {
42611
+ const after = await cappedBookIds(root);
42612
+ const added = {};
42613
+ for (const book of Object.keys(after)) {
42614
+ const n = [...after[book]].filter((id) => !before[book].has(id)).length;
42615
+ if (n > 0) added[book] = n;
42616
+ }
42617
+ if (Object.keys(added).length > 0) await ctx.request(WsChannels.AUTONOMOUS_BOOK_USAGE, { added });
42618
+ } catch {
42619
+ }
42620
+ }
42487
42621
  async function dayLog(root, line) {
42488
42622
  try {
42489
42623
  const dir2 = join20(root, ".claude", "logs");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.25.0",
3
+ "version": "1.27.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",