@4pm/cli 1.5.37 → 1.5.42

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 +292 -157
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5713,14 +5713,14 @@ var require_react_development = __commonJS({
5713
5713
  var thenableResult = result;
5714
5714
  var wasAwaited = false;
5715
5715
  var thenable = {
5716
- then: function(resolve9, reject) {
5716
+ then: function(resolve10, reject) {
5717
5717
  wasAwaited = true;
5718
5718
  thenableResult.then(function(returnValue2) {
5719
5719
  popActScope(prevActScopeDepth);
5720
5720
  if (actScopeDepth === 0) {
5721
- recursivelyFlushAsyncActWork(returnValue2, resolve9, reject);
5721
+ recursivelyFlushAsyncActWork(returnValue2, resolve10, reject);
5722
5722
  } else {
5723
- resolve9(returnValue2);
5723
+ resolve10(returnValue2);
5724
5724
  }
5725
5725
  }, function(error2) {
5726
5726
  popActScope(prevActScopeDepth);
@@ -5750,20 +5750,20 @@ var require_react_development = __commonJS({
5750
5750
  ReactCurrentActQueue.current = null;
5751
5751
  }
5752
5752
  var _thenable = {
5753
- then: function(resolve9, reject) {
5753
+ then: function(resolve10, reject) {
5754
5754
  if (ReactCurrentActQueue.current === null) {
5755
5755
  ReactCurrentActQueue.current = [];
5756
- recursivelyFlushAsyncActWork(returnValue, resolve9, reject);
5756
+ recursivelyFlushAsyncActWork(returnValue, resolve10, reject);
5757
5757
  } else {
5758
- resolve9(returnValue);
5758
+ resolve10(returnValue);
5759
5759
  }
5760
5760
  }
5761
5761
  };
5762
5762
  return _thenable;
5763
5763
  } else {
5764
5764
  var _thenable2 = {
5765
- then: function(resolve9, reject) {
5766
- resolve9(returnValue);
5765
+ then: function(resolve10, reject) {
5766
+ resolve10(returnValue);
5767
5767
  }
5768
5768
  };
5769
5769
  return _thenable2;
@@ -5779,7 +5779,7 @@ var require_react_development = __commonJS({
5779
5779
  actScopeDepth = prevActScopeDepth;
5780
5780
  }
5781
5781
  }
5782
- function recursivelyFlushAsyncActWork(returnValue, resolve9, reject) {
5782
+ function recursivelyFlushAsyncActWork(returnValue, resolve10, reject) {
5783
5783
  {
5784
5784
  var queue = ReactCurrentActQueue.current;
5785
5785
  if (queue !== null) {
@@ -5788,16 +5788,16 @@ var require_react_development = __commonJS({
5788
5788
  enqueueTask(function() {
5789
5789
  if (queue.length === 0) {
5790
5790
  ReactCurrentActQueue.current = null;
5791
- resolve9(returnValue);
5791
+ resolve10(returnValue);
5792
5792
  } else {
5793
- recursivelyFlushAsyncActWork(returnValue, resolve9, reject);
5793
+ recursivelyFlushAsyncActWork(returnValue, resolve10, reject);
5794
5794
  }
5795
5795
  });
5796
5796
  } catch (error2) {
5797
5797
  reject(error2);
5798
5798
  }
5799
5799
  } else {
5800
- resolve9(returnValue);
5800
+ resolve10(returnValue);
5801
5801
  }
5802
5802
  }
5803
5803
  }
@@ -28560,8 +28560,22 @@ function normalizeLocale(value) {
28560
28560
 
28561
28561
  // vendor/@4pm/constants/project-template.ts
28562
28562
  var PROJECT_TEMPLATE = {
28563
- version: "1.0.1",
28563
+ version: "1.0.2",
28564
28564
  changelog: [
28565
+ {
28566
+ version: "1.0.2",
28567
+ date: "2026-09-16",
28568
+ summary: "AI_DONE.md changed to a Markdown table \u2014 catch-up entry for projects scaffolded before the conversion.",
28569
+ notes: [
28570
+ "AI_DONE.md's Done and Incidents sections are Markdown tables (| Timestamp | ID | Task description | Files | Notes | and | Timestamp | Note |), matching the other autonomous books.",
28571
+ "The conversion itself shipped with the baseline scaffold, but was never recorded as a template version, so the 1.0.1 changelog assumed AI_DONE was already tabular. Projects scaffolded before it still carry the old two-section prose layout even at 1.0.1; this entry lets them detect the drift and convert AI_DONE on the next Update."
28572
+ ],
28573
+ files: [
28574
+ "AI_DONE.md",
28575
+ ".claude/templates/AI_DONE.empty.md",
28576
+ ".claude/templates/AI_DONE.sample.md"
28577
+ ]
28578
+ },
28565
28579
  {
28566
28580
  version: "1.0.1",
28567
28581
  date: "2026-09-13",
@@ -29621,7 +29635,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
29621
29635
  // src/core/ws-client.ts
29622
29636
  init_wrapper();
29623
29637
  import { randomUUID as randomUUID2 } from "crypto";
29624
- import { basename as basename6, join as join28 } from "path";
29638
+ import { basename as basename7, join as join28 } from "path";
29625
29639
  import { existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
29626
29640
 
29627
29641
  // vendor/@4pm/utils/helpers.ts
@@ -33978,6 +33992,16 @@ var fsMutateRequestSchema = external_exports.object({
33978
33992
  from: external_exports.string().max(1024).optional(),
33979
33993
  to: external_exports.string().max(1024).optional()
33980
33994
  });
33995
+ var FS_TRANSFER_MAX_BYTES = 10 * 1024 * 1024;
33996
+ var FS_TRANSFER_MAX_BASE64 = Math.ceil(FS_TRANSFER_MAX_BYTES * 4 / 3) + 16;
33997
+ var fsUploadRequestSchema = external_exports.object({
33998
+ /** Destination path relative to the physic-project root; a `..` escaping the root is rejected on the cli. */
33999
+ path: external_exports.string().min(1).max(1024),
34000
+ /** File bytes, base64-encoded (capped to the transfer limit). */
34001
+ contentBase64: external_exports.string().min(1).max(FS_TRANSFER_MAX_BASE64),
34002
+ /** Optional MIME type from the browser (informational). */
34003
+ contentType: external_exports.string().max(255).optional()
34004
+ });
33981
34005
  var taskApproveRequestSchema = external_exports.object({
33982
34006
  taskId: external_exports.string().min(1).max(64),
33983
34007
  approved: external_exports.boolean()
@@ -34578,6 +34602,12 @@ var WsChannels = {
34578
34602
  // server → cli (reply): create/rename/move/delete a file or folder in the project tree,
34579
34603
  // each op path-clamped to the physic root (machine-0059, ADR-0260, `project.files_write`).
34580
34604
  FS_MUTATE: "fs.mutate",
34605
+ // server → cli (reply): move file bytes between browser and worker as base64, root-clamped +
34606
+ // size-capped (machine-0061/0062, ADR-0278). `fs.upload` writes an uploaded/pasted file into the
34607
+ // tree (`project.files_write`); `fs.download` reads a file's raw bytes for the browser to save
34608
+ // (`project.read`). Binary transfer the text-only fs.read/fs.write can't carry.
34609
+ FS_UPLOAD: "fs.upload",
34610
+ FS_DOWNLOAD: "fs.download",
34581
34611
  GIT_DIFF: "git.diff",
34582
34612
  GIT_ENV: "git.env",
34583
34613
  // server → cli (reply): read/write the paired profile's config.json (machine-0025/0026,
@@ -34890,8 +34920,8 @@ function resolveInstallTimeoutMs(sec) {
34890
34920
  return sec && sec > 0 ? sec * 1e3 : TOOL_INSTALL_TIMEOUT_DEFAULT_MS;
34891
34921
  }
34892
34922
  function sleep(ms) {
34893
- return new Promise((resolve9) => {
34894
- const t2 = setTimeout(resolve9, ms);
34923
+ return new Promise((resolve10) => {
34924
+ const t2 = setTimeout(resolve10, ms);
34895
34925
  t2.unref?.();
34896
34926
  });
34897
34927
  }
@@ -34913,14 +34943,14 @@ function classifyToolFailure(code, out) {
34913
34943
  return { reason: "other", retryable: false };
34914
34944
  }
34915
34945
  function run(cmd, args, onLine, timeoutMs = 18e4) {
34916
- return new Promise((resolve9) => {
34946
+ return new Promise((resolve10) => {
34917
34947
  let out = "";
34918
34948
  let buf = "";
34919
34949
  let child = null;
34920
34950
  const done = (code) => {
34921
34951
  clearTimeout(timer);
34922
34952
  if (onLine && buf.trim()) onLine(buf.trim());
34923
- resolve9({ code, out });
34953
+ resolve10({ code, out });
34924
34954
  };
34925
34955
  const timer = setTimeout(() => {
34926
34956
  child?.kill();
@@ -35624,16 +35654,16 @@ async function fetchUsage(token) {
35624
35654
  }
35625
35655
  }
35626
35656
  function runUsageCli(dir2, aiCli2) {
35627
- return new Promise((resolve9) => {
35657
+ return new Promise((resolve10) => {
35628
35658
  let out = "";
35629
35659
  let child = null;
35630
35660
  const finish = () => {
35631
35661
  clearTimeout(timer);
35632
- resolve9(out);
35662
+ resolve10(out);
35633
35663
  };
35634
35664
  const timer = setTimeout(() => {
35635
35665
  child?.kill();
35636
- resolve9(out);
35666
+ resolve10(out);
35637
35667
  }, 3e4);
35638
35668
  timer.unref?.();
35639
35669
  try {
@@ -36194,7 +36224,7 @@ function parseModeratedAnswer(text) {
36194
36224
  return { body: text.trim() };
36195
36225
  }
36196
36226
  function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir) {
36197
- return new Promise((resolve9, reject) => {
36227
+ return new Promise((resolve10, reject) => {
36198
36228
  const isClaude = cmd.includes("claude");
36199
36229
  const args = [
36200
36230
  "-p",
@@ -36227,7 +36257,7 @@ function runClaudeOnce(cmd, profile, prompt, extraEnv, imageDir) {
36227
36257
  child.on("close", (code) => {
36228
36258
  clearTimeout(timer);
36229
36259
  out += parser.flush();
36230
- resolve9({ code: code ?? -1, out, err, usage: parser.usage() });
36260
+ resolve10({ code: code ?? -1, out, err, usage: parser.usage() });
36231
36261
  });
36232
36262
  child.stdin.write(prompt);
36233
36263
  child.stdin.end();
@@ -36361,10 +36391,10 @@ async function crontabList() {
36361
36391
  }
36362
36392
  }
36363
36393
  async function crontabSet(content) {
36364
- return new Promise((resolve9) => {
36394
+ return new Promise((resolve10) => {
36365
36395
  const child = spawn5("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
36366
- child.on("error", () => resolve9(false));
36367
- child.on("close", (code) => resolve9(code === 0));
36396
+ child.on("error", () => resolve10(false));
36397
+ child.on("close", (code) => resolve10(code === 0));
36368
36398
  child.stdin.end(content.endsWith("\n") ? content : content + "\n");
36369
36399
  });
36370
36400
  }
@@ -36488,14 +36518,14 @@ function detectContainerized() {
36488
36518
  }
36489
36519
  }
36490
36520
  function canReach(host, port) {
36491
- return new Promise((resolve9) => {
36521
+ return new Promise((resolve10) => {
36492
36522
  const socket = connect({ host, port });
36493
36523
  let done = false;
36494
36524
  const finish = (ok) => {
36495
36525
  if (done) return;
36496
36526
  done = true;
36497
36527
  socket.destroy();
36498
- resolve9(ok);
36528
+ resolve10(ok);
36499
36529
  };
36500
36530
  socket.setTimeout(PROBE_TIMEOUT_MS);
36501
36531
  socket.once("connect", () => finish(true));
@@ -36667,7 +36697,7 @@ async function runCommand(dispatch, emit, opts) {
36667
36697
  });
36668
36698
  child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
36669
36699
  child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
36670
- await new Promise((resolve9) => {
36700
+ await new Promise((resolve10) => {
36671
36701
  child.on("close", (exitCode) => {
36672
36702
  clearTimers();
36673
36703
  batcher.flush();
@@ -36680,7 +36710,7 @@ async function runCommand(dispatch, emit, opts) {
36680
36710
  // server settles the command as failed and the web surfaces the note instead of spinning.
36681
36711
  exitCode: timedOut ? 124 : exitCode ?? -1
36682
36712
  });
36683
- resolve9();
36713
+ resolve10();
36684
36714
  });
36685
36715
  child.on("error", (err) => {
36686
36716
  clearTimers();
@@ -36692,7 +36722,7 @@ async function runCommand(dispatch, emit, opts) {
36692
36722
  done: true,
36693
36723
  exitCode: -1
36694
36724
  });
36695
- resolve9();
36725
+ resolve10();
36696
36726
  });
36697
36727
  });
36698
36728
  }
@@ -36714,15 +36744,15 @@ function isOutOfCredits(text) {
36714
36744
  );
36715
36745
  }
36716
36746
  function runAttempt(commandId, cmd, args, stdin, cwd2, env3, onChunk, timeoutMs) {
36717
- return new Promise((resolve9) => {
36747
+ return new Promise((resolve10) => {
36718
36748
  runCommand(
36719
36749
  { commandId, cmd, args, path: cwd2, env: env3 },
36720
36750
  (out) => {
36721
36751
  if (out.chunk) onChunk(out.chunk);
36722
- if (out.done) resolve9(out.exitCode ?? -1);
36752
+ if (out.done) resolve10(out.exitCode ?? -1);
36723
36753
  },
36724
36754
  { stdin, ...timeoutMs > 0 ? { timeoutMs } : {} }
36725
- ).catch(() => resolve9(-1));
36755
+ ).catch(() => resolve10(-1));
36726
36756
  });
36727
36757
  }
36728
36758
  async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
@@ -37220,7 +37250,7 @@ function buildPrompt2(input) {
37220
37250
  ].join("\n");
37221
37251
  }
37222
37252
  function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
37223
- return new Promise((resolve9, reject) => {
37253
+ return new Promise((resolve10, reject) => {
37224
37254
  const args = ["-p", ...profile?.model ? ["--model", profile.model] : []];
37225
37255
  const env3 = {
37226
37256
  ...process.env,
@@ -37243,7 +37273,7 @@ function runOnce(cmd, profile, prompt, cwd2, extraEnv) {
37243
37273
  });
37244
37274
  child.on("close", (code) => {
37245
37275
  clearTimeout(timer);
37246
- resolve9({ code: code ?? -1, out, err });
37276
+ resolve10({ code: code ?? -1, out, err });
37247
37277
  });
37248
37278
  child.stdin.write(prompt);
37249
37279
  child.stdin.end();
@@ -37814,10 +37844,19 @@ async function listDir(path, root) {
37814
37844
 
37815
37845
  // src/core/fs-read.ts
37816
37846
  import { readFile as readFile3 } from "fs/promises";
37817
- import { resolve as resolve2 } from "path";
37847
+ import { resolve as resolve2, sep as sep3 } from "path";
37818
37848
  var MAX_BYTES = 256 * 1024;
37819
- async function readWorkerFile(path) {
37820
- const target = resolve2(path);
37849
+ function resolveInRoot(root, relPath) {
37850
+ if (!root) return null;
37851
+ const base = resolve2(root);
37852
+ const target = resolve2(base, relPath || ".");
37853
+ return target === base || target.startsWith(base + sep3) ? target : null;
37854
+ }
37855
+ async function readWorkerFile(root, path) {
37856
+ const target = resolveInRoot(root, path);
37857
+ if (!target) {
37858
+ return { path, content: "", truncated: false };
37859
+ }
37821
37860
  try {
37822
37861
  const buf = await readFile3(target);
37823
37862
  const truncated = buf.byteLength > MAX_BYTES;
@@ -37830,16 +37869,16 @@ async function readWorkerFile(path) {
37830
37869
 
37831
37870
  // src/core/fs-write.ts
37832
37871
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
37833
- import { dirname as dirname3, resolve as resolve3, sep as sep3 } from "path";
37872
+ import { dirname as dirname3, resolve as resolve3, sep as sep4 } from "path";
37834
37873
  var MAX_BYTES2 = 2 * 1024 * 1024;
37835
- function resolveInRoot(root, relPath) {
37874
+ function resolveInRoot2(root, relPath) {
37836
37875
  if (!root) return null;
37837
37876
  const base = resolve3(root);
37838
37877
  const target = resolve3(base, relPath || ".");
37839
- return target === base || target.startsWith(base + sep3) ? target : null;
37878
+ return target === base || target.startsWith(base + sep4) ? target : null;
37840
37879
  }
37841
37880
  async function writeWorkerFile(root, relPath, content) {
37842
- const target = resolveInRoot(root, relPath);
37881
+ const target = resolveInRoot2(root, relPath);
37843
37882
  if (!target) {
37844
37883
  return { ok: false, path: relPath, bytes: 0, error: "path escapes the project root" };
37845
37884
  }
@@ -37858,12 +37897,12 @@ async function writeWorkerFile(root, relPath, content) {
37858
37897
 
37859
37898
  // src/core/fs-mutate.ts
37860
37899
  import { mkdir as mkdir3, writeFile as writeFile3, rename, rm, stat } from "fs/promises";
37861
- import { basename as basename3, dirname as dirname4, resolve as resolve4, sep as sep4 } from "path";
37900
+ import { basename as basename3, dirname as dirname4, resolve as resolve4, sep as sep5 } from "path";
37862
37901
  var MAX_BYTES3 = 2 * 1024 * 1024;
37863
- function resolveInRoot2(root, rel) {
37902
+ function resolveInRoot3(root, rel) {
37864
37903
  const base = resolve4(root);
37865
37904
  const target = resolve4(base, rel || ".");
37866
- return target === base || target.startsWith(base + sep4) ? target : null;
37905
+ return target === base || target.startsWith(base + sep5) ? target : null;
37867
37906
  }
37868
37907
  async function isDir(p) {
37869
37908
  try {
@@ -37886,13 +37925,13 @@ async function mutateFs(root, req) {
37886
37925
  try {
37887
37926
  switch (req.op) {
37888
37927
  case "mkdir": {
37889
- const target = resolveInRoot2(root, req.path ?? "");
37928
+ const target = resolveInRoot3(root, req.path ?? "");
37890
37929
  if (!target || target === resolve4(root)) return escaped(req.path ?? "");
37891
37930
  await mkdir3(target, { recursive: true });
37892
37931
  return { ok: true, path: target };
37893
37932
  }
37894
37933
  case "create": {
37895
- const target = resolveInRoot2(root, req.path ?? "");
37934
+ const target = resolveInRoot3(root, req.path ?? "");
37896
37935
  if (!target || target === resolve4(root)) return escaped(req.path ?? "");
37897
37936
  if (await exists(target)) return { ok: false, path: target, error: "already exists" };
37898
37937
  const content = req.content ?? "";
@@ -37904,19 +37943,19 @@ async function mutateFs(root, req) {
37904
37943
  return { ok: true, path: target };
37905
37944
  }
37906
37945
  case "move": {
37907
- const from = resolveInRoot2(root, req.from ?? "");
37908
- let to = resolveInRoot2(root, req.to ?? "");
37946
+ const from = resolveInRoot3(root, req.from ?? "");
37947
+ let to = resolveInRoot3(root, req.to ?? "");
37909
37948
  if (!from || !to) return escaped(req.from ?? req.to ?? "");
37910
37949
  if (from === resolve4(root)) return { ok: false, path: from, error: "cannot move the project root" };
37911
37950
  if (await isDir(to)) to = resolve4(to, basename3(from));
37912
- if (to !== resolve4(root) && !to.startsWith(resolve4(root) + sep4)) return escaped(req.to ?? "");
37951
+ if (to !== resolve4(root) && !to.startsWith(resolve4(root) + sep5)) return escaped(req.to ?? "");
37913
37952
  if (await exists(to)) return { ok: false, path: to, error: "destination already exists" };
37914
37953
  await mkdir3(dirname4(to), { recursive: true });
37915
37954
  await rename(from, to);
37916
37955
  return { ok: true, path: to };
37917
37956
  }
37918
37957
  case "delete": {
37919
- const target = resolveInRoot2(root, req.path ?? "");
37958
+ const target = resolveInRoot3(root, req.path ?? "");
37920
37959
  if (!target) return escaped(req.path ?? "");
37921
37960
  if (target === resolve4(root)) return { ok: false, path: target, error: "cannot delete the project root" };
37922
37961
  await rm(target, { recursive: true, force: true });
@@ -37930,6 +37969,79 @@ async function mutateFs(root, req) {
37930
37969
  }
37931
37970
  }
37932
37971
 
37972
+ // src/core/fs-transfer.ts
37973
+ import { mkdir as mkdir4, readFile as readFile4, stat as stat2, writeFile as writeFile4 } from "fs/promises";
37974
+ import { basename as basename4, dirname as dirname5, extname, resolve as resolve5, sep as sep6 } from "path";
37975
+ function resolveInRoot4(root, relPath) {
37976
+ if (!root) return null;
37977
+ const base = resolve5(root);
37978
+ const target = resolve5(base, relPath || ".");
37979
+ return target === base || target.startsWith(base + sep6) ? target : null;
37980
+ }
37981
+ var MIME_BY_EXT = {
37982
+ ".png": "image/png",
37983
+ ".jpg": "image/jpeg",
37984
+ ".jpeg": "image/jpeg",
37985
+ ".gif": "image/gif",
37986
+ ".webp": "image/webp",
37987
+ ".svg": "image/svg+xml",
37988
+ ".bmp": "image/bmp",
37989
+ ".ico": "image/x-icon",
37990
+ ".pdf": "application/pdf",
37991
+ ".zip": "application/zip",
37992
+ ".json": "application/json",
37993
+ ".txt": "text/plain",
37994
+ ".md": "text/markdown",
37995
+ ".csv": "text/csv",
37996
+ ".html": "text/html",
37997
+ ".xml": "application/xml"
37998
+ };
37999
+ function guessMime(path) {
38000
+ return MIME_BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
38001
+ }
38002
+ async function uploadBinaryFile(root, relPath, contentBase64) {
38003
+ const target = resolveInRoot4(root, relPath);
38004
+ if (!target) {
38005
+ return { ok: false, path: relPath, bytes: 0, error: "path escapes the project root" };
38006
+ }
38007
+ let buf;
38008
+ try {
38009
+ buf = Buffer.from(contentBase64, "base64");
38010
+ } catch {
38011
+ return { ok: false, path: target, bytes: 0, error: "invalid base64 payload" };
38012
+ }
38013
+ if (buf.byteLength > FS_TRANSFER_MAX_BYTES) {
38014
+ return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
38015
+ }
38016
+ try {
38017
+ await mkdir4(dirname5(target), { recursive: true });
38018
+ await writeFile4(target, buf);
38019
+ return { ok: true, path: target, bytes: buf.byteLength };
38020
+ } catch (err) {
38021
+ return { ok: false, path: target, bytes: 0, error: err instanceof Error ? err.message : String(err) };
38022
+ }
38023
+ }
38024
+ async function downloadBinaryFile(root, relPath) {
38025
+ const target = resolveInRoot4(root, relPath);
38026
+ if (!target) return { error: "path escapes the project root" };
38027
+ try {
38028
+ const info = await stat2(target);
38029
+ if (!info.isFile()) return { error: "not a file" };
38030
+ if (info.size > FS_TRANSFER_MAX_BYTES) {
38031
+ return { error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
38032
+ }
38033
+ const buf = await readFile4(target);
38034
+ return {
38035
+ contentBase64: buf.toString("base64"),
38036
+ contentType: guessMime(target),
38037
+ name: basename4(target),
38038
+ size: buf.byteLength
38039
+ };
38040
+ } catch (err) {
38041
+ return { error: err instanceof Error ? err.message : String(err) };
38042
+ }
38043
+ }
38044
+
37933
38045
  // src/core/ws-client/handlers/fs.ts
37934
38046
  function handleFsChannels(ctx, message, payload) {
37935
38047
  switch (message.channel) {
@@ -37939,7 +38051,7 @@ function handleFsChannels(ctx, message, payload) {
37939
38051
  );
37940
38052
  return true;
37941
38053
  case WsChannels.FS_READ:
37942
- void readWorkerFile(payload.path).then(
38054
+ void readWorkerFile(ctx.physicRoot, payload.path).then(
37943
38055
  (reply) => ctx.send(WsChannels.FS_READ, reply, message.id)
37944
38056
  );
37945
38057
  return true;
@@ -37957,24 +38069,47 @@ function handleFsChannels(ctx, message, payload) {
37957
38069
  );
37958
38070
  return true;
37959
38071
  }
38072
+ case WsChannels.FS_UPLOAD: {
38073
+ const req = payload;
38074
+ void uploadBinaryFile(ctx.physicRoot, req.path, req.contentBase64).then(
38075
+ (reply) => ctx.send(WsChannels.FS_UPLOAD, reply, message.id)
38076
+ );
38077
+ return true;
38078
+ }
38079
+ case WsChannels.FS_DOWNLOAD: {
38080
+ const req = payload;
38081
+ void downloadBinaryFile(ctx.physicRoot, req.path).then(
38082
+ (reply) => ctx.send(WsChannels.FS_DOWNLOAD, reply, message.id)
38083
+ );
38084
+ return true;
38085
+ }
37960
38086
  default:
37961
38087
  return false;
37962
38088
  }
37963
38089
  }
37964
38090
 
37965
38091
  // src/core/git-diff.ts
37966
- import { readFile as readFile4 } from "fs/promises";
38092
+ import { readFile as readFile5 } from "fs/promises";
37967
38093
  import { execFile as execFile4 } from "child_process";
37968
- import { basename as basename4, dirname as dirname5, resolve as resolve5 } from "path";
38094
+ import { basename as basename5, dirname as dirname6, resolve as resolve6, sep as sep7 } from "path";
37969
38095
  import { promisify as promisify4 } from "util";
37970
38096
  var run4 = promisify4(execFile4);
37971
- async function gitDiff(path) {
37972
- const target = resolve5(path);
37973
- const dir2 = dirname5(target);
37974
- const base = basename4(target);
38097
+ function resolveInRoot5(root, relPath) {
38098
+ if (!root) return null;
38099
+ const base = resolve6(root);
38100
+ const target = resolve6(base, relPath || ".");
38101
+ return target === base || target.startsWith(base + sep7) ? target : null;
38102
+ }
38103
+ async function gitDiff(root, path) {
38104
+ const target = resolveInRoot5(root, path);
38105
+ if (!target) {
38106
+ return { path, oldContent: "", newContent: "" };
38107
+ }
38108
+ const dir2 = dirname6(target);
38109
+ const base = basename5(target);
37975
38110
  let newContent = "";
37976
38111
  try {
37977
- newContent = await readFile4(target, "utf8");
38112
+ newContent = await readFile5(target, "utf8");
37978
38113
  } catch {
37979
38114
  }
37980
38115
  let oldContent = "";
@@ -38023,7 +38158,7 @@ ${stderr}`;
38023
38158
 
38024
38159
  // src/core/git-ssh-key.ts
38025
38160
  import { execFile as execFile6 } from "child_process";
38026
- import { readFile as readFile5, rm as rm2, mkdir as mkdir4 } from "fs/promises";
38161
+ import { readFile as readFile6, rm as rm2, mkdir as mkdir5 } from "fs/promises";
38027
38162
  import { join as join17 } from "path";
38028
38163
  import { promisify as promisify6 } from "util";
38029
38164
  var run6 = promisify6(execFile6);
@@ -38034,7 +38169,7 @@ function keyPaths(profileDir2) {
38034
38169
  }
38035
38170
  async function readPublicKey(pub) {
38036
38171
  try {
38037
- return (await readFile5(pub, "utf8")).trim();
38172
+ return (await readFile6(pub, "utf8")).trim();
38038
38173
  } catch {
38039
38174
  return null;
38040
38175
  }
@@ -38056,7 +38191,7 @@ async function configureGit(key) {
38056
38191
  }
38057
38192
  async function generate(profileDir2) {
38058
38193
  const { dir: dir2, key, pub } = keyPaths(profileDir2);
38059
- await mkdir4(dir2, { recursive: true });
38194
+ await mkdir5(dir2, { recursive: true });
38060
38195
  await rm2(key, { force: true });
38061
38196
  await rm2(pub, { force: true });
38062
38197
  await run6("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
@@ -38092,16 +38227,16 @@ async function manageSshKey(op, profileDir2) {
38092
38227
  // src/core/git-history.ts
38093
38228
  import { readdir as readdir3 } from "fs/promises";
38094
38229
  import { execFile as execFile7 } from "child_process";
38095
- import { resolve as resolve6, sep as sep5 } from "path";
38230
+ import { resolve as resolve7, sep as sep8 } from "path";
38096
38231
  import { promisify as promisify7 } from "util";
38097
38232
  var run7 = promisify7(execFile7);
38098
38233
  var FS = "";
38099
38234
  var RS = "";
38100
38235
  function resolveRepo(root, repo) {
38101
38236
  if (!root) return null;
38102
- const base = resolve6(root);
38103
- const requested = resolve6(base, repo && repo.trim() ? repo : ".");
38104
- return requested === base || requested.startsWith(base + sep5) ? requested : base;
38237
+ const base = resolve7(root);
38238
+ const requested = resolve7(base, repo && repo.trim() ? repo : ".");
38239
+ return requested === base || requested.startsWith(base + sep8) ? requested : base;
38105
38240
  }
38106
38241
  async function git(cwd2, args) {
38107
38242
  try {
@@ -38117,13 +38252,13 @@ async function git(cwd2, args) {
38117
38252
  }
38118
38253
  async function gitRepos(root) {
38119
38254
  if (!root) return { repos: [] };
38120
- const base = resolve6(root);
38255
+ const base = resolve7(root);
38121
38256
  const found = /* @__PURE__ */ new Set();
38122
38257
  const repos = [];
38123
38258
  const add = async (dir2) => {
38124
38259
  const inside = await git(dir2, ["rev-parse", "--show-toplevel"]);
38125
38260
  const top = inside.trim();
38126
- if (!top || found.has(top) || !(top === base || top.startsWith(base + sep5))) return;
38261
+ if (!top || found.has(top) || !(top === base || top.startsWith(base + sep8))) return;
38127
38262
  found.add(top);
38128
38263
  const subdir = top === base ? "" : top.slice(base.length + 1);
38129
38264
  const remote = (await git(top, ["remote", "get-url", "origin"])).trim();
@@ -38134,13 +38269,13 @@ async function gitRepos(root) {
38134
38269
  const lvl1 = await readdir3(base, { withFileTypes: true });
38135
38270
  for (const d of lvl1) {
38136
38271
  if (!d.isDirectory() || d.name === ".git" || d.name === "node_modules") continue;
38137
- const p1 = resolve6(base, d.name);
38272
+ const p1 = resolve7(base, d.name);
38138
38273
  await add(p1);
38139
38274
  try {
38140
38275
  const lvl2 = await readdir3(p1, { withFileTypes: true });
38141
38276
  for (const d2 of lvl2) {
38142
38277
  if (!d2.isDirectory() || d2.name === ".git" || d2.name === "node_modules") continue;
38143
- await add(resolve6(p1, d2.name));
38278
+ await add(resolve7(p1, d2.name));
38144
38279
  }
38145
38280
  } catch {
38146
38281
  }
@@ -38210,7 +38345,7 @@ async function gitCommitDiff(root, repo, hash, path) {
38210
38345
  function handleGitChannels(ctx, message, payload) {
38211
38346
  switch (message.channel) {
38212
38347
  case WsChannels.GIT_DIFF:
38213
- void gitDiff(payload.path).then(
38348
+ void gitDiff(ctx.physicRoot, payload.path).then(
38214
38349
  (reply) => ctx.send(WsChannels.GIT_DIFF, reply, message.id)
38215
38350
  );
38216
38351
  return true;
@@ -38443,10 +38578,10 @@ import { mkdirSync as mkdirSync6, rmSync as rmSync6 } from "fs";
38443
38578
  import { join as join20 } from "path";
38444
38579
 
38445
38580
  // src/core/scaffold.ts
38446
- import { cp, mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
38581
+ import { cp, mkdir as mkdir6, writeFile as writeFile5 } from "fs/promises";
38447
38582
  import { existsSync as existsSync12 } from "fs";
38448
38583
  import { execFile as execFile9 } from "child_process";
38449
- import { dirname as dirname6, join as join19, resolve as resolve7 } from "path";
38584
+ import { dirname as dirname7, join as join19, resolve as resolve8 } from "path";
38450
38585
  import { fileURLToPath as fileURLToPath3 } from "url";
38451
38586
  import { promisify as promisify9 } from "util";
38452
38587
  var run9 = promisify9(execFile9);
@@ -38481,16 +38616,16 @@ async function provisionRepos(target, repos, emit, opts = {}) {
38481
38616
  await run9("git", ["clone", repo.url, dir2], { timeout: 12e4 });
38482
38617
  } else {
38483
38618
  emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
38484
- await mkdir5(dir2, { recursive: true });
38619
+ await mkdir6(dir2, { recursive: true });
38485
38620
  await run9("git", ["init"], { cwd: dir2, timeout: 2e4 });
38486
38621
  }
38487
38622
  }
38488
38623
  }
38489
38624
  function sampleDir() {
38490
38625
  if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
38491
- const here = dirname6(fileURLToPath3(import.meta.url));
38492
- const bundled = resolve7(here, "project-sample");
38493
- for (const candidate of [bundled, resolve7(here, "../project-sample"), resolve7(here, "../../project-sample")]) {
38626
+ const here = dirname7(fileURLToPath3(import.meta.url));
38627
+ const bundled = resolve8(here, "project-sample");
38628
+ for (const candidate of [bundled, resolve8(here, "../project-sample"), resolve8(here, "../../project-sample")]) {
38494
38629
  if (existsSync12(candidate)) return candidate;
38495
38630
  }
38496
38631
  return bundled;
@@ -38502,14 +38637,14 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
38502
38637
  onProgress?.({ projectId: payload.projectId, step, message });
38503
38638
  };
38504
38639
  try {
38505
- const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
38640
+ const root = process.env.SCAFFOLD_ROOT ? resolve8(process.env.SCAFFOLD_ROOT) : resolve8(profileDir2);
38506
38641
  const target = join19(root, payload.projectName);
38507
- await mkdir5(target, { recursive: true });
38642
+ await mkdir6(target, { recursive: true });
38508
38643
  emit("copy", "Copying the sample template\u2026");
38509
38644
  await cp(sampleDir(), target, { recursive: true, force: false, errorOnExist: false });
38510
38645
  if (payload.spec) {
38511
38646
  emit("spec", "Writing project.spec.json\u2026");
38512
- await writeFile4(
38647
+ await writeFile5(
38513
38648
  join19(target, "project.spec.json"),
38514
38649
  JSON.stringify(payload.spec, null, 2),
38515
38650
  "utf8"
@@ -38528,15 +38663,15 @@ async function scaffoldProject(payload, profileDir2, onProgress) {
38528
38663
  }
38529
38664
  async function writeTemplateMarker(target) {
38530
38665
  const dir2 = join19(target, ".4pm");
38531
- await mkdir5(dir2, { recursive: true });
38666
+ await mkdir6(dir2, { recursive: true });
38532
38667
  const marker = { templateVersion: PROJECT_TEMPLATE.version, scaffoldedAt: (/* @__PURE__ */ new Date()).toISOString() };
38533
- await writeFile4(join19(dir2, ".4pm.json"), JSON.stringify(marker, null, 2) + "\n", "utf8");
38668
+ await writeFile5(join19(dir2, ".4pm.json"), JSON.stringify(marker, null, 2) + "\n", "utf8");
38534
38669
  }
38535
38670
  async function aiInit(target, spec, emit) {
38536
38671
  emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
38537
38672
  const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
38538
38673
  if (subagents.length > 0) {
38539
- await mkdir5(join19(target, ".claude", "agents"), { recursive: true });
38674
+ await mkdir6(join19(target, ".claude", "agents"), { recursive: true });
38540
38675
  for (const sa of subagents) {
38541
38676
  const name = (sa.name || "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
38542
38677
  if (!name) continue;
@@ -38546,7 +38681,7 @@ name: ${name}
38546
38681
 
38547
38682
  ${(sa.description || "").trim()}
38548
38683
  `;
38549
- await writeFile4(join19(target, ".claude", "agents", `${name}.md`), body, "utf8");
38684
+ await writeFile5(join19(target, ".claude", "agents", `${name}.md`), body, "utf8");
38550
38685
  }
38551
38686
  }
38552
38687
  const specJson = JSON.stringify(spec);
@@ -38564,7 +38699,7 @@ ${specJson}`
38564
38699
  async function generateFile(path, prompt) {
38565
38700
  try {
38566
38701
  const text = await aiGenerate(prompt);
38567
- if (text.trim()) await writeFile4(path, text.trim() + "\n", "utf8");
38702
+ if (text.trim()) await writeFile5(path, text.trim() + "\n", "utf8");
38568
38703
  } catch {
38569
38704
  }
38570
38705
  }
@@ -38575,9 +38710,9 @@ async function addProject(payload, profileDir2, onProgress) {
38575
38710
  onProgress?.({ projectId: payload.projectId, step, message });
38576
38711
  };
38577
38712
  try {
38578
- const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
38713
+ const root = process.env.SCAFFOLD_ROOT ? resolve8(process.env.SCAFFOLD_ROOT) : resolve8(profileDir2);
38579
38714
  const target = join19(root, payload.projectName);
38580
- await mkdir5(target, { recursive: true });
38715
+ await mkdir6(target, { recursive: true });
38581
38716
  const repos = payload.repos ?? [];
38582
38717
  if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true });
38583
38718
  onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
@@ -38672,7 +38807,7 @@ Extra focus from the author: ${req.prompt}` : "";
38672
38807
  ].join("\n");
38673
38808
  }
38674
38809
  function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
38675
- return new Promise((resolve9, reject) => {
38810
+ return new Promise((resolve10, reject) => {
38676
38811
  const args = ["-p", ...profile?.model ? ["--model", profile.model] : []];
38677
38812
  const env3 = {
38678
38813
  ...process.env,
@@ -38695,7 +38830,7 @@ function runOnce2(cmd, profile, prompt, cwd2, extraEnv) {
38695
38830
  });
38696
38831
  child.on("close", (code) => {
38697
38832
  clearTimeout(timer);
38698
- resolve9({ code: code ?? -1, out, err });
38833
+ resolve10({ code: code ?? -1, out, err });
38699
38834
  });
38700
38835
  child.stdin.write(prompt);
38701
38836
  child.stdin.end();
@@ -38874,7 +39009,7 @@ function handleToolsChannels(ctx, message, payload) {
38874
39009
  }
38875
39010
 
38876
39011
  // src/core/agents.ts
38877
- import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6, readdir as readdir4, rm as rm3 } from "fs/promises";
39012
+ import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir7, readdir as readdir4, rm as rm3 } from "fs/promises";
38878
39013
  import { join as join21 } from "path";
38879
39014
  var AGENTS_REL = ".claude/agents";
38880
39015
  var SKILLS_REL = ".claude/skills";
@@ -38884,7 +39019,7 @@ function safeName(name) {
38884
39019
  }
38885
39020
  async function readText2(path, fallback = "") {
38886
39021
  try {
38887
- return await readFile6(path, "utf8");
39022
+ return await readFile7(path, "utf8");
38888
39023
  } catch {
38889
39024
  return fallback;
38890
39025
  }
@@ -38943,8 +39078,8 @@ async function writeAgent(root, req) {
38943
39078
  }
38944
39079
  if (req.content === void 0) return { ok: false, error: "content is required" };
38945
39080
  const path = itemPath(root, req.kind, n);
38946
- await mkdir6(join21(path, ".."), { recursive: true });
38947
- await writeFile5(path, req.content, "utf8");
39081
+ await mkdir7(join21(path, ".."), { recursive: true });
39082
+ await writeFile6(path, req.content, "utf8");
38948
39083
  return { ok: true };
38949
39084
  } catch (err) {
38950
39085
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -38952,21 +39087,21 @@ async function writeAgent(root, req) {
38952
39087
  }
38953
39088
 
38954
39089
  // src/core/secrets.ts
38955
- import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
39090
+ import { readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
38956
39091
  import { join as join22 } from "path";
38957
39092
  var SECURITY_REL = "AI_SECURITY.md";
38958
39093
  var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
38959
39094
  var SECRETS_REL = "project.secrets.json";
38960
39095
  async function readText3(path, fallback = "") {
38961
39096
  try {
38962
- return await readFile7(path, "utf8");
39097
+ return await readFile8(path, "utf8");
38963
39098
  } catch {
38964
39099
  return fallback;
38965
39100
  }
38966
39101
  }
38967
39102
  async function readSecretsObject(root) {
38968
39103
  try {
38969
- const v = JSON.parse(await readFile7(join22(root, SECRETS_REL), "utf8"));
39104
+ const v = JSON.parse(await readFile8(join22(root, SECRETS_REL), "utf8"));
38970
39105
  return v && typeof v === "object" ? v : {};
38971
39106
  } catch {
38972
39107
  return {};
@@ -38999,14 +39134,14 @@ async function readSecrets(root) {
38999
39134
  async function writeSecrets(root, req) {
39000
39135
  try {
39001
39136
  if (req.kind === "security") {
39002
- await writeFile6(join22(root, SECURITY_REL), req.content, "utf8");
39137
+ await writeFile7(join22(root, SECURITY_REL), req.content, "utf8");
39003
39138
  } else if (req.kind === "placeholder") {
39004
- await writeFile6(join22(root, PLACEHOLDER_REL), req.content, "utf8");
39139
+ await writeFile7(join22(root, PLACEHOLDER_REL), req.content, "utf8");
39005
39140
  } else {
39006
39141
  const secrets = await readSecretsObject(root);
39007
39142
  if (req.kind === "secret") secrets[req.key] = req.value;
39008
39143
  else delete secrets[req.key];
39009
- await writeFile6(join22(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
39144
+ await writeFile7(join22(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
39010
39145
  }
39011
39146
  return { ok: true };
39012
39147
  } catch (err) {
@@ -39015,8 +39150,8 @@ async function writeSecrets(root, req) {
39015
39150
  }
39016
39151
 
39017
39152
  // src/core/agent-tools.ts
39018
- import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
39019
- import { dirname as dirname7, join as join23 } from "path";
39153
+ import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
39154
+ import { dirname as dirname8, join as join23 } from "path";
39020
39155
  var SHARED_REL = ".claude/settings.json";
39021
39156
  var LOCAL_REL = ".claude/settings.local.json";
39022
39157
  var SECRETS_DENY = "Read(./project.secrets.json)";
@@ -39026,7 +39161,7 @@ function fileFor(root, scope) {
39026
39161
  }
39027
39162
  async function readSettingsObject(path) {
39028
39163
  try {
39029
- const v = JSON.parse(await readFile8(path, "utf8"));
39164
+ const v = JSON.parse(await readFile9(path, "utf8"));
39030
39165
  return v && typeof v === "object" ? v : {};
39031
39166
  } catch {
39032
39167
  return {};
@@ -39049,8 +39184,8 @@ async function writeAgentTools(root, scope, permissions) {
39049
39184
  const next = normalizePermissions(permissions);
39050
39185
  if (scope === "shared" && !next.deny.includes(SECRETS_DENY)) next.deny = [...next.deny, SECRETS_DENY];
39051
39186
  obj.permissions = next;
39052
- await mkdir7(dirname7(path), { recursive: true });
39053
- await writeFile7(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
39187
+ await mkdir8(dirname8(path), { recursive: true });
39188
+ await writeFile8(path, JSON.stringify(obj, null, 2) + "\n", "utf8");
39054
39189
  return { ok: true };
39055
39190
  } catch (err) {
39056
39191
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -39059,8 +39194,8 @@ async function writeAgentTools(root, scope, permissions) {
39059
39194
 
39060
39195
  // src/core/packages.ts
39061
39196
  import { createHash as createHash4 } from "crypto";
39062
- import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir8, readdir as readdir5, rm as rm4, stat as stat2 } from "fs/promises";
39063
- import { dirname as dirname8, join as join24, relative } from "path";
39197
+ import { readFile as readFile10, writeFile as writeFile9, mkdir as mkdir9, readdir as readdir5, rm as rm4, stat as stat3 } from "fs/promises";
39198
+ import { dirname as dirname9, join as join24, relative } from "path";
39064
39199
  var AGENTS_REL2 = ".claude/agents";
39065
39200
  var SKILLS_REL2 = ".claude/skills";
39066
39201
  var MANIFEST_REL = ".claude/.4pm-packages.json";
@@ -39069,7 +39204,7 @@ function safeName2(name) {
39069
39204
  }
39070
39205
  async function readMaybe(path) {
39071
39206
  try {
39072
- return await readFile9(path);
39207
+ return await readFile10(path);
39073
39208
  } catch {
39074
39209
  return null;
39075
39210
  }
@@ -39124,7 +39259,7 @@ async function artifactSha256(root, kind, slug) {
39124
39259
  async function artifactExists(root, kind, slug) {
39125
39260
  const target = kind === "subagent" ? join24(root, AGENTS_REL2, `${slug}.md`) : join24(root, SKILLS_REL2, slug);
39126
39261
  try {
39127
- await stat2(target);
39262
+ await stat3(target);
39128
39263
  return true;
39129
39264
  } catch {
39130
39265
  return false;
@@ -39141,34 +39276,34 @@ async function readManifest(root) {
39141
39276
  }
39142
39277
  async function writeManifest(root, manifest) {
39143
39278
  const path = join24(root, MANIFEST_REL);
39144
- await mkdir8(dirname8(path), { recursive: true });
39145
- await writeFile8(path, JSON.stringify(manifest, null, 2), "utf8");
39279
+ await mkdir9(dirname9(path), { recursive: true });
39280
+ await writeFile9(path, JSON.stringify(manifest, null, 2), "utf8");
39146
39281
  }
39147
39282
  async function writeArtifactFiles(root, kind, slug, files) {
39148
39283
  const byPath = new Map(files.map((f) => [f.path, Buffer.from(f.contentBase64, "base64")]));
39149
39284
  if (kind === "subagent") {
39150
39285
  const agent = byPath.get("agent.md");
39151
39286
  if (agent) {
39152
- await mkdir8(join24(root, AGENTS_REL2), { recursive: true });
39153
- await writeFile8(join24(root, AGENTS_REL2, `${slug}.md`), agent);
39287
+ await mkdir9(join24(root, AGENTS_REL2), { recursive: true });
39288
+ await writeFile9(join24(root, AGENTS_REL2, `${slug}.md`), agent);
39154
39289
  }
39155
39290
  const short = byPath.get("short-memory.md");
39156
39291
  if (short) {
39157
- await mkdir8(join24(root, AGENTS_REL2, "short-memory"), { recursive: true });
39158
- await writeFile8(join24(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
39292
+ await mkdir9(join24(root, AGENTS_REL2, "short-memory"), { recursive: true });
39293
+ await writeFile9(join24(root, AGENTS_REL2, "short-memory", `${slug}.md`), short);
39159
39294
  }
39160
39295
  const long = byPath.get("long-memory.md");
39161
39296
  if (long) {
39162
- await mkdir8(join24(root, AGENTS_REL2, "long-memory"), { recursive: true });
39163
- await writeFile8(join24(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
39297
+ await mkdir9(join24(root, AGENTS_REL2, "long-memory"), { recursive: true });
39298
+ await writeFile9(join24(root, AGENTS_REL2, "long-memory", `${slug}.md`), long);
39164
39299
  }
39165
39300
  } else {
39166
39301
  const base = join24(root, SKILLS_REL2, slug);
39167
39302
  for (const [rel, buf] of byPath) {
39168
39303
  if (rel.includes("..") || rel.startsWith("/")) continue;
39169
39304
  const dest = join24(base, rel);
39170
- await mkdir8(dirname8(dest), { recursive: true });
39171
- await writeFile8(dest, buf);
39305
+ await mkdir9(dirname9(dest), { recursive: true });
39306
+ await writeFile9(dest, buf);
39172
39307
  }
39173
39308
  }
39174
39309
  }
@@ -39247,8 +39382,8 @@ async function removePackage(root, slug) {
39247
39382
  }
39248
39383
 
39249
39384
  // src/core/graph.ts
39250
- import { readFile as readFile10, readdir as readdir6 } from "fs/promises";
39251
- import { join as join25, relative as relative2, dirname as dirname9, resolve as resolve8, extname, basename as basename5 } from "path";
39385
+ import { readFile as readFile11, readdir as readdir6 } from "fs/promises";
39386
+ import { join as join25, relative as relative2, dirname as dirname10, resolve as resolve9, extname as extname2, basename as basename6 } from "path";
39252
39387
  var MAX_FILES = 2e3;
39253
39388
  var MAX_NODES = 3e3;
39254
39389
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
@@ -39284,11 +39419,11 @@ function orphansOf(nodes, edges) {
39284
39419
  return nodes.filter((n) => !hasInbound.has(n.id)).map((n) => n.id);
39285
39420
  }
39286
39421
  async function buildDocs(root) {
39287
- const files = await walk(root, (p) => extname(p) === ".md");
39422
+ const files = await walk(root, (p) => extname2(p) === ".md");
39288
39423
  const ids = new Set(files.map((f) => relative2(root, f).split("\\").join("/")));
39289
39424
  const nodes = files.map((f) => {
39290
39425
  const rel = relative2(root, f).split("\\").join("/");
39291
- return { id: rel, label: basename5(rel), kind: "doc", file: rel };
39426
+ return { id: rel, label: basename6(rel), kind: "doc", file: rel };
39292
39427
  });
39293
39428
  const edges = [];
39294
39429
  const seen = /* @__PURE__ */ new Set();
@@ -39296,14 +39431,14 @@ async function buildDocs(root) {
39296
39431
  const rel = relative2(root, f).split("\\").join("/");
39297
39432
  let text = "";
39298
39433
  try {
39299
- text = await readFile10(f, "utf8");
39434
+ text = await readFile11(f, "utf8");
39300
39435
  } catch {
39301
39436
  continue;
39302
39437
  }
39303
39438
  for (const m of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
39304
39439
  const href = (m[1] ?? "").split("#")[0].trim();
39305
39440
  if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
39306
- const target = relative2(root, resolve8(dirname9(f), href)).split("\\").join("/");
39441
+ const target = relative2(root, resolve9(dirname10(f), href)).split("\\").join("/");
39307
39442
  if (ids.has(target) && target !== rel) {
39308
39443
  const key = `${rel}->${target}`;
39309
39444
  if (!seen.has(key)) {
@@ -39326,7 +39461,7 @@ var DEF_PATTERNS = [
39326
39461
  // Go
39327
39462
  ];
39328
39463
  async function buildCode(root) {
39329
- const files = await walk(root, (p) => CODE_EXT.has(extname(p)));
39464
+ const files = await walk(root, (p) => CODE_EXT.has(extname2(p)));
39330
39465
  const nodes = [];
39331
39466
  const nameToIds = /* @__PURE__ */ new Map();
39332
39467
  const fileDefs = /* @__PURE__ */ new Map();
@@ -39335,7 +39470,7 @@ async function buildCode(root) {
39335
39470
  const rel = relative2(root, f).split("\\").join("/");
39336
39471
  let text = "";
39337
39472
  try {
39338
- text = await readFile10(f, "utf8");
39473
+ text = await readFile11(f, "utf8");
39339
39474
  } catch {
39340
39475
  continue;
39341
39476
  }
@@ -39387,7 +39522,7 @@ async function buildGraph(root, mode) {
39387
39522
  }
39388
39523
 
39389
39524
  // src/core/rag.ts
39390
- import { readFile as readFile11, writeFile as writeFile9, statfs as statfs2, mkdir as mkdir9 } from "fs/promises";
39525
+ import { readFile as readFile12, writeFile as writeFile10, statfs as statfs2, mkdir as mkdir10 } from "fs/promises";
39391
39526
  import { existsSync as existsSync13 } from "fs";
39392
39527
  import { execFile as execFile10, spawn as spawn9 } from "child_process";
39393
39528
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
@@ -39412,7 +39547,7 @@ async function detectPython() {
39412
39547
  }
39413
39548
  async function installLog(root) {
39414
39549
  try {
39415
- const text = await readFile11(join26(root, RAG_REL, "install.log"), "utf8");
39550
+ const text = await readFile12(join26(root, RAG_REL, "install.log"), "utf8");
39416
39551
  return text.split("\n").filter((l) => l.length > 0).slice(-200);
39417
39552
  } catch {
39418
39553
  return [];
@@ -39474,7 +39609,7 @@ async function ragStatus(root) {
39474
39609
  };
39475
39610
  let modelName = "";
39476
39611
  try {
39477
- modelName = (await readFile11(join26(root, RAG_REL, ".installed"), "utf8")).trim();
39612
+ modelName = (await readFile12(join26(root, RAG_REL, ".installed"), "utf8")).trim();
39478
39613
  } catch {
39479
39614
  modelName = "";
39480
39615
  }
@@ -39494,7 +39629,7 @@ async function ragStatus(root) {
39494
39629
  async function readIndexMeta(root) {
39495
39630
  const indexing = existsSync13(join26(root, RAG_REL, ".indexing"));
39496
39631
  try {
39497
- const m = JSON.parse(await readFile11(join26(root, RAG_REL, ".index.json"), "utf8"));
39632
+ const m = JSON.parse(await readFile12(join26(root, RAG_REL, ".index.json"), "utf8"));
39498
39633
  return {
39499
39634
  present: true,
39500
39635
  indexing,
@@ -39561,8 +39696,8 @@ if __name__ == "__main__":
39561
39696
  elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
39562
39697
  `;
39563
39698
  async function ensureEngine(root) {
39564
- await mkdir9(join26(root, RAG_REL), { recursive: true }).catch(() => void 0);
39565
- await writeFile9(join26(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
39699
+ await mkdir10(join26(root, RAG_REL), { recursive: true }).catch(() => void 0);
39700
+ await writeFile10(join26(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
39566
39701
  }
39567
39702
  async function ragReindex(root) {
39568
39703
  const py = await detectPython();
@@ -39610,7 +39745,7 @@ async function ragInstall(root, model) {
39610
39745
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
39611
39746
  if (existsSync13(join26(root, RAG_REL, ".installing"))) return { ok: true, started: true };
39612
39747
  try {
39613
- await mkdir9(join26(root, RAG_REL), { recursive: true });
39748
+ await mkdir10(join26(root, RAG_REL), { recursive: true });
39614
39749
  } catch {
39615
39750
  }
39616
39751
  const script = `
@@ -40118,14 +40253,14 @@ var WsClient = class {
40118
40253
  */
40119
40254
  awaitConnected(timeoutMs) {
40120
40255
  if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
40121
- return new Promise((resolve9) => {
40256
+ return new Promise((resolve10) => {
40122
40257
  const waiter = () => {
40123
40258
  clearTimeout(timer);
40124
- resolve9(true);
40259
+ resolve10(true);
40125
40260
  };
40126
40261
  const timer = setTimeout(() => {
40127
40262
  this.connectWaiters = this.connectWaiters.filter((w) => w !== waiter);
40128
- resolve9(false);
40263
+ resolve10(false);
40129
40264
  }, timeoutMs);
40130
40265
  this.connectWaiters.push(waiter);
40131
40266
  });
@@ -40152,16 +40287,16 @@ var WsClient = class {
40152
40287
  this.forceReconnect = false;
40153
40288
  return Promise.resolve();
40154
40289
  }
40155
- return new Promise((resolve9) => {
40290
+ return new Promise((resolve10) => {
40156
40291
  const timer = setTimeout(() => {
40157
40292
  this.wakeReconnect = null;
40158
- resolve9();
40293
+ resolve10();
40159
40294
  }, ms);
40160
40295
  this.wakeReconnect = () => {
40161
40296
  clearTimeout(timer);
40162
40297
  this.wakeReconnect = null;
40163
40298
  this.forceReconnect = false;
40164
- resolve9();
40299
+ resolve10();
40165
40300
  };
40166
40301
  });
40167
40302
  }
@@ -40215,7 +40350,7 @@ var WsClient = class {
40215
40350
  * One WS session: token → connect → handshake → handle messages until close.
40216
40351
  */
40217
40352
  connectOnce() {
40218
- return new Promise((resolve9, reject) => {
40353
+ return new Promise((resolve10, reject) => {
40219
40354
  void (async () => {
40220
40355
  this.sessionEstablished = false;
40221
40356
  this.bus.setStatus("connecting");
@@ -40309,7 +40444,7 @@ var WsClient = class {
40309
40444
  if (code === CLOSE_DRAINING) {
40310
40445
  this.bus.log(t("error.serverDraining"));
40311
40446
  }
40312
- resolve9();
40447
+ resolve10();
40313
40448
  });
40314
40449
  socket.on("error", (err) => {
40315
40450
  this.clearHeartbeat();
@@ -40435,7 +40570,7 @@ var WsClient = class {
40435
40570
  * Used for quota.check before spawning an AI cli (ADR-0020).
40436
40571
  */
40437
40572
  request(channel, data) {
40438
- return new Promise((resolve9, reject) => {
40573
+ return new Promise((resolve10, reject) => {
40439
40574
  if (!this.socket || !this.sessionKey) {
40440
40575
  reject(new Error("Not connected to the server."));
40441
40576
  return;
@@ -40455,7 +40590,7 @@ var WsClient = class {
40455
40590
  }, 15e3);
40456
40591
  timer.unref?.();
40457
40592
  this.pending.set(envelope.id, {
40458
- resolve: (value) => resolve9(value),
40593
+ resolve: (value) => resolve10(value),
40459
40594
  timer
40460
40595
  });
40461
40596
  this.socket.send(JSON.stringify(envelope));
@@ -40581,7 +40716,7 @@ var WsClient = class {
40581
40716
  const working = getWorkingProfile(this.context.profileDir, aiCli2);
40582
40717
  const configured = claudeHomeDirs(config);
40583
40718
  const dirs = working ? [working, ...configured.filter((d) => d !== working)] : configured;
40584
- const profile = working ? basename6(working) : dirs[0] ? basename6(dirs[0]) : "default";
40719
+ const profile = working ? basename7(working) : dirs[0] ? basename7(dirs[0]) : "default";
40585
40720
  const api = await checkClaudeUsage(dirs, aiCli2);
40586
40721
  const snapshot = api ?? {
40587
40722
  plan: "?",
@@ -46490,8 +46625,8 @@ var Ink = class {
46490
46625
  }
46491
46626
  }
46492
46627
  async waitUntilExit() {
46493
- this.exitPromise ||= new Promise((resolve9, reject) => {
46494
- this.resolveExitPromise = resolve9;
46628
+ this.exitPromise ||= new Promise((resolve10, reject) => {
46629
+ this.resolveExitPromise = resolve10;
46495
46630
  this.rejectExitPromise = reject;
46496
46631
  });
46497
46632
  return this.exitPromise;
@@ -47544,7 +47679,7 @@ async function runStart(profileDir2, profileName) {
47544
47679
  logger.error("ws.run.crash", { message: e.message, stack: e.stack });
47545
47680
  if (client.isStopped) return;
47546
47681
  bus.log(t("start.recovered", { message: e.message }), "error");
47547
- await new Promise((resolve9) => setTimeout(resolve9, 1e3));
47682
+ await new Promise((resolve10) => setTimeout(resolve10, 1e3));
47548
47683
  }
47549
47684
  }
47550
47685
  };
@@ -47590,7 +47725,7 @@ import { join as join33 } from "path";
47590
47725
  // src/core/control-client.ts
47591
47726
  import { connect as connect2 } from "net";
47592
47727
  function connectControl(socketPath, bus) {
47593
- return new Promise((resolve9, reject) => {
47728
+ return new Promise((resolve10, reject) => {
47594
47729
  const socket = connect2(socketPath);
47595
47730
  socket.setEncoding("utf8");
47596
47731
  let ready = false;
@@ -47637,7 +47772,7 @@ function connectControl(socketPath, bus) {
47637
47772
  bus.setTokens(frame.tokens);
47638
47773
  for (const entry of frame.transcript) bus.pushEntry(entry);
47639
47774
  ready = true;
47640
- resolve9({ info: frame.info, close });
47775
+ resolve10({ info: frame.info, close });
47641
47776
  break;
47642
47777
  case "transcript":
47643
47778
  bus.pushEntry(frame.entry);
@@ -47717,18 +47852,18 @@ async function runAttach(profileDir2, profileName) {
47717
47852
  // src/commands/ai-login.ts
47718
47853
  import { spawn as spawn11 } from "child_process";
47719
47854
  import { mkdirSync as mkdirSync8 } from "fs";
47720
- import { basename as basename7 } from "path";
47855
+ import { basename as basename8 } from "path";
47721
47856
  function runProviderLogin(cmd, envVar, dir2) {
47722
- return new Promise((resolve9) => {
47857
+ return new Promise((resolve10) => {
47723
47858
  const child = spawn11(cmd, [], {
47724
47859
  stdio: "inherit",
47725
47860
  env: { ...process.env, [envVar]: dir2 }
47726
47861
  });
47727
47862
  child.on("error", (err) => {
47728
47863
  console.error(t("aiLogin.launchFailed", { cmd, message: err.message }));
47729
- resolve9(1);
47864
+ resolve10(1);
47730
47865
  });
47731
- child.on("exit", (code) => resolve9(code ?? 0));
47866
+ child.on("exit", (code) => resolve10(code ?? 0));
47732
47867
  });
47733
47868
  }
47734
47869
  async function runAiLogin(profileDir2, profileName, aiFilter) {
@@ -47740,7 +47875,7 @@ async function runAiLogin(profileDir2, profileName, aiFilter) {
47740
47875
  return;
47741
47876
  }
47742
47877
  const selected = aiFilter ? credentials.filter(
47743
- (c, i) => c.profile === aiFilter || basename7(resolveHomePath(c.profile)) === aiFilter || String(i + 1) === aiFilter
47878
+ (c, i) => c.profile === aiFilter || basename8(resolveHomePath(c.profile)) === aiFilter || String(i + 1) === aiFilter
47744
47879
  ) : credentials;
47745
47880
  if (selected.length === 0) {
47746
47881
  console.log(t("aiLogin.noMatch", { filter: aiFilter ?? "", list: credentials.map((c) => c.profile).join(", ") }));
@@ -47868,10 +48003,10 @@ function Picker({
47868
48003
  ] });
47869
48004
  }
47870
48005
  function selectFromList(items, title) {
47871
- return new Promise((resolve9) => {
48006
+ return new Promise((resolve10) => {
47872
48007
  let chosen = null;
47873
48008
  const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v) => chosen = v }));
47874
- void app.waitUntilExit().then(() => resolve9(chosen));
48009
+ void app.waitUntilExit().then(() => resolve10(chosen));
47875
48010
  });
47876
48011
  }
47877
48012
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "1.5.37",
3
+ "version": "1.5.42",
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",