@4pm/cli 1.10.2 → 1.12.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 +61 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28541,8 +28541,14 @@ function selfUnlink(serverUrl, hashcode3) {
28541
28541
  return post(serverUrl, "/machine-links/self-unlink", { hashcode3 });
28542
28542
  }
28543
28543
  async function fetchCliVersion(serverUrl) {
28544
- const res = await fetch(`${serverUrl}/meta/cli-version`);
28545
- return unwrap(res);
28544
+ const ctrl = new AbortController();
28545
+ const timer = setTimeout(() => ctrl.abort(), 2e4);
28546
+ try {
28547
+ const res = await fetch(`${serverUrl}/meta/cli-version`, { signal: ctrl.signal });
28548
+ return unwrap(res);
28549
+ } finally {
28550
+ clearTimeout(timer);
28551
+ }
28546
28552
  }
28547
28553
 
28548
28554
  // vendor/@4pm/constants/preferences.ts
@@ -32520,6 +32526,9 @@ function compareSemver(a, b2) {
32520
32526
  }
32521
32527
  return 0;
32522
32528
  }
32529
+ function shouldUpdateTo(current2, latest) {
32530
+ return current2 !== latest && compareSemver(current2, latest) <= 0;
32531
+ }
32523
32532
  function runningInstallRoot() {
32524
32533
  return dirname2(dirname2(fileURLToPath2(import.meta.url)));
32525
32534
  }
@@ -32572,10 +32581,10 @@ async function updateViaDownload(tarballUrl, checksum, signature) {
32572
32581
  throw new Error(`Failed to extract the update tarball into ${installRoot}: ${String(err)}`);
32573
32582
  }
32574
32583
  }
32575
- async function updateToLatest(serverUrl) {
32584
+ async function updateToLatest(serverUrl, force = false) {
32576
32585
  if (CLI_VERSION.startsWith("0.0.0")) return { action: "dev-build", version: CLI_VERSION };
32577
32586
  const meta = await fetchCliVersion(serverUrl);
32578
- if (compareSemver(CLI_VERSION, meta.latest) >= 0) {
32587
+ if (!force && !shouldUpdateTo(CLI_VERSION, meta.latest)) {
32579
32588
  return { action: "already-latest", version: CLI_VERSION };
32580
32589
  }
32581
32590
  console.log(t("update.updating", { from: CLI_VERSION, to: meta.latest }));
@@ -32593,7 +32602,7 @@ async function updateToLatest(serverUrl) {
32593
32602
  return { action: "failed", error: String(err), toVersion: meta.latest };
32594
32603
  }
32595
32604
  const installed = readInstalledVersion();
32596
- if (compareSemver(installed, meta.latest) < 0) {
32605
+ if (shouldUpdateTo(installed, meta.latest)) {
32597
32606
  return {
32598
32607
  action: "failed",
32599
32608
  error: `update ran but the installed version is still ${installed} (expected ${meta.latest}) \u2014 it did not apply to the running install at ${runningInstallRoot()}.`,
@@ -32612,7 +32621,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
32612
32621
  return { action: "none" };
32613
32622
  }
32614
32623
  const mandatory = compareSemver(CLI_VERSION, meta.minSupported) < 0;
32615
- const outdated = compareSemver(CLI_VERSION, meta.latest) < 0;
32624
+ const outdated = shouldUpdateTo(CLI_VERSION, meta.latest);
32616
32625
  if (!outdated) return { action: "none" };
32617
32626
  if (!mandatory && !autoUpdate) {
32618
32627
  console.warn(t("update.newVersionAutoUpdateOff", { latest: meta.latest, current: CLI_VERSION }));
@@ -32644,7 +32653,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
32644
32653
  return { action: "none" };
32645
32654
  }
32646
32655
  const installed = readInstalledVersion();
32647
- if (compareSemver(installed, meta.latest) < 0) {
32656
+ if (shouldUpdateTo(installed, meta.latest)) {
32648
32657
  const msg = t("update.notApplied", { installed, expected: meta.latest });
32649
32658
  if (mandatory) {
32650
32659
  console.error(msg);
@@ -37003,6 +37012,10 @@ var workerToolCopySchema = external_exports.object({
37003
37012
  /** Target machine-link ids to queue the manifest for (pushed on their next connect). */
37004
37013
  targetLinkIds: external_exports.array(external_exports.string().uuid()).min(1).max(200)
37005
37014
  });
37015
+ var adminPoolToolsCopySchema = external_exports.object({
37016
+ sourceUserId: external_exports.string().uuid(),
37017
+ targetUserIds: external_exports.array(external_exports.string().uuid()).min(1).max(200)
37018
+ });
37006
37019
  var workerToolInstallSchema = external_exports.object({
37007
37020
  /** Catalog id or npm package name (validated npm-name shape on the cli). */
37008
37021
  name: external_exports.string().trim().min(1).max(214),
@@ -37610,8 +37623,20 @@ async function writeTemplateMarker(target) {
37610
37623
  const marker = { templateVersion: PROJECT_TEMPLATE.version, scaffoldedAt: (/* @__PURE__ */ new Date()).toISOString() };
37611
37624
  await writeFile(join6(dir2, ".4pm.json"), JSON.stringify(marker, null, 2) + "\n", "utf8");
37612
37625
  }
37626
+ function readSpecField(spec, key) {
37627
+ const fields = spec.fields;
37628
+ if (Array.isArray(fields)) {
37629
+ const f2 = fields.find(
37630
+ (x) => !!x && typeof x.id === "string" && x.id === key
37631
+ );
37632
+ if (f2 && typeof f2.value === "string") return f2.value;
37633
+ }
37634
+ return typeof spec[key] === "string" ? spec[key] : "";
37635
+ }
37613
37636
  async function aiInit(target, spec, emit) {
37614
- emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
37637
+ const guideFile = readSpecField(spec, "ai_guide_file") === "AGENT.md" ? "AGENT.md" : "CLAUDE.md";
37638
+ const guideInstructions = readSpecField(spec, "ai_guide_instructions").trim();
37639
+ emit("ai-init", `AI init: subagents, README, ${guideFile}\u2026`);
37615
37640
  const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
37616
37641
  if (subagents.length > 0) {
37617
37642
  await mkdir(join6(target, ".claude", "agents"), { recursive: true });
@@ -37634,14 +37659,12 @@ ${(sa.description || "").trim()}
37634
37659
  ${specJson}`
37635
37660
  );
37636
37661
  await generateFile(
37637
- join6(target, "CLAUDE.md"),
37638
- `Write a CLAUDE.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
37639
- ${specJson}`
37640
- );
37641
- await generateFile(
37642
- join6(target, "AGENT.md"),
37643
- `Write an AGENT.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
37644
- ${specJson}`
37662
+ join6(target, guideFile),
37663
+ `Write a ${guideFile} (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
37664
+ ${specJson}` + (guideInstructions ? `
37665
+ Also incorporate these additional instructions/content:
37666
+ ${guideInstructions}
37667
+ ` : "")
37645
37668
  );
37646
37669
  }
37647
37670
  async function generateFile(path, prompt) {
@@ -38282,6 +38305,12 @@ function sleep(ms2) {
38282
38305
  t2.unref?.();
38283
38306
  });
38284
38307
  }
38308
+ function extractToolError(out) {
38309
+ const lines = out.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !/^npm warn\b/i.test(l));
38310
+ const errLines = lines.filter((l) => /npm error|npm err!|ERR_|ELIFECYCLE|unsupported engine|e404|etarget/i.test(l));
38311
+ const msg = (errLines.length ? errLines : lines).slice(-3).join(" \xB7 ");
38312
+ return msg.length > 500 ? `${msg.slice(0, 500)}\u2026` : msg;
38313
+ }
38285
38314
  function classifyToolFailure(code, out) {
38286
38315
  if (code === 124) return { reason: "timeout", retryable: true };
38287
38316
  const t2 = out.toLowerCase();
@@ -38421,8 +38450,10 @@ async function runWorkerToolOp(op, name, manager, onLine, timeoutMs = TOOL_INSTA
38421
38450
  return { ok: false, exitCode: 1, error: resolved.error ?? "Invalid package." };
38422
38451
  }
38423
38452
  onLine(`$ ${manager} ${opArgs(op, manager, resolved.pkg).join(" ")}`);
38424
- const { code } = await run3(manager, opArgs(op, manager, resolved.pkg), onLine, timeoutMs);
38425
- return { ok: code === 0, exitCode: code, error: code === 0 ? void 0 : `${manager} exited ${code}` };
38453
+ const { code, out } = await run3(manager, opArgs(op, manager, resolved.pkg), onLine, timeoutMs);
38454
+ if (code === 0) return { ok: true, exitCode: code };
38455
+ const detail = extractToolError(out);
38456
+ return { ok: false, exitCode: code, error: detail ? `${manager} exited ${code}: ${detail}` : `${manager} exited ${code}` };
38426
38457
  }
38427
38458
  function setToolAutoUpdate(profileDir2, name, enabled) {
38428
38459
  const resolved = resolvePackage(name, "update");
@@ -38456,7 +38487,8 @@ async function installPinned(name, version, manager, onLine, timeoutMs = TOOL_IN
38456
38487
  if (code === 0) return { ok: true };
38457
38488
  const { reason, retryable } = classifyToolFailure(code, out);
38458
38489
  if (!retryable || attempt === TOOL_INSTALL_MAX_ATTEMPTS) {
38459
- return { ok: false, error: `${manager} exited ${code}`, reason, retryable };
38490
+ const detail = extractToolError(out);
38491
+ return { ok: false, error: detail ? `${manager} exited ${code}: ${detail}` : `${manager} exited ${code}`, reason, retryable };
38460
38492
  }
38461
38493
  const base = TOOL_RETRY_BASE_MS * 3 ** (attempt - 1);
38462
38494
  const wait = Math.round(base * (0.8 + Math.random() * 0.4));
@@ -38592,7 +38624,7 @@ var UpdateScheduler = class {
38592
38624
  if (this.forcedPending) {
38593
38625
  if (this.bus.busy !== null) return;
38594
38626
  this.forcedPending = false;
38595
- await this.runUpdate();
38627
+ await this.runUpdate(true);
38596
38628
  return;
38597
38629
  }
38598
38630
  const p2 = this.policy;
@@ -38608,15 +38640,16 @@ var UpdateScheduler = class {
38608
38640
  if (this.bus.busy !== null) return;
38609
38641
  this.lastDoneDateKey = clock.dateKey;
38610
38642
  this.pendingDateKey = null;
38611
- await this.runUpdate();
38643
+ await this.runUpdate(false);
38612
38644
  }
38613
- /** Check + update, then re-exec the new binary on success (keeps `.cre`/profile). */
38614
- async runUpdate() {
38645
+ /** Check + update, then re-exec the new binary on success (keeps `.cre`/profile). `force` installs the
38646
+ * server's latest unconditionally (an operator push) vs. the conditional daily-scheduled update. */
38647
+ async runUpdate(force) {
38615
38648
  this.updating = true;
38616
38649
  try {
38617
38650
  await this.updateFlaggedTools();
38618
38651
  this.bus.log(t("update.scheduledChecking"));
38619
- const result = await updateToLatest(this.serverUrl);
38652
+ const result = await updateToLatest(this.serverUrl, force);
38620
38653
  if (result.action === "already-latest") {
38621
38654
  logger.info("update.scheduled.latest", { version: result.version });
38622
38655
  return;
@@ -42201,7 +42234,10 @@ function handleToolsChannels(ctx, message, payload) {
42201
42234
  ctx.send(WsChannels.TOOLS_DONE, {
42202
42235
  opId,
42203
42236
  ok: restoreFailed.length === 0,
42204
- exitCode: restoreFailed.length === 0 ? 0 : 1
42237
+ exitCode: restoreFailed.length === 0 ? 0 : 1,
42238
+ // Name the tools that failed + their classified reason so the web card shows more than a
42239
+ // bare "failed" (the real per-tool npm/pnpm error already streamed in the log above).
42240
+ error: restoreFailed.length === 0 ? void 0 : restoreFailed.map((f2) => `${f2.name} (${f2.reason})`).join(", ")
42205
42241
  });
42206
42242
  await ctx.reportWorkerTools(restoreTrigger, restoreFailed);
42207
42243
  }).catch(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.10.2",
3
+ "version": "1.12.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",