@4pm/cli 1.3.0 → 1.3.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.
Files changed (2) hide show
  1. package/dist/index.js +175 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28549,7 +28549,7 @@ ${INSECURE_URL_WARNING}
28549
28549
  }
28550
28550
 
28551
28551
  // src/commands/start.ts
28552
- import { spawn as spawn8 } from "child_process";
28552
+ import { spawn as spawn9 } from "child_process";
28553
28553
  import { join as join30 } from "path";
28554
28554
 
28555
28555
  // src/core/update.ts
@@ -28780,6 +28780,21 @@ function isValidRoleSet(roles) {
28780
28780
  return true;
28781
28781
  }
28782
28782
 
28783
+ // vendor/@4pm/constants/worker-tools.ts
28784
+ var WORKER_TOOL_CATALOG = [
28785
+ { id: "git", label: "Git", category: "vcs", versionArg: "--version", installPackage: null },
28786
+ { id: "node", label: "Node.js", category: "runtime", versionArg: "--version", installPackage: null },
28787
+ { id: "npm", label: "npm", category: "runtime", versionArg: "--version", installPackage: null },
28788
+ { id: "pnpm", label: "pnpm", category: "runtime", versionArg: "--version", installPackage: "pnpm" },
28789
+ { id: "claude", label: "Claude Code", category: "ai-cli", versionArg: "--version", installPackage: "@anthropic-ai/claude-code" },
28790
+ { id: "codex", label: "Codex", category: "ai-cli", versionArg: "--version", installPackage: "@openai/codex" },
28791
+ { id: "gh", label: "GitHub CLI", category: "vcs", versionArg: "--version", installPackage: null },
28792
+ { id: "glab", label: "GitLab CLI", category: "vcs", versionArg: "--version", installPackage: null }
28793
+ ];
28794
+ var WORKER_TOOL_PREREQUISITE_IDS = WORKER_TOOL_CATALOG.filter(
28795
+ (t) => t.installPackage === null
28796
+ ).map((t) => t.id);
28797
+
28783
28798
  // vendor/@4pm/constants/ws.ts
28784
28799
  var WsChannel = {
28785
28800
  COMMAND_DISPATCH: "command.dispatch",
@@ -32962,6 +32977,12 @@ var listMachineLinksQuerySchema = baseRequestSchema.extend({
32962
32977
  var updateMachineLinkRequestSchema = external_exports.object({
32963
32978
  hashcode3ExpiresAt: external_exports.string().datetime().nullable()
32964
32979
  });
32980
+ var workerToolInstallSchema = external_exports.object({
32981
+ /** Catalog id or npm package name (validated npm-name shape on the cli). */
32982
+ name: external_exports.string().trim().min(1).max(214),
32983
+ /** Package manager to run the global install with. */
32984
+ manager: external_exports.enum(["npm", "pnpm"])
32985
+ });
32965
32986
  var configWriteRequestSchema = external_exports.object({
32966
32987
  config: external_exports.string().max(256 * 1024)
32967
32988
  });
@@ -33346,6 +33367,15 @@ var WsChannels = {
33346
33367
  // ADR-0141) — the machine-user Settings tab syncs the worker cli's config.
33347
33368
  CONFIG_READ: "config.read",
33348
33369
  CONFIG_WRITE: "config.write",
33370
+ // worker tools management (ADR-0206) — the Tools tab. `tools.list` probes the default
33371
+ // catalog + extra globals (reply); `tools.install`/`tools.uninstall` start a streamed op
33372
+ // (reply = accepted/rejected), then the cli pushes `tools.progress` lines and one
33373
+ // `tools.done` per opId back to the server (relayed to the browser over SSE).
33374
+ TOOLS_LIST: "tools.list",
33375
+ TOOLS_INSTALL: "tools.install",
33376
+ TOOLS_UNINSTALL: "tools.uninstall",
33377
+ TOOLS_PROGRESS: "tools.progress",
33378
+ TOOLS_DONE: "tools.done",
33349
33379
  // autonomous mode control (ADR-0152): read settings+status+books+approvals, tail logs, and
33350
33380
  // write {settings|approvals|userTodo|cron} — the dashboard drives the worker's autonomous engine.
33351
33381
  AUTONOMOUS_READ: "autonomous.read",
@@ -36200,6 +36230,120 @@ function applyConfigText(profileDir2, text) {
36200
36230
  return { ok: true };
36201
36231
  }
36202
36232
 
36233
+ // src/core/worker-tools.ts
36234
+ import { spawn as spawn8 } from "child_process";
36235
+ var NPM_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
36236
+ function run10(cmd, args, onLine, timeoutMs = 18e4) {
36237
+ return new Promise((resolve8) => {
36238
+ let out = "";
36239
+ let buf = "";
36240
+ let child = null;
36241
+ const done = (code) => {
36242
+ clearTimeout(timer);
36243
+ if (onLine && buf.trim()) onLine(buf.trim());
36244
+ resolve8({ code, out });
36245
+ };
36246
+ const timer = setTimeout(() => {
36247
+ child?.kill();
36248
+ if (onLine) onLine(`timed out after ${Math.round(timeoutMs / 1e3)}s`);
36249
+ done(124);
36250
+ }, timeoutMs);
36251
+ timer.unref?.();
36252
+ const feed = (d) => {
36253
+ const text = d.toString("utf8");
36254
+ out += text;
36255
+ if (!onLine) return;
36256
+ buf += text;
36257
+ let nl;
36258
+ while ((nl = buf.indexOf("\n")) >= 0) {
36259
+ const line = buf.slice(0, nl).replace(/\r$/, "");
36260
+ buf = buf.slice(nl + 1);
36261
+ onLine(line);
36262
+ }
36263
+ };
36264
+ try {
36265
+ child = spawn8(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
36266
+ child.stdout?.on("data", feed);
36267
+ child.stderr?.on("data", feed);
36268
+ child.on("exit", (code) => done(code ?? 0));
36269
+ child.on("error", () => done(127));
36270
+ } catch {
36271
+ done(127);
36272
+ }
36273
+ });
36274
+ }
36275
+ async function detectOne(cmd, versionArg) {
36276
+ const { code, out } = await run10(cmd, [versionArg], void 0, 15e3);
36277
+ if (code !== 0) return null;
36278
+ const first = out.split("\n").map((l) => l.trim()).find(Boolean);
36279
+ return first ?? "";
36280
+ }
36281
+ async function listExtras() {
36282
+ const { out } = await run10("npm", ["ls", "-g", "--depth=0", "--json"], void 0, 2e4);
36283
+ if (!out.trim()) return [];
36284
+ let deps = {};
36285
+ try {
36286
+ deps = JSON.parse(out).dependencies ?? {};
36287
+ } catch {
36288
+ return [];
36289
+ }
36290
+ const catalogPkgs = new Set(
36291
+ WORKER_TOOL_CATALOG.map((t) => t.installPackage).filter((p) => !!p)
36292
+ );
36293
+ return Object.entries(deps).filter(([name]) => !catalogPkgs.has(name)).map(([name, info]) => ({
36294
+ id: name,
36295
+ label: name,
36296
+ category: "runtime",
36297
+ installed: true,
36298
+ version: info.version ?? null,
36299
+ installable: true
36300
+ }));
36301
+ }
36302
+ async function detectWorkerTools() {
36303
+ const catalog = await Promise.all(
36304
+ WORKER_TOOL_CATALOG.map(async (t) => {
36305
+ const version = await detectOne(t.id, t.versionArg);
36306
+ return {
36307
+ id: t.id,
36308
+ label: t.label,
36309
+ category: t.category,
36310
+ installed: version !== null,
36311
+ version,
36312
+ installable: t.installPackage !== null
36313
+ };
36314
+ })
36315
+ );
36316
+ const extras = await listExtras().catch(() => []);
36317
+ return { catalog, extras };
36318
+ }
36319
+ function resolvePackage(name, op) {
36320
+ const entry = WORKER_TOOL_CATALOG.find((t) => t.id === name);
36321
+ if (entry) {
36322
+ if (entry.installPackage === null) {
36323
+ return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
36324
+ }
36325
+ return { pkg: entry.installPackage };
36326
+ }
36327
+ if (WORKER_TOOL_PREREQUISITE_IDS.includes(name)) {
36328
+ return { error: `"${name}" is a prerequisite and cannot be ${op}ed from here.` };
36329
+ }
36330
+ if (!NPM_NAME.test(name)) return { error: `Invalid package name: "${name}".` };
36331
+ return { pkg: name };
36332
+ }
36333
+ function opArgs(op, manager, pkg) {
36334
+ if (manager === "pnpm") return [op === "install" ? "add" : "remove", "-g", pkg];
36335
+ return [op === "install" ? "install" : "uninstall", "-g", pkg];
36336
+ }
36337
+ async function runWorkerToolOp(op, name, manager, onLine) {
36338
+ const resolved = resolvePackage(name, op);
36339
+ if (resolved.error || !resolved.pkg) {
36340
+ return { ok: false, exitCode: 1, error: resolved.error ?? "Invalid package." };
36341
+ }
36342
+ onLine(`$ ${manager} ${opArgs(op, manager, resolved.pkg).join(" ")}`);
36343
+ const { code } = await run10(manager, opArgs(op, manager, resolved.pkg), onLine);
36344
+ return { ok: code === 0, exitCode: code, error: code === 0 ? void 0 : `${manager} exited ${code}` };
36345
+ }
36346
+
36203
36347
  // src/core/ws-client.ts
36204
36348
  var HEARTBEAT_MS = 3e4;
36205
36349
  var USAGE_POLL_MS = 5 * 6e4;
@@ -36929,6 +37073,31 @@ var WsClient = class {
36929
37073
  this.send(WsChannels.CONFIG_WRITE, res, message.id);
36930
37074
  break;
36931
37075
  }
37076
+ case WsChannels.TOOLS_LIST:
37077
+ void detectWorkerTools().then(
37078
+ (reply) => this.send(WsChannels.TOOLS_LIST, reply, message.id)
37079
+ );
37080
+ break;
37081
+ case WsChannels.TOOLS_INSTALL:
37082
+ case WsChannels.TOOLS_UNINSTALL: {
37083
+ const req = payload;
37084
+ const op = message.channel === WsChannels.TOOLS_INSTALL ? "install" : "uninstall";
37085
+ this.send(message.channel, { started: true }, message.id);
37086
+ void runWorkerToolOp(
37087
+ op,
37088
+ req.name,
37089
+ req.manager,
37090
+ (line) => this.send(WsChannels.TOOLS_PROGRESS, { opId: req.opId, line })
37091
+ ).then(
37092
+ (res) => this.send(WsChannels.TOOLS_DONE, {
37093
+ opId: req.opId,
37094
+ ok: res.ok,
37095
+ exitCode: res.exitCode,
37096
+ error: res.error
37097
+ })
37098
+ );
37099
+ break;
37100
+ }
36932
37101
  case WsChannels.GIT_DIFF:
36933
37102
  void gitDiff(payload.path).then(
36934
37103
  (reply) => this.send(WsChannels.GIT_DIFF, reply, message.id)
@@ -40021,7 +40190,7 @@ var src_default = Yoga;
40021
40190
 
40022
40191
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
40023
40192
  var import_react_reconciler = __toESM(require_react_reconciler(), 1);
40024
- var import_constants5 = __toESM(require_constants2(), 1);
40193
+ var import_constants6 = __toESM(require_constants2(), 1);
40025
40194
  import process3 from "process";
40026
40195
 
40027
40196
  // ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
@@ -41263,7 +41432,7 @@ var reconciler_default = (0, import_react_reconciler.default)({
41263
41432
  scheduleTimeout: setTimeout,
41264
41433
  cancelTimeout: clearTimeout,
41265
41434
  noTimeout: -1,
41266
- getCurrentEventPriority: () => import_constants5.DefaultEventPriority,
41435
+ getCurrentEventPriority: () => import_constants6.DefaultEventPriority,
41267
41436
  beforeActiveInstanceBlur() {
41268
41437
  },
41269
41438
  afterActiveInstanceBlur() {
@@ -44664,7 +44833,7 @@ async function runStart(profileDir2, profileName) {
44664
44833
  if (result.action === "updated") {
44665
44834
  logger.info("update.updated", { version: result.version });
44666
44835
  console.log("Restarting with the new version\u2026");
44667
- const child = spawn8(process.execPath, process.argv.slice(1), {
44836
+ const child = spawn9(process.execPath, process.argv.slice(1), {
44668
44837
  stdio: "inherit",
44669
44838
  env: { ...process.env, FOURPM_NO_UPDATE: "1" }
44670
44839
  });
@@ -44881,12 +45050,12 @@ async function runAttach(profileDir2, profileName) {
44881
45050
  }
44882
45051
 
44883
45052
  // src/commands/ai-login.ts
44884
- import { spawn as spawn9 } from "child_process";
45053
+ import { spawn as spawn10 } from "child_process";
44885
45054
  import { mkdirSync as mkdirSync6 } from "fs";
44886
45055
  import { basename as basename5 } from "path";
44887
45056
  function runProviderLogin(cmd, envVar, dir2) {
44888
45057
  return new Promise((resolve8) => {
44889
- const child = spawn9(cmd, [], {
45058
+ const child = spawn10(cmd, [], {
44890
45059
  stdio: "inherit",
44891
45060
  env: { ...process.env, [envVar]: dir2 }
44892
45061
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
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",