@4pm/cli 0.8.0 → 0.8.2

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.
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  __require,
12
12
  __toESM,
13
13
  wrapper_default
14
- } from "./chunk-YRQBADPD.js";
14
+ } from "./chunk-I7WQWC33.js";
15
15
 
16
16
  // ../../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
17
17
  var require_react_production_min = __commonJS({
@@ -1995,14 +1995,14 @@ var require_react_development = __commonJS({
1995
1995
  var thenableResult = result;
1996
1996
  var wasAwaited = false;
1997
1997
  var thenable = {
1998
- then: function(resolve7, reject) {
1998
+ then: function(resolve8, reject) {
1999
1999
  wasAwaited = true;
2000
2000
  thenableResult.then(function(returnValue2) {
2001
2001
  popActScope(prevActScopeDepth);
2002
2002
  if (actScopeDepth === 0) {
2003
- recursivelyFlushAsyncActWork(returnValue2, resolve7, reject);
2003
+ recursivelyFlushAsyncActWork(returnValue2, resolve8, reject);
2004
2004
  } else {
2005
- resolve7(returnValue2);
2005
+ resolve8(returnValue2);
2006
2006
  }
2007
2007
  }, function(error2) {
2008
2008
  popActScope(prevActScopeDepth);
@@ -2032,20 +2032,20 @@ var require_react_development = __commonJS({
2032
2032
  ReactCurrentActQueue.current = null;
2033
2033
  }
2034
2034
  var _thenable = {
2035
- then: function(resolve7, reject) {
2035
+ then: function(resolve8, reject) {
2036
2036
  if (ReactCurrentActQueue.current === null) {
2037
2037
  ReactCurrentActQueue.current = [];
2038
- recursivelyFlushAsyncActWork(returnValue, resolve7, reject);
2038
+ recursivelyFlushAsyncActWork(returnValue, resolve8, reject);
2039
2039
  } else {
2040
- resolve7(returnValue);
2040
+ resolve8(returnValue);
2041
2041
  }
2042
2042
  }
2043
2043
  };
2044
2044
  return _thenable;
2045
2045
  } else {
2046
2046
  var _thenable2 = {
2047
- then: function(resolve7, reject) {
2048
- resolve7(returnValue);
2047
+ then: function(resolve8, reject) {
2048
+ resolve8(returnValue);
2049
2049
  }
2050
2050
  };
2051
2051
  return _thenable2;
@@ -2061,7 +2061,7 @@ var require_react_development = __commonJS({
2061
2061
  actScopeDepth = prevActScopeDepth;
2062
2062
  }
2063
2063
  }
2064
- function recursivelyFlushAsyncActWork(returnValue, resolve7, reject) {
2064
+ function recursivelyFlushAsyncActWork(returnValue, resolve8, reject) {
2065
2065
  {
2066
2066
  var queue = ReactCurrentActQueue.current;
2067
2067
  if (queue !== null) {
@@ -2070,16 +2070,16 @@ var require_react_development = __commonJS({
2070
2070
  enqueueTask(function() {
2071
2071
  if (queue.length === 0) {
2072
2072
  ReactCurrentActQueue.current = null;
2073
- resolve7(returnValue);
2073
+ resolve8(returnValue);
2074
2074
  } else {
2075
- recursivelyFlushAsyncActWork(returnValue, resolve7, reject);
2075
+ recursivelyFlushAsyncActWork(returnValue, resolve8, reject);
2076
2076
  }
2077
2077
  });
2078
2078
  } catch (error2) {
2079
2079
  reject(error2);
2080
2080
  }
2081
2081
  } else {
2082
- resolve7(returnValue);
2082
+ resolve8(returnValue);
2083
2083
  }
2084
2084
  }
2085
2085
  }
@@ -24691,8 +24691,8 @@ async function runLink(serverUrl, explicitProfile) {
24691
24691
  }
24692
24692
 
24693
24693
  // src/commands/start.ts
24694
- import { spawn as spawn5 } from "child_process";
24695
- import { join as join19 } from "path";
24694
+ import { spawn as spawn6 } from "child_process";
24695
+ import { join as join23 } from "path";
24696
24696
 
24697
24697
  // src/core/update.ts
24698
24698
  import { execSync as execSync2 } from "child_process";
@@ -24863,7 +24863,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
24863
24863
 
24864
24864
  // src/core/ws-client.ts
24865
24865
  import { randomUUID } from "crypto";
24866
- import { basename as basename3, join as join15 } from "path";
24866
+ import { basename as basename4, join as join19 } from "path";
24867
24867
 
24868
24868
  // ../../22-packages/11-shared/51-utils/src/helpers.ts
24869
24869
  function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
@@ -24972,6 +24972,12 @@ var PermissionCode = {
24972
24972
  PROJECT_GIT_WRITE: "project.git_write",
24973
24973
  // Autonomous mode (ADR-0152): settings/start-pause/VERIFY-approve/post-USER_TODO
24974
24974
  PROJECT_AUTONOMOUS: "project.autonomous",
24975
+ // Subagents & skills management (ADR-0153): create/edit/delete .claude/agents + skills
24976
+ PROJECT_AGENTS: "project.agents",
24977
+ // Security & placeholder management (ADR-0154): AI_SECURITY/AI_PLACEHOLDER docs + write-only secrets
24978
+ PROJECT_SECRETS: "project.secrets",
24979
+ // RAG capability install (ADR-0156): install/manage the worker's RAG engine
24980
+ PROJECT_RAG: "project.rag",
24975
24981
  // permission
24976
24982
  PERMISSION_LIST: "permission.list",
24977
24983
  PERMISSION_READ: "permission.read",
@@ -25074,6 +25080,9 @@ var ROLE_DEFAULT_PERMISSIONS = {
25074
25080
  P.PROJECT_GIT,
25075
25081
  P.PROJECT_GIT_WRITE,
25076
25082
  P.PROJECT_AUTONOMOUS,
25083
+ P.PROJECT_AGENTS,
25084
+ P.PROJECT_SECRETS,
25085
+ P.PROJECT_RAG,
25077
25086
  P.PERMISSION_LIST,
25078
25087
  P.PERMISSION_READ,
25079
25088
  P.MACHINE_PAIR,
@@ -25099,6 +25108,12 @@ var ROLE_DEFAULT_PERMISSIONS = {
25099
25108
  P.PROJECT_MEMO_READ,
25100
25109
  P.PROJECT_MEMO_WRITE,
25101
25110
  P.PROJECT_GIT,
25111
+ // manage subagents/skills (dev artifacts) — ADR-0153
25112
+ P.PROJECT_AGENTS,
25113
+ // manage security/placeholder + set (write-only) secrets — ADR-0154
25114
+ P.PROJECT_SECRETS,
25115
+ // install/manage the worker RAG engine — ADR-0156
25116
+ P.PROJECT_RAG,
25102
25117
  P.INTEGRATION_READ
25103
25118
  ],
25104
25119
  [Role.QA]: [
@@ -25133,6 +25148,8 @@ var ROLE_DEFAULT_PERMISSIONS = {
25133
25148
  P.PROJECT_GIT_WRITE,
25134
25149
  // autonomous mode control on its own physic project (ADR-0152)
25135
25150
  P.PROJECT_AUTONOMOUS,
25151
+ // manage its own subagents/skills (ADR-0153)
25152
+ P.PROJECT_AGENTS,
25136
25153
  // view one's own worker/cli + configure the physic project
25137
25154
  P.MACHINE_LIST,
25138
25155
  P.MACHINE_PHYSIC_MANAGE,
@@ -25163,7 +25180,10 @@ var PROJECT_SCOPE_CHECKED_CODES = [
25163
25180
  P.PROJECT_AI_ASSIST,
25164
25181
  P.PROJECT_GIT,
25165
25182
  P.PROJECT_GIT_WRITE,
25166
- P.PROJECT_AUTONOMOUS
25183
+ P.PROJECT_AUTONOMOUS,
25184
+ P.PROJECT_AGENTS,
25185
+ P.PROJECT_SECRETS,
25186
+ P.PROJECT_RAG
25167
25187
  ];
25168
25188
  var SELF_SERVICE_CODES = [
25169
25189
  P.AUTH_LOGOUT,
@@ -25206,6 +25226,9 @@ var TEAM_GRANTABLE_CODES = [
25206
25226
  P.PROJECT_GIT,
25207
25227
  P.PROJECT_GIT_WRITE,
25208
25228
  P.PROJECT_AUTONOMOUS,
25229
+ P.PROJECT_AGENTS,
25230
+ P.PROJECT_SECRETS,
25231
+ P.PROJECT_RAG,
25209
25232
  P.COMMAND_EXECUTE,
25210
25233
  P.COMMAND_READ,
25211
25234
  P.MACHINE_LIST,
@@ -25230,6 +25253,9 @@ var PROJECT_GRANTABLE_CODES = [
25230
25253
  P.PROJECT_GIT,
25231
25254
  P.PROJECT_GIT_WRITE,
25232
25255
  P.PROJECT_AUTONOMOUS,
25256
+ P.PROJECT_AGENTS,
25257
+ P.PROJECT_SECRETS,
25258
+ P.PROJECT_RAG,
25233
25259
  P.COMMAND_EXECUTE,
25234
25260
  P.COMMAND_READ,
25235
25261
  P.MACHINE_PHYSIC_MANAGE
@@ -29342,7 +29368,7 @@ var pinnedFilterSchema = external_exports.enum(["true", "false"]).transform((v)
29342
29368
  var deletedFilterSchema = external_exports.enum(["true", "false"]).transform((v) => v === "true").optional();
29343
29369
 
29344
29370
  // ../../22-packages/11-shared/41-validation/src/schemas.ts
29345
- var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(72, "password: at most 72 characters").regex(/[a-zA-Z]/, "password: needs at least 1 letter").regex(/[0-9]/, "password: needs at least 1 digit");
29371
+ var passwordSchema = external_exports.string().min(8, "password: at least 8 characters").max(32, "password: at most 32 characters").regex(/[a-zA-Z]/, "password: needs at least 1 letter").regex(/[0-9]/, "password: needs at least 1 digit");
29346
29372
  var usernameSchema = external_exports.string().min(3).max(32).regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/, "username: invalid characters");
29347
29373
  var emailSchema = external_exports.string().email().max(254).toLowerCase();
29348
29374
  var phoneSchema = external_exports.string().regex(/^\+?[0-9]{8,15}$/, "phone: invalid format");
@@ -29371,6 +29397,9 @@ var registerRequestSchema = external_exports.object({
29371
29397
  var mailTokenRequestSchema = external_exports.object({
29372
29398
  token: hexTokenSchema
29373
29399
  });
29400
+ var resendVerifyRequestSchema = external_exports.object({
29401
+ email: emailSchema
29402
+ });
29374
29403
  var loginRequestSchema = external_exports.object({
29375
29404
  identifier: external_exports.string().min(1).max(254),
29376
29405
  password: external_exports.string().min(1).max(72),
@@ -29762,6 +29791,38 @@ var autonomousWriteRequestSchema = external_exports.discriminatedUnion("kind", [
29762
29791
  var autonomousLogsQuerySchema = external_exports.object({
29763
29792
  date: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
29764
29793
  });
29794
+ var agentNameSchema = external_exports.string().min(1).max(64).regex(/^[\w.-]+$/);
29795
+ var agentReadQuerySchema = external_exports.object({
29796
+ kind: external_exports.enum(["subagent", "skill"]),
29797
+ name: agentNameSchema
29798
+ });
29799
+ var ragInstallRequestSchema = external_exports.object({
29800
+ model: external_exports.string().min(1).max(200)
29801
+ });
29802
+ var ragQueryQuerySchema = external_exports.object({
29803
+ q: external_exports.string().min(1).max(2e3),
29804
+ k: external_exports.coerce.number().int().min(1).max(20).optional()
29805
+ });
29806
+ var graphBuildQuerySchema = external_exports.object({
29807
+ mode: external_exports.enum(["docs", "code"])
29808
+ });
29809
+ var secretKeySchema = external_exports.string().min(1).max(128).regex(/^[\w.-]+$/);
29810
+ var secretsWriteRequestSchema = external_exports.discriminatedUnion("kind", [
29811
+ external_exports.object({ kind: external_exports.literal("security"), content: external_exports.string().max(128 * 1024) }),
29812
+ external_exports.object({ kind: external_exports.literal("placeholder"), content: external_exports.string().max(128 * 1024) }),
29813
+ external_exports.object({ kind: external_exports.literal("secret"), key: secretKeySchema, value: external_exports.string().max(8192) }),
29814
+ external_exports.object({ kind: external_exports.literal("secretDelete"), key: secretKeySchema })
29815
+ ]);
29816
+ var agentWriteRequestSchema = external_exports.object({
29817
+ kind: external_exports.enum(["subagent", "skill"]),
29818
+ name: agentNameSchema,
29819
+ action: external_exports.enum(["write", "delete"]),
29820
+ content: external_exports.string().max(128 * 1024).optional()
29821
+ }).superRefine((v, ctx) => {
29822
+ if (v.action === "write" && (v.content === void 0 || v.content.length === 0)) {
29823
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["content"], message: "content is required for write" });
29824
+ }
29825
+ });
29765
29826
 
29766
29827
  // ../../22-packages/11-shared/31-dto/src/command.ts
29767
29828
  var COMMAND_MAX_LEN = 8e3;
@@ -30021,6 +30082,21 @@ var WsChannels = {
30021
30082
  AUTONOMOUS_READ: "autonomous.read",
30022
30083
  AUTONOMOUS_WRITE: "autonomous.write",
30023
30084
  AUTONOMOUS_LOGS: "autonomous.logs",
30085
+ // subagents & skills management (ADR-0153): list/read/write .claude/agents + .claude/skills
30086
+ AGENTS_LIST: "agents.list",
30087
+ AGENTS_READ: "agents.read",
30088
+ AGENTS_WRITE: "agents.write",
30089
+ // security & placeholder management (ADR-0154): AI_SECURITY/AI_PLACEHOLDER + write-only secrets
30090
+ SECRETS_READ: "secrets.read",
30091
+ SECRETS_WRITE: "secrets.write",
30092
+ // docs & code dependency graph (ADR-0155): build a {nodes,edges,orphans} graph per mode
30093
+ GRAPH_BUILD: "graph.build",
30094
+ // RAG capability + install (ADR-0156): probe the worker + install a worker-tuned model
30095
+ RAG_STATUS: "rag.status",
30096
+ RAG_INSTALL: "rag.install",
30097
+ // RAG index + query (ADR-0157): reindex docs into sqlite-vec, semantic query
30098
+ RAG_REINDEX: "rag.reindex",
30099
+ RAG_QUERY: "rag.query",
30024
30100
  // git history browse (read-through cli — ADR-0089): repos on the physic project,
30025
30101
  // commit log, changed files of a commit, parent↔commit diff of one file.
30026
30102
  GIT_REPOS: "git.repos",
@@ -30180,7 +30256,7 @@ var OutputBatcher = class {
30180
30256
  };
30181
30257
 
30182
30258
  // src/core/ws-client.ts
30183
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, rmSync as rmSync4 } from "fs";
30259
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, rmSync as rmSync4 } from "fs";
30184
30260
  import { randomUUID as randomCommandId } from "crypto";
30185
30261
 
30186
30262
  // src/core/update-scheduler.ts
@@ -30423,7 +30499,7 @@ async function runCommand(dispatch, emit) {
30423
30499
  });
30424
30500
  child.stdout?.on("data", (data) => batcher.push(data.toString("utf8")));
30425
30501
  child.stderr?.on("data", (data) => batcher.push(data.toString("utf8")));
30426
- await new Promise((resolve7) => {
30502
+ await new Promise((resolve8) => {
30427
30503
  child.on("close", (exitCode) => {
30428
30504
  batcher.flush();
30429
30505
  emit({
@@ -30433,7 +30509,7 @@ async function runCommand(dispatch, emit) {
30433
30509
  done: true,
30434
30510
  exitCode: exitCode ?? -1
30435
30511
  });
30436
- resolve7();
30512
+ resolve8();
30437
30513
  });
30438
30514
  child.on("error", (err) => {
30439
30515
  emit({
@@ -30444,7 +30520,7 @@ async function runCommand(dispatch, emit) {
30444
30520
  done: true,
30445
30521
  exitCode: -1
30446
30522
  });
30447
- resolve7();
30523
+ resolve8();
30448
30524
  });
30449
30525
  });
30450
30526
  }
@@ -30524,10 +30600,10 @@ function isSessionLimit(text) {
30524
30600
  );
30525
30601
  }
30526
30602
  function runAttempt(commandId, cmd, args, cwd2, env3, onChunk) {
30527
- return new Promise((resolve7) => {
30603
+ return new Promise((resolve8) => {
30528
30604
  void runCommand({ commandId, cmd, args, path: cwd2, env: env3 }, (out) => {
30529
30605
  if (out.chunk) onChunk(out.chunk);
30530
- if (out.done) resolve7(out.exitCode ?? -1);
30606
+ if (out.done) resolve8(out.exitCode ?? -1);
30531
30607
  });
30532
30608
  });
30533
30609
  }
@@ -30705,16 +30781,16 @@ async function fetchUsage(token) {
30705
30781
  }
30706
30782
  }
30707
30783
  function runUsageCli(dir2, aiCli2) {
30708
- return new Promise((resolve7) => {
30784
+ return new Promise((resolve8) => {
30709
30785
  let out = "";
30710
30786
  let child = null;
30711
30787
  const finish = () => {
30712
30788
  clearTimeout(timer);
30713
- resolve7(out);
30789
+ resolve8(out);
30714
30790
  };
30715
30791
  const timer = setTimeout(() => {
30716
30792
  child?.kill();
30717
- resolve7(out);
30793
+ resolve8(out);
30718
30794
  }, 3e4);
30719
30795
  timer.unref?.();
30720
30796
  try {
@@ -31220,10 +31296,10 @@ async function crontabList() {
31220
31296
  }
31221
31297
  }
31222
31298
  async function crontabSet(content) {
31223
- return new Promise((resolve7) => {
31299
+ return new Promise((resolve8) => {
31224
31300
  const child = spawn4("crontab", ["-"], { stdio: ["pipe", "ignore", "ignore"] });
31225
- child.on("error", () => resolve7(false));
31226
- child.on("close", (code) => resolve7(code === 0));
31301
+ child.on("error", () => resolve8(false));
31302
+ child.on("close", (code) => resolve8(code === 0));
31227
31303
  child.stdin.end(content.endsWith("\n") ? content : content + "\n");
31228
31304
  });
31229
31305
  }
@@ -31331,24 +31407,552 @@ ${req.content.trim()}
31331
31407
  }
31332
31408
  }
31333
31409
 
31334
- // src/core/git-diff.ts
31335
- import { readFile as readFile3 } from "fs/promises";
31336
- import { execFile as execFile3 } from "child_process";
31337
- import { basename as basename2, dirname as dirname4, resolve as resolve4 } from "path";
31410
+ // src/core/agents.ts
31411
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir as readdir3, rm } from "fs/promises";
31412
+ import { join as join13 } from "path";
31413
+ var AGENTS_REL = ".claude/agents";
31414
+ var SKILLS_REL = ".claude/skills";
31415
+ var MEMORY_DIRS = /* @__PURE__ */ new Set(["short-memory", "long-memory"]);
31416
+ function safeName(name) {
31417
+ return name.replace(/[^\w.-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "");
31418
+ }
31419
+ async function readText2(path, fallback = "") {
31420
+ try {
31421
+ return await readFile3(path, "utf8");
31422
+ } catch {
31423
+ return fallback;
31424
+ }
31425
+ }
31426
+ function frontmatter(text) {
31427
+ const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
31428
+ const body = m?.[1] ?? "";
31429
+ const get = (key) => body.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1]?.trim().replace(/^["']|["']$/g, "") ?? "";
31430
+ return { name: get("name"), description: get("description") };
31431
+ }
31432
+ async function listAgents(root) {
31433
+ const subagents = [];
31434
+ const skills = [];
31435
+ try {
31436
+ for (const e of await readdir3(join13(root, AGENTS_REL), { withFileTypes: true })) {
31437
+ if (!e.isFile() || !e.name.endsWith(".md") || MEMORY_DIRS.has(e.name)) continue;
31438
+ const fm = frontmatter(await readText2(join13(root, AGENTS_REL, e.name)));
31439
+ subagents.push({ name: e.name.replace(/\.md$/, ""), description: fm.description });
31440
+ }
31441
+ } catch {
31442
+ }
31443
+ try {
31444
+ for (const e of await readdir3(join13(root, SKILLS_REL), { withFileTypes: true })) {
31445
+ if (!e.isDirectory()) continue;
31446
+ const fm = frontmatter(await readText2(join13(root, SKILLS_REL, e.name, "SKILL.md")));
31447
+ skills.push({ name: e.name, description: fm.description });
31448
+ }
31449
+ } catch {
31450
+ }
31451
+ subagents.sort((a, b) => a.name.localeCompare(b.name));
31452
+ skills.sort((a, b) => a.name.localeCompare(b.name));
31453
+ return { subagents, skills };
31454
+ }
31455
+ function itemPath(root, kind, name) {
31456
+ const n = safeName(name);
31457
+ return kind === "subagent" ? join13(root, AGENTS_REL, `${n}.md`) : join13(root, SKILLS_REL, n, "SKILL.md");
31458
+ }
31459
+ async function readAgent(root, kind, name) {
31460
+ const content = await readText2(itemPath(root, kind, name));
31461
+ if (kind !== "subagent") return { content };
31462
+ const n = safeName(name);
31463
+ const [short, long] = await Promise.all([
31464
+ readText2(join13(root, AGENTS_REL, "short-memory", `${n}.md`)),
31465
+ readText2(join13(root, AGENTS_REL, "long-memory", `${n}.md`))
31466
+ ]);
31467
+ return { content, memory: { short, long } };
31468
+ }
31469
+ async function writeAgent(root, req) {
31470
+ const n = safeName(req.name);
31471
+ if (!n) return { ok: false, error: "invalid name" };
31472
+ try {
31473
+ if (req.action === "delete") {
31474
+ if (req.kind === "subagent") await rm(itemPath(root, "subagent", n), { force: true });
31475
+ else await rm(join13(root, SKILLS_REL, n), { recursive: true, force: true });
31476
+ return { ok: true };
31477
+ }
31478
+ if (req.content === void 0) return { ok: false, error: "content is required" };
31479
+ const path = itemPath(root, req.kind, n);
31480
+ await mkdir3(join13(path, ".."), { recursive: true });
31481
+ await writeFile3(path, req.content, "utf8");
31482
+ return { ok: true };
31483
+ } catch (err) {
31484
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
31485
+ }
31486
+ }
31487
+
31488
+ // src/core/secrets.ts
31489
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
31490
+ import { join as join14 } from "path";
31491
+ var SECURITY_REL = "AI_SECURITY.md";
31492
+ var PLACEHOLDER_REL = "AI_PLACEHOLDER.md";
31493
+ var SECRETS_REL = "project.secrets.json";
31494
+ async function readText3(path, fallback = "") {
31495
+ try {
31496
+ return await readFile4(path, "utf8");
31497
+ } catch {
31498
+ return fallback;
31499
+ }
31500
+ }
31501
+ async function readSecretsObject(root) {
31502
+ try {
31503
+ const v = JSON.parse(await readFile4(join14(root, SECRETS_REL), "utf8"));
31504
+ return v && typeof v === "object" ? v : {};
31505
+ } catch {
31506
+ return {};
31507
+ }
31508
+ }
31509
+ function placeholderKeys(text) {
31510
+ const keys = [];
31511
+ for (const line of text.split("\n")) {
31512
+ const s = line.trim();
31513
+ if (!s.startsWith("|")) continue;
31514
+ if (/^\|[\s:|-]+\|?$/.test(s)) continue;
31515
+ const first = s.replace(/^\|/, "").split("|")[0]?.trim() ?? "";
31516
+ if (/^[\w.-]+$/.test(first) && first.toLowerCase() !== "key") keys.push(first);
31517
+ }
31518
+ return keys;
31519
+ }
31520
+ async function readSecrets(root) {
31521
+ const [security, placeholder, secrets] = await Promise.all([
31522
+ readText3(join14(root, SECURITY_REL)),
31523
+ readText3(join14(root, PLACEHOLDER_REL)),
31524
+ readSecretsObject(root)
31525
+ ]);
31526
+ const names = /* @__PURE__ */ new Set([...placeholderKeys(placeholder), ...Object.keys(secrets)]);
31527
+ const keys = [...names].sort().map((name) => ({
31528
+ name,
31529
+ set: secrets[name] !== void 0 && String(secrets[name]).length > 0
31530
+ }));
31531
+ return { security, placeholder, keys };
31532
+ }
31533
+ async function writeSecrets(root, req) {
31534
+ try {
31535
+ if (req.kind === "security") {
31536
+ await writeFile4(join14(root, SECURITY_REL), req.content, "utf8");
31537
+ } else if (req.kind === "placeholder") {
31538
+ await writeFile4(join14(root, PLACEHOLDER_REL), req.content, "utf8");
31539
+ } else {
31540
+ const secrets = await readSecretsObject(root);
31541
+ if (req.kind === "secret") secrets[req.key] = req.value;
31542
+ else delete secrets[req.key];
31543
+ await writeFile4(join14(root, SECRETS_REL), JSON.stringify(secrets, null, 2) + "\n", "utf8");
31544
+ }
31545
+ return { ok: true };
31546
+ } catch (err) {
31547
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
31548
+ }
31549
+ }
31550
+
31551
+ // src/core/graph.ts
31552
+ import { readFile as readFile5, readdir as readdir4 } from "fs/promises";
31553
+ import { join as join15, relative, dirname as dirname4, resolve as resolve4, extname, basename as basename2 } from "path";
31554
+ var MAX_FILES = 2e3;
31555
+ var MAX_NODES = 3e3;
31556
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "out", "coverage", ".turbo"]);
31557
+ var CODE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".rs", ".java", ".rb", ".php"]);
31558
+ async function walk(root, accept) {
31559
+ const out = [];
31560
+ const stack = [root];
31561
+ while (stack.length && out.length < MAX_FILES) {
31562
+ const dir2 = stack.pop();
31563
+ let entries2;
31564
+ try {
31565
+ entries2 = await readdir4(dir2, { withFileTypes: true });
31566
+ } catch {
31567
+ continue;
31568
+ }
31569
+ for (const e of entries2) {
31570
+ if (e.name.startsWith(".") && e.name !== ".claude") {
31571
+ if (e.isDirectory()) continue;
31572
+ }
31573
+ const full = join15(dir2, e.name);
31574
+ if (e.isDirectory()) {
31575
+ if (!SKIP_DIRS.has(e.name)) stack.push(full);
31576
+ } else if (accept(full)) {
31577
+ out.push(full);
31578
+ if (out.length >= MAX_FILES) break;
31579
+ }
31580
+ }
31581
+ }
31582
+ return out;
31583
+ }
31584
+ function orphansOf(nodes, edges) {
31585
+ const hasInbound = new Set(edges.map((e) => e.to));
31586
+ return nodes.filter((n) => !hasInbound.has(n.id)).map((n) => n.id);
31587
+ }
31588
+ async function buildDocs(root) {
31589
+ const files = await walk(root, (p) => extname(p) === ".md");
31590
+ const ids = new Set(files.map((f) => relative(root, f).split("\\").join("/")));
31591
+ const nodes = files.map((f) => {
31592
+ const rel = relative(root, f).split("\\").join("/");
31593
+ return { id: rel, label: basename2(rel), kind: "doc", file: rel };
31594
+ });
31595
+ const edges = [];
31596
+ const seen = /* @__PURE__ */ new Set();
31597
+ for (const f of files) {
31598
+ const rel = relative(root, f).split("\\").join("/");
31599
+ let text = "";
31600
+ try {
31601
+ text = await readFile5(f, "utf8");
31602
+ } catch {
31603
+ continue;
31604
+ }
31605
+ for (const m of text.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
31606
+ const href = (m[1] ?? "").split("#")[0].trim();
31607
+ if (!href || !href.endsWith(".md") || href.startsWith("http")) continue;
31608
+ const target = relative(root, resolve4(dirname4(f), href)).split("\\").join("/");
31609
+ if (ids.has(target) && target !== rel) {
31610
+ const key = `${rel}->${target}`;
31611
+ if (!seen.has(key)) {
31612
+ seen.add(key);
31613
+ edges.push({ from: rel, to: target });
31614
+ }
31615
+ }
31616
+ }
31617
+ }
31618
+ return { nodes, edges, orphans: orphansOf(nodes, edges) };
31619
+ }
31620
+ var DEF_PATTERNS = [
31621
+ /\bfunction\s+([A-Za-z_$][\w$]*)/g,
31622
+ // JS/TS function decl
31623
+ /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/g,
31624
+ // arrow/func expr
31625
+ /^\s*def\s+([A-Za-z_]\w*)/gm,
31626
+ // Python
31627
+ /\bfunc\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/g
31628
+ // Go
31629
+ ];
31630
+ async function buildCode(root) {
31631
+ const files = await walk(root, (p) => CODE_EXT.has(extname(p)));
31632
+ const nodes = [];
31633
+ const nameToIds = /* @__PURE__ */ new Map();
31634
+ const fileDefs = /* @__PURE__ */ new Map();
31635
+ for (const f of files) {
31636
+ if (nodes.length >= MAX_NODES) break;
31637
+ const rel = relative(root, f).split("\\").join("/");
31638
+ let text = "";
31639
+ try {
31640
+ text = await readFile5(f, "utf8");
31641
+ } catch {
31642
+ continue;
31643
+ }
31644
+ const defs = [];
31645
+ for (const re of DEF_PATTERNS) {
31646
+ for (const m of text.matchAll(re)) {
31647
+ const name = m[1] ?? "";
31648
+ if (!name || name.length < 2) continue;
31649
+ const id = `${rel}#${name}`;
31650
+ if (nameToIds.has(name) && (nameToIds.get(name) ?? []).includes(id)) continue;
31651
+ if (nodes.length >= MAX_NODES) break;
31652
+ defs.push({ name, id, start: m.index ?? 0 });
31653
+ nodes.push({ id, label: name, kind: "fn", file: rel });
31654
+ nameToIds.set(name, [...nameToIds.get(name) ?? [], id]);
31655
+ }
31656
+ }
31657
+ defs.sort((a, b) => a.start - b.start);
31658
+ fileDefs.set(rel, { rel, text, defs });
31659
+ }
31660
+ const edges = [];
31661
+ const seen = /* @__PURE__ */ new Set();
31662
+ for (const { text, defs } of fileDefs.values()) {
31663
+ for (let i = 0; i < defs.length; i++) {
31664
+ const def = defs[i];
31665
+ const end = i + 1 < defs.length ? defs[i + 1].start : text.length;
31666
+ const body = text.slice(def.start, end);
31667
+ for (const m of body.matchAll(/([A-Za-z_$][\w$]*)\s*\(/g)) {
31668
+ const callee = m[1] ?? "";
31669
+ if (callee === def.name) continue;
31670
+ for (const targetId of nameToIds.get(callee) ?? []) {
31671
+ if (targetId === def.id) continue;
31672
+ const key = `${def.id}->${targetId}`;
31673
+ if (!seen.has(key)) {
31674
+ seen.add(key);
31675
+ edges.push({ from: def.id, to: targetId });
31676
+ }
31677
+ }
31678
+ }
31679
+ }
31680
+ }
31681
+ return { nodes, edges, orphans: orphansOf(nodes, edges) };
31682
+ }
31683
+ async function buildGraph(root, mode) {
31684
+ try {
31685
+ return mode === "docs" ? await buildDocs(root) : await buildCode(root);
31686
+ } catch {
31687
+ return { nodes: [], edges: [], orphans: [] };
31688
+ }
31689
+ }
31690
+
31691
+ // src/core/rag.ts
31692
+ import { readFile as readFile6, writeFile as writeFile5, statfs, mkdir as mkdir4 } from "fs/promises";
31693
+ import { existsSync as existsSync7 } from "fs";
31694
+ import { execFile as execFile3, spawn as spawn5 } from "child_process";
31695
+ import { totalmem, cpus } from "os";
31696
+ import { join as join16 } from "path";
31338
31697
  import { promisify as promisify3 } from "util";
31339
31698
  var run3 = promisify3(execFile3);
31699
+ var RAG_REL = ".claude/rag";
31700
+ async function tryRun(cmd, args) {
31701
+ try {
31702
+ const { stdout } = await run3(cmd, args, { timeout: 8e3 });
31703
+ return { ok: true, out: stdout.trim() };
31704
+ } catch {
31705
+ return { ok: false, out: "" };
31706
+ }
31707
+ }
31708
+ async function detectPython() {
31709
+ for (const bin of ["python3", "python"]) {
31710
+ const r = await tryRun(bin, ["--version"]);
31711
+ if (r.ok) return { bin, found: true, version: r.out.replace(/^Python\s*/i, "") };
31712
+ }
31713
+ return { bin: "python3", found: false, version: "" };
31714
+ }
31715
+ async function installLog(root) {
31716
+ try {
31717
+ const text = await readFile6(join16(root, RAG_REL, "install.log"), "utf8");
31718
+ return text.split("\n").filter((l) => l.length > 0).slice(-200);
31719
+ } catch {
31720
+ return [];
31721
+ }
31722
+ }
31723
+ function optionsFor(m) {
31724
+ const opts = [
31725
+ {
31726
+ id: "small",
31727
+ label: "Small (bge-small, EN) \xB7 ~130 MB",
31728
+ model: "BAAI/bge-small-en-v1.5",
31729
+ sizeMB: 130,
31730
+ recommended: m.ramGB < 8,
31731
+ note: "Lightweight, low RAM."
31732
+ }
31733
+ ];
31734
+ if (m.diskFreeGB > 1.5) {
31735
+ opts.push({
31736
+ id: "multilingual",
31737
+ label: "Multilingual (MiniLM, incl. Vietnamese) \xB7 ~470 MB",
31738
+ model: "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
31739
+ sizeMB: 470,
31740
+ recommended: m.ramGB >= 8 && m.diskFreeGB > 2,
31741
+ note: "Best for mixed VI/EN content."
31742
+ });
31743
+ }
31744
+ if (m.ramGB >= 8 && m.diskFreeGB > 2) {
31745
+ opts.push({
31746
+ id: "base",
31747
+ label: "Base (bge-base, EN) \xB7 ~440 MB",
31748
+ model: "BAAI/bge-base-en-v1.5",
31749
+ sizeMB: 440,
31750
+ recommended: false,
31751
+ note: "Higher quality EN, needs \u2265 8 GB RAM."
31752
+ });
31753
+ }
31754
+ return opts;
31755
+ }
31756
+ async function ragStatus(root) {
31757
+ const py = await detectPython();
31758
+ const [pip, fastembed, sqliteVec] = py.found ? await Promise.all([
31759
+ tryRun(py.bin, ["-m", "pip", "--version"]).then((r) => r.ok),
31760
+ tryRun(py.bin, ["-c", "import fastembed"]).then((r) => r.ok),
31761
+ tryRun(py.bin, ["-c", "import sqlite_vec"]).then((r) => r.ok)
31762
+ ]) : [false, false, false];
31763
+ let diskFreeGB = 0;
31764
+ try {
31765
+ const s = await statfs(root);
31766
+ diskFreeGB = Math.round(s.bavail * s.bsize / 1e9 * 10) / 10;
31767
+ } catch {
31768
+ diskFreeGB = 0;
31769
+ }
31770
+ const gpu = (await tryRun("nvidia-smi", ["-L"])).ok;
31771
+ const machine = {
31772
+ ramGB: Math.round(totalmem() / 1e9 * 10) / 10,
31773
+ cpus: cpus().length,
31774
+ diskFreeGB,
31775
+ gpu
31776
+ };
31777
+ let modelName = "";
31778
+ try {
31779
+ modelName = (await readFile6(join16(root, RAG_REL, ".installed"), "utf8")).trim();
31780
+ } catch {
31781
+ modelName = "";
31782
+ }
31783
+ const modelPresent = modelName.length > 0;
31784
+ return {
31785
+ installed: fastembed && sqliteVec && modelPresent,
31786
+ installing: existsSync7(join16(root, RAG_REL, ".installing")),
31787
+ python: { found: py.found, version: py.version },
31788
+ deps: { pip, fastembed, sqliteVec },
31789
+ model: { present: modelPresent, name: modelName },
31790
+ machine,
31791
+ options: optionsFor(machine),
31792
+ installLog: await installLog(root),
31793
+ index: await readIndexMeta(root)
31794
+ };
31795
+ }
31796
+ async function readIndexMeta(root) {
31797
+ const indexing = existsSync7(join16(root, RAG_REL, ".indexing"));
31798
+ try {
31799
+ const m = JSON.parse(await readFile6(join16(root, RAG_REL, ".index.json"), "utf8"));
31800
+ return {
31801
+ present: true,
31802
+ indexing,
31803
+ chunks: typeof m.chunks === "number" ? m.chunks : 0,
31804
+ indexedAt: typeof m.indexedAt === "string" ? m.indexedAt : ""
31805
+ };
31806
+ } catch {
31807
+ return { present: false, indexing, chunks: 0, indexedAt: "" };
31808
+ }
31809
+ }
31810
+ var RAG_ENGINE_PY = `import sys, os, json, glob, sqlite3, datetime
31811
+ BASE = os.path.join(".claude", "rag")
31812
+ DB = os.path.join(BASE, "index.db")
31813
+ def model_name():
31814
+ return open(os.path.join(BASE, ".installed"), encoding="utf-8").read().strip()
31815
+ def connect():
31816
+ import sqlite_vec
31817
+ db = sqlite3.connect(DB)
31818
+ db.enable_load_extension(True)
31819
+ sqlite_vec.load(db)
31820
+ db.enable_load_extension(False)
31821
+ return db
31822
+ def embedder():
31823
+ from fastembed import TextEmbedding
31824
+ return TextEmbedding(model_name())
31825
+ def reindex():
31826
+ import sqlite_vec
31827
+ emb = embedder()
31828
+ chunks = []
31829
+ for p in glob.glob("**/*.md", recursive=True):
31830
+ parts = p.split(os.sep)
31831
+ if "node_modules" in parts or ".git" in parts: continue
31832
+ try: txt = open(p, encoding="utf-8").read()
31833
+ except Exception: continue
31834
+ for i in range(0, len(txt), 800):
31835
+ c = txt[i:i+1000].strip()
31836
+ if len(c) > 20: chunks.append((p, c))
31837
+ if len(chunks) >= 5000: break
31838
+ vectors = list(emb.embed([c for _, c in chunks])) if chunks else []
31839
+ dim = len(vectors[0]) if vectors else 384
31840
+ os.makedirs(BASE, exist_ok=True)
31841
+ db = connect()
31842
+ db.execute("DROP TABLE IF EXISTS chunks")
31843
+ db.execute("CREATE VIRTUAL TABLE chunks USING vec0(embedding float[%d], +path text, +snippet text)" % dim)
31844
+ for (p, c), v in zip(chunks, vectors):
31845
+ db.execute("INSERT INTO chunks(embedding, path, snippet) VALUES (?, ?, ?)",
31846
+ (sqlite_vec.serialize_float32(list(v)), p, c[:400]))
31847
+ db.commit(); db.close()
31848
+ json.dump({"chunks": len(chunks), "indexedAt": datetime.datetime.now().isoformat(timespec="seconds"), "model": model_name()},
31849
+ open(os.path.join(BASE, ".index.json"), "w", encoding="utf-8"))
31850
+ print("[reindex] DONE %d chunks" % len(chunks))
31851
+ def query(q, k):
31852
+ import sqlite_vec
31853
+ v = list(embedder().embed([q]))[0]
31854
+ db = connect()
31855
+ rows = db.execute("SELECT path, snippet, distance FROM chunks WHERE embedding MATCH ? ORDER BY distance LIMIT ?",
31856
+ (sqlite_vec.serialize_float32(list(v)), k)).fetchall()
31857
+ db.close()
31858
+ out = [{"path": r[0], "snippet": r[1], "score": round(1.0/(1.0+float(r[2])), 4)} for r in rows]
31859
+ print(json.dumps({"results": out}, ensure_ascii=False))
31860
+ if __name__ == "__main__":
31861
+ cmd = sys.argv[1] if len(sys.argv) > 1 else ""
31862
+ if cmd == "reindex": reindex()
31863
+ elif cmd == "query": query(sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 8)
31864
+ `;
31865
+ async function ensureEngine(root) {
31866
+ await mkdir4(join16(root, RAG_REL), { recursive: true }).catch(() => void 0);
31867
+ await writeFile5(join16(root, RAG_REL, "rag_engine.py"), RAG_ENGINE_PY, "utf8");
31868
+ }
31869
+ async function ragReindex(root) {
31870
+ const py = await detectPython();
31871
+ if (!py.found) return { ok: false, started: false, error: "python3 not found" };
31872
+ if (!existsSync7(join16(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
31873
+ if (existsSync7(join16(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
31874
+ await ensureEngine(root);
31875
+ const script = `
31876
+ set +e
31877
+ touch "${RAG_REL}/.indexing"
31878
+ { echo "[reindex] $(date)"; "$RAG_PY" "${RAG_REL}/rag_engine.py" reindex 2>&1; } >> "${RAG_REL}/index.log" 2>&1
31879
+ rm -f "${RAG_REL}/.indexing"
31880
+ `;
31881
+ const child = spawn5("bash", ["-c", script], {
31882
+ cwd: root,
31883
+ env: { ...process.env, RAG_PY: py.bin },
31884
+ detached: true,
31885
+ stdio: "ignore"
31886
+ });
31887
+ child.unref();
31888
+ return { ok: true, started: true };
31889
+ }
31890
+ async function ragQuery(root, queryText, k = 8) {
31891
+ const py = await detectPython();
31892
+ if (!py.found) return { results: [], error: "python3 not found" };
31893
+ if (!existsSync7(join16(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
31894
+ await ensureEngine(root);
31895
+ try {
31896
+ const { stdout } = await run3(
31897
+ py.bin,
31898
+ [join16(RAG_REL, "rag_engine.py"), "query", queryText, String(Math.min(Math.max(k, 1), 20))],
31899
+ { cwd: root, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
31900
+ );
31901
+ const parsed = JSON.parse(stdout.trim() || '{"results":[]}');
31902
+ return { results: Array.isArray(parsed.results) ? parsed.results : [] };
31903
+ } catch (err) {
31904
+ return { results: [], error: err instanceof Error ? err.message : String(err) };
31905
+ }
31906
+ }
31907
+ async function ragInstall(root, model) {
31908
+ const py = await detectPython();
31909
+ if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
31910
+ if (existsSync7(join16(root, RAG_REL, ".installing"))) return { ok: true, started: true };
31911
+ try {
31912
+ await mkdir4(join16(root, RAG_REL), { recursive: true });
31913
+ } catch {
31914
+ }
31915
+ const script = `
31916
+ set +e
31917
+ touch "${RAG_REL}/.installing"
31918
+ rm -f "${RAG_REL}/.installed"
31919
+ {
31920
+ echo "[install] $(date) pip install fastembed sqlite-vec"
31921
+ "$RAG_PY" -m pip install --quiet --user fastembed sqlite-vec 2>&1
31922
+ echo "[install] warming model: $RAG_MODEL (first run downloads it)"
31923
+ "$RAG_PY" -c "from fastembed import TextEmbedding; TextEmbedding('$RAG_MODEL'); print('[install] model ready')" 2>&1
31924
+ if [ $? -eq 0 ]; then printf '%s' "$RAG_MODEL" > "${RAG_REL}/.installed"; echo "[install] DONE"; else echo "[install] FAILED"; fi
31925
+ } >> "${RAG_REL}/install.log" 2>&1
31926
+ rm -f "${RAG_REL}/.installing"
31927
+ `;
31928
+ const child = spawn5("bash", ["-c", script], {
31929
+ cwd: root,
31930
+ env: { ...process.env, RAG_PY: py.bin, RAG_MODEL: model },
31931
+ detached: true,
31932
+ stdio: "ignore"
31933
+ });
31934
+ child.unref();
31935
+ return { ok: true, started: true };
31936
+ }
31937
+
31938
+ // src/core/git-diff.ts
31939
+ import { readFile as readFile7 } from "fs/promises";
31940
+ import { execFile as execFile4 } from "child_process";
31941
+ import { basename as basename3, dirname as dirname5, resolve as resolve5 } from "path";
31942
+ import { promisify as promisify4 } from "util";
31943
+ var run4 = promisify4(execFile4);
31340
31944
  async function gitDiff(path) {
31341
- const target = resolve4(path);
31342
- const dir2 = dirname4(target);
31343
- const base = basename2(target);
31945
+ const target = resolve5(path);
31946
+ const dir2 = dirname5(target);
31947
+ const base = basename3(target);
31344
31948
  let newContent = "";
31345
31949
  try {
31346
- newContent = await readFile3(target, "utf8");
31950
+ newContent = await readFile7(target, "utf8");
31347
31951
  } catch {
31348
31952
  }
31349
31953
  let oldContent = "";
31350
31954
  try {
31351
- const { stdout } = await run3("git", ["show", `HEAD:./${base}`], {
31955
+ const { stdout } = await run4("git", ["show", `HEAD:./${base}`], {
31352
31956
  cwd: dir2,
31353
31957
  timeout: 15e3,
31354
31958
  maxBuffer: 4 * 1024 * 1024
@@ -31360,12 +31964,12 @@ async function gitDiff(path) {
31360
31964
  }
31361
31965
 
31362
31966
  // src/core/git-env.ts
31363
- import { execFile as execFile4 } from "child_process";
31364
- import { promisify as promisify4 } from "util";
31365
- var run4 = promisify4(execFile4);
31967
+ import { execFile as execFile5 } from "child_process";
31968
+ import { promisify as promisify5 } from "util";
31969
+ var run5 = promisify5(execFile5);
31366
31970
  async function hasBinary(bin, args = ["--version"]) {
31367
31971
  try {
31368
- await run4(bin, args, { timeout: 5e3 });
31972
+ await run5(bin, args, { timeout: 5e3 });
31369
31973
  return true;
31370
31974
  } catch {
31371
31975
  return false;
@@ -31378,7 +31982,7 @@ async function checkGitEnv(provider) {
31378
31982
  let account = null;
31379
31983
  if (installed) {
31380
31984
  try {
31381
- const { stdout, stderr } = await run4(bin, ["auth", "status"], { timeout: 8e3 });
31985
+ const { stdout, stderr } = await run5(bin, ["auth", "status"], { timeout: 8e3 });
31382
31986
  const out = `${stdout}
31383
31987
  ${stderr}`;
31384
31988
  authenticated = /logged in|logged into|✓/i.test(out);
@@ -31391,22 +31995,22 @@ ${stderr}`;
31391
31995
  }
31392
31996
 
31393
31997
  // src/core/git-history.ts
31394
- import { readdir as readdir3 } from "fs/promises";
31395
- import { execFile as execFile5 } from "child_process";
31396
- import { resolve as resolve5, sep as sep4 } from "path";
31397
- import { promisify as promisify5 } from "util";
31398
- var run5 = promisify5(execFile5);
31998
+ import { readdir as readdir5 } from "fs/promises";
31999
+ import { execFile as execFile6 } from "child_process";
32000
+ import { resolve as resolve6, sep as sep4 } from "path";
32001
+ import { promisify as promisify6 } from "util";
32002
+ var run6 = promisify6(execFile6);
31399
32003
  var FS = "";
31400
32004
  var RS = "";
31401
32005
  function resolveRepo(root, repo) {
31402
32006
  if (!root) return null;
31403
- const base = resolve5(root);
31404
- const requested = resolve5(base, repo && repo.trim() ? repo : ".");
32007
+ const base = resolve6(root);
32008
+ const requested = resolve6(base, repo && repo.trim() ? repo : ".");
31405
32009
  return requested === base || requested.startsWith(base + sep4) ? requested : base;
31406
32010
  }
31407
32011
  async function git(cwd2, args) {
31408
32012
  try {
31409
- const { stdout } = await run5("git", args, {
32013
+ const { stdout } = await run6("git", args, {
31410
32014
  cwd: cwd2,
31411
32015
  timeout: 15e3,
31412
32016
  maxBuffer: 8 * 1024 * 1024
@@ -31418,7 +32022,7 @@ async function git(cwd2, args) {
31418
32022
  }
31419
32023
  async function gitRepos(root) {
31420
32024
  if (!root) return { repos: [] };
31421
- const base = resolve5(root);
32025
+ const base = resolve6(root);
31422
32026
  const found = /* @__PURE__ */ new Set();
31423
32027
  const repos = [];
31424
32028
  const add = async (dir2) => {
@@ -31432,16 +32036,16 @@ async function gitRepos(root) {
31432
32036
  };
31433
32037
  await add(base);
31434
32038
  try {
31435
- const lvl1 = await readdir3(base, { withFileTypes: true });
32039
+ const lvl1 = await readdir5(base, { withFileTypes: true });
31436
32040
  for (const d of lvl1) {
31437
32041
  if (!d.isDirectory() || d.name === ".git" || d.name === "node_modules") continue;
31438
- const p1 = resolve5(base, d.name);
32042
+ const p1 = resolve6(base, d.name);
31439
32043
  await add(p1);
31440
32044
  try {
31441
- const lvl2 = await readdir3(p1, { withFileTypes: true });
32045
+ const lvl2 = await readdir5(p1, { withFileTypes: true });
31442
32046
  for (const d2 of lvl2) {
31443
32047
  if (!d2.isDirectory() || d2.name === ".git" || d2.name === "node_modules") continue;
31444
- await add(resolve5(p1, d2.name));
32048
+ await add(resolve6(p1, d2.name));
31445
32049
  }
31446
32050
  } catch {
31447
32051
  }
@@ -31508,24 +32112,24 @@ async function gitCommitDiff(root, repo, hash, path) {
31508
32112
  }
31509
32113
 
31510
32114
  // src/core/git-commit-identity.ts
31511
- import { execFile as execFile6 } from "child_process";
31512
- import { promisify as promisify6 } from "util";
31513
- var run6 = promisify6(execFile6);
32115
+ import { execFile as execFile7 } from "child_process";
32116
+ import { promisify as promisify7 } from "util";
32117
+ var run7 = promisify7(execFile7);
31514
32118
  async function setCommitAuthor(cwd2, machineUsername) {
31515
32119
  if (!machineUsername) return;
31516
32120
  try {
31517
- await run6("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
32121
+ await run7("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
31518
32122
  } catch {
31519
32123
  }
31520
32124
  }
31521
32125
 
31522
32126
  // src/core/scaffold.ts
31523
- import { cp, mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
31524
- import { execFile as execFile7 } from "child_process";
31525
- import { dirname as dirname5, join as join13, resolve as resolve6 } from "path";
32127
+ import { cp, mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
32128
+ import { execFile as execFile8 } from "child_process";
32129
+ import { dirname as dirname6, join as join17, resolve as resolve7 } from "path";
31526
32130
  import { fileURLToPath as fileURLToPath3 } from "url";
31527
- import { promisify as promisify7 } from "util";
31528
- var run7 = promisify7(execFile7);
32131
+ import { promisify as promisify8 } from "util";
32132
+ var run8 = promisify8(execFile8);
31529
32133
  function reposOf(spec) {
31530
32134
  const repos = spec?.repos;
31531
32135
  return Array.isArray(repos) ? repos : [];
@@ -31539,46 +32143,46 @@ async function provisionRepos(target, repos, emit, opts = {}) {
31539
32143
  if (repo.primary) {
31540
32144
  if (opts.clonePrimary && repo.op === "existing" && repo.url) {
31541
32145
  emit("git", `Cloning ${repo.url}\u2026`);
31542
- await run7("git", ["clone", repo.url, target], { timeout: 12e4 });
32146
+ await run8("git", ["clone", repo.url, target], { timeout: 12e4 });
31543
32147
  continue;
31544
32148
  }
31545
32149
  emit("git", "Initializing the primary repo\u2026");
31546
- await run7("git", ["init"], { cwd: target, timeout: 2e4 });
32150
+ await run8("git", ["init"], { cwd: target, timeout: 2e4 });
31547
32151
  if (repo.op === "existing" && repo.url) {
31548
- await run7("git", ["remote", "add", "origin", repo.url], { cwd: target, timeout: 2e4 }).catch(
32152
+ await run8("git", ["remote", "add", "origin", repo.url], { cwd: target, timeout: 2e4 }).catch(
31549
32153
  () => void 0
31550
32154
  );
31551
32155
  }
31552
32156
  continue;
31553
32157
  }
31554
- const dir2 = join13(target, subDirName(repo));
32158
+ const dir2 = join17(target, subDirName(repo));
31555
32159
  if (repo.op === "existing" && repo.url) {
31556
32160
  emit("git", `Cloning sub-repo ${repo.url}\u2026`);
31557
- await run7("git", ["clone", repo.url, dir2], { timeout: 12e4 });
32161
+ await run8("git", ["clone", repo.url, dir2], { timeout: 12e4 });
31558
32162
  } else {
31559
32163
  emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
31560
- await mkdir3(dir2, { recursive: true });
31561
- await run7("git", ["init"], { cwd: dir2, timeout: 2e4 });
32164
+ await mkdir5(dir2, { recursive: true });
32165
+ await run8("git", ["init"], { cwd: dir2, timeout: 2e4 });
31562
32166
  }
31563
32167
  }
31564
32168
  }
31565
32169
  function sampleDir() {
31566
32170
  if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
31567
- const here = dirname5(fileURLToPath3(import.meta.url));
31568
- return resolve6(here, "../../project-sample");
32171
+ const here = dirname6(fileURLToPath3(import.meta.url));
32172
+ return resolve7(here, "../../project-sample");
31569
32173
  }
31570
32174
  async function scaffoldProject(payload, profileDir2, onProgress) {
31571
32175
  const emit = (step, message) => onProgress?.({ projectId: payload.projectId, step, message });
31572
32176
  try {
31573
- const root = process.env.SCAFFOLD_ROOT ? resolve6(process.env.SCAFFOLD_ROOT) : resolve6(profileDir2);
31574
- const target = join13(root, payload.projectName);
31575
- await mkdir3(target, { recursive: true });
32177
+ const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
32178
+ const target = join17(root, payload.projectName);
32179
+ await mkdir5(target, { recursive: true });
31576
32180
  emit("copy", "Copying the sample template\u2026");
31577
32181
  await cp(sampleDir(), target, { recursive: true, force: false, errorOnExist: false });
31578
32182
  if (payload.spec) {
31579
32183
  emit("spec", "Writing project.spec.json\u2026");
31580
- await writeFile3(
31581
- join13(target, "project.spec.json"),
32184
+ await writeFile6(
32185
+ join17(target, "project.spec.json"),
31582
32186
  JSON.stringify(payload.spec, null, 2),
31583
32187
  "utf8"
31584
32188
  );
@@ -31596,7 +32200,7 @@ async function aiInit(target, spec, emit) {
31596
32200
  emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
31597
32201
  const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
31598
32202
  if (subagents.length > 0) {
31599
- await mkdir3(join13(target, ".claude", "agents"), { recursive: true });
32203
+ await mkdir5(join17(target, ".claude", "agents"), { recursive: true });
31600
32204
  for (const sa of subagents) {
31601
32205
  const name = (sa.name || "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
31602
32206
  if (!name) continue;
@@ -31606,17 +32210,17 @@ name: ${name}
31606
32210
 
31607
32211
  ${(sa.description || "").trim()}
31608
32212
  `;
31609
- await writeFile3(join13(target, ".claude", "agents", `${name}.md`), body, "utf8");
32213
+ await writeFile6(join17(target, ".claude", "agents", `${name}.md`), body, "utf8");
31610
32214
  }
31611
32215
  }
31612
32216
  const specJson = JSON.stringify(spec);
31613
32217
  await generateFile(
31614
- join13(target, "README.md"),
32218
+ join17(target, "README.md"),
31615
32219
  `Write a concise README.md (Markdown only, no preamble) for this project from its spec JSON:
31616
32220
  ${specJson}`
31617
32221
  );
31618
32222
  await generateFile(
31619
- join13(target, "CLAUDE.md"),
32223
+ join17(target, "CLAUDE.md"),
31620
32224
  `Write a CLAUDE.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
31621
32225
  ${specJson}`
31622
32226
  );
@@ -31624,16 +32228,16 @@ ${specJson}`
31624
32228
  async function generateFile(path, prompt) {
31625
32229
  try {
31626
32230
  const text = await aiGenerate(prompt);
31627
- if (text.trim()) await writeFile3(path, text.trim() + "\n", "utf8");
32231
+ if (text.trim()) await writeFile6(path, text.trim() + "\n", "utf8");
31628
32232
  } catch {
31629
32233
  }
31630
32234
  }
31631
32235
  async function addProject(payload, profileDir2, onProgress) {
31632
32236
  const emit = (step, message) => onProgress?.({ projectId: payload.projectId, step, message });
31633
32237
  try {
31634
- const root = process.env.SCAFFOLD_ROOT ? resolve6(process.env.SCAFFOLD_ROOT) : resolve6(profileDir2);
31635
- const target = join13(root, payload.projectName);
31636
- await mkdir3(target, { recursive: true });
32238
+ const root = process.env.SCAFFOLD_ROOT ? resolve7(process.env.SCAFFOLD_ROOT) : resolve7(profileDir2);
32239
+ const target = join17(root, payload.projectName);
32240
+ await mkdir5(target, { recursive: true });
31637
32241
  const repos = payload.repos ?? [];
31638
32242
  if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true });
31639
32243
  onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
@@ -31644,12 +32248,12 @@ async function addProject(payload, profileDir2, onProgress) {
31644
32248
  }
31645
32249
 
31646
32250
  // src/core/config-sync.ts
31647
- import { existsSync as existsSync7, readFileSync as readFileSync13 } from "fs";
31648
- import { join as join14 } from "path";
32251
+ import { existsSync as existsSync8, readFileSync as readFileSync13 } from "fs";
32252
+ import { join as join18 } from "path";
31649
32253
  var SERVER_MANAGED_KEYS = ["physicPath", "sessionSwitchPct", "perPromptTokenLimit"];
31650
32254
  function readConfigText(profileDir2) {
31651
- const path = join14(profileDir2, "config.json");
31652
- if (existsSync7(path)) {
32255
+ const path = join18(profileDir2, "config.json");
32256
+ if (existsSync8(path)) {
31653
32257
  try {
31654
32258
  return readFileSync13(path, "utf8");
31655
32259
  } catch {
@@ -31713,7 +32317,7 @@ var WsClient = class {
31713
32317
  context.bus.onTranscriptUpdate(
31714
32318
  (entry) => this.emitConsole({ kind: "update", entry: toDtoEntry(entry) })
31715
32319
  );
31716
- context.bus.onClear(() => this.emitConsole({ kind: "clear" }));
32320
+ context.bus.onClear(() => this.emitConsoleClear());
31717
32321
  }
31718
32322
  context;
31719
32323
  socket = null;
@@ -31737,6 +32341,13 @@ var WsClient = class {
31737
32341
  wakeReconnect = null;
31738
32342
  /** When set, the next backoff sleep returns immediately (forced reconnect). */
31739
32343
  forceReconnect = false;
32344
+ /**
32345
+ * The single "reconnecting…" transcript line for the current outage (approach B): created on
32346
+ * the first failed attempt and updated in place each retry, then finalized to the "connected"
32347
+ * line on the next hello_ack — so one outage adds one line, not an error + a wait line per
32348
+ * attempt. Null while connected / between outages.
32349
+ */
32350
+ reconnectEntryId = null;
31740
32351
  /** Org-configured reconnect cap (seconds) from the last ws_token — overrides env. */
31741
32352
  orgReconnectMaxSec = null;
31742
32353
  /** Env override for the reconnect cap (seconds); resolved once at construction. */
@@ -31805,14 +32416,14 @@ var WsClient = class {
31805
32416
  */
31806
32417
  awaitConnected(timeoutMs) {
31807
32418
  if (this.sessionKey && this.bus.status === "connected") return Promise.resolve(true);
31808
- return new Promise((resolve7) => {
32419
+ return new Promise((resolve8) => {
31809
32420
  const waiter = () => {
31810
32421
  clearTimeout(timer);
31811
- resolve7(true);
32422
+ resolve8(true);
31812
32423
  };
31813
32424
  const timer = setTimeout(() => {
31814
32425
  this.connectWaiters = this.connectWaiters.filter((w) => w !== waiter);
31815
- resolve7(false);
32426
+ resolve8(false);
31816
32427
  }, timeoutMs);
31817
32428
  this.connectWaiters.push(waiter);
31818
32429
  });
@@ -31839,16 +32450,16 @@ var WsClient = class {
31839
32450
  this.forceReconnect = false;
31840
32451
  return Promise.resolve();
31841
32452
  }
31842
- return new Promise((resolve7) => {
32453
+ return new Promise((resolve8) => {
31843
32454
  const timer = setTimeout(() => {
31844
32455
  this.wakeReconnect = null;
31845
- resolve7();
32456
+ resolve8();
31846
32457
  }, ms);
31847
32458
  this.wakeReconnect = () => {
31848
32459
  clearTimeout(timer);
31849
32460
  this.wakeReconnect = null;
31850
32461
  this.forceReconnect = false;
31851
- resolve7();
32462
+ resolve8();
31852
32463
  };
31853
32464
  });
31854
32465
  }
@@ -31858,6 +32469,7 @@ var WsClient = class {
31858
32469
  async run() {
31859
32470
  let hashcodeInvalidStreak = 0;
31860
32471
  for (let attempt = 0; !this.stopped; attempt++) {
32472
+ let reconnectDetail = "Disconnected";
31861
32473
  try {
31862
32474
  await this.connectOnce();
31863
32475
  if (this.sessionEstablished) {
@@ -31875,17 +32487,14 @@ var WsClient = class {
31875
32487
  this.logout(err.errorCode);
31876
32488
  return;
31877
32489
  }
31878
- this.bus.log(
31879
- `Token request rejected (HASHCODE_INVALID) \u2014 retrying (${hashcodeInvalidStreak}/${MAX_HASHCODE_INVALID_RETRIES}).`,
31880
- "warn"
31881
- );
32490
+ reconnectDetail = `Token rejected (HASHCODE_INVALID ${hashcodeInvalidStreak}/${MAX_HASHCODE_INVALID_RETRIES})`;
31882
32491
  } else if (err.errorCode === "USER_PAUSED") {
31883
- this.bus.log("This account is paused \u2014 retrying until it is resumed.", "warn");
32492
+ reconnectDetail = "This account is paused \u2014 retrying until it is resumed";
31884
32493
  } else {
31885
- this.bus.log(`Connection error: ${err}`, "error");
32494
+ reconnectDetail = `Connection error: ${err}`;
31886
32495
  }
31887
32496
  } else {
31888
- this.bus.log(`Connection error: ${err}`, "error");
32497
+ reconnectDetail = `Connection error: ${err}`;
31889
32498
  }
31890
32499
  }
31891
32500
  if (this.stopped) return;
@@ -31896,7 +32505,7 @@ var WsClient = class {
31896
32505
  );
31897
32506
  logger.info("ws.reconnect", { attempt, waitMs: wait });
31898
32507
  this.bus.setStatus("reconnecting");
31899
- this.bus.log(`Reconnecting in ${Math.round(wait / 1e3)}s\u2026 (/reconnect to retry now)`);
32508
+ this.showReconnectStatus(reconnectDetail, Math.round(wait / 1e3), attempt + 1);
31900
32509
  await this.interruptibleSleep(wait);
31901
32510
  }
31902
32511
  }
@@ -31904,7 +32513,7 @@ var WsClient = class {
31904
32513
  * One WS session: token → connect → handshake → handle messages until close.
31905
32514
  */
31906
32515
  connectOnce() {
31907
- return new Promise((resolve7, reject) => {
32516
+ return new Promise((resolve8, reject) => {
31908
32517
  void (async () => {
31909
32518
  this.sessionEstablished = false;
31910
32519
  this.bus.setStatus("connecting");
@@ -31973,7 +32582,7 @@ var WsClient = class {
31973
32582
  if (code === CLOSE_DRAINING) {
31974
32583
  this.bus.log("Server draining \u2014 will reconnect (backoff + jitter).");
31975
32584
  }
31976
- resolve7();
32585
+ resolve8();
31977
32586
  });
31978
32587
  socket.on("error", (err) => {
31979
32588
  this.clearHeartbeat();
@@ -32002,7 +32611,7 @@ var WsClient = class {
32002
32611
  logger.info("ws.connected", { version: CLI_VERSION });
32003
32612
  this.stopConsoleSync();
32004
32613
  this.bus.setStatus("connected");
32005
- this.bus.log("\u2714 Connected to the server (session encryption ready).");
32614
+ this.finishReconnect();
32006
32615
  this.sendMachineStatus();
32007
32616
  this.flushOfflineBuffer();
32008
32617
  this.startHeartbeat();
@@ -32069,8 +32678,8 @@ var WsClient = class {
32069
32678
  const oldRoot = this.physicFolderPath(sync.oldName);
32070
32679
  if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
32071
32680
  }
32072
- if (sync.oldName) rmSync4(join15(this.context.profileDir, sync.oldName), { recursive: true, force: true });
32073
- mkdirSync4(join15(this.context.profileDir, sync.newName), { recursive: true });
32681
+ if (sync.oldName) rmSync4(join19(this.context.profileDir, sync.oldName), { recursive: true, force: true });
32682
+ mkdirSync4(join19(this.context.profileDir, sync.newName), { recursive: true });
32074
32683
  this.physicRoot = this.physicFolderPath(sync.newName);
32075
32684
  this.bus.setProject(sync.newName);
32076
32685
  this.bus.log(`Physic project folder synced \u2192 ${sync.newName}`);
@@ -32081,7 +32690,7 @@ var WsClient = class {
32081
32690
  if (del.name) {
32082
32691
  const delRoot = this.physicFolderPath(del.name);
32083
32692
  if (delRoot) void uninstallCron(delRoot).catch(() => void 0);
32084
- rmSync4(join15(this.context.profileDir, del.name), { recursive: true, force: true });
32693
+ rmSync4(join19(this.context.profileDir, del.name), { recursive: true, force: true });
32085
32694
  this.bus.log(`Physic project folder deleted (project removed) \u2192 ${del.name}`);
32086
32695
  }
32087
32696
  this.bus.setProject(null);
@@ -32178,6 +32787,88 @@ var WsClient = class {
32178
32787
  }
32179
32788
  break;
32180
32789
  }
32790
+ case WsChannels.AGENTS_LIST:
32791
+ if (this.physicRoot) {
32792
+ void listAgents(this.physicRoot).then(
32793
+ (reply) => this.send(WsChannels.AGENTS_LIST, reply, message.id)
32794
+ );
32795
+ }
32796
+ break;
32797
+ case WsChannels.AGENTS_READ: {
32798
+ const req = payload;
32799
+ if (this.physicRoot) {
32800
+ void readAgent(this.physicRoot, req.kind, req.name).then(
32801
+ (reply) => this.send(WsChannels.AGENTS_READ, reply, message.id)
32802
+ );
32803
+ }
32804
+ break;
32805
+ }
32806
+ case WsChannels.AGENTS_WRITE: {
32807
+ const req = payload;
32808
+ if (this.physicRoot) {
32809
+ void writeAgent(this.physicRoot, req).then(
32810
+ (reply) => this.send(WsChannels.AGENTS_WRITE, reply, message.id)
32811
+ );
32812
+ }
32813
+ break;
32814
+ }
32815
+ case WsChannels.SECRETS_READ:
32816
+ if (this.physicRoot) {
32817
+ void readSecrets(this.physicRoot).then(
32818
+ (reply) => this.send(WsChannels.SECRETS_READ, reply, message.id)
32819
+ );
32820
+ }
32821
+ break;
32822
+ case WsChannels.SECRETS_WRITE: {
32823
+ const req = payload;
32824
+ if (this.physicRoot) {
32825
+ void writeSecrets(this.physicRoot, req).then(
32826
+ (reply) => this.send(WsChannels.SECRETS_WRITE, reply, message.id)
32827
+ );
32828
+ }
32829
+ break;
32830
+ }
32831
+ case WsChannels.GRAPH_BUILD: {
32832
+ const req = payload;
32833
+ if (this.physicRoot) {
32834
+ void buildGraph(this.physicRoot, req.mode).then(
32835
+ (reply) => this.send(WsChannels.GRAPH_BUILD, reply, message.id)
32836
+ );
32837
+ }
32838
+ break;
32839
+ }
32840
+ case WsChannels.RAG_STATUS:
32841
+ if (this.physicRoot) {
32842
+ void ragStatus(this.physicRoot).then(
32843
+ (reply) => this.send(WsChannels.RAG_STATUS, reply, message.id)
32844
+ );
32845
+ }
32846
+ break;
32847
+ case WsChannels.RAG_INSTALL: {
32848
+ const req = payload;
32849
+ if (this.physicRoot) {
32850
+ void ragInstall(this.physicRoot, req.model).then(
32851
+ (reply) => this.send(WsChannels.RAG_INSTALL, reply, message.id)
32852
+ );
32853
+ }
32854
+ break;
32855
+ }
32856
+ case WsChannels.RAG_REINDEX:
32857
+ if (this.physicRoot) {
32858
+ void ragReindex(this.physicRoot).then(
32859
+ (reply) => this.send(WsChannels.RAG_REINDEX, reply, message.id)
32860
+ );
32861
+ }
32862
+ break;
32863
+ case WsChannels.RAG_QUERY: {
32864
+ const req = payload;
32865
+ if (this.physicRoot) {
32866
+ void ragQuery(this.physicRoot, req.query, req.k).then(
32867
+ (reply) => this.send(WsChannels.RAG_QUERY, reply, message.id)
32868
+ );
32869
+ }
32870
+ break;
32871
+ }
32181
32872
  case WsChannels.CONFIG_READ:
32182
32873
  this.send(
32183
32874
  WsChannels.CONFIG_READ,
@@ -32231,7 +32922,7 @@ var WsClient = class {
32231
32922
  }
32232
32923
  case WsChannels.LOG_READ: {
32233
32924
  const limit = Math.min(Math.max(payload.limit ?? 200, 1), 2e3);
32234
- const lines = readRecentLogLines(join15(this.context.profileDir, "logs"), limit);
32925
+ const lines = readRecentLogLines(join19(this.context.profileDir, "logs"), limit);
32235
32926
  this.send(WsChannels.LOG_READ, { lines }, message.id);
32236
32927
  break;
32237
32928
  }
@@ -32571,6 +33262,46 @@ var WsClient = class {
32571
33262
  if (!this.consoleWatching) return;
32572
33263
  this.send(WsChannels.CONSOLE_SYNC, { ...ev, rev: ++this.consoleRev });
32573
33264
  }
33265
+ /**
33266
+ * A `/clear` (or idle auto-clear) wiped the transcript (ADR-0150). Emit it as an absolute empty
33267
+ * **snapshot** rather than an incremental `clear` event: a snapshot is applied unconditionally on
33268
+ * the web (no `rev`-gap check), so the clear can't be silently dropped when the web's revision
33269
+ * drifted during a reconnect flap. No-op while unwatched — the next `console.watch` re-arm
33270
+ * snapshots the (now empty) buffer anyway. The history is already emptied before this fires, so
33271
+ * the snapshot carries no entries.
33272
+ */
33273
+ emitConsoleClear() {
33274
+ if (!this.consoleWatching) return;
33275
+ this.sendConsoleSnapshot();
33276
+ }
33277
+ /**
33278
+ * Show the reconnect-backoff status as a SINGLE transcript line for the current outage
33279
+ * (approach B): create it on the first failed attempt, then update it in place on every retry —
33280
+ * so an outage adds one line instead of an error + a "reconnecting in Ns" line per attempt
33281
+ * (which flooded the TUI and, on the next snapshot, the web Console).
33282
+ */
33283
+ showReconnectStatus(detail, waitSec, attempt) {
33284
+ const text = `\u27F3 ${detail} \u2014 reconnecting in ${waitSec}s (attempt ${attempt}, /reconnect to retry now)`;
33285
+ if (this.reconnectEntryId) {
33286
+ this.bus.updateEntry(this.reconnectEntryId, text, "warn");
33287
+ } else {
33288
+ this.reconnectEntryId = this.bus.push({ source: "system", kind: "log", text, level: "warn" });
33289
+ }
33290
+ }
33291
+ /**
33292
+ * A session became ready (hello_ack): finalize any in-flight reconnect line in place to the
33293
+ * "connected" line (so the outage's single line resolves cleanly), or — on a first connect with
33294
+ * no prior outage — push a fresh connected line.
33295
+ */
33296
+ finishReconnect() {
33297
+ const msg = "\u2714 Connected to the server (session encryption ready).";
33298
+ if (this.reconnectEntryId) {
33299
+ this.bus.updateEntry(this.reconnectEntryId, msg, "info");
33300
+ this.reconnectEntryId = null;
33301
+ } else {
33302
+ this.bus.log(msg);
33303
+ }
33304
+ }
32574
33305
  /** Send a full transcript snapshot (on attach + the periodic self-heal — ADR-0150). */
32575
33306
  sendConsoleSnapshot() {
32576
33307
  const entries2 = this.bus.snapshot().map(toDtoEntry);
@@ -32620,7 +33351,7 @@ var WsClient = class {
32620
33351
  * Used for quota.check before spawning an AI cli (ADR-0020).
32621
33352
  */
32622
33353
  request(channel, data) {
32623
- return new Promise((resolve7, reject) => {
33354
+ return new Promise((resolve8, reject) => {
32624
33355
  if (!this.socket || !this.sessionKey) {
32625
33356
  reject(new Error("Not connected to the server."));
32626
33357
  return;
@@ -32640,7 +33371,7 @@ var WsClient = class {
32640
33371
  }, 15e3);
32641
33372
  timer.unref?.();
32642
33373
  this.pending.set(envelope.id, {
32643
- resolve: (value) => resolve7(value),
33374
+ resolve: (value) => resolve8(value),
32644
33375
  timer
32645
33376
  });
32646
33377
  this.socket.send(JSON.stringify(envelope));
@@ -32687,7 +33418,7 @@ var WsClient = class {
32687
33418
  /** Create the physic folder at an absolute path if missing (best-effort). */
32688
33419
  ensurePhysicFolderPath(folder) {
32689
33420
  try {
32690
- if (!existsSync8(folder)) {
33421
+ if (!existsSync9(folder)) {
32691
33422
  mkdirSync4(folder, { recursive: true });
32692
33423
  this.bus.log(`Created physic project folder: ${folder}`);
32693
33424
  }
@@ -32702,7 +33433,7 @@ var WsClient = class {
32702
33433
  */
32703
33434
  physicFolderPath(projectName) {
32704
33435
  const safe = projectName.replace(/[/\\]/g, "_").replace(/\.\./g, "_").trim();
32705
- return safe ? join15(this.context.profileDir, safe) : null;
33436
+ return safe ? join19(this.context.profileDir, safe) : null;
32706
33437
  }
32707
33438
  /**
32708
33439
  * Report machine.status: version + the state of the physic project being served
@@ -32716,7 +33447,7 @@ var WsClient = class {
32716
33447
  projects: [],
32717
33448
  version: CLI_VERSION,
32718
33449
  physicPath,
32719
- physicPathExists: physicPath ? existsSync8(physicPath) : void 0,
33450
+ physicPathExists: physicPath ? existsSync9(physicPath) : void 0,
32720
33451
  // Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
32721
33452
  autonomousRunning: physicPath ? isAutonomousRunning(physicPath) : false
32722
33453
  });
@@ -32751,7 +33482,7 @@ var WsClient = class {
32751
33482
  const working = getWorkingProfile(this.context.profileDir, aiCli2);
32752
33483
  const configured = claudeHomeDirs(config);
32753
33484
  const dirs = working ? [working, ...configured.filter((d) => d !== working)] : configured;
32754
- const profile = working ? basename3(working) : dirs[0] ? basename3(dirs[0]) : "default";
33485
+ const profile = working ? basename4(working) : dirs[0] ? basename4(dirs[0]) : "default";
32755
33486
  const api = await checkClaudeUsage(dirs, aiCli2);
32756
33487
  const snapshot = api ?? {
32757
33488
  plan: "?",
@@ -32788,7 +33519,7 @@ var WsClient = class {
32788
33519
  */
32789
33520
  uploadLog() {
32790
33521
  try {
32791
- const upload = readLogUpload(join15(this.context.profileDir, "logs"));
33522
+ const upload = readLogUpload(join19(this.context.profileDir, "logs"));
32792
33523
  if (!upload) return;
32793
33524
  const sig = `${upload.fileName}:${upload.totalBytes}`;
32794
33525
  if (sig === this.lastLogSig) return;
@@ -32817,9 +33548,9 @@ var WsClient = class {
32817
33548
 
32818
33549
  // src/core/instance-lock.ts
32819
33550
  import { readFileSync as readFileSync14, rmSync as rmSync5, writeFileSync as writeFileSync6 } from "fs";
32820
- import { join as join16 } from "path";
33551
+ import { join as join20 } from "path";
32821
33552
  function lockPath(profileDir2) {
32822
- return join16(profileDir2, "start.lock");
33553
+ return join20(profileDir2, "start.lock");
32823
33554
  }
32824
33555
  function isAlive(pid) {
32825
33556
  try {
@@ -32912,14 +33643,15 @@ var SessionBus = class {
32912
33643
  return [...this.history];
32913
33644
  }
32914
33645
  /**
32915
- * Producer → consumers: replace the text of an already-pushed entry (ADR-0108) — used to
32916
- * grow a streaming `result` block in place without pushing a new line per chunk. No-op if the
32917
- * entry has aged out of the replay window.
33646
+ * Producer → consumers: replace the text (and optionally the level) of an already-pushed entry
33647
+ * (ADR-0108) — used to grow a streaming `result` block in place, or to update a single reconnect
33648
+ * status line without pushing a new line per attempt. No-op if the entry has aged out.
32918
33649
  */
32919
- updateEntry(id, text) {
33650
+ updateEntry(id, text, level) {
32920
33651
  const entry = this.history.find((e) => e.id === id);
32921
33652
  if (!entry) return;
32922
33653
  entry.text = text;
33654
+ if (level) entry.level = level;
32923
33655
  this.emitter.emit("transcript-update", entry);
32924
33656
  }
32925
33657
  /** Subscribe to in-place entry updates (a `result` block growing — ADR-0108). */
@@ -36128,7 +36860,7 @@ var styles_default = styles2;
36128
36860
  // ../../node_modules/.pnpm/ink@5.2.1_@types+react@18.3.31_react@18.3.1/node_modules/ink/build/reconciler.js
36129
36861
  if (process3.env["DEV"] === "true") {
36130
36862
  try {
36131
- await import("./devtools-JZIQKAXX.js");
36863
+ await import("./devtools-ZL4ACXHE.js");
36132
36864
  } catch (error) {
36133
36865
  if (error.code === "ERR_MODULE_NOT_FOUND") {
36134
36866
  console.warn(`
@@ -38346,8 +39078,8 @@ var Ink = class {
38346
39078
  }
38347
39079
  }
38348
39080
  async waitUntilExit() {
38349
- this.exitPromise ||= new Promise((resolve7, reject) => {
38350
- this.resolveExitPromise = resolve7;
39081
+ this.exitPromise ||= new Promise((resolve8, reject) => {
39082
+ this.resolveExitPromise = resolve8;
38351
39083
  this.rejectExitPromise = reject;
38352
39084
  });
38353
39085
  return this.exitPromise;
@@ -38714,15 +39446,15 @@ var import_react21 = __toESM(require_react(), 1);
38714
39446
  var import_react26 = __toESM(require_react(), 1);
38715
39447
 
38716
39448
  // src/core/input-history.ts
38717
- import { existsSync as existsSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
38718
- import { join as join17 } from "path";
39449
+ import { existsSync as existsSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
39450
+ import { join as join21 } from "path";
38719
39451
  var MAX_HISTORY = 200;
38720
39452
  function historyPath(profileDir2) {
38721
- return join17(profileDir2, "input-history.json");
39453
+ return join21(profileDir2, "input-history.json");
38722
39454
  }
38723
39455
  function loadInputHistory(profileDir2) {
38724
39456
  const path = historyPath(profileDir2);
38725
- if (!existsSync10(path)) return [];
39457
+ if (!existsSync11(path)) return [];
38726
39458
  try {
38727
39459
  const data = JSON.parse(readFileSync16(path, "utf8"));
38728
39460
  return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];
@@ -38984,8 +39716,8 @@ function TranscriptLine({ line }) {
38984
39716
  var import_react24 = __toESM(require_react(), 1);
38985
39717
 
38986
39718
  // src/ui/slash-commands.ts
38987
- import { existsSync as existsSync11 } from "fs";
38988
- import { join as join18 } from "path";
39719
+ import { existsSync as existsSync12 } from "fs";
39720
+ import { join as join22 } from "path";
38989
39721
  var CONFIG_KEYS = [
38990
39722
  "aiCli",
38991
39723
  "claudeHome",
@@ -38997,7 +39729,7 @@ var CONFIG_KEYS = [
38997
39729
  "aiEnv"
38998
39730
  ];
38999
39731
  function configPath(profileDir2) {
39000
- return join18(profileDir2, "config.json");
39732
+ return join22(profileDir2, "config.json");
39001
39733
  }
39002
39734
  function parseConfigAssignment(key, valueTokens) {
39003
39735
  const value = valueTokens.join(" ").trim();
@@ -39047,7 +39779,7 @@ function runConfig(ctx) {
39047
39779
  ctx.print(`\u2714 ${verb} default config: ${path}`);
39048
39780
  ctx.print("add claude profiles: /config set claudeHome .claude .claude-1 (tried in order)");
39049
39781
  };
39050
- if (existsSync11(path)) {
39782
+ if (existsSync12(path)) {
39051
39783
  ctx.confirm(
39052
39784
  `config exists: ${path} \u2014 replace it with defaults? (y/N)`,
39053
39785
  () => writeDefaults("replaced with")
@@ -39132,7 +39864,7 @@ function runOutput(ctx) {
39132
39864
  }
39133
39865
  function runLogs(ctx) {
39134
39866
  const n = Math.max(1, Math.min(200, Number(ctx.args[0]) || 20));
39135
- const dir2 = join18(ctx.info.profileDir, "logs");
39867
+ const dir2 = join22(ctx.info.profileDir, "logs");
39136
39868
  const lines = readRecentLogLines(dir2, n);
39137
39869
  if (lines.length === 0) {
39138
39870
  ctx.print("No log lines yet.");
@@ -39633,7 +40365,7 @@ async function runStart(profileDir2, profileName) {
39633
40365
  return;
39634
40366
  }
39635
40367
  logger.configure({
39636
- dir: join19(profileDir2, "logs"),
40368
+ dir: join23(profileDir2, "logs"),
39637
40369
  level: resolveLogLevel(),
39638
40370
  base: { version: CLI_VERSION, profile: profileName, scope: credential.scope }
39639
40371
  });
@@ -39653,7 +40385,7 @@ async function runStart(profileDir2, profileName) {
39653
40385
  if (result.action === "updated") {
39654
40386
  logger.info("update.updated", { version: result.version });
39655
40387
  console.log("Restarting with the new version\u2026");
39656
- const child = spawn5(process.execPath, process.argv.slice(1), {
40388
+ const child = spawn6(process.execPath, process.argv.slice(1), {
39657
40389
  stdio: "inherit",
39658
40390
  env: { ...process.env, FOURPM_NO_UPDATE: "1" }
39659
40391
  });
@@ -39691,7 +40423,7 @@ async function runStart(profileDir2, profileName) {
39691
40423
  logger.error("ws.run.crash", { message: e.message, stack: e.stack });
39692
40424
  if (client.isStopped) return;
39693
40425
  bus.log(`Recovered from an internal error \u2014 reconnecting: ${e.message}`, "error");
39694
- await new Promise((resolve7) => setTimeout(resolve7, 1e3));
40426
+ await new Promise((resolve8) => setTimeout(resolve8, 1e3));
39695
40427
  }
39696
40428
  }
39697
40429
  };
@@ -39818,10 +40550,10 @@ function Picker({
39818
40550
  ] });
39819
40551
  }
39820
40552
  function selectFromList(items, title) {
39821
- return new Promise((resolve7) => {
40553
+ return new Promise((resolve8) => {
39822
40554
  let chosen = null;
39823
40555
  const app = render_default(/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Picker, { items, title, onDone: (v) => chosen = v }));
39824
- void app.waitUntilExit().then(() => resolve7(chosen));
40556
+ void app.waitUntilExit().then(() => resolve8(chosen));
39825
40557
  });
39826
40558
  }
39827
40559