@mtreeai/msapling-cli 2.3.6-beta.47 → 2.3.6-beta.49

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 +754 -122
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1032,6 +1032,47 @@ var init_src = __esm({
1032
1032
  body: JSON.stringify({ query, max_results: maxResults })
1033
1033
  });
1034
1034
  }
1035
+ /**
1036
+ * CLI-LOOP-01: cached backend capability map (from GET /chat/health). `null`
1037
+ * until first probed. Cached for the client's lifetime — capabilities are a
1038
+ * deploy-time property of the backend, so a single probe per session suffices.
1039
+ */
1040
+ _capabilities = null;
1041
+ _capabilitiesProbe = null;
1042
+ /**
1043
+ * CLI-LOOP-01: fetch (and cache) the backend capability map from
1044
+ * GET /chat/health. Returns an empty object when the backend is older / the
1045
+ * probe fails, so callers degrade to legacy behavior rather than throwing.
1046
+ *
1047
+ * Concurrent callers share a single in-flight probe.
1048
+ */
1049
+ async getCapabilities(force = false) {
1050
+ if (this._capabilities && !force) return this._capabilities;
1051
+ if (this._capabilitiesProbe && !force) return this._capabilitiesProbe;
1052
+ this._capabilitiesProbe = (async () => {
1053
+ try {
1054
+ const data = await this.request("/api/chat/health");
1055
+ const caps = data && typeof data === "object" && data.capabilities && typeof data.capabilities === "object" ? data.capabilities : {};
1056
+ this._capabilities = caps;
1057
+ return caps;
1058
+ } catch {
1059
+ this._capabilities = {};
1060
+ return this._capabilities;
1061
+ } finally {
1062
+ this._capabilitiesProbe = null;
1063
+ }
1064
+ })();
1065
+ return this._capabilitiesProbe;
1066
+ }
1067
+ /**
1068
+ * CLI-LOOP-01: convenience — does the backend accept the additive structured
1069
+ * `tool_results` array (native parallel multi-tool-use)? When false the Agent
1070
+ * loop uses the legacy `[TOOL_RESULT]`-per-prompt path.
1071
+ */
1072
+ async supportsStructuredToolResults() {
1073
+ const caps = await this.getCapabilities();
1074
+ return caps.structured_tool_results === true;
1075
+ }
1035
1076
  async getHistory(chatId) {
1036
1077
  const data = await this.request(`/api/projects/chat/${chatId}/history`);
1037
1078
  return data.messages.map((m) => ({
@@ -5356,17 +5397,17 @@ async function backupFile(absPath) {
5356
5397
  try {
5357
5398
  const { readFile: readFile30, writeFile: writeFile19, mkdir: mkdir10 } = await import("fs/promises");
5358
5399
  const { homedir: homedir25 } = await import("os");
5359
- const { join: join41 } = await import("path");
5400
+ const { join: join42 } = await import("path");
5360
5401
  const filename = absPath.split(/[\\/]/).pop() ?? "file";
5361
5402
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5362
5403
  const suffix = randomBytes7(4).toString("hex");
5363
- const backupPath = join41(
5404
+ const backupPath = join42(
5364
5405
  homedir25(),
5365
5406
  ".msapling",
5366
5407
  "backups",
5367
5408
  `${filename}.backup-${stamp}-${suffix}.bak`
5368
5409
  );
5369
- await mkdir10(join41(homedir25(), ".msapling", "backups"), { recursive: true });
5410
+ await mkdir10(join42(homedir25(), ".msapling", "backups"), { recursive: true });
5370
5411
  const content = await readFile30(absPath, "utf8");
5371
5412
  await writeFile19(backupPath, content, "utf8");
5372
5413
  return backupPath;
@@ -5976,10 +6017,10 @@ var init_Sandbox = __esm({
5976
6017
  * to a minimal platform default so subprocess spawn never sees an empty PATH.
5977
6018
  */
5978
6019
  static curatePath(rawPath) {
5979
- const sep4 = process.platform === "win32" ? ";" : ":";
5980
- const entries = (rawPath ?? "").split(sep4);
6020
+ const sep5 = process.platform === "win32" ? ";" : ":";
6021
+ const entries = (rawPath ?? "").split(sep5);
5981
6022
  const kept = entries.filter((e) => _Sandbox.isSafePathEntry(e));
5982
- if (kept.length > 0) return kept.join(sep4);
6023
+ if (kept.length > 0) return kept.join(sep5);
5983
6024
  return process.platform === "win32" ? "C:\\Windows\\System32;C:\\Windows" : "/usr/local/bin:/usr/bin:/bin";
5984
6025
  }
5985
6026
  getRestrictedEnv(pathOverride) {
@@ -6553,6 +6594,124 @@ var init_ResourceGovernor = __esm({
6553
6594
  }
6554
6595
  });
6555
6596
 
6597
+ // ../core/src/SessionStats.ts
6598
+ function getSessionStats() {
6599
+ if (!singleton) singleton = new SessionStats();
6600
+ return singleton;
6601
+ }
6602
+ function _resetSessionStatsSingleton() {
6603
+ singleton = null;
6604
+ }
6605
+ function fmtDuration(ms) {
6606
+ const s = Math.floor(ms / 1e3);
6607
+ const h = Math.floor(s / 3600);
6608
+ const m = Math.floor(s % 3600 / 60);
6609
+ const sec = s % 60;
6610
+ const parts = [];
6611
+ if (h) parts.push(`${h}h`);
6612
+ if (m || h) parts.push(`${m}m`);
6613
+ parts.push(`${sec}s`);
6614
+ return parts.join(" ");
6615
+ }
6616
+ function renderSessionStats(snap) {
6617
+ const lines = [];
6618
+ lines.push("=== Session Stats ===");
6619
+ lines.push(` Duration: ${fmtDuration(snap.wallMs)}`);
6620
+ const toolNames = Object.keys(snap.tools).sort();
6621
+ lines.push("");
6622
+ lines.push(` Tool calls: ${snap.totalToolCalls}`);
6623
+ if (toolNames.length === 0) {
6624
+ lines.push(" (no tools invoked yet)");
6625
+ } else {
6626
+ for (const name of toolNames) {
6627
+ const t = snap.tools[name];
6628
+ const avg = t.count > 0 ? t.totalMs / t.count : 0;
6629
+ const errStr = t.errors > 0 ? `, ${t.errors} err` : "";
6630
+ lines.push(
6631
+ ` - ${name}: ${t.count}\xD7 (avg ${avg.toFixed(0)}ms, total ${t.totalMs.toFixed(0)}ms${errStr})`
6632
+ );
6633
+ }
6634
+ }
6635
+ const a = snap.approvals;
6636
+ if (a.approved || a.denied || a.autoApproved) {
6637
+ lines.push("");
6638
+ lines.push(" Approvals:");
6639
+ lines.push(` - approved: ${a.approved}`);
6640
+ lines.push(` - denied: ${a.denied}`);
6641
+ lines.push(` - auto (trusted):${a.autoApproved}`);
6642
+ }
6643
+ const u = snap.usage;
6644
+ const totalTokens = u.promptTokens + u.completionTokens;
6645
+ lines.push("");
6646
+ lines.push(" Usage:");
6647
+ lines.push(` - turns: ${u.turns}`);
6648
+ lines.push(` - prompt tokens: ${u.promptTokens.toLocaleString()}`);
6649
+ lines.push(` - output tokens: ${u.completionTokens.toLocaleString()}`);
6650
+ lines.push(` - total tokens: ${totalTokens.toLocaleString()}`);
6651
+ lines.push(` - cost (USD): $${u.costUsd.toFixed(4)}`);
6652
+ if (u.turns > 0) {
6653
+ lines.push(` - avg cost/turn: $${(u.costUsd / u.turns).toFixed(4)}`);
6654
+ }
6655
+ return lines;
6656
+ }
6657
+ var SessionStats, singleton;
6658
+ var init_SessionStats = __esm({
6659
+ "../core/src/SessionStats.ts"() {
6660
+ "use strict";
6661
+ init_esm_shims();
6662
+ SessionStats = class {
6663
+ startedAt = Date.now();
6664
+ tools = /* @__PURE__ */ new Map();
6665
+ usage = { costUsd: 0, promptTokens: 0, completionTokens: 0, turns: 0 };
6666
+ approvals = { approved: 0, denied: 0, autoApproved: 0 };
6667
+ /** Record a single tool invocation's outcome + latency. */
6668
+ recordTool(name, latencyMs, isError) {
6669
+ const cur = this.tools.get(name) ?? { count: 0, errors: 0, totalMs: 0 };
6670
+ cur.count += 1;
6671
+ if (isError) cur.errors += 1;
6672
+ cur.totalMs += Math.max(0, latencyMs);
6673
+ this.tools.set(name, cur);
6674
+ }
6675
+ /** Record an approval-gate outcome for a tool call. */
6676
+ recordApproval(kind) {
6677
+ this.approvals[kind] += 1;
6678
+ }
6679
+ /** Record backend usage from a completed agent turn. */
6680
+ recordUsage(costUsd, promptTokens, completionTokens) {
6681
+ if (Number.isFinite(costUsd)) this.usage.costUsd += costUsd;
6682
+ if (Number.isFinite(promptTokens)) this.usage.promptTokens += promptTokens;
6683
+ if (Number.isFinite(completionTokens)) this.usage.completionTokens += completionTokens;
6684
+ this.usage.turns += 1;
6685
+ }
6686
+ /** Immutable snapshot for rendering. */
6687
+ snapshot() {
6688
+ const tools = {};
6689
+ let totalToolCalls = 0;
6690
+ for (const [name, s] of this.tools) {
6691
+ tools[name] = { ...s };
6692
+ totalToolCalls += s.count;
6693
+ }
6694
+ return {
6695
+ startedAt: this.startedAt,
6696
+ wallMs: Date.now() - this.startedAt,
6697
+ tools,
6698
+ totalToolCalls,
6699
+ usage: { ...this.usage },
6700
+ approvals: { ...this.approvals }
6701
+ };
6702
+ }
6703
+ /** Reset everything (used by tests and a potential /stats reset). */
6704
+ reset() {
6705
+ this.startedAt = Date.now();
6706
+ this.tools.clear();
6707
+ this.usage = { costUsd: 0, promptTokens: 0, completionTokens: 0, turns: 0 };
6708
+ this.approvals = { approved: 0, denied: 0, autoApproved: 0 };
6709
+ }
6710
+ };
6711
+ singleton = null;
6712
+ }
6713
+ });
6714
+
6556
6715
  // ../core/src/agent/ToolExecutor.ts
6557
6716
  import { readFile as readFile10 } from "fs/promises";
6558
6717
  var APPROVAL_GATED, ToolExecutor;
@@ -6587,6 +6746,7 @@ var init_ToolExecutor = __esm({
6587
6746
  init_ShadowService();
6588
6747
  init_Hooks();
6589
6748
  init_ResourceGovernor();
6749
+ init_SessionStats();
6590
6750
  APPROVAL_GATED = /* @__PURE__ */ new Set(["run_command", "bash_command", "bash_background", "edit_file", "write_file", "patch_file", "multi_edit_file", "notebook_edit_cell", "move_file", "delete_file", "open_sub_shell"]);
6591
6751
  ToolExecutor = class {
6592
6752
  tools = /* @__PURE__ */ new Map();
@@ -6900,8 +7060,10 @@ ${blocker.stderr || "(empty)"}`,
6900
7060
  reason: `${toolName} requested by agent in '${this.mode}' mode`
6901
7061
  });
6902
7062
  if (decision === "no") {
7063
+ getSessionStats().recordApproval("denied");
6903
7064
  return { content: `User denied ${toolName} approval.`, isError: true };
6904
7065
  }
7066
+ getSessionStats().recordApproval("approved");
6905
7067
  if (decision === "always") {
6906
7068
  if (this.trustStore) {
6907
7069
  this.trustStore.add(cmdKey).catch(() => {
@@ -6910,6 +7072,8 @@ ${blocker.stderr || "(empty)"}`,
6910
7072
  this.sessionTrust.add(cmdKey);
6911
7073
  }
6912
7074
  }
7075
+ } else {
7076
+ getSessionStats().recordApproval("autoApproved");
6913
7077
  }
6914
7078
  }
6915
7079
  if (toolName === "run_command" || toolName === "bash_command" || toolName === "bash_background" || toolName === "edit_file" || toolName === "write_file" || toolName === "patch_file" || toolName === "multi_edit_file" || toolName === "notebook_edit_cell" || toolName === "move_file" || toolName === "delete_file") {
@@ -6941,10 +7105,20 @@ Please approve the diff in the UI to sync this change locally.`
6941
7105
  const governor = await getGlobalGovernor();
6942
7106
  await governor.acquireTool();
6943
7107
  let result;
7108
+ const startedAt = Date.now();
7109
+ let threw = false;
6944
7110
  try {
6945
7111
  result = await tool.execute(args2, projectRoot);
7112
+ } catch (e) {
7113
+ threw = true;
7114
+ throw e;
6946
7115
  } finally {
6947
7116
  governor.releaseTool();
7117
+ getSessionStats().recordTool(
7118
+ toolName,
7119
+ Date.now() - startedAt,
7120
+ threw || !!(result && result.isError)
7121
+ );
6948
7122
  }
6949
7123
  if (this.hooks) {
6950
7124
  this.hooks.fire({
@@ -7153,13 +7327,14 @@ async function loadProjectConfig(cwd = process.cwd()) {
7153
7327
  const [user, project] = await Promise.all([findUserConfig(), findProjectConfig(cwd)]);
7154
7328
  return { user, project, combined: buildCombined(user, project) };
7155
7329
  }
7156
- var FILENAMES, TRUNCATE_AT;
7330
+ var FILENAMES, TRUNCATE_AT, PROJECT_CONFIG_FILENAMES;
7157
7331
  var init_ProjectConfig = __esm({
7158
7332
  "../core/src/ProjectConfig.ts"() {
7159
7333
  "use strict";
7160
7334
  init_esm_shims();
7161
7335
  FILENAMES = ["MSAPLING.md", "CLAUDE.md", "GEMINI.md", "AGENTS.md"];
7162
7336
  TRUNCATE_AT = 32e3;
7337
+ PROJECT_CONFIG_FILENAMES = FILENAMES;
7163
7338
  }
7164
7339
  });
7165
7340
 
@@ -7407,8 +7582,15 @@ var init_Agent = __esm({
7407
7582
  }
7408
7583
  const config = await this.getProjectConfig();
7409
7584
  const MAX_WORKER_TURN_DEPTH = 25;
7585
+ let structuredToolResults = false;
7586
+ try {
7587
+ structuredToolResults = await this.client.supportsStructuredToolResults();
7588
+ } catch {
7589
+ structuredToolResults = false;
7590
+ }
7410
7591
  const queue = [prompt4];
7411
7592
  let rounds = 0;
7593
+ let toolCallSeq = 0;
7412
7594
  let streamUsage = null;
7413
7595
  while (queue.length > 0 && rounds < MAX_WORKER_TURN_DEPTH) {
7414
7596
  if (this.contextBudget.needsCompaction() && queue.length > 0) {
@@ -7452,14 +7634,25 @@ var init_Agent = __esm({
7452
7634
  this.contextBudget.reset();
7453
7635
  if (compactionSummary) {
7454
7636
  const next = queue[0];
7455
- queue[0] = `[CONTEXT_SUMMARY]: ${compactionSummary}
7637
+ if (typeof next === "string") {
7638
+ queue[0] = `[CONTEXT_SUMMARY]: ${compactionSummary}
7456
7639
 
7457
7640
  ${next}`;
7641
+ } else {
7642
+ queue[0] = {
7643
+ ...next,
7644
+ prompt: `[CONTEXT_SUMMARY]: ${compactionSummary}
7645
+
7646
+ ${next.prompt}`
7647
+ };
7648
+ }
7458
7649
  }
7459
7650
  continue;
7460
7651
  }
7461
- const currentPrompt = queue.shift();
7652
+ const currentItem = queue.shift();
7462
7653
  rounds++;
7654
+ const currentPrompt = typeof currentItem === "string" ? currentItem : currentItem.prompt;
7655
+ const currentToolResults = typeof currentItem === "string" ? void 0 : currentItem.toolResults;
7463
7656
  const stream = this.chatWithFallback(
7464
7657
  {
7465
7658
  chat_id: chatId,
@@ -7468,11 +7661,16 @@ ${next}`;
7468
7661
  tools: this.executor.getToolSchemas(),
7469
7662
  project_root: this.projectRoot,
7470
7663
  mode: this.executor.getMode(),
7664
+ // CLI-LOOP-01: carry the concurrently-executed tool_results from the
7665
+ // previous turn as a structured batch. Only set in the capability-
7666
+ // enabled path; absent => backend sees the legacy single-prompt turn.
7667
+ ...currentToolResults && currentToolResults.length > 0 ? { tool_results: currentToolResults } : {},
7471
7668
  ...config.combined ? { project_context: config.combined } : {}
7472
7669
  },
7473
7670
  chatId,
7474
7671
  currentPrompt
7475
7672
  );
7673
+ const pendingToolCalls = [];
7476
7674
  for await (const chunk of stream) {
7477
7675
  if (chunk.content) {
7478
7676
  onContent(chunk.content);
@@ -7489,11 +7687,39 @@ ${next}`;
7489
7687
  });
7490
7688
  }
7491
7689
  if (chunk.tool_use) {
7492
- const result = await this.executor.execute(
7493
- chunk.tool_use.name,
7494
- chunk.tool_use.args,
7495
- this.projectRoot
7496
- );
7690
+ const id = chunk.tool_use.id ?? `call_${rounds}_${toolCallSeq++}`;
7691
+ pendingToolCalls.push({ id, name: chunk.tool_use.name, args: chunk.tool_use.args });
7692
+ }
7693
+ }
7694
+ if (pendingToolCalls.length === 0) {
7695
+ continue;
7696
+ }
7697
+ if (structuredToolResults) {
7698
+ const toolResults = await Promise.all(
7699
+ pendingToolCalls.map(async (call) => {
7700
+ try {
7701
+ const result = await this.executor.execute(call.name, call.args, this.projectRoot);
7702
+ const redactedContent = SafetyGuard.redact(result.content);
7703
+ return {
7704
+ tool_use_id: call.id,
7705
+ name: call.name,
7706
+ content: redactedContent,
7707
+ is_error: !!result.isError
7708
+ };
7709
+ } catch (e) {
7710
+ return {
7711
+ tool_use_id: call.id,
7712
+ name: call.name,
7713
+ content: SafetyGuard.redact(`Tool execution failed: ${e?.message ?? String(e)}`),
7714
+ is_error: true
7715
+ };
7716
+ }
7717
+ })
7718
+ );
7719
+ queue.push({ prompt: "", toolResults });
7720
+ } else {
7721
+ for (const call of pendingToolCalls) {
7722
+ const result = await this.executor.execute(call.name, call.args, this.projectRoot);
7497
7723
  const redactedContent = SafetyGuard.redact(result.content);
7498
7724
  const redactedResult = { ...result, content: redactedContent };
7499
7725
  queue.push(`[TOOL_RESULT]: ${JSON.stringify(redactedResult)}`);
@@ -9125,8 +9351,10 @@ __export(src_exports2, {
9125
9351
  MultiEditFileTool: () => MultiEditFileTool,
9126
9352
  NotebookEditTool: () => NotebookEditTool,
9127
9353
  NotebookReadTool: () => NotebookReadTool,
9354
+ PROJECT_CONFIG_FILENAMES: () => PROJECT_CONFIG_FILENAMES,
9128
9355
  PatchFileTool: () => PatchFileTool,
9129
9356
  ReadBackgroundShellTool: () => ReadBackgroundShellTool,
9357
+ SessionStats: () => SessionStats,
9130
9358
  StorageManager: () => StorageManager,
9131
9359
  SwarmManager: () => SwarmManager,
9132
9360
  TOOL_NAME_ALIASES: () => TOOL_NAME_ALIASES,
@@ -9138,6 +9366,7 @@ __export(src_exports2, {
9138
9366
  WebFetchTool: () => WebFetchTool,
9139
9367
  WebSearchTool: () => WebSearchTool,
9140
9368
  WriteFileTool: () => WriteFileTool,
9369
+ _resetSessionStatsSingleton: () => _resetSessionStatsSingleton,
9141
9370
  _setBackupDirOverride: () => _setBackupDirOverride,
9142
9371
  backupDir: () => backupDir,
9143
9372
  buildCompactionPrompt: () => buildCompactionPrompt,
@@ -9151,6 +9380,7 @@ __export(src_exports2, {
9151
9380
  formatNotebookHeader: () => formatNotebookHeader,
9152
9381
  formatTodos: () => formatTodos,
9153
9382
  getOrCreateJournalKey: () => getOrCreateJournalKey,
9383
+ getSessionStats: () => getSessionStats,
9154
9384
  initJournalEncryption: () => initJournalEncryption,
9155
9385
  listCheckpoints: () => listCheckpoints,
9156
9386
  loadNamedAgents: () => loadNamedAgents,
@@ -9162,6 +9392,7 @@ __export(src_exports2, {
9162
9392
  parseAgentFile: () => parseAgentFile,
9163
9393
  parseToolsValue: () => parseToolsValue,
9164
9394
  recordBackup: () => recordBackup,
9395
+ renderSessionStats: () => renderSessionStats,
9165
9396
  restoreCheckpoint: () => restoreCheckpoint,
9166
9397
  takeSnapshot: () => takeSnapshot
9167
9398
  });
@@ -9180,6 +9411,7 @@ var init_src3 = __esm({
9180
9411
  init_Storage();
9181
9412
  init_journalCrypto();
9182
9413
  init_Mutex();
9414
+ init_SessionStats();
9183
9415
  init_ProjectConfig();
9184
9416
  init_Settings();
9185
9417
  init_client();
@@ -11255,40 +11487,405 @@ var init_compact = __esm({
11255
11487
  }
11256
11488
  });
11257
11489
 
11490
+ // src/runtime/codebaseScan.ts
11491
+ import { existsSync as existsSync23, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync7 } from "fs";
11492
+ import { join as join27, relative as relative16, sep as sep4, basename as basename3 } from "path";
11493
+ function parseGitignore(content) {
11494
+ const patterns = content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("!"));
11495
+ const matchers = patterns.map((raw) => {
11496
+ let p = raw;
11497
+ const dirOnly = p.endsWith("/");
11498
+ if (dirOnly) p = p.slice(0, -1);
11499
+ const anchored = p.startsWith("/");
11500
+ if (anchored) p = p.slice(1);
11501
+ const re = new RegExp(
11502
+ "^" + p.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(//g, ".*").replace(/\?/g, "[^/]") + "$"
11503
+ );
11504
+ return { re, dirOnly, anchored, base: p };
11505
+ });
11506
+ return (relPath, isDir) => {
11507
+ const posix = relPath.split(sep4).join("/");
11508
+ for (const m of matchers) {
11509
+ if (m.dirOnly && !isDir) continue;
11510
+ if (m.anchored) {
11511
+ if (m.re.test(posix)) return true;
11512
+ } else {
11513
+ if (m.re.test(posix)) return true;
11514
+ if (m.re.test(basename3(posix))) return true;
11515
+ const segs = posix.split("/");
11516
+ for (let i = 0; i < segs.length; i++) {
11517
+ if (m.re.test(segs[i])) return true;
11518
+ }
11519
+ }
11520
+ }
11521
+ return false;
11522
+ };
11523
+ }
11524
+ function loadIgnore(root) {
11525
+ try {
11526
+ const gi = join27(root, ".gitignore");
11527
+ if (existsSync23(gi)) {
11528
+ return parseGitignore(readFileSync4(gi, "utf8"));
11529
+ }
11530
+ } catch {
11531
+ }
11532
+ return () => false;
11533
+ }
11534
+ function readJsonSafe(path2) {
11535
+ try {
11536
+ return JSON.parse(readFileSync4(path2, "utf8"));
11537
+ } catch {
11538
+ return null;
11539
+ }
11540
+ }
11541
+ function detectStacks(root) {
11542
+ const found = [];
11543
+ const seen = /* @__PURE__ */ new Set();
11544
+ for (const probe of STACK_PROBES) {
11545
+ const hit = probe.detect(root);
11546
+ if (hit && !seen.has(hit.name)) {
11547
+ seen.add(hit.name);
11548
+ found.push(hit);
11549
+ }
11550
+ }
11551
+ return found;
11552
+ }
11553
+ function inferCommands(root, stacks) {
11554
+ const cmds = { extra: {} };
11555
+ for (const stack of stacks) {
11556
+ if (stack.manifest === "package.json") {
11557
+ const pkg = readJsonSafe(join27(root, "package.json"));
11558
+ const scripts = pkg?.scripts ?? {};
11559
+ const pm = stack.packageManager ?? "npm";
11560
+ const runScript = (name) => pm === "npm" ? `npm run ${name}` : `${pm} run ${name}`;
11561
+ if (scripts.build) cmds.build = runScript("build");
11562
+ if (scripts.test) cmds.test = pm === "npm" ? "npm test" : `${pm} test`;
11563
+ if (scripts.start) cmds.run = pm === "npm" ? "npm start" : `${pm} start`;
11564
+ else if (scripts.dev) cmds.run = runScript("dev");
11565
+ if (scripts.lint) cmds.lint = runScript("lint");
11566
+ for (const name of ["typecheck", "format", "e2e", "coverage"]) {
11567
+ if (scripts[name]) cmds.extra[name] = runScript(name);
11568
+ }
11569
+ } else if (stack.name === "Python") {
11570
+ cmds.test = cmds.test ?? "pytest";
11571
+ if (stack.packageManager === "poetry") cmds.run = cmds.run ?? "poetry run python -m <module>";
11572
+ } else if (stack.name === "Go") {
11573
+ cmds.build = cmds.build ?? "go build ./...";
11574
+ cmds.test = cmds.test ?? "go test ./...";
11575
+ cmds.run = cmds.run ?? "go run .";
11576
+ } else if (stack.name === "Rust") {
11577
+ cmds.build = cmds.build ?? "cargo build";
11578
+ cmds.test = cmds.test ?? "cargo test";
11579
+ cmds.run = cmds.run ?? "cargo run";
11580
+ } else if (stack.name === "Java / Maven") {
11581
+ cmds.build = cmds.build ?? "mvn package";
11582
+ cmds.test = cmds.test ?? "mvn test";
11583
+ } else if (stack.name === "Java / Kotlin (Gradle)") {
11584
+ cmds.build = cmds.build ?? "gradle build";
11585
+ cmds.test = cmds.test ?? "gradle test";
11586
+ } else if (stack.name === "Ruby") {
11587
+ cmds.test = cmds.test ?? "bundle exec rspec";
11588
+ } else if (stack.name === "PHP") {
11589
+ cmds.test = cmds.test ?? "composer test";
11590
+ }
11591
+ }
11592
+ return cmds;
11593
+ }
11594
+ function detectEntryPoints(root, stacks) {
11595
+ const out = [];
11596
+ for (const stack of stacks) {
11597
+ if (stack.manifest === "package.json") {
11598
+ const pkg = readJsonSafe(join27(root, "package.json"));
11599
+ if (pkg?.main && existsSync23(join27(root, pkg.main))) out.push(pkg.main);
11600
+ if (typeof pkg?.bin === "string" && existsSync23(join27(root, pkg.bin))) out.push(pkg.bin);
11601
+ else if (pkg?.bin && typeof pkg.bin === "object") {
11602
+ for (const v of Object.values(pkg.bin)) {
11603
+ if (typeof v === "string" && existsSync23(join27(root, v))) out.push(v);
11604
+ }
11605
+ }
11606
+ }
11607
+ }
11608
+ for (const c of ENTRY_CANDIDATES) {
11609
+ if (existsSync23(join27(root, c)) && !out.includes(c)) out.push(c);
11610
+ }
11611
+ return out.slice(0, 8);
11612
+ }
11613
+ function scanCodebase(root, opts = {}) {
11614
+ const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES;
11615
+ const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
11616
+ const ignored = loadIgnore(root);
11617
+ let fileCount = 0;
11618
+ let dirCount = 0;
11619
+ let truncated = false;
11620
+ const keyDirs = /* @__PURE__ */ new Set();
11621
+ const walk = (dir, depth) => {
11622
+ if (truncated || depth > maxDepth) return;
11623
+ let entries;
11624
+ try {
11625
+ entries = readdirSync3(dir);
11626
+ } catch {
11627
+ return;
11628
+ }
11629
+ for (const name of entries) {
11630
+ if (fileCount >= maxFiles) {
11631
+ truncated = true;
11632
+ return;
11633
+ }
11634
+ const abs = join27(dir, name);
11635
+ let isDir = false;
11636
+ try {
11637
+ isDir = statSync7(abs).isDirectory();
11638
+ } catch {
11639
+ continue;
11640
+ }
11641
+ const rel = relative16(root, abs);
11642
+ if (isDir) {
11643
+ if (ALWAYS_IGNORE_DIRS.has(name)) continue;
11644
+ if (name.startsWith(".") && depth >= 1) continue;
11645
+ if (ignored(rel, true)) continue;
11646
+ dirCount++;
11647
+ if (depth === 0) keyDirs.add(rel.split(sep4).join("/"));
11648
+ walk(abs, depth + 1);
11649
+ } else {
11650
+ if (ignored(rel, false)) continue;
11651
+ fileCount++;
11652
+ }
11653
+ }
11654
+ };
11655
+ walk(root, 0);
11656
+ const stacks = detectStacks(root);
11657
+ const commands2 = inferCommands(root, stacks);
11658
+ const entryPoints = detectEntryPoints(root, stacks);
11659
+ return {
11660
+ root,
11661
+ stacks,
11662
+ keyDirs: Array.from(keyDirs).sort(),
11663
+ entryPoints,
11664
+ commands: commands2,
11665
+ fileCount,
11666
+ dirCount,
11667
+ truncated
11668
+ };
11669
+ }
11670
+ function renderMemoryFile(scan, title = "Project Memory") {
11671
+ const lines = [];
11672
+ lines.push(`# ${title}`);
11673
+ lines.push("");
11674
+ lines.push(
11675
+ "> Auto-generated by `/init` (deep codebase scan). Edit freely \u2014 re-running `/init` refreshes the scanned sections but preserves this file if you have customized it."
11676
+ );
11677
+ lines.push("");
11678
+ lines.push("## Stack");
11679
+ if (scan.stacks.length === 0) {
11680
+ lines.push("- No recognized manifest found (no package.json / pyproject.toml / go.mod / Cargo.toml / etc.).");
11681
+ } else {
11682
+ for (const s of scan.stacks) {
11683
+ const pm = s.packageManager ? ` (${s.packageManager})` : "";
11684
+ lines.push(`- **${s.name}**${pm} \u2014 detected via \`${s.manifest}\``);
11685
+ }
11686
+ }
11687
+ lines.push("");
11688
+ lines.push("## Commands");
11689
+ const c = scan.commands;
11690
+ const cmdRows = [
11691
+ ["Build", c.build],
11692
+ ["Test", c.test],
11693
+ ["Run", c.run],
11694
+ ["Lint", c.lint]
11695
+ ];
11696
+ const anyCmd = cmdRows.some(([, v]) => v) || Object.keys(c.extra).length > 0;
11697
+ if (!anyCmd) {
11698
+ lines.push("- No build/test/run commands could be inferred from the manifests.");
11699
+ } else {
11700
+ for (const [label, val] of cmdRows) {
11701
+ if (val) lines.push(`- **${label}:** \`${val}\``);
11702
+ }
11703
+ for (const [name, val] of Object.entries(c.extra)) {
11704
+ lines.push(`- **${name}:** \`${val}\``);
11705
+ }
11706
+ }
11707
+ lines.push("");
11708
+ lines.push("## Structure");
11709
+ lines.push(
11710
+ `- ${scan.dirCount} directories, ${scan.fileCount} files scanned${scan.truncated ? " (scan capped \u2014 large repo)" : ""}.`
11711
+ );
11712
+ if (scan.keyDirs.length > 0) {
11713
+ lines.push("- Key directories:");
11714
+ for (const d of scan.keyDirs) lines.push(` - \`${d}/\``);
11715
+ }
11716
+ lines.push("");
11717
+ lines.push("## Entry points");
11718
+ if (scan.entryPoints.length === 0) {
11719
+ lines.push("- No obvious entry point detected.");
11720
+ } else {
11721
+ for (const e of scan.entryPoints) lines.push(`- \`${e}\``);
11722
+ }
11723
+ lines.push("");
11724
+ lines.push("## Guidelines");
11725
+ lines.push("- Follow the existing code style and conventions in this repository.");
11726
+ lines.push("- Run the test command above before considering a change complete.");
11727
+ lines.push("");
11728
+ return lines.join("\n");
11729
+ }
11730
+ var DEFAULT_MAX_FILES, DEFAULT_MAX_DEPTH, ALWAYS_IGNORE_DIRS, STACK_PROBES, ENTRY_CANDIDATES;
11731
+ var init_codebaseScan = __esm({
11732
+ "src/runtime/codebaseScan.ts"() {
11733
+ "use strict";
11734
+ init_esm_shims();
11735
+ DEFAULT_MAX_FILES = 5e3;
11736
+ DEFAULT_MAX_DEPTH = 6;
11737
+ ALWAYS_IGNORE_DIRS = /* @__PURE__ */ new Set([
11738
+ "node_modules",
11739
+ ".git",
11740
+ ".hg",
11741
+ ".svn",
11742
+ "dist",
11743
+ "build",
11744
+ "out",
11745
+ "target",
11746
+ ".next",
11747
+ ".nuxt",
11748
+ ".cache",
11749
+ "coverage",
11750
+ ".venv",
11751
+ "venv",
11752
+ "__pycache__",
11753
+ ".idea",
11754
+ ".vscode",
11755
+ "vendor",
11756
+ ".bun",
11757
+ ".turbo"
11758
+ ]);
11759
+ STACK_PROBES = [
11760
+ {
11761
+ manifest: "package.json",
11762
+ detect: (root) => {
11763
+ const path2 = join27(root, "package.json");
11764
+ if (!existsSync23(path2)) return null;
11765
+ const pkg = readJsonSafe(path2);
11766
+ let pm = "npm";
11767
+ if (existsSync23(join27(root, "bun.lock")) || existsSync23(join27(root, "bun.lockb"))) pm = "bun";
11768
+ else if (existsSync23(join27(root, "pnpm-lock.yaml"))) pm = "pnpm";
11769
+ else if (existsSync23(join27(root, "yarn.lock"))) pm = "yarn";
11770
+ const hasTs = existsSync23(join27(root, "tsconfig.json")) || !!(pkg?.devDependencies?.typescript || pkg?.dependencies?.typescript);
11771
+ return {
11772
+ name: hasTs ? "Node.js / TypeScript" : "Node.js / JavaScript",
11773
+ manifest: "package.json",
11774
+ packageManager: pm
11775
+ };
11776
+ }
11777
+ },
11778
+ {
11779
+ manifest: "pyproject.toml",
11780
+ detect: (root) => {
11781
+ if (!existsSync23(join27(root, "pyproject.toml"))) return null;
11782
+ let pm = "pip";
11783
+ if (existsSync23(join27(root, "poetry.lock"))) pm = "poetry";
11784
+ else if (existsSync23(join27(root, "uv.lock"))) pm = "uv";
11785
+ return { name: "Python", manifest: "pyproject.toml", packageManager: pm };
11786
+ }
11787
+ },
11788
+ {
11789
+ manifest: "requirements.txt",
11790
+ detect: (root) => {
11791
+ if (!existsSync23(join27(root, "requirements.txt"))) return null;
11792
+ return { name: "Python", manifest: "requirements.txt", packageManager: "pip" };
11793
+ }
11794
+ },
11795
+ {
11796
+ manifest: "go.mod",
11797
+ detect: (root) => existsSync23(join27(root, "go.mod")) ? { name: "Go", manifest: "go.mod", packageManager: "go" } : null
11798
+ },
11799
+ {
11800
+ manifest: "Cargo.toml",
11801
+ detect: (root) => existsSync23(join27(root, "Cargo.toml")) ? { name: "Rust", manifest: "Cargo.toml", packageManager: "cargo" } : null
11802
+ },
11803
+ {
11804
+ manifest: "pom.xml",
11805
+ detect: (root) => existsSync23(join27(root, "pom.xml")) ? { name: "Java / Maven", manifest: "pom.xml", packageManager: "maven" } : null
11806
+ },
11807
+ {
11808
+ manifest: "build.gradle",
11809
+ detect: (root) => existsSync23(join27(root, "build.gradle")) || existsSync23(join27(root, "build.gradle.kts")) ? { name: "Java / Kotlin (Gradle)", manifest: "build.gradle", packageManager: "gradle" } : null
11810
+ },
11811
+ {
11812
+ manifest: "Gemfile",
11813
+ detect: (root) => existsSync23(join27(root, "Gemfile")) ? { name: "Ruby", manifest: "Gemfile", packageManager: "bundler" } : null
11814
+ },
11815
+ {
11816
+ manifest: "composer.json",
11817
+ detect: (root) => existsSync23(join27(root, "composer.json")) ? { name: "PHP", manifest: "composer.json", packageManager: "composer" } : null
11818
+ }
11819
+ ];
11820
+ ENTRY_CANDIDATES = [
11821
+ "src/index.ts",
11822
+ "src/index.tsx",
11823
+ "src/index.js",
11824
+ "src/main.ts",
11825
+ "src/main.tsx",
11826
+ "src/main.py",
11827
+ "src/main.rs",
11828
+ "index.ts",
11829
+ "index.js",
11830
+ "main.py",
11831
+ "main.go",
11832
+ "app.py",
11833
+ "manage.py",
11834
+ "cmd/main.go",
11835
+ "src/main/java"
11836
+ ];
11837
+ }
11838
+ });
11839
+
11258
11840
  // src/commands/init.ts
11259
- import { join as join27 } from "path";
11260
- import { existsSync as existsSync23 } from "fs";
11841
+ import { join as join28 } from "path";
11842
+ import { existsSync as existsSync24 } from "fs";
11261
11843
  import { writeFile as writeFile13 } from "fs/promises";
11844
+ function resolveTargetFile(cwd) {
11845
+ for (const filename2 of PROJECT_CONFIG_FILENAMES) {
11846
+ const path2 = join28(cwd, filename2);
11847
+ if (existsSync24(path2)) return { path: path2, filename: filename2, existed: true };
11848
+ }
11849
+ const filename = PROJECT_CONFIG_FILENAMES[0];
11850
+ return { path: join28(cwd, filename), filename, existed: false };
11851
+ }
11262
11852
  var initCommand;
11263
11853
  var init_init = __esm({
11264
11854
  "src/commands/init.ts"() {
11265
11855
  "use strict";
11266
11856
  init_esm_shims();
11857
+ init_src3();
11858
+ init_codebaseScan();
11267
11859
  initCommand = {
11268
11860
  name: "init",
11269
- description: "Create MSAPLING.md (or CLAUDE.md-compat) in cwd with project-detected boilerplate",
11861
+ args: "[--force]",
11862
+ description: "Scan the codebase (stack, commands, structure, entry points) and write/refresh a project-memory file (MSAPLING.md/CLAUDE.md/\u2026)",
11270
11863
  category: "project",
11271
11864
  handler: async (args2, context) => {
11272
11865
  try {
11273
11866
  const cwd = process.cwd();
11274
- const path2 = join27(cwd, "MSAPLING.md");
11275
- if (existsSync23(path2)) {
11276
- context.addMessage("error", "MSAPLING.md already exists in current directory.");
11867
+ const force = args2.includes("--force");
11868
+ const target = resolveTargetFile(cwd);
11869
+ if (target.existed && !force) {
11870
+ context.addMessage(
11871
+ "error",
11872
+ `${target.filename} already exists. Re-run \`/init --force\` to refresh it from a fresh codebase scan.`
11873
+ );
11277
11874
  return;
11278
11875
  }
11279
- const content = `# MSapling Project Configuration
11280
-
11281
- ## Build
11282
- - \`npm run build\` or equivalent.
11283
-
11284
- ## Test
11285
- - \`npm test\` or equivalent.
11286
-
11287
- ## Guidelines
11288
- - Follow existing code style.
11289
- `;
11290
- await writeFile13(path2, content, "utf8");
11291
- context.addMessage("system", `Created MSAPLING.md at ${path2}`);
11876
+ context.addMessage("system", `Scanning codebase at ${cwd} ...`);
11877
+ const scan = scanCodebase(cwd);
11878
+ const title = target.filename === "MSAPLING.md" ? "MSapling Project Memory" : "Project Memory";
11879
+ const content = renderMemoryFile(scan, title);
11880
+ await writeFile13(target.path, content, "utf8");
11881
+ const stackSummary = scan.stacks.length > 0 ? scan.stacks.map((s) => s.name).join(", ") : "unknown stack";
11882
+ context.addMessage(
11883
+ "system",
11884
+ `${target.existed ? "Refreshed" : "Created"} ${target.filename} at ${target.path}
11885
+ Stack: ${stackSummary}
11886
+ Scanned: ${scan.dirCount} dirs, ${scan.fileCount} files${scan.truncated ? " (capped)" : ""}
11887
+ Entry points: ${scan.entryPoints.length > 0 ? scan.entryPoints.join(", ") : "none detected"}`
11888
+ );
11292
11889
  } catch (e) {
11293
11890
  context.addMessage("error", `Failed to initialize project: ${e.message}`);
11294
11891
  }
@@ -11298,7 +11895,7 @@ var init_init = __esm({
11298
11895
  });
11299
11896
 
11300
11897
  // src/commands/review.ts
11301
- import { existsSync as existsSync24 } from "fs";
11898
+ import { existsSync as existsSync25 } from "fs";
11302
11899
  import { readFile as readFile21 } from "fs/promises";
11303
11900
  var reviewCommand;
11304
11901
  var init_review = __esm({
@@ -11318,7 +11915,7 @@ var init_review = __esm({
11318
11915
  }
11319
11916
  let content = "";
11320
11917
  try {
11321
- if (existsSync24(target)) {
11918
+ if (existsSync25(target)) {
11322
11919
  content = await readFile21(target, "utf8");
11323
11920
  } else {
11324
11921
  content = `Review target: ${target}`;
@@ -11412,15 +12009,15 @@ var init_swarm = __esm({
11412
12009
 
11413
12010
  // src/commands/recipe.ts
11414
12011
  import { parse as parseYaml } from "yaml";
11415
- import { existsSync as existsSync25 } from "fs";
12012
+ import { existsSync as existsSync26 } from "fs";
11416
12013
  import { readFile as readFile22 } from "fs/promises";
11417
- import { join as join28 } from "path";
12014
+ import { join as join29 } from "path";
11418
12015
  function findRecipe(name, cwd) {
11419
12016
  for (const dir of RECIPE_DIRS) {
11420
12017
  for (const suffix of NAME_SUFFIXES) {
11421
12018
  for (const ext of FILE_EXTS) {
11422
- const p = join28(cwd, dir, `${name}${suffix}${ext}`);
11423
- if (existsSync25(p)) return p;
12019
+ const p = join29(cwd, dir, `${name}${suffix}${ext}`);
12020
+ if (existsSync26(p)) return p;
11424
12021
  }
11425
12022
  }
11426
12023
  }
@@ -11533,13 +12130,13 @@ ${rendered}` : rendered;
11533
12130
  });
11534
12131
 
11535
12132
  // src/commands/skill.ts
11536
- import { existsSync as existsSync26, readdirSync as readdirSync3, statSync as statSync7 } from "fs";
12133
+ import { existsSync as existsSync27, readdirSync as readdirSync4, statSync as statSync8 } from "fs";
11537
12134
  import { readFile as readFile23 } from "fs/promises";
11538
- import { join as join29, resolve as resolve16 } from "path";
12135
+ import { join as join30, resolve as resolve16 } from "path";
11539
12136
  function findSkillsRoot(cwd) {
11540
12137
  for (const candidate of SKILLS_DIRS) {
11541
12138
  const full = resolve16(cwd, candidate);
11542
- if (existsSync26(full) && statSync7(full).isDirectory()) return full;
12139
+ if (existsSync27(full) && statSync8(full).isDirectory()) return full;
11543
12140
  }
11544
12141
  return null;
11545
12142
  }
@@ -11547,28 +12144,28 @@ function listAllSkills(root) {
11547
12144
  const out = [];
11548
12145
  let domains;
11549
12146
  try {
11550
- domains = readdirSync3(root);
12147
+ domains = readdirSync4(root);
11551
12148
  } catch {
11552
12149
  return out;
11553
12150
  }
11554
12151
  for (const domain of domains) {
11555
- const dir = join29(root, domain);
12152
+ const dir = join30(root, domain);
11556
12153
  let s;
11557
12154
  try {
11558
- s = statSync7(dir);
12155
+ s = statSync8(dir);
11559
12156
  } catch {
11560
12157
  continue;
11561
12158
  }
11562
12159
  if (!s.isDirectory()) continue;
11563
12160
  let files;
11564
12161
  try {
11565
- files = readdirSync3(dir);
12162
+ files = readdirSync4(dir);
11566
12163
  } catch {
11567
12164
  continue;
11568
12165
  }
11569
12166
  for (const f of files) {
11570
12167
  if (!f.endsWith(".md")) continue;
11571
- out.push({ domain, name: f.slice(0, -3), path: join29(dir, f) });
12168
+ out.push({ domain, name: f.slice(0, -3), path: join30(dir, f) });
11572
12169
  }
11573
12170
  }
11574
12171
  return out.sort(
@@ -11658,7 +12255,7 @@ ${prompt4}`;
11658
12255
 
11659
12256
  // src/commands/benchmark.ts
11660
12257
  import { homedir as homedir16 } from "os";
11661
- import { join as join30 } from "path";
12258
+ import { join as join31 } from "path";
11662
12259
  import { mkdirSync as mkdirSync5 } from "fs";
11663
12260
  import * as fs2 from "fs";
11664
12261
  function parseArgs(args2) {
@@ -11675,7 +12272,7 @@ function parseArgs(args2) {
11675
12272
  }
11676
12273
  function formatTable(results) {
11677
12274
  const header = `${"Model".padEnd(40)} ${"TTFT(ms)".padStart(9)} ${"TPS".padStart(7)} ${"Tokens".padStart(8)} ${"Cost($)".padStart(9)}`;
11678
- const sep4 = "-".repeat(header.length);
12275
+ const sep5 = "-".repeat(header.length);
11679
12276
  const rows = results.map((r) => {
11680
12277
  const model = r.model.slice(0, 39).padEnd(40);
11681
12278
  const ttft = r.ttft_ms != null ? r.ttft_ms.toFixed(0).padStart(9) : " - ";
@@ -11685,7 +12282,7 @@ function formatTable(results) {
11685
12282
  const err = r.error ? ` \u26A0 ${r.error}` : "";
11686
12283
  return `${model} ${ttft} ${tps} ${tok} ${cost}${err}`;
11687
12284
  });
11688
- return [sep4, header, sep4, ...rows, sep4].join("\n");
12285
+ return [sep5, header, sep5, ...rows, sep5].join("\n");
11689
12286
  }
11690
12287
  var DEFAULT_PROMPTS, benchmarkCommand;
11691
12288
  var init_benchmark = __esm({
@@ -11778,10 +12375,10 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
11778
12375
  `[HW at run time: CPU ${hwAtEnd.cpuPct}% / RAM ${hwAtEnd.ramPct}% | ${hw.ramGiB} GiB RAM, ${hw.cores} cores]`
11779
12376
  );
11780
12377
  try {
11781
- const dir = join30(homedir16(), ".msapling", "benchmarks");
12378
+ const dir = join31(homedir16(), ".msapling", "benchmarks");
11782
12379
  mkdirSync5(dir, { recursive: true });
11783
12380
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
11784
- const file = join30(dir, `${ts}.json`);
12381
+ const file = join31(dir, `${ts}.json`);
11785
12382
  const run = {
11786
12383
  ts: (/* @__PURE__ */ new Date()).toISOString(),
11787
12384
  rounds,
@@ -12027,22 +12624,22 @@ var init_theme = __esm({
12027
12624
  });
12028
12625
 
12029
12626
  // src/commands/theme.ts
12030
- import { join as join31 } from "path";
12627
+ import { join as join32 } from "path";
12031
12628
  import { homedir as homedir17 } from "os";
12032
- import { existsSync as existsSync27 } from "fs";
12629
+ import { existsSync as existsSync28 } from "fs";
12033
12630
  import { readFile as readFile24, writeFile as writeFile14 } from "fs/promises";
12034
12631
  async function persistTheme(storage, themeName) {
12035
- const settingsPath = join31(homedir17(), ".msapling", "settings.json");
12632
+ const settingsPath = join32(homedir17(), ".msapling", "settings.json");
12036
12633
  let existing = {};
12037
12634
  try {
12038
- if (existsSync27(settingsPath)) {
12635
+ if (existsSync28(settingsPath)) {
12039
12636
  const text = await readFile24(settingsPath, "utf8");
12040
12637
  if (text.trim()) existing = JSON.parse(text);
12041
12638
  }
12042
12639
  } catch {
12043
12640
  }
12044
12641
  existing["theme"] = themeName;
12045
- ensureConfigDir(join31(homedir17(), ".msapling"));
12642
+ ensureConfigDir(join32(homedir17(), ".msapling"));
12046
12643
  await writeFile14(settingsPath, JSON.stringify(existing, null, 2), "utf8");
12047
12644
  }
12048
12645
  var VALID_THEMES, themeCommand;
@@ -12114,7 +12711,7 @@ var init_version = __esm({
12114
12711
  description: "Show version information for CLI and core packages",
12115
12712
  category: "debug",
12116
12713
  handler: async (_args, context) => {
12117
- const cliVersion = true ? "2.3.6-beta.47" : "(dev)";
12714
+ const cliVersion = true ? "2.3.6-beta.49" : "(dev)";
12118
12715
  const coreVersion = true ? "2.3.6-beta.43" : "(dev)";
12119
12716
  const runtime = process.version;
12120
12717
  context.addMessage("system", "MSapling Version Info");
@@ -12123,7 +12720,7 @@ var init_version = __esm({
12123
12720
  context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
12124
12721
  context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
12125
12722
  try {
12126
- const ts = "2026-06-20T08:11:43.445Z";
12723
+ const ts = "2026-06-20T08:28:18.242Z";
12127
12724
  if (ts && ts !== "__BUILD_TIMESTAMP__") {
12128
12725
  context.addMessage("system", row2("Build Timestamp", ts));
12129
12726
  }
@@ -12136,14 +12733,14 @@ var init_version = __esm({
12136
12733
  });
12137
12734
 
12138
12735
  // src/commands/feedback.ts
12139
- import { join as join32 } from "path";
12140
- import { existsSync as existsSync28 } from "fs";
12736
+ import { join as join33 } from "path";
12737
+ import { existsSync as existsSync29 } from "fs";
12141
12738
  import { readFile as readFile25 } from "fs/promises";
12142
12739
  async function readCliVersion() {
12143
12740
  try {
12144
12741
  const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
12145
- const pkgPath = join32(baseDir, "..", "..", "package.json");
12146
- if (!existsSync28(pkgPath)) return "unknown";
12742
+ const pkgPath = join33(baseDir, "..", "..", "package.json");
12743
+ if (!existsSync29(pkgPath)) return "unknown";
12147
12744
  const text = await readFile25(pkgPath, "utf8");
12148
12745
  const json = JSON.parse(text);
12149
12746
  return json.version ?? "unknown";
@@ -12185,7 +12782,7 @@ var init_feedback = __esm({
12185
12782
 
12186
12783
  // src/commands/export.ts
12187
12784
  import { homedir as homedir18 } from "os";
12188
- import { join as join33 } from "path";
12785
+ import { join as join34 } from "path";
12189
12786
  import { writeFile as writeFile15, mkdir as mkdir8 } from "fs/promises";
12190
12787
  function formatTimestamp(date) {
12191
12788
  return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
@@ -12235,10 +12832,10 @@ var init_export = __esm({
12235
12832
  let outputPath;
12236
12833
  let content;
12237
12834
  if (arg === "" || arg === "json") {
12238
- outputPath = join33(homedir18(), `msapling-export-${timestamp}.json`);
12835
+ outputPath = join34(homedir18(), `msapling-export-${timestamp}.json`);
12239
12836
  content = buildJsonExport(history);
12240
12837
  } else if (arg === "markdown" || arg === "md") {
12241
- outputPath = join33(homedir18(), `msapling-export-${timestamp}.md`);
12838
+ outputPath = join34(homedir18(), `msapling-export-${timestamp}.md`);
12242
12839
  content = buildMarkdownExport(history);
12243
12840
  } else {
12244
12841
  outputPath = arg;
@@ -12250,7 +12847,7 @@ var init_export = __esm({
12250
12847
  }
12251
12848
  }
12252
12849
  try {
12253
- const dir = join33(outputPath, "..");
12850
+ const dir = join34(outputPath, "..");
12254
12851
  await mkdir8(dir, { recursive: true });
12255
12852
  await writeFile15(outputPath, content, "utf8");
12256
12853
  context.addMessage("system", `Exported to: ${outputPath}`);
@@ -12446,15 +13043,15 @@ var init_plan = __esm({
12446
13043
 
12447
13044
  // src/commands/note.ts
12448
13045
  import { homedir as homedir19 } from "os";
12449
- import { join as join34 } from "path";
12450
- import { existsSync as existsSync29 } from "fs";
13046
+ import { join as join35 } from "path";
13047
+ import { existsSync as existsSync30 } from "fs";
12451
13048
  import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
12452
13049
  function getNotesFilePath() {
12453
- return join34(homedir19(), ".msapling", "notes.json");
13050
+ return join35(homedir19(), ".msapling", "notes.json");
12454
13051
  }
12455
13052
  async function readNotes(filePath = getNotesFilePath()) {
12456
13053
  try {
12457
- if (!existsSync29(filePath)) return [];
13054
+ if (!existsSync30(filePath)) return [];
12458
13055
  const raw = await readFile26(filePath, "utf8");
12459
13056
  const parsed = JSON.parse(raw);
12460
13057
  if (!Array.isArray(parsed)) return [];
@@ -12464,7 +13061,7 @@ async function readNotes(filePath = getNotesFilePath()) {
12464
13061
  }
12465
13062
  }
12466
13063
  async function writeNotes(notes, filePath = getNotesFilePath()) {
12467
- const dir = join34(homedir19(), ".msapling");
13064
+ const dir = join35(homedir19(), ".msapling");
12468
13065
  ensureConfigDir(dir);
12469
13066
  await writeFile16(filePath, JSON.stringify(notes, null, 2), "utf8");
12470
13067
  }
@@ -12611,16 +13208,16 @@ var init_todo = __esm({
12611
13208
 
12612
13209
  // src/commands/outputStyle.ts
12613
13210
  import { homedir as homedir20 } from "os";
12614
- import { join as join35, basename as basename3, extname as extname3 } from "path";
12615
- import { existsSync as existsSync30, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
13211
+ import { join as join36, basename as basename4, extname as extname3 } from "path";
13212
+ import { existsSync as existsSync31, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
12616
13213
  function resolveHome() {
12617
13214
  return process.env.HOME || process.env.USERPROFILE || homedir20();
12618
13215
  }
12619
13216
  function stylesDir() {
12620
- return join35(resolveHome(), ".msapling", "output-styles");
13217
+ return join36(resolveHome(), ".msapling", "output-styles");
12621
13218
  }
12622
13219
  function activeFile() {
12623
- return join35(stylesDir(), ".active");
13220
+ return join36(stylesDir(), ".active");
12624
13221
  }
12625
13222
  function parseStyleFile(text) {
12626
13223
  const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
@@ -12641,16 +13238,16 @@ function parseStyleFile(text) {
12641
13238
  }
12642
13239
  function listUserStyles() {
12643
13240
  const dir = stylesDir();
12644
- if (!existsSync30(dir)) return [];
13241
+ if (!existsSync31(dir)) return [];
12645
13242
  const out = [];
12646
- for (const entry of readdirSync4(dir)) {
13243
+ for (const entry of readdirSync5(dir)) {
12647
13244
  if (extname3(entry).toLowerCase() !== ".md") continue;
12648
- const full = join35(dir, entry);
13245
+ const full = join36(dir, entry);
12649
13246
  try {
12650
- const text = readFileSync4(full, "utf8");
13247
+ const text = readFileSync5(full, "utf8");
12651
13248
  const { description, body } = parseStyleFile(text);
12652
13249
  out.push({
12653
- name: basename3(entry, ".md"),
13250
+ name: basename4(entry, ".md"),
12654
13251
  description,
12655
13252
  body,
12656
13253
  source: "user",
@@ -12673,15 +13270,15 @@ function findStyle(name) {
12673
13270
  function getActiveStyleName() {
12674
13271
  try {
12675
13272
  const f = activeFile();
12676
- if (!existsSync30(f)) return "default";
12677
- return readFileSync4(f, "utf8").trim() || "default";
13273
+ if (!existsSync31(f)) return "default";
13274
+ return readFileSync5(f, "utf8").trim() || "default";
12678
13275
  } catch {
12679
13276
  return "default";
12680
13277
  }
12681
13278
  }
12682
13279
  function setActiveStyleName(name) {
12683
13280
  const dir = stylesDir();
12684
- if (!existsSync30(dir)) mkdirSync6(dir, { recursive: true });
13281
+ if (!existsSync31(dir)) mkdirSync6(dir, { recursive: true });
12685
13282
  writeFileSync5(activeFile(), `${name}
12686
13283
  `, "utf8");
12687
13284
  }
@@ -12694,8 +13291,8 @@ function createUserStyle(name, description, body) {
12694
13291
  throw new Error(`Invalid style name "${name}" \u2014 use letters, digits, _ and - only.`);
12695
13292
  }
12696
13293
  const dir = stylesDir();
12697
- if (!existsSync30(dir)) mkdirSync6(dir, { recursive: true });
12698
- const target = join35(dir, `${name}.md`);
13294
+ if (!existsSync31(dir)) mkdirSync6(dir, { recursive: true });
13295
+ const target = join36(dir, `${name}.md`);
12699
13296
  const frontmatter = `---
12700
13297
  description: ${description.replace(/\n/g, " ")}
12701
13298
  ---
@@ -14112,6 +14709,32 @@ var init_agents = __esm({
14112
14709
  }
14113
14710
  });
14114
14711
 
14712
+ // src/commands/stats.ts
14713
+ var statsCommand;
14714
+ var init_stats = __esm({
14715
+ "src/commands/stats.ts"() {
14716
+ "use strict";
14717
+ init_esm_shims();
14718
+ init_src3();
14719
+ statsCommand = {
14720
+ name: "stats",
14721
+ args: "[--json]",
14722
+ description: "Show this session's tool, token, cost, and approval stats",
14723
+ category: "debug",
14724
+ handler: (args2, context) => {
14725
+ const snap = getSessionStats().snapshot();
14726
+ if (args2.includes("--json")) {
14727
+ context.addMessage("system", JSON.stringify(snap, null, 2));
14728
+ return;
14729
+ }
14730
+ for (const line of renderSessionStats(snap)) {
14731
+ context.addMessage("system", line);
14732
+ }
14733
+ }
14734
+ };
14735
+ }
14736
+ });
14737
+
14115
14738
  // src/commands/index.ts
14116
14739
  var commands_exports = {};
14117
14740
  __export(commands_exports, {
@@ -14186,6 +14809,7 @@ var init_commands = __esm({
14186
14809
  init_rewind();
14187
14810
  init_bashes();
14188
14811
  init_agents();
14812
+ init_stats();
14189
14813
  commands = [
14190
14814
  loginCommand,
14191
14815
  logoutCommand,
@@ -14252,7 +14876,8 @@ var init_commands = __esm({
14252
14876
  remoteAgentCommand,
14253
14877
  rewindCommand,
14254
14878
  bashesCommand,
14255
- agentsCommand
14879
+ agentsCommand,
14880
+ statsCommand
14256
14881
  ];
14257
14882
  }
14258
14883
  });
@@ -14322,15 +14947,15 @@ var exec_exports = {};
14322
14947
  __export(exec_exports, {
14323
14948
  runExec: () => runExec
14324
14949
  });
14325
- import { existsSync as existsSync33 } from "fs";
14950
+ import { existsSync as existsSync34 } from "fs";
14326
14951
  import { readFile as readFile29 } from "fs/promises";
14327
14952
  import { homedir as homedir23 } from "os";
14328
- import { join as join37 } from "path";
14953
+ import { join as join38 } from "path";
14329
14954
  async function loadPersistedSettings() {
14330
14955
  const out = { mode: "default", theme: null };
14331
14956
  try {
14332
- const p = join37(homedir23(), ".msapling", "settings.json");
14333
- if (!existsSync33(p)) return out;
14957
+ const p = join38(homedir23(), ".msapling", "settings.json");
14958
+ if (!existsSync34(p)) return out;
14334
14959
  const raw = JSON.parse(await readFile29(p, "utf8"));
14335
14960
  const parsed = parseApprovalMode(raw, Date.now());
14336
14961
  if (parsed.kind === "ok") out.mode = parsed.mode;
@@ -15189,8 +15814,8 @@ __export(doctor_exports, {
15189
15814
  runDoctor: () => runDoctor
15190
15815
  });
15191
15816
  import { homedir as homedir24, platform as platform4, tmpdir } from "os";
15192
- import { join as join38 } from "path";
15193
- import { existsSync as existsSync34, statSync as statSync8 } from "fs";
15817
+ import { join as join39 } from "path";
15818
+ import { existsSync as existsSync35, statSync as statSync9 } from "fs";
15194
15819
  import { readdir as readdir3, mkdir as mkdir9, rm as rm3 } from "fs/promises";
15195
15820
  import { exec as exec2 } from "child_process";
15196
15821
  import { promisify } from "util";
@@ -15213,8 +15838,8 @@ async function checkNodeVersion() {
15213
15838
  };
15214
15839
  }
15215
15840
  async function checkConfigDir() {
15216
- const configDir = join38(homedir24(), ".msapling");
15217
- if (!existsSync34(configDir)) {
15841
+ const configDir = join39(homedir24(), ".msapling");
15842
+ if (!existsSync35(configDir)) {
15218
15843
  return {
15219
15844
  name: "Config directory",
15220
15845
  status: "WARN",
@@ -15222,7 +15847,7 @@ async function checkConfigDir() {
15222
15847
  remediation: `mkdir -p "${configDir}" && chmod 700 "${configDir}"`
15223
15848
  };
15224
15849
  }
15225
- const stats = statSync8(configDir);
15850
+ const stats = statSync9(configDir);
15226
15851
  if (!stats.isDirectory()) {
15227
15852
  return {
15228
15853
  name: "Config directory",
@@ -15283,7 +15908,7 @@ async function checkPathConflicts() {
15283
15908
  const timedOutDirs = [];
15284
15909
  const DIR_TIMEOUT_MS = 1500;
15285
15910
  for (const dir of paths) {
15286
- if (!dir || !existsSync34(dir)) continue;
15911
+ if (!dir || !existsSync35(dir)) continue;
15287
15912
  try {
15288
15913
  const files = await Promise.race([
15289
15914
  readdir3(dir),
@@ -15296,7 +15921,7 @@ async function checkPathConflicts() {
15296
15921
  ]);
15297
15922
  for (const file of files) {
15298
15923
  if (file === "msapling" || file === "msapling.exe" || file === "msapling.py") {
15299
- const fullPath = join38(dir, file);
15924
+ const fullPath = join39(dir, file);
15300
15925
  conflicts.push(fullPath);
15301
15926
  }
15302
15927
  }
@@ -15414,9 +16039,9 @@ async function checkTokenValidity() {
15414
16039
  }
15415
16040
  async function checkOsSpecific() {
15416
16041
  if (platform4() === "win32") {
15417
- const testDir = join38(tmpdir(), `msapling-longpath-test-${Date.now()}`);
16042
+ const testDir = join39(tmpdir(), `msapling-longpath-test-${Date.now()}`);
15418
16043
  const longDirName = "A".repeat(260);
15419
- const testPath = join38(testDir, longDirName);
16044
+ const testPath = join39(testDir, longDirName);
15420
16045
  try {
15421
16046
  await mkdir9(testDir, { recursive: true });
15422
16047
  try {
@@ -17093,8 +17718,8 @@ var init_libesm = __esm({
17093
17718
  });
17094
17719
 
17095
17720
  // ../core/src/mcp/catalog.ts
17096
- import { readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync9 } from "fs";
17097
- import { join as join39, relative as relative16 } from "path";
17721
+ import { readdirSync as readdirSync6, readFileSync as readFileSync6, statSync as statSync10 } from "fs";
17722
+ import { join as join40, relative as relative17 } from "path";
17098
17723
  function buildFileTree(root, maxFiles) {
17099
17724
  const SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "build", "dist", ".venv", "venv", ".next", "__pycache__", ".dart_tool", ".bun", "target"]);
17100
17725
  const SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".kt", ".swift", ".dart", ".rb", ".cs", ".cpp", ".c", ".h", ".md", ".json", ".yaml", ".yml", ".toml", ".sh", ".sql"]);
@@ -17104,17 +17729,17 @@ function buildFileTree(root, maxFiles) {
17104
17729
  const dir = queue.shift();
17105
17730
  let entries;
17106
17731
  try {
17107
- entries = readdirSync5(dir);
17732
+ entries = readdirSync6(dir);
17108
17733
  } catch {
17109
17734
  continue;
17110
17735
  }
17111
17736
  for (const name of entries) {
17112
17737
  if (out.length >= maxFiles) break;
17113
17738
  if (SKIP_DIRS2.has(name)) continue;
17114
- const full = join39(dir, name);
17739
+ const full = join40(dir, name);
17115
17740
  let s;
17116
17741
  try {
17117
- s = statSync9(full);
17742
+ s = statSync10(full);
17118
17743
  } catch {
17119
17744
  continue;
17120
17745
  }
@@ -17135,12 +17760,12 @@ function readFilesAsContext(root, files, maxKB) {
17135
17760
  for (const f of files) {
17136
17761
  let body;
17137
17762
  try {
17138
- body = readFileSync5(f, "utf8");
17763
+ body = readFileSync6(f, "utf8");
17139
17764
  } catch {
17140
17765
  continue;
17141
17766
  }
17142
17767
  if (body.length > cap) body = body.slice(0, cap) + "\n[...truncated]";
17143
- const rel = relative16(root, f).replace(/\\/g, "/");
17768
+ const rel = relative17(root, f).replace(/\\/g, "/");
17144
17769
  parts.push(`### ${rel}
17145
17770
 
17146
17771
  \`\`\`
@@ -17917,7 +18542,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
17917
18542
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
17918
18543
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
17919
18544
  "\u25CF MSapling CLI v",
17920
- "2.3.6-beta.47"
18545
+ "2.3.6-beta.49"
17921
18546
  ] }),
17922
18547
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
17923
18548
  ] });
@@ -18320,6 +18945,7 @@ function createIdleAwarePoll(pollingIntervalRef, lastActivityRef, client, setUse
18320
18945
 
18321
18946
  // src/state/commandHandler.ts
18322
18947
  init_esm_shims();
18948
+ init_src3();
18323
18949
  init_commands();
18324
18950
  init_plan();
18325
18951
  import { spawn as spawn10 } from "child_process";
@@ -18327,14 +18953,14 @@ import { spawn as spawn10 } from "child_process";
18327
18953
  // src/state/persistentState.ts
18328
18954
  init_esm_shims();
18329
18955
  import { homedir as homedir22 } from "os";
18330
- import { join as join36, dirname as dirname5 } from "path";
18331
- import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
18956
+ import { join as join37, dirname as dirname5 } from "path";
18957
+ import { existsSync as existsSync32, mkdirSync as mkdirSync7 } from "fs";
18332
18958
  import { readFile as readFile27, writeFile as writeFile17, rename as rename3 } from "fs/promises";
18333
18959
  import { randomBytes as randomBytes15 } from "crypto";
18334
- var STATE_PATH = join36(homedir22(), ".msapling", "state.json");
18960
+ var STATE_PATH = join37(homedir22(), ".msapling", "state.json");
18335
18961
  async function loadPersistentState(statePath = STATE_PATH) {
18336
18962
  try {
18337
- if (!existsSync31(statePath)) return { version: 1 };
18963
+ if (!existsSync32(statePath)) return { version: 1 };
18338
18964
  const text = await readFile27(statePath, "utf8");
18339
18965
  const parsed = JSON.parse(text);
18340
18966
  if (parsed.version !== 1) return { version: 1 };
@@ -18350,7 +18976,7 @@ async function loadPersistentState(statePath = STATE_PATH) {
18350
18976
  async function savePersistentState(state, statePath = STATE_PATH) {
18351
18977
  try {
18352
18978
  const dir = dirname5(statePath);
18353
- if (!existsSync31(dir)) mkdirSync7(dir, { recursive: true });
18979
+ if (!existsSync32(dir)) mkdirSync7(dir, { recursive: true });
18354
18980
  const existing = await loadPersistentState(statePath);
18355
18981
  const merged = {
18356
18982
  version: 1,
@@ -18419,6 +19045,11 @@ ${prompt4}` : prompt4;
18419
19045
  if (usage !== null) {
18420
19046
  ctx.setLastCost(usage.cost_usd);
18421
19047
  ctx.setSessionCost((prev) => prev + usage.cost_usd);
19048
+ getSessionStats().recordUsage(
19049
+ usage.cost_usd,
19050
+ usage.prompt_tokens,
19051
+ usage.completion_tokens
19052
+ );
18422
19053
  }
18423
19054
  ctx.setContextBudgetSnap(snapshotBudget(ctx.agent.getContextBudget()));
18424
19055
  } catch (e) {
@@ -18497,9 +19128,9 @@ ${prompt4}` : prompt4;
18497
19128
  for (const mention of fileMentions) {
18498
19129
  const filePath = mention.slice(1);
18499
19130
  try {
18500
- const { existsSync: existsSync35 } = await import("fs");
19131
+ const { existsSync: existsSync36 } = await import("fs");
18501
19132
  const { readFile: readFile30 } = await import("fs/promises");
18502
- if (existsSync35(filePath)) {
19133
+ if (existsSync36(filePath)) {
18503
19134
  const content = await readFile30(filePath, "utf8");
18504
19135
  const MAX_LEN = 32768;
18505
19136
  const truncated = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n...[TRUNCATED]" : content;
@@ -18525,6 +19156,7 @@ ${finalCmd}`;
18525
19156
  if (usage !== null) {
18526
19157
  ctx.setLastCost(usage.cost_usd);
18527
19158
  ctx.setSessionCost((prev) => prev + usage.cost_usd);
19159
+ getSessionStats().recordUsage(usage.cost_usd, usage.prompt_tokens, usage.completion_tokens);
18528
19160
  }
18529
19161
  ctx.setContextBudgetSnap(snapshotBudget(ctx.agent.getContextBudget()));
18530
19162
  } catch (e) {
@@ -18561,7 +19193,7 @@ init_src3();
18561
19193
  init_src();
18562
19194
  init_parseApprovalMode();
18563
19195
  import { readFile as readFile28 } from "fs/promises";
18564
- import { existsSync as existsSync32 } from "fs";
19196
+ import { existsSync as existsSync33 } from "fs";
18565
19197
  async function initSession(ctx) {
18566
19198
  try {
18567
19199
  const journalEncrypted = await initJournalEncryption();
@@ -18587,9 +19219,9 @@ async function initSession(ctx) {
18587
19219
  }
18588
19220
  try {
18589
19221
  const { homedir: homedir25 } = await import("os");
18590
- const { join: join41 } = await import("path");
18591
- const userSettingsPath = join41(homedir25(), ".msapling", "settings.json");
18592
- if (existsSync32(userSettingsPath)) {
19222
+ const { join: join42 } = await import("path");
19223
+ const userSettingsPath = join42(homedir25(), ".msapling", "settings.json");
19224
+ if (existsSync33(userSettingsPath)) {
18593
19225
  const userText = await readFile28(userSettingsPath, "utf8");
18594
19226
  let parsed;
18595
19227
  try {
@@ -19006,14 +19638,14 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
19006
19638
 
19007
19639
  // src/runtime/bootstrap.ts
19008
19640
  init_esm_shims();
19009
- import { readFileSync as readFileSync6 } from "fs";
19641
+ import { readFileSync as readFileSync7 } from "fs";
19010
19642
  import { fileURLToPath as fileURLToPath2 } from "url";
19011
- import { dirname as dirname6, join as join40 } from "path";
19643
+ import { dirname as dirname6, join as join41 } from "path";
19012
19644
  function readCliVersion2() {
19013
19645
  const here = dirname6(fileURLToPath2(import.meta.url));
19014
19646
  for (const rel of ["../package.json", "../../package.json"]) {
19015
19647
  try {
19016
- const pkg = JSON.parse(readFileSync6(join40(here, rel), "utf8"));
19648
+ const pkg = JSON.parse(readFileSync7(join41(here, rel), "utf8"));
19017
19649
  if (pkg.name && pkg.version) {
19018
19650
  return { name: pkg.name, version: pkg.version };
19019
19651
  }