@4pm/cli 1.4.2 → 1.4.4

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 +216 -103
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28550,7 +28550,7 @@ ${INSECURE_URL_WARNING}
28550
28550
 
28551
28551
  // src/commands/start.ts
28552
28552
  import { spawn as spawn9 } from "child_process";
28553
- import { join as join30 } from "path";
28553
+ import { join as join29 } from "path";
28554
28554
 
28555
28555
  // src/core/update.ts
28556
28556
  import { execSync as execSync2 } from "child_process";
@@ -28723,7 +28723,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
28723
28723
  // src/core/ws-client.ts
28724
28724
  init_wrapper();
28725
28725
  import { randomUUID } from "crypto";
28726
- import { basename as basename4, join as join25 } from "path";
28726
+ import { basename as basename4, join as join24 } from "path";
28727
28727
 
28728
28728
  // vendor/@4pm/utils/helpers.ts
28729
28729
  function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
@@ -33087,6 +33087,19 @@ var createProjectRequestSchema = external_exports.object({
33087
33087
  var copyProjectRequestSchema = external_exports.object({
33088
33088
  name: external_exports.string().min(1).max(100)
33089
33089
  });
33090
+ var PROJECT_ALERT_KINDS = ["budgetPercent", "absolute"];
33091
+ var projectAlertRuleSchema = external_exports.object({
33092
+ id: external_exports.string().min(1).max(64),
33093
+ kind: external_exports.enum(PROJECT_ALERT_KINDS),
33094
+ metric: external_exports.enum([UsageMetric.AI_TOKENS, UsageMetric.COMMANDS]),
33095
+ threshold: external_exports.number().int().min(1),
33096
+ email: external_exports.union([external_exports.string().email(), external_exports.literal("")]).optional(),
33097
+ notifyUserId: external_exports.union([external_exports.string().uuid(), external_exports.literal("")]).optional(),
33098
+ enabled: external_exports.boolean().default(true)
33099
+ });
33100
+ var projectAlertsSchema = external_exports.object({
33101
+ rules: external_exports.array(projectAlertRuleSchema).max(20).default([])
33102
+ });
33090
33103
  var DEFAULT_OUTBOUND_RULES = {
33091
33104
  secret: [
33092
33105
  String.raw`/\b(api[_-]?key|secret|password|passwd|access[_-]?token|bearer)\b\s*[:=]\s*\S+/i`,
@@ -33650,6 +33663,10 @@ var WsChannels = {
33650
33663
  USAGE_REPORT: "usage.report",
33651
33664
  // cli → server: Claude subscription usage snapshot (5h / weekly — ADR-0072)
33652
33665
  MACHINE_USAGE: "machine.usage",
33666
+ // cli → server: last-run result of an external tool (claude/codex/gh/glab/git — ADR-0223).
33667
+ // One-way, sent immediately after each direct tool invocation (last-wins per tool); the
33668
+ // server merges it into `MachineLink.usageSnapshot.toolHealth` for the admin pool lists.
33669
+ TOOL_HEALTH: "tool.health",
33653
33670
  // cli → server: durable per-command history record on finish (ADR-0072)
33654
33671
  COMMAND_HISTORY: "command.history",
33655
33672
  // cli → server: periodic upload of the cli's own JSONL log file (ADR-0122) — stored
@@ -33729,7 +33746,7 @@ function decryptPayload(key, payloadBase64, nonceBase64) {
33729
33746
  }
33730
33747
 
33731
33748
  // src/core/ws-client.ts
33732
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, rmSync as rmSync5 } from "fs";
33749
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, rmSync as rmSync4 } from "fs";
33733
33750
  import { randomUUID as randomCommandId } from "crypto";
33734
33751
 
33735
33752
  // src/core/update-scheduler.ts
@@ -33998,6 +34015,24 @@ async function runCommand(dispatch, emit) {
33998
34015
  });
33999
34016
  }
34000
34017
 
34018
+ // src/core/tool-health.ts
34019
+ var sink = null;
34020
+ function setToolHealthSink(fn) {
34021
+ sink = fn;
34022
+ }
34023
+ function reportToolResult(tool, ok, message) {
34024
+ try {
34025
+ const reason = (message ?? "").trim().slice(0, 200);
34026
+ sink?.({
34027
+ tool,
34028
+ ok,
34029
+ message: ok ? null : reason || "failed",
34030
+ at: (/* @__PURE__ */ new Date()).toISOString()
34031
+ });
34032
+ } catch {
34033
+ }
34034
+ }
34035
+
34001
34036
  // src/core/ai-stream.ts
34002
34037
  function assistantText(ev) {
34003
34038
  const parts = ev.message?.content ?? [];
@@ -34104,8 +34139,11 @@ function runAttempt(commandId, cmd, args, cwd2, env3, onChunk) {
34104
34139
  async function runAiFailover(plan, commandId, cwd2, handlers) {
34105
34140
  const total = plan.attempts.length;
34106
34141
  let finalExit = -1;
34142
+ let lastCmd = plan.attempts[0]?.cmd ?? "claude";
34143
+ let lastCaptured = "";
34107
34144
  for (let i = 0; i < total; i++) {
34108
34145
  const attempt = plan.attempts[i];
34146
+ lastCmd = attempt.cmd;
34109
34147
  if (total > 1) handlers.onAttemptStart(attempt.label, i, total, attempt.cmd);
34110
34148
  const parser = createAiStreamParser(attempt.cmd);
34111
34149
  let captured = "";
@@ -34122,17 +34160,26 @@ async function runAiFailover(plan, commandId, cwd2, handlers) {
34122
34160
  captured += tail;
34123
34161
  handlers.onChunk(tail);
34124
34162
  }
34163
+ lastCaptured = captured;
34125
34164
  if (finalExit === 0) {
34126
34165
  const usage = parser.usage();
34127
34166
  if (usage.tokens === 0) usage.tokens = estimateTokens(captured);
34167
+ reportToolResult(attempt.cmd, true);
34128
34168
  return { exitCode: 0, workedDir: attempt.dir, workedKey: attempt.key, workedCmd: attempt.cmd, usage };
34129
34169
  }
34130
34170
  const reason = isAuthFailure(captured) ? "auth" : isSessionLimit(captured) ? "limit" : null;
34131
34171
  if (!reason || i === total - 1) break;
34132
34172
  handlers.onAttemptFail(attempt.label, reason);
34133
34173
  }
34174
+ reportToolResult(lastCmd, false, summarizeAiFailure(lastCaptured, finalExit));
34134
34175
  return { exitCode: finalExit, workedDir: null, workedKey: null, workedCmd: null, usage: { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 } };
34135
34176
  }
34177
+ function summarizeAiFailure(captured, exitCode) {
34178
+ if (isAuthFailure(captured)) return "Not logged in";
34179
+ if (isSessionLimit(captured)) return "Usage limit reached";
34180
+ const firstLine = captured.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
34181
+ return firstLine || `exited ${exitCode}`;
34182
+ }
34136
34183
 
34137
34184
  // src/core/ai-profile-state.ts
34138
34185
  import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
@@ -35055,7 +35102,10 @@ async function runKnowledgeCompose(req, ai, physicRoot) {
35055
35102
  for (let i = 0; i < attempts.length; i++) {
35056
35103
  try {
35057
35104
  const { code, out, err } = await runOnce(ai.cmd, attempts[i], prompt, physicRoot, ai.env);
35058
- if (code === 0 && out.trim()) return { bodyMarkdown: out.trim() };
35105
+ if (code === 0 && out.trim()) {
35106
+ reportToolResult(ai.cmd, true);
35107
+ return { bodyMarkdown: out.trim() };
35108
+ }
35059
35109
  const combined = err || out;
35060
35110
  lastReason = `exited ${code}: ${combined.slice(0, 500)}`;
35061
35111
  if (!isAuthFailure(combined) && !isSessionLimit(combined) || i === attempts.length - 1) break;
@@ -35064,6 +35114,7 @@ async function runKnowledgeCompose(req, ai, physicRoot) {
35064
35114
  break;
35065
35115
  }
35066
35116
  }
35117
+ reportToolResult(ai.cmd, false, lastReason);
35067
35118
  return { bodyMarkdown: "", error: lastReason };
35068
35119
  }
35069
35120
 
@@ -35492,28 +35543,87 @@ async function removePackage(root, slug) {
35492
35543
  }
35493
35544
  }
35494
35545
 
35495
- // src/core/sandbox.ts
35496
- import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
35497
- import { join as join16 } from "path";
35498
- var EGRESS_ALLOWLIST_FILE = "sandbox-egress.txt";
35499
- function writeEgressAllowlist(profileDir2, policy) {
35500
- const path = join16(profileDir2, EGRESS_ALLOWLIST_FILE);
35546
+ // src/core/network-probe.ts
35547
+ import { existsSync as existsSync9, readFileSync as readFileSync14 } from "fs";
35548
+ import { connect } from "net";
35549
+ var CANARY_TARGETS = [
35550
+ { host: "1.1.1.1", port: 443 },
35551
+ { host: "8.8.8.8", port: 443 }
35552
+ ];
35553
+ var PROBE_TIMEOUT_MS = 2500;
35554
+ function detectContainerized() {
35501
35555
  try {
35502
- if (policy && policy.network === "allowlist") {
35503
- const body = ["# 4PM egress allowlist (ADR-0192) \u2014 generated from the project sandbox policy"].concat(policy.allowedDomains).join("\n");
35504
- writeFileSync6(path, `${body}
35505
- `, "utf8");
35506
- } else {
35507
- rmSync4(path, { force: true });
35556
+ if (existsSync9("/.dockerenv")) return true;
35557
+ const cgroup = readFileSync14("/proc/1/cgroup", "utf8");
35558
+ return /docker|containerd|kubepods|libpod/.test(cgroup);
35559
+ } catch {
35560
+ return false;
35561
+ }
35562
+ }
35563
+ function canReach(host, port) {
35564
+ return new Promise((resolve8) => {
35565
+ const socket = connect({ host, port });
35566
+ let done = false;
35567
+ const finish = (ok) => {
35568
+ if (done) return;
35569
+ done = true;
35570
+ socket.destroy();
35571
+ resolve8(ok);
35572
+ };
35573
+ socket.setTimeout(PROBE_TIMEOUT_MS);
35574
+ socket.once("connect", () => finish(true));
35575
+ socket.once("timeout", () => finish(false));
35576
+ socket.once("error", () => finish(false));
35577
+ });
35578
+ }
35579
+ async function probeOutbound() {
35580
+ try {
35581
+ const results = await Promise.all(CANARY_TARGETS.map((t) => canReach(t.host, t.port)));
35582
+ return results.some(Boolean) ? "open" : "restricted";
35583
+ } catch {
35584
+ return "restricted";
35585
+ }
35586
+ }
35587
+ function probeInbound() {
35588
+ const isExposed = (path, loopbackHexPrefixes) => {
35589
+ let body;
35590
+ try {
35591
+ body = readFileSync14(path, "utf8");
35592
+ } catch {
35593
+ return false;
35594
+ }
35595
+ for (const line of body.split("\n").slice(1)) {
35596
+ const cols = line.trim().split(/\s+/);
35597
+ const local = cols[1];
35598
+ const st = cols[3];
35599
+ if (!local || st !== "0A") continue;
35600
+ const addrHex = local.split(":")[0]?.toUpperCase() ?? "";
35601
+ if (loopbackHexPrefixes.some((p) => addrHex === p)) continue;
35602
+ return true;
35508
35603
  }
35604
+ return false;
35605
+ };
35606
+ try {
35607
+ const v4 = isExposed("/proc/net/tcp", ["0100007F"]);
35608
+ const v6 = isExposed("/proc/net/tcp6", ["00000000000000000000000001000000"]);
35609
+ return v4 || v6 ? "exposed" : "isolated";
35509
35610
  } catch {
35611
+ return "isolated";
35510
35612
  }
35511
35613
  }
35614
+ async function probeNetwork() {
35615
+ return {
35616
+ outbound: await probeOutbound(),
35617
+ inbound: probeInbound(),
35618
+ containerized: detectContainerized(),
35619
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
35620
+ };
35621
+ }
35512
35622
 
35513
35623
  // src/core/git-auth.ts
35514
- import { writeFileSync as writeFileSync7 } from "fs";
35624
+ import { writeFileSync as writeFileSync6 } from "fs";
35515
35625
  import { homedir as homedir4 } from "os";
35516
- import { join as join17 } from "path";
35626
+ import { join as join16 } from "path";
35517
35627
  function tokenTarget(method) {
35518
35628
  if (method === "gitlab-group-token") {
35519
35629
  return { host: process.env.FOURPM_GIT_HOST || "gitlab.com", user: "oauth2" };
@@ -35531,7 +35641,7 @@ function applyGitAuth(method) {
35531
35641
  try {
35532
35642
  const line = `https://${target.user}:${token}@${target.host}
35533
35643
  `;
35534
- writeFileSync7(join17(homedir4(), ".git-credentials"), line, { encoding: "utf8", mode: 384 });
35644
+ writeFileSync6(join16(homedir4(), ".git-credentials"), line, { encoding: "utf8", mode: 384 });
35535
35645
  return true;
35536
35646
  } catch {
35537
35647
  return false;
@@ -35540,7 +35650,7 @@ function applyGitAuth(method) {
35540
35650
 
35541
35651
  // src/core/secrets.ts
35542
35652
  import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
35543
- import { join as join18 } from "path";
35653
+ import { join as join17 } from "path";
35544
35654
  var SECURITY_REL = "AI_SECURITY.md";
35545
35655
  var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
35546
35656
  var SECRETS_REL = "project.secrets.json";
@@ -35553,7 +35663,7 @@ async function readText3(path, fallback = "") {
35553
35663
  }
35554
35664
  async function readSecretsObject(root) {
35555
35665
  try {
35556
- const v = JSON.parse(await readFile6(join18(root, SECRETS_REL), "utf8"));
35666
+ const v = JSON.parse(await readFile6(join17(root, SECRETS_REL), "utf8"));
35557
35667
  return v && typeof v === "object" ? v : {};
35558
35668
  } catch {
35559
35669
  return {};
@@ -35572,8 +35682,8 @@ function placeholderKeys(text) {
35572
35682
  }
35573
35683
  async function readSecrets(root) {
35574
35684
  const [security, placeholder, secrets] = await Promise.all([
35575
- readText3(join18(root, SECURITY_REL)),
35576
- readText3(join18(root, PLACEHOLDER_REL)),
35685
+ readText3(join17(root, SECURITY_REL)),
35686
+ readText3(join17(root, PLACEHOLDER_REL)),
35577
35687
  readSecretsObject(root)
35578
35688
  ]);
35579
35689
  const names = /* @__PURE__ */ new Set([...placeholderKeys(placeholder), ...Object.keys(secrets)]);
@@ -35586,14 +35696,14 @@ async function readSecrets(root) {
35586
35696
  async function writeSecrets(root, req) {
35587
35697
  try {
35588
35698
  if (req.kind === "security") {
35589
- await writeFile5(join18(root, SECURITY_REL), req.content, "utf8");
35699
+ await writeFile5(join17(root, SECURITY_REL), req.content, "utf8");
35590
35700
  } else if (req.kind === "placeholder") {
35591
- await writeFile5(join18(root, PLACEHOLDER_REL), req.content, "utf8");
35701
+ await writeFile5(join17(root, PLACEHOLDER_REL), req.content, "utf8");
35592
35702
  } else {
35593
35703
  const secrets = await readSecretsObject(root);
35594
35704
  if (req.kind === "secret") secrets[req.key] = req.value;
35595
35705
  else delete secrets[req.key];
35596
- await writeFile5(join18(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
35706
+ await writeFile5(join17(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
35597
35707
  }
35598
35708
  return { ok: true };
35599
35709
  } catch (err) {
@@ -35603,13 +35713,13 @@ async function writeSecrets(root, req) {
35603
35713
 
35604
35714
  // src/core/agent-tools.ts
35605
35715
  import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
35606
- import { dirname as dirname5, join as join19 } from "path";
35716
+ import { dirname as dirname5, join as join18 } from "path";
35607
35717
  var SHARED_REL = ".claude/settings.json";
35608
35718
  var LOCAL_REL = ".claude/settings.local.json";
35609
35719
  var SECRETS_DENY = "Read(./project.secrets.json)";
35610
35720
  var MODES = ["default", "acceptEdits", "plan", "bypassPermissions"];
35611
35721
  function fileFor(root, scope) {
35612
- return join19(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
35722
+ return join18(root, scope === "shared" ? SHARED_REL : LOCAL_REL);
35613
35723
  }
35614
35724
  async function readSettingsObject(path) {
35615
35725
  try {
@@ -35646,7 +35756,7 @@ async function writeAgentTools(root, scope, permissions) {
35646
35756
 
35647
35757
  // src/core/graph.ts
35648
35758
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
35649
- import { join as join20, relative as relative2, dirname as dirname6, resolve as resolve4, extname, basename as basename2 } from "path";
35759
+ import { join as join19, relative as relative2, dirname as dirname6, resolve as resolve4, extname, basename as basename2 } from "path";
35650
35760
  var MAX_FILES = 2e3;
35651
35761
  var MAX_NODES = 3e3;
35652
35762
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
@@ -35666,7 +35776,7 @@ async function walk(root, accept) {
35666
35776
  if (e.name.startsWith(".") && e.name !== ".claude") {
35667
35777
  if (e.isDirectory()) continue;
35668
35778
  }
35669
- const full = join20(dir2, e.name);
35779
+ const full = join19(dir2, e.name);
35670
35780
  if (e.isDirectory()) {
35671
35781
  if (!SKIP_DIRS.has(e.name)) stack.push(full);
35672
35782
  } else if (accept(full)) {
@@ -35786,10 +35896,10 @@ async function buildGraph(root, mode) {
35786
35896
 
35787
35897
  // src/core/rag.ts
35788
35898
  import { readFile as readFile9, writeFile as writeFile7, statfs as statfs2, mkdir as mkdir6 } from "fs/promises";
35789
- import { existsSync as existsSync9 } from "fs";
35899
+ import { existsSync as existsSync10 } from "fs";
35790
35900
  import { execFile as execFile4, spawn as spawn7 } from "child_process";
35791
35901
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
35792
- import { join as join21 } from "path";
35902
+ import { join as join20 } from "path";
35793
35903
  import { promisify as promisify4 } from "util";
35794
35904
  var run3 = promisify4(execFile4);
35795
35905
  var RAG_REL = ".claude/rag";
@@ -35810,7 +35920,7 @@ async function detectPython() {
35810
35920
  }
35811
35921
  async function installLog(root) {
35812
35922
  try {
35813
- const text = await readFile9(join21(root, RAG_REL, "install.log"), "utf8");
35923
+ const text = await readFile9(join20(root, RAG_REL, "install.log"), "utf8");
35814
35924
  return text.split("\n").filter((l) => l.length > 0).slice(-200);
35815
35925
  } catch {
35816
35926
  return [];
@@ -35872,14 +35982,14 @@ async function ragStatus(root) {
35872
35982
  };
35873
35983
  let modelName = "";
35874
35984
  try {
35875
- modelName = (await readFile9(join21(root, RAG_REL, ".installed"), "utf8")).trim();
35985
+ modelName = (await readFile9(join20(root, RAG_REL, ".installed"), "utf8")).trim();
35876
35986
  } catch {
35877
35987
  modelName = "";
35878
35988
  }
35879
35989
  const modelPresent = modelName.length > 0;
35880
35990
  return {
35881
35991
  installed: fastembed && sqliteVec && modelPresent,
35882
- installing: existsSync9(join21(root, RAG_REL, ".installing")),
35992
+ installing: existsSync10(join20(root, RAG_REL, ".installing")),
35883
35993
  python: { found: py.found, version: py.version },
35884
35994
  deps: { pip, fastembed, sqliteVec },
35885
35995
  model: { present: modelPresent, name: modelName },
@@ -35890,9 +36000,9 @@ async function ragStatus(root) {
35890
36000
  };
35891
36001
  }
35892
36002
  async function readIndexMeta(root) {
35893
- const indexing = existsSync9(join21(root, RAG_REL, ".indexing"));
36003
+ const indexing = existsSync10(join20(root, RAG_REL, ".indexing"));
35894
36004
  try {
35895
- const m = JSON.parse(await readFile9(join21(root, RAG_REL, ".index.json"), "utf8"));
36005
+ const m = JSON.parse(await readFile9(join20(root, RAG_REL, ".index.json"), "utf8"));
35896
36006
  return {
35897
36007
  present: true,
35898
36008
  indexing,
@@ -35959,14 +36069,14 @@ if __name__ == "__main__":
35959
36069
  elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
35960
36070
  `;
35961
36071
  async function ensureEngine(root) {
35962
- await mkdir6(join21(root, RAG_REL), { recursive: true }).catch(() => void 0);
35963
- await writeFile7(join21(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
36072
+ await mkdir6(join20(root, RAG_REL), { recursive: true }).catch(() => void 0);
36073
+ await writeFile7(join20(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
35964
36074
  }
35965
36075
  async function ragReindex(root) {
35966
36076
  const py = await detectPython();
35967
36077
  if (!py.found) return { ok: false, started: false, error: "python3 not found" };
35968
- if (!existsSync9(join21(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
35969
- if (existsSync9(join21(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
36078
+ if (!existsSync10(join20(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
36079
+ if (existsSync10(join20(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
35970
36080
  await ensureEngine(root);
35971
36081
  const script = `
35972
36082
  set +e
@@ -35986,12 +36096,12 @@ rm -f "${RAG_REL}/.indexing"
35986
36096
  async function ragQuery(root, queryText, k = 8) {
35987
36097
  const py = await detectPython();
35988
36098
  if (!py.found) return { results: [], error: "python3 not found" };
35989
- if (!existsSync9(join21(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
36099
+ if (!existsSync10(join20(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
35990
36100
  await ensureEngine(root);
35991
36101
  try {
35992
36102
  const { stdout } = await run3(
35993
36103
  py.bin,
35994
- [join21(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k, 1), 20))],
36104
+ [join20(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k, 1), 20))],
35995
36105
  { cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
35996
36106
  );
35997
36107
  const parsed = JSON.parse(stdout.trim() || '{"results":[]}');
@@ -36003,9 +36113,9 @@ async function ragQuery(root, queryText, k = 8) {
36003
36113
  async function ragInstall(root, model) {
36004
36114
  const py = await detectPython();
36005
36115
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
36006
- if (existsSync9(join21(root, RAG_REL, ".installing"))) return { ok: true, started: true };
36116
+ if (existsSync10(join20(root, RAG_REL, ".installing"))) return { ok: true, started: true };
36007
36117
  try {
36008
- await mkdir6(join21(root, RAG_REL), { recursive: true });
36118
+ await mkdir6(join20(root, RAG_REL), { recursive: true });
36009
36119
  } catch {
36010
36120
  }
36011
36121
  const script = `
@@ -36093,12 +36203,12 @@ ${stderr}`;
36093
36203
  // src/core/git-ssh-key.ts
36094
36204
  import { execFile as execFile7 } from "child_process";
36095
36205
  import { readFile as readFile11, rm as rm3, mkdir as mkdir7 } from "fs/promises";
36096
- import { join as join22 } from "path";
36206
+ import { join as join21 } from "path";
36097
36207
  import { promisify as promisify7 } from "util";
36098
36208
  var run6 = promisify7(execFile7);
36099
36209
  function keyPaths(profileDir2) {
36100
- const dir2 = join22(profileDir2, ".ssh");
36101
- const key = join22(dir2, "id_4pm");
36210
+ const dir2 = join21(profileDir2, ".ssh");
36211
+ const key = join21(dir2, "id_4pm");
36102
36212
  return { dir: dir2, key, pub: `${key}.pub` };
36103
36213
  }
36104
36214
  async function readPublicKey(pub) {
@@ -36290,7 +36400,7 @@ async function setCommitAuthor(cwd2, machineUsername) {
36290
36400
  // src/core/scaffold.ts
36291
36401
  import { cp, mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
36292
36402
  import { execFile as execFile10 } from "child_process";
36293
- import { dirname as dirname8, join as join23, resolve as resolve7 } from "path";
36403
+ import { dirname as dirname8, join as join22, resolve as resolve7 } from "path";
36294
36404
  import { fileURLToPath as fileURLToPath3 } from "url";
36295
36405
  import { promisify as promisify10 } from "util";
36296
36406
  var run9 = promisify10(execFile10);
@@ -36319,7 +36429,7 @@ async function provisionRepos(target, repos, emit, opts = {}) {
36319
36429
  }
36320
36430
  continue;
36321
36431
  }
36322
- const dir2 = join23(target, subDirName(repo));
36432
+ const dir2 = join22(target, subDirName(repo));
36323
36433
  if (repo.url) {
36324
36434
  emit("git", `Cloning sub-repo ${repo.url}\u2026`);
36325
36435
  await run9("git", ["clone", repo.url, dir2], { timeout: 12e4 });
@@ -36339,14 +36449,14 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
36339
36449
  const emit = (step, message) => onProgress?.({ projectId: payload.projectId, step, message });
36340
36450
  try {
36341
36451
  const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
36342
- const target = join23(root, payload.projectName);
36452
+ const target = join22(root, payload.projectName);
36343
36453
  await mkdir8(target, { recursive: true });
36344
36454
  emit("copy", "Copying the sample template\u2026");
36345
36455
  await cp(sampleDir(), target, { recursive: true, force: false, errorOnExist: false });
36346
36456
  if (payload.spec) {
36347
36457
  emit("spec", "Writing project.spec.json\u2026");
36348
36458
  await writeFile8(
36349
- join23(target, "project.spec.json"),
36459
+ join22(target, "project.spec.json"),
36350
36460
  JSON.stringify(payload.spec, null, 2),
36351
36461
  "utf8"
36352
36462
  );
@@ -36364,7 +36474,7 @@ async function aiInit(target, spec, emit) {
36364
36474
  emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
36365
36475
  const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
36366
36476
  if (subagents.length > 0) {
36367
- await mkdir8(join23(target, ".claude", "agents"), { recursive: true });
36477
+ await mkdir8(join22(target, ".claude", "agents"), { recursive: true });
36368
36478
  for (const sa of subagents) {
36369
36479
  const name = (sa.name || "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
36370
36480
  if (!name) continue;
@@ -36374,17 +36484,17 @@ name: ${name}
36374
36484
 
36375
36485
  ${(sa.description || "").trim()}
36376
36486
  `;
36377
- await writeFile8(join23(target, ".claude", "agents", `${name}.md`), body, "utf8");
36487
+ await writeFile8(join22(target, ".claude", "agents", `${name}.md`), body, "utf8");
36378
36488
  }
36379
36489
  }
36380
36490
  const specJson = JSON.stringify(spec);
36381
36491
  await generateFile(
36382
- join23(target, "README.md"),
36492
+ join22(target, "README.md"),
36383
36493
  `Write a concise README.md (Markdown only, no preamble) for this project from its spec JSON:
36384
36494
  ${specJson}`
36385
36495
  );
36386
36496
  await generateFile(
36387
- join23(target, "CLAUDE.md"),
36497
+ join22(target, "CLAUDE.md"),
36388
36498
  `Write a CLAUDE.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
36389
36499
  ${specJson}`
36390
36500
  );
@@ -36400,7 +36510,7 @@ async function addProject(payload, profileDir2, onProgress) {
36400
36510
  const emit = (step, message) => onProgress?.({ projectId: payload.projectId, step, message });
36401
36511
  try {
36402
36512
  const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
36403
- const target = join23(root, payload.projectName);
36513
+ const target = join22(root, payload.projectName);
36404
36514
  await mkdir8(target, { recursive: true });
36405
36515
  const repos = payload.repos ?? [];
36406
36516
  if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true });
@@ -36412,14 +36522,14 @@ async function addProject(payload, profileDir2, onProgress) {
36412
36522
  }
36413
36523
 
36414
36524
  // src/core/config-sync.ts
36415
- import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
36416
- import { join as join24 } from "path";
36525
+ import { existsSync as existsSync11, readFileSync as readFileSync15 } from "fs";
36526
+ import { join as join23 } from "path";
36417
36527
  var SERVER_MANAGED_KEYS = ["physicPath", "sessionSwitchPct", "perPromptTokenLimit"];
36418
36528
  function readConfigText(profileDir2) {
36419
- const path = join24(profileDir2, "config.json");
36420
- if (existsSync10(path)) {
36529
+ const path = join23(profileDir2, "config.json");
36530
+ if (existsSync11(path)) {
36421
36531
  try {
36422
- return readFileSync14(path, "utf8");
36532
+ return readFileSync15(path, "utf8");
36423
36533
  } catch {
36424
36534
  }
36425
36535
  }
@@ -36844,7 +36954,6 @@ var WsClient = class {
36844
36954
  });
36845
36955
  this.outboundReviewEnabled = token.outboundReview?.enabled === true;
36846
36956
  this.restrictToFolder = token.aiRestrictToFolder === true;
36847
- writeEgressAllowlist(this.context.profileDir, token.aiSandbox ?? null);
36848
36957
  applyGitAuth(token.gitAuth ?? null);
36849
36958
  this.machineUsername = token.machineUsername ?? "";
36850
36959
  if (token.scope && token.scope !== this.credential.scope) {
@@ -36890,6 +36999,7 @@ var WsClient = class {
36890
36999
  socket.on("close", (code) => {
36891
37000
  this.clearHeartbeat();
36892
37001
  this.sessionKey = null;
37002
+ setToolHealthSink(null);
36893
37003
  logger.debug("ws.close", { code });
36894
37004
  if (code === CLOSE_DRAINING) {
36895
37005
  this.bus.log("Server draining \u2014 will reconnect (backoff + jitter).");
@@ -36931,6 +37041,7 @@ var WsClient = class {
36931
37041
  this.startUsagePolling();
36932
37042
  this.startLogUpload();
36933
37043
  this.startKbRefresh();
37044
+ setToolHealthSink((report) => this.send(WsChannels.TOOL_HEALTH, report));
36934
37045
  this.connectWaiters.splice(0).forEach((fn) => fn());
36935
37046
  return;
36936
37047
  }
@@ -36992,8 +37103,8 @@ var WsClient = class {
36992
37103
  const oldRoot = this.physicFolderPath(sync.oldName);
36993
37104
  if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
36994
37105
  }
36995
- if (sync.oldName) rmSync5(join25(this.context.profileDir, sync.oldName), { recursive: true, force: true });
36996
- mkdirSync5(join25(this.context.profileDir, sync.newName), { recursive: true });
37106
+ if (sync.oldName) rmSync4(join24(this.context.profileDir, sync.oldName), { recursive: true, force: true });
37107
+ mkdirSync5(join24(this.context.profileDir, sync.newName), { recursive: true });
36997
37108
  this.physicRoot = this.physicFolderPath(sync.newName);
36998
37109
  this.bus.setProject(sync.newName);
36999
37110
  this.bus.log(`Physic project folder synced \u2192 ${sync.newName}`);
@@ -37004,7 +37115,7 @@ var WsClient = class {
37004
37115
  if (del.name) {
37005
37116
  const delRoot = this.physicFolderPath(del.name);
37006
37117
  if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
37007
- rmSync5(join25(this.context.profileDir, del.name), { recursive: true, force: true });
37118
+ rmSync4(join24(this.context.profileDir, del.name), { recursive: true, force: true });
37008
37119
  this.bus.log(`Physic project folder deleted (project removed) \u2192 ${del.name}`);
37009
37120
  }
37010
37121
  void manageSshKey("delete", this.context.profileDir).catch(() => void 0);
@@ -37103,6 +37214,7 @@ var WsClient = class {
37103
37214
  this.bus.push({ source: "server", kind: "aires", text: `${supportCmd} \u203A` });
37104
37215
  this.bus.push({ source: "server", kind: "out", text: reply.body });
37105
37216
  }
37217
+ reportToolResult(supportCmd, !reply.error, reply.error);
37106
37218
  this.send(WsChannels.SUPPORT_ANSWER, reply, message.id);
37107
37219
  }).finally(() => this.bus.endBusy("support-answer"));
37108
37220
  break;
@@ -37376,7 +37488,7 @@ var WsClient = class {
37376
37488
  }
37377
37489
  case WsChannels.LOG_READ: {
37378
37490
  const limit = Math.min(Math.max(payload.limit ?? 200, 1), 2e3);
37379
- const lines = readRecentLogLines(join25(this.context.profileDir, "logs"), limit);
37491
+ const lines = readRecentLogLines(join24(this.context.profileDir, "logs"), limit);
37380
37492
  this.send(WsChannels.LOG_READ, { lines }, message.id);
37381
37493
  break;
37382
37494
  }
@@ -37962,7 +38074,7 @@ var WsClient = class {
37962
38074
  /** Create the physic folder at an absolute path if missing (best-effort). */
37963
38075
  ensurePhysicFolderPath(folder) {
37964
38076
  try {
37965
- if (!existsSync11(folder)) {
38077
+ if (!existsSync12(folder)) {
37966
38078
  mkdirSync5(folder, { recursive: true });
37967
38079
  this.bus.log(`Created physic project folder: ${folder}`);
37968
38080
  }
@@ -37977,7 +38089,7 @@ var WsClient = class {
37977
38089
  */
37978
38090
  physicFolderPath(projectName) {
37979
38091
  const safe = projectName.replace(/[/\\]/g, "_").replace(/\.\./g, "_").trim();
37980
- return safe ? join25(this.context.profileDir, safe) : null;
38092
+ return safe ? join24(this.context.profileDir, safe) : null;
37981
38093
  }
37982
38094
  /**
37983
38095
  * Report machine.status: version + the state of the physic project being served
@@ -37991,7 +38103,7 @@ var WsClient = class {
37991
38103
  projects: [],
37992
38104
  version: CLI_VERSION,
37993
38105
  physicPath,
37994
- physicPathExists: physicPath ? existsSync11(physicPath) : void 0,
38106
+ physicPathExists: physicPath ? existsSync12(physicPath) : void 0,
37995
38107
  // Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
37996
38108
  autonomousRunning: physicPath ? isAutonomousRunning(physicPath) : false
37997
38109
  });
@@ -38039,6 +38151,7 @@ var WsClient = class {
38039
38151
  };
38040
38152
  snapshot.aiCli = aiCli2;
38041
38153
  snapshot.profile = profile;
38154
+ snapshot.network = await probeNetwork();
38042
38155
  this.usageSnapshot = snapshot;
38043
38156
  this.bus.setUsage?.(snapshot);
38044
38157
  this.send(WsChannels.MACHINE_USAGE, snapshot);
@@ -38080,7 +38193,7 @@ var WsClient = class {
38080
38193
  */
38081
38194
  uploadLog() {
38082
38195
  try {
38083
- const upload = readLogUpload(join25(this.context.profileDir, "logs"));
38196
+ const upload = readLogUpload(join24(this.context.profileDir, "logs"));
38084
38197
  if (!upload) return;
38085
38198
  const sig = `${upload.fileName}:${upload.totalBytes}`;
38086
38199
  if (sig === this.lastLogSig) return;
@@ -38108,10 +38221,10 @@ var WsClient = class {
38108
38221
  };
38109
38222
 
38110
38223
  // src/core/instance-lock.ts
38111
- import { readFileSync as readFileSync15, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "fs";
38112
- import { join as join26 } from "path";
38224
+ import { readFileSync as readFileSync16, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
38225
+ import { join as join25 } from "path";
38113
38226
  function lockPath(profileDir2) {
38114
- return join26(profileDir2, "start.lock");
38227
+ return join25(profileDir2, "start.lock");
38115
38228
  }
38116
38229
  function isAlive(pid) {
38117
38230
  try {
@@ -38123,7 +38236,7 @@ function isAlive(pid) {
38123
38236
  }
38124
38237
  function lockHolder(profileDir2) {
38125
38238
  try {
38126
- const pid = Number.parseInt(readFileSync15(lockPath(profileDir2), "utf8").trim(), 10);
38239
+ const pid = Number.parseInt(readFileSync16(lockPath(profileDir2), "utf8").trim(), 10);
38127
38240
  if (Number.isInteger(pid) && pid > 0 && pid !== process.pid && isAlive(pid)) return pid;
38128
38241
  } catch {
38129
38242
  }
@@ -38131,13 +38244,13 @@ function lockHolder(profileDir2) {
38131
38244
  }
38132
38245
  function acquireInstanceLock(profileDir2) {
38133
38246
  if (lockHolder(profileDir2) !== null) return false;
38134
- writeFileSync8(lockPath(profileDir2), String(process.pid), "utf8");
38247
+ writeFileSync7(lockPath(profileDir2), String(process.pid), "utf8");
38135
38248
  return true;
38136
38249
  }
38137
38250
  function releaseInstanceLock(profileDir2) {
38138
38251
  try {
38139
- const pid = Number.parseInt(readFileSync15(lockPath(profileDir2), "utf8").trim(), 10);
38140
- if (pid === process.pid) rmSync6(lockPath(profileDir2), { force: true });
38252
+ const pid = Number.parseInt(readFileSync16(lockPath(profileDir2), "utf8").trim(), 10);
38253
+ if (pid === process.pid) rmSync5(lockPath(profileDir2), { force: true });
38141
38254
  } catch {
38142
38255
  }
38143
38256
  }
@@ -38398,8 +38511,8 @@ var SessionBus = class {
38398
38511
 
38399
38512
  // src/core/control-server.ts
38400
38513
  import { createServer } from "net";
38401
- import { rmSync as rmSync7 } from "fs";
38402
- import { join as join27 } from "path";
38514
+ import { rmSync as rmSync6 } from "fs";
38515
+ import { join as join26 } from "path";
38403
38516
 
38404
38517
  // src/core/control-protocol.ts
38405
38518
  var CONTROL_SOCKET_FILE = "control.sock";
@@ -38428,9 +38541,9 @@ function createFrameParser() {
38428
38541
 
38429
38542
  // src/core/control-server.ts
38430
38543
  function startControlServer(bus, profileDir2, info) {
38431
- const socketPath = join27(profileDir2, CONTROL_SOCKET_FILE);
38544
+ const socketPath = join26(profileDir2, CONTROL_SOCKET_FILE);
38432
38545
  try {
38433
- rmSync7(socketPath, { force: true });
38546
+ rmSync6(socketPath, { force: true });
38434
38547
  } catch {
38435
38548
  }
38436
38549
  const clients = /* @__PURE__ */ new Set();
@@ -38496,7 +38609,7 @@ function startControlServer(bus, profileDir2, info) {
38496
38609
  clients.clear();
38497
38610
  server.close();
38498
38611
  try {
38499
- rmSync7(socketPath, { force: true });
38612
+ rmSync6(socketPath, { force: true });
38500
38613
  } catch {
38501
38614
  }
38502
38615
  };
@@ -40476,7 +40589,7 @@ var src_default = Yoga;
40476
40589
 
40477
40590
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
40478
40591
  var import_react_reconciler = __toESM(require_react_reconciler(), 1);
40479
- var import_constants6 = __toESM(require_constants2(), 1);
40592
+ var import_constants7 = __toESM(require_constants2(), 1);
40480
40593
  import process3 from "process";
40481
40594
 
40482
40595
  // ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
@@ -41718,7 +41831,7 @@ var reconciler_default = (0, import_react_reconciler.default)({
41718
41831
  scheduleTimeout: setTimeout,
41719
41832
  cancelTimeout: clearTimeout,
41720
41833
  noTimeout: -1,
41721
- getCurrentEventPriority: () => import_constants6.DefaultEventPriority,
41834
+ getCurrentEventPriority: () => import_constants7.DefaultEventPriority,
41722
41835
  beforeActiveInstanceBlur() {
41723
41836
  },
41724
41837
  afterActiveInstanceBlur() {
@@ -44160,17 +44273,17 @@ var import_react21 = __toESM(require_react(), 1);
44160
44273
  var import_react26 = __toESM(require_react(), 1);
44161
44274
 
44162
44275
  // src/core/input-history.ts
44163
- import { existsSync as existsSync13, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
44164
- import { join as join28 } from "path";
44276
+ import { existsSync as existsSync14, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
44277
+ import { join as join27 } from "path";
44165
44278
  var MAX_HISTORY = 200;
44166
44279
  function historyPath(profileDir2) {
44167
- return join28(profileDir2, "input-history.json");
44280
+ return join27(profileDir2, "input-history.json");
44168
44281
  }
44169
44282
  function loadInputHistory(profileDir2) {
44170
44283
  const path = historyPath(profileDir2);
44171
- if (!existsSync13(path)) return [];
44284
+ if (!existsSync14(path)) return [];
44172
44285
  try {
44173
- const data = JSON.parse(readFileSync17(path, "utf8"));
44286
+ const data = JSON.parse(readFileSync18(path, "utf8"));
44174
44287
  return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
44175
44288
  } catch {
44176
44289
  return [];
@@ -44181,7 +44294,7 @@ function appendInputHistory(profileDir2, line, current) {
44181
44294
  if (!trimmed || current[current.length - 1] === trimmed) return current;
44182
44295
  const next = [...current, trimmed].slice(-MAX_HISTORY);
44183
44296
  try {
44184
- writeFileSync9(historyPath(profileDir2), JSON.stringify(next), "utf8");
44297
+ writeFileSync8(historyPath(profileDir2), JSON.stringify(next), "utf8");
44185
44298
  } catch {
44186
44299
  }
44187
44300
  return next;
@@ -44430,8 +44543,8 @@ function TranscriptLine({ line }) {
44430
44543
  var import_react24 = __toESM(require_react(), 1);
44431
44544
 
44432
44545
  // src/ui/slash-commands.ts
44433
- import { existsSync as existsSync14 } from "fs";
44434
- import { join as join29 } from "path";
44546
+ import { existsSync as existsSync15 } from "fs";
44547
+ import { join as join28 } from "path";
44435
44548
  var CONFIG_KEYS = [
44436
44549
  "aiCli",
44437
44550
  "claudeHome",
@@ -44443,7 +44556,7 @@ var CONFIG_KEYS = [
44443
44556
  "aiEnv"
44444
44557
  ];
44445
44558
  function configPath(profileDir2) {
44446
- return join29(profileDir2, "config.json");
44559
+ return join28(profileDir2, "config.json");
44447
44560
  }
44448
44561
  function parseConfigAssignment(key, valueTokens) {
44449
44562
  const value = valueTokens.join(" ").trim();
@@ -44493,7 +44606,7 @@ function runConfig(ctx) {
44493
44606
  ctx.print(`\u2714 ${verb} default config: ${path}`);
44494
44607
  ctx.print("add claude profiles: /config set claudeHome .claude .claude-1 (tried in order)");
44495
44608
  };
44496
- if (existsSync14(path)) {
44609
+ if (existsSync15(path)) {
44497
44610
  ctx.confirm(
44498
44611
  `config exists: ${path} \u2014 replace it with defaults? (y/N)`,
44499
44612
  () => writeDefaults("replaced with")
@@ -44578,7 +44691,7 @@ function runOutput(ctx) {
44578
44691
  }
44579
44692
  function runLogs(ctx) {
44580
44693
  const n = Math.max(1, Math.min(200, Number(ctx.args[0]) || 20));
44581
- const dir2 = join29(ctx.info.profileDir, "logs");
44694
+ const dir2 = join28(ctx.info.profileDir, "logs");
44582
44695
  const lines = readRecentLogLines(dir2, n);
44583
44696
  if (lines.length === 0) {
44584
44697
  ctx.print("No log lines yet.");
@@ -45099,7 +45212,7 @@ async function runStart(profileDir2, profileName) {
45099
45212
  return;
45100
45213
  }
45101
45214
  logger.configure({
45102
- dir: join30(profileDir2, "logs"),
45215
+ dir: join29(profileDir2, "logs"),
45103
45216
  level: resolveLogLevel(),
45104
45217
  base: { version: CLI_VERSION, profile: profileName, scope: credential.scope }
45105
45218
  });
@@ -45205,13 +45318,13 @@ async function runStart(profileDir2, profileName) {
45205
45318
  }
45206
45319
 
45207
45320
  // src/commands/attach.ts
45208
- import { join as join31 } from "path";
45321
+ import { join as join30 } from "path";
45209
45322
 
45210
45323
  // src/core/control-client.ts
45211
- import { connect } from "net";
45324
+ import { connect as connect2 } from "net";
45212
45325
  function connectControl(socketPath, bus) {
45213
45326
  return new Promise((resolve8, reject) => {
45214
- const socket = connect(socketPath);
45327
+ const socket = connect2(socketPath);
45215
45328
  socket.setEncoding("utf8");
45216
45329
  let ready = false;
45217
45330
  const forwardSubmit = bus.onLocalSubmit((input) => {
@@ -45304,7 +45417,7 @@ async function runAttach(profileDir2, profileName) {
45304
45417
  process.exitCode = 1;
45305
45418
  return;
45306
45419
  }
45307
- const socketPath = join31(profileDir2, CONTROL_SOCKET_FILE);
45420
+ const socketPath = join30(profileDir2, CONTROL_SOCKET_FILE);
45308
45421
  const bus = new SessionBus();
45309
45422
  let conn;
45310
45423
  try {
@@ -45389,7 +45502,7 @@ Tip: for the credentials to survive \`docker rm\`/recreate, keep each profile pa
45389
45502
  }
45390
45503
 
45391
45504
  // src/commands/unlink.ts
45392
- import { rmSync as rmSync8 } from "fs";
45505
+ import { rmSync as rmSync7 } from "fs";
45393
45506
  async function runUnlink(profileDir2, profileName) {
45394
45507
  const cred = readCredential(profileDir2, profileName);
45395
45508
  if (!cred) {
@@ -45404,7 +45517,7 @@ async function runUnlink(profileDir2, profileName) {
45404
45517
  `\u26A0 Could not reach the server to revoke \u2014 revoke it at web /machines. (${err.message})`
45405
45518
  );
45406
45519
  }
45407
- rmSync8(profileDir2, { recursive: true, force: true });
45520
+ rmSync7(profileDir2, { recursive: true, force: true });
45408
45521
  clearDefaultProfileIf(profileName);
45409
45522
  console.log("\u2714 Deleted the profile and its physic project folder.");
45410
45523
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
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",