@mtreeai/msapling-cli 2.3.6-beta.28 → 2.3.6-beta.30

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 +1286 -245
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -53,11 +53,438 @@ var init_esm_shims = __esm({
53
53
  }
54
54
  });
55
55
 
56
+ // ../api-client/src/localLlm.ts
57
+ import { homedir } from "os";
58
+ import { readFile } from "fs/promises";
59
+ import { join } from "path";
60
+ async function probeDialect(baseUrl) {
61
+ const normalizedUrl = baseUrl.replace(/\/$/, "");
62
+ const timeout = 800;
63
+ try {
64
+ const controller = new AbortController();
65
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
66
+ const response = await fetch(`${normalizedUrl}/api/tags`, { signal: controller.signal });
67
+ clearTimeout(timeoutId);
68
+ if (response.ok) return "ollama";
69
+ } catch {
70
+ }
71
+ try {
72
+ const controller = new AbortController();
73
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
74
+ const response = await fetch(`${normalizedUrl}/v1/models`, { signal: controller.signal });
75
+ clearTimeout(timeoutId);
76
+ if (response.ok) return "openai_compat";
77
+ } catch {
78
+ }
79
+ return null;
80
+ }
81
+ async function detectLocalLlm() {
82
+ const endpoints = [
83
+ process.env.MSAPLING_LOCAL_LLM_URL,
84
+ await readConfigLocalLlmUrl(),
85
+ "http://localhost:11434",
86
+ // Ollama
87
+ "http://localhost:8080",
88
+ // llama.cpp
89
+ "http://localhost:1234"
90
+ // LM Studio
91
+ ].filter((url) => !!url);
92
+ for (const baseUrl of endpoints) {
93
+ try {
94
+ const dialect = await probeDialect(baseUrl);
95
+ if (dialect) {
96
+ return new LocalLlmClient({
97
+ baseUrl,
98
+ model: process.env.MSAPLING_LOCAL_LLM_MODEL ?? "default",
99
+ dialect
100
+ });
101
+ }
102
+ } catch {
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ async function readConfigLocalLlmUrl() {
108
+ try {
109
+ const configPath = join(homedir(), ".msapling", "config.json");
110
+ const content = await readFile(configPath, "utf-8");
111
+ const config = JSON.parse(content);
112
+ return config.localLlm?.baseUrl ?? null;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ var LocalLlmClient, OllamaClient;
118
+ var init_localLlm = __esm({
119
+ "../api-client/src/localLlm.ts"() {
120
+ "use strict";
121
+ init_esm_shims();
122
+ LocalLlmClient = class {
123
+ baseUrl;
124
+ model;
125
+ apiKey;
126
+ dialect;
127
+ constructor(config) {
128
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
129
+ this.model = config.model;
130
+ this.apiKey = config.apiKey;
131
+ this.dialect = config.dialect ?? "openai_compat";
132
+ }
133
+ /**
134
+ * Detect dialect from baseUrl if not explicitly provided
135
+ */
136
+ async detectDialect() {
137
+ if (this.dialect !== "openai_compat") return this.dialect;
138
+ const detected = await probeDialect(this.baseUrl);
139
+ if (detected) {
140
+ this.dialect = detected;
141
+ }
142
+ return this.dialect;
143
+ }
144
+ /**
145
+ * Check if local LLM server is reachable
146
+ */
147
+ async isAvailable() {
148
+ try {
149
+ const dialect = await this.detectDialect();
150
+ const endpoint = dialect === "ollama" ? "/api/tags" : "/v1/models";
151
+ const controller = new AbortController();
152
+ const timeout = setTimeout(() => controller.abort(), 1e3);
153
+ const response = await fetch(`${this.baseUrl}${endpoint}`, { signal: controller.signal });
154
+ clearTimeout(timeout);
155
+ return response.ok;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ /**
161
+ * List available models
162
+ */
163
+ async listModels() {
164
+ const dialect = await this.detectDialect();
165
+ if (dialect === "ollama") {
166
+ const response = await fetch(`${this.baseUrl}/api/tags`);
167
+ if (!response.ok) throw new Error(`Ollama /api/tags failed: ${response.status}`);
168
+ const data = await response.json();
169
+ return (data.models ?? []).map((m) => ({ name: m.name, size: m.size }));
170
+ } else {
171
+ const response = await fetch(`${this.baseUrl}/v1/models`);
172
+ if (!response.ok) throw new Error(`OpenAI /v1/models failed: ${response.status}`);
173
+ const data = await response.json();
174
+ return (data.data ?? []).map((m) => ({ name: m.id, size: 0 }));
175
+ }
176
+ }
177
+ /**
178
+ * Stream chat messages
179
+ */
180
+ async *streamChat(model, messages) {
181
+ const dialect = await this.detectDialect();
182
+ if (dialect === "ollama") {
183
+ yield* this.streamChatOllama(model, messages);
184
+ } else {
185
+ yield* this.streamChatOpenAI(model, messages);
186
+ }
187
+ }
188
+ async *streamChatOllama(model, messages) {
189
+ const response = await fetch(`${this.baseUrl}/api/chat`, {
190
+ method: "POST",
191
+ headers: { "Content-Type": "application/json" },
192
+ body: JSON.stringify({ model, messages, stream: true })
193
+ });
194
+ if (!response.ok) throw new Error(`Ollama /api/chat failed: ${response.status}`);
195
+ if (!response.body) throw new Error("No response body");
196
+ const reader = response.body.getReader();
197
+ const decoder = new TextDecoder();
198
+ let pending = "";
199
+ try {
200
+ while (true) {
201
+ const { done, value } = await reader.read();
202
+ if (done) break;
203
+ pending += decoder.decode(value, { stream: true });
204
+ let nlIdx;
205
+ while ((nlIdx = pending.indexOf("\n")) !== -1) {
206
+ const line = pending.slice(0, nlIdx).trim();
207
+ pending = pending.slice(nlIdx + 1);
208
+ if (!line) continue;
209
+ try {
210
+ const obj = JSON.parse(line);
211
+ yield {
212
+ delta: obj.message?.content ?? "",
213
+ done: obj.done ?? false,
214
+ prompt_tokens: obj.prompt_eval_count,
215
+ completion_tokens: obj.eval_count
216
+ };
217
+ } catch {
218
+ }
219
+ }
220
+ }
221
+ pending += decoder.decode();
222
+ if (pending.trim()) {
223
+ try {
224
+ const obj = JSON.parse(pending);
225
+ yield {
226
+ delta: obj.message?.content ?? "",
227
+ done: obj.done ?? true,
228
+ prompt_tokens: obj.prompt_eval_count,
229
+ completion_tokens: obj.eval_count
230
+ };
231
+ } catch {
232
+ }
233
+ }
234
+ } finally {
235
+ reader.cancel();
236
+ }
237
+ }
238
+ async *streamChatOpenAI(model, messages) {
239
+ const headers = { "Content-Type": "application/json" };
240
+ if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
241
+ const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
242
+ method: "POST",
243
+ headers,
244
+ body: JSON.stringify({ model, messages, stream: true })
245
+ });
246
+ if (!response.ok) throw new Error(`OpenAI /v1/chat/completions failed: ${response.status}`);
247
+ if (!response.body) throw new Error("No response body");
248
+ const reader = response.body.getReader();
249
+ const decoder = new TextDecoder();
250
+ let pending = "";
251
+ let promptTokens = 0;
252
+ let completionTokens = 0;
253
+ try {
254
+ while (true) {
255
+ const { done, value } = await reader.read();
256
+ if (done) break;
257
+ pending += decoder.decode(value, { stream: true });
258
+ let nlIdx;
259
+ while ((nlIdx = pending.indexOf("\n")) !== -1) {
260
+ const line = pending.slice(0, nlIdx).trim();
261
+ pending = pending.slice(nlIdx + 1);
262
+ if (!line || !line.startsWith("data:")) continue;
263
+ const data = line.slice(5).trim();
264
+ if (data === "[DONE]") continue;
265
+ try {
266
+ const obj = JSON.parse(data);
267
+ const delta = obj.choices?.[0]?.delta?.content ?? "";
268
+ const done2 = obj.choices?.[0]?.finish_reason !== null;
269
+ if (obj.usage) {
270
+ promptTokens = obj.usage.prompt_tokens ?? 0;
271
+ completionTokens = obj.usage.completion_tokens ?? 0;
272
+ }
273
+ yield {
274
+ delta,
275
+ done: done2 || obj.choices?.[0]?.finish_reason !== null && delta === "",
276
+ prompt_tokens: promptTokens,
277
+ completion_tokens: completionTokens
278
+ };
279
+ } catch {
280
+ }
281
+ }
282
+ }
283
+ } finally {
284
+ reader.cancel();
285
+ }
286
+ }
287
+ };
288
+ OllamaClient = class extends LocalLlmClient {
289
+ constructor(baseUrl = process.env.OLLAMA_HOST ?? "http://localhost:11434") {
290
+ super({ baseUrl, model: "default", dialect: "ollama" });
291
+ }
292
+ };
293
+ }
294
+ });
295
+
296
+ // ../api-client/src/journal.ts
297
+ import { homedir as homedir2 } from "os";
298
+ import { join as join2 } from "path";
299
+ function getJournal() {
300
+ if (!journalInstance) {
301
+ journalInstance = new Journal();
302
+ }
303
+ return journalInstance;
304
+ }
305
+ var DatabaseConnection, Journal, journalInstance;
306
+ var init_journal = __esm({
307
+ "../api-client/src/journal.ts"() {
308
+ "use strict";
309
+ init_esm_shims();
310
+ try {
311
+ const sqlite = __require("sqlite");
312
+ DatabaseConnection = sqlite;
313
+ } catch {
314
+ DatabaseConnection = null;
315
+ }
316
+ Journal = class {
317
+ dbPath;
318
+ jsonlPath;
319
+ useJsonl = false;
320
+ db = null;
321
+ jsonlEntries = [];
322
+ constructor() {
323
+ const home = homedir2();
324
+ const msaplingDir = join2(home, ".msapling");
325
+ this.dbPath = join2(msaplingDir, "journal.sqlite");
326
+ this.jsonlPath = join2(msaplingDir, "journal.jsonl");
327
+ this.initialize();
328
+ }
329
+ initialize() {
330
+ if (DatabaseConnection) {
331
+ try {
332
+ const { DatabaseSync } = DatabaseConnection;
333
+ this.db = new DatabaseSync(this.dbPath);
334
+ this.initializeSchema();
335
+ } catch (e) {
336
+ console.warn("[Journal] SQLite init failed, falling back to JSONL", e);
337
+ this.useJsonl = true;
338
+ this.loadJsonl();
339
+ }
340
+ } else {
341
+ this.useJsonl = true;
342
+ this.loadJsonl();
343
+ }
344
+ }
345
+ initializeSchema() {
346
+ if (!this.db) return;
347
+ try {
348
+ this.db.exec(`
349
+ CREATE TABLE IF NOT EXISTS messages (
350
+ id TEXT PRIMARY KEY,
351
+ chat_id TEXT NOT NULL,
352
+ project_id TEXT,
353
+ role TEXT NOT NULL,
354
+ content TEXT NOT NULL,
355
+ ts INTEGER NOT NULL,
356
+ model TEXT,
357
+ token_count INTEGER,
358
+ source TEXT NOT NULL DEFAULT 'offline',
359
+ synced_at INTEGER
360
+ );
361
+ CREATE INDEX IF NOT EXISTS idx_synced ON messages(synced_at, ts);
362
+ `);
363
+ } catch (e) {
364
+ console.warn("[Journal] Schema creation failed", e);
365
+ }
366
+ }
367
+ loadJsonl() {
368
+ }
369
+ async journalAppend(entry) {
370
+ if (this.useJsonl) {
371
+ this.jsonlEntries.push(entry);
372
+ } else if (this.db) {
373
+ try {
374
+ const stmt = this.db.prepare(`
375
+ INSERT INTO messages (id, chat_id, project_id, role, content, ts, model, token_count, source)
376
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
377
+ `);
378
+ stmt.run(
379
+ entry.id,
380
+ entry.chat_id,
381
+ entry.project_id || null,
382
+ entry.role,
383
+ entry.content,
384
+ entry.ts,
385
+ entry.model || null,
386
+ entry.token_count || null,
387
+ entry.source
388
+ );
389
+ } catch (e) {
390
+ console.warn("[Journal] Failed to append message", e);
391
+ }
392
+ }
393
+ }
394
+ async journalListPending() {
395
+ if (this.useJsonl) {
396
+ return this.jsonlEntries.filter((e) => !e.synced_at);
397
+ }
398
+ if (!this.db) return [];
399
+ try {
400
+ const stmt = this.db.prepare(`
401
+ SELECT id, chat_id, project_id, role, content, ts, model, token_count, source, synced_at
402
+ FROM messages
403
+ WHERE synced_at IS NULL
404
+ ORDER BY ts ASC
405
+ `);
406
+ const rows = stmt.all();
407
+ return rows.map((r) => ({
408
+ id: r.id,
409
+ chat_id: r.chat_id,
410
+ project_id: r.project_id,
411
+ role: r.role,
412
+ content: r.content,
413
+ ts: r.ts,
414
+ model: r.model,
415
+ token_count: r.token_count,
416
+ source: r.source,
417
+ synced_at: r.synced_at
418
+ }));
419
+ } catch (e) {
420
+ console.warn("[Journal] Failed to list pending messages", e);
421
+ return [];
422
+ }
423
+ }
424
+ async journalMarkSynced(ids) {
425
+ if (this.useJsonl) {
426
+ const now = Date.now();
427
+ this.jsonlEntries = this.jsonlEntries.map(
428
+ (e) => ids.includes(e.id) ? { ...e, synced_at: now } : e
429
+ );
430
+ return;
431
+ }
432
+ if (!this.db) return;
433
+ try {
434
+ const now = Date.now();
435
+ const placeholders = ids.map(() => "?").join(",");
436
+ const stmt = this.db.prepare(`
437
+ UPDATE messages SET synced_at = ? WHERE id IN (${placeholders})
438
+ `);
439
+ stmt.run(now, ...ids);
440
+ } catch (e) {
441
+ console.warn("[Journal] Failed to mark synced", e);
442
+ }
443
+ }
444
+ async journalCount() {
445
+ if (this.useJsonl) {
446
+ return {
447
+ pending: this.jsonlEntries.filter((e) => !e.synced_at).length,
448
+ total: this.jsonlEntries.length
449
+ };
450
+ }
451
+ if (!this.db) return { pending: 0, total: 0 };
452
+ try {
453
+ const pendingStmt = this.db.prepare("SELECT COUNT(*) as cnt FROM messages WHERE synced_at IS NULL");
454
+ const totalStmt = this.db.prepare("SELECT COUNT(*) as cnt FROM messages");
455
+ const pending = pendingStmt.get()?.cnt ?? 0;
456
+ const total = totalStmt.get()?.cnt ?? 0;
457
+ return { pending, total };
458
+ } catch (e) {
459
+ console.warn("[Journal] Failed to count messages", e);
460
+ return { pending: 0, total: 0 };
461
+ }
462
+ }
463
+ close() {
464
+ if (this.db) {
465
+ try {
466
+ this.db.close();
467
+ } catch (e) {
468
+ console.warn("[Journal] Failed to close", e);
469
+ }
470
+ }
471
+ }
472
+ };
473
+ journalInstance = null;
474
+ }
475
+ });
476
+
56
477
  // ../api-client/src/index.ts
57
478
  var src_exports = {};
58
479
  __export(src_exports, {
480
+ Journal: () => Journal,
481
+ LocalLlmClient: () => LocalLlmClient,
59
482
  MSaplingClient: () => MSaplingClient,
60
- MSaplingError: () => MSaplingError
483
+ MSaplingError: () => MSaplingError,
484
+ OllamaClient: () => OllamaClient,
485
+ detectLocalLlm: () => detectLocalLlm,
486
+ getJournal: () => getJournal,
487
+ probeDialect: () => probeDialect
61
488
  });
62
489
  function normalizeDetail(detail) {
63
490
  if (detail == null) return "";
@@ -86,6 +513,8 @@ var init_src = __esm({
86
513
  "../api-client/src/index.ts"() {
87
514
  "use strict";
88
515
  init_esm_shims();
516
+ init_localLlm();
517
+ init_journal();
89
518
  MSaplingError = class extends Error {
90
519
  constructor(message, status, code) {
91
520
  super(message);
@@ -956,6 +1385,23 @@ var init_src = __esm({
956
1385
  clearTimeout(timeout);
957
1386
  }
958
1387
  }
1388
+ /**
1389
+ * CLI-OFFLINE-02: Import journaled messages from offline sessions
1390
+ * POST /api/chat/import with batch of messages
1391
+ */
1392
+ async importChat(req) {
1393
+ const response = await this.request("/api/chat/import", {
1394
+ method: "POST",
1395
+ body: JSON.stringify(req)
1396
+ });
1397
+ if (!response.ok) {
1398
+ const err = await response.json().catch(() => ({ detail: response.statusText }));
1399
+ throw new MSaplingError(
1400
+ normalizeDetail(err.detail) || "Chat import failed",
1401
+ response.status
1402
+ );
1403
+ }
1404
+ }
959
1405
  };
960
1406
  }
961
1407
  });
@@ -973,7 +1419,7 @@ var init_BaseTool = __esm({
973
1419
 
974
1420
  // ../core/src/tools/ReadFileTool.ts
975
1421
  import { resolve, normalize, relative, isAbsolute } from "path";
976
- import { readFile } from "fs/promises";
1422
+ import { readFile as readFile2 } from "fs/promises";
977
1423
  import { existsSync } from "fs";
978
1424
  var ReadFileTool;
979
1425
  var init_ReadFileTool = __esm({
@@ -1024,7 +1470,7 @@ var init_ReadFileTool = __esm({
1024
1470
  const limit = args2.limit ? parseInt(args2.limit) : 0;
1025
1471
  const MAX_BYTES = 32768;
1026
1472
  let content = "";
1027
- const fileContent = await readFile(fullPath, "utf8");
1473
+ const fileContent = await readFile2(fullPath, "utf8");
1028
1474
  const lines = fileContent.split("\n");
1029
1475
  let currentLine = 1;
1030
1476
  let startLine = offset > 0 ? offset : 1;
@@ -1097,10 +1543,10 @@ var init_EditFileTool = __esm({
1097
1543
  });
1098
1544
 
1099
1545
  // ../core/src/tools/WriteFileTool.ts
1100
- import { resolve as resolve2, normalize as normalize2, relative as relative2, isAbsolute as isAbsolute2, join as join2 } from "path";
1101
- import { writeFile, readFile as readFile2, mkdir } from "fs/promises";
1546
+ import { resolve as resolve2, normalize as normalize2, relative as relative2, isAbsolute as isAbsolute2, join as join4 } from "path";
1547
+ import { writeFile, readFile as readFile3, mkdir } from "fs/promises";
1102
1548
  import { existsSync as existsSync2 } from "fs";
1103
- import { homedir } from "os";
1549
+ import { homedir as homedir3 } from "os";
1104
1550
  import { randomBytes } from "crypto";
1105
1551
  var MAX_CONTENT_BYTES, WriteFileTool;
1106
1552
  var init_WriteFileTool = __esm({
@@ -1175,12 +1621,12 @@ var init_WriteFileTool = __esm({
1175
1621
  let backedUpTo = null;
1176
1622
  try {
1177
1623
  if (existsSync2(resolvedTarget)) {
1178
- const existingContent = await readFile2(resolvedTarget, "utf8");
1624
+ const existingContent = await readFile3(resolvedTarget, "utf8");
1179
1625
  const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
1180
1626
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1181
1627
  const suffix = randomBytes(4).toString("hex");
1182
- const backupPath = join2(homedir(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
1183
- await mkdir(join2(homedir(), ".msapling", "backups"), { recursive: true });
1628
+ const backupPath = join4(homedir3(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
1629
+ await mkdir(join4(homedir3(), ".msapling", "backups"), { recursive: true });
1184
1630
  await writeFile(backupPath, existingContent, "utf8");
1185
1631
  backedUpTo = backupPath;
1186
1632
  }
@@ -1834,9 +2280,9 @@ var init_SubShellTool = __esm({
1834
2280
 
1835
2281
  // ../core/src/tools/SearchTools.ts
1836
2282
  import { spawn as spawn4 } from "child_process";
1837
- import { readFile as readFile3, readdir, stat } from "fs/promises";
2283
+ import { readFile as readFile4, readdir, stat } from "fs/promises";
1838
2284
  import { existsSync as existsSync3, statSync } from "fs";
1839
- import { join as join4, relative as relative4, sep, resolve as resolve4, normalize as normalize3, isAbsolute as isAbsolute4 } from "path";
2285
+ import { join as join6, relative as relative4, sep, resolve as resolve4, normalize as normalize3, isAbsolute as isAbsolute4 } from "path";
1840
2286
  function globToRegExp(pattern) {
1841
2287
  const normalised = pattern.replace(/\\/g, "/");
1842
2288
  let re = "^";
@@ -1889,7 +2335,7 @@ async function* walkFiles(root, current = root) {
1889
2335
  return;
1890
2336
  }
1891
2337
  for (const entry of entries) {
1892
- const full = join4(current, entry.name);
2338
+ const full = join6(current, entry.name);
1893
2339
  if (entry.isDirectory()) {
1894
2340
  if (SKIP_DIRS.has(entry.name)) continue;
1895
2341
  yield* walkFiles(root, full);
@@ -1941,12 +2387,12 @@ async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive)
1941
2387
  const matches2 = [];
1942
2388
  for await (const relPath of walkFiles(searchRoot)) {
1943
2389
  if (matches2.length >= maxMatches) break;
1944
- const fullPath = join4(searchRoot, relPath);
2390
+ const fullPath = join6(searchRoot, relPath);
1945
2391
  try {
1946
2392
  const s = await stat(fullPath);
1947
2393
  if (!s.isFile()) continue;
1948
2394
  if (s.size > 524288) continue;
1949
- const content = await readFile3(fullPath, "utf8");
2395
+ const content = await readFile4(fullPath, "utf8");
1950
2396
  const lines = content.split("\n");
1951
2397
  for (let i = 0; i < lines.length; i++) {
1952
2398
  if (regex.test(lines[i])) {
@@ -2031,7 +2477,7 @@ var init_SearchTools = __esm({
2031
2477
  const files = [];
2032
2478
  for await (const rel of walkFiles(scanRoot)) {
2033
2479
  if (!re.test(rel)) continue;
2034
- const full = scanRoot === projectRoot ? rel : join4(args2.path, rel).split(sep).join("/");
2480
+ const full = scanRoot === projectRoot ? rel : join6(args2.path, rel).split(sep).join("/");
2035
2481
  files.push(full);
2036
2482
  if (files.length >= MAX_GLOB_RESULTS) {
2037
2483
  files.push(`... [capped at ${MAX_GLOB_RESULTS} results \u2014 refine your pattern]`);
@@ -2084,7 +2530,7 @@ var init_SearchTools = __esm({
2084
2530
  isError: true
2085
2531
  };
2086
2532
  }
2087
- const searchPath = args2?.path ? join4(projectRoot, String(args2.path)) : projectRoot;
2533
+ const searchPath = args2?.path ? join6(projectRoot, String(args2.path)) : projectRoot;
2088
2534
  if (!existsSync3(searchPath)) {
2089
2535
  return {
2090
2536
  content: `Error: path "${args2.path}" does not exist.`,
@@ -2123,7 +2569,7 @@ var init_SearchTools = __esm({
2123
2569
  const isFile = statSync(searchPath).isFile();
2124
2570
  let matches2;
2125
2571
  if (isFile) {
2126
- const content = await readFile3(searchPath, "utf8");
2572
+ const content = await readFile4(searchPath, "utf8");
2127
2573
  const regex = new RegExp(pattern, caseSensitive ? "" : "i");
2128
2574
  const lines = content.split("\n");
2129
2575
  const relPath = args2?.path ?? searchPath;
@@ -2144,7 +2590,7 @@ var init_SearchTools = __esm({
2144
2590
  });
2145
2591
 
2146
2592
  // ../core/src/tools/ListDirectoryTool.ts
2147
- import { resolve as resolve5, normalize as normalize4, relative as relative5, isAbsolute as isAbsolute5, join as join5, sep as sep2 } from "path";
2593
+ import { resolve as resolve5, normalize as normalize4, relative as relative5, isAbsolute as isAbsolute5, join as join7, sep as sep2 } from "path";
2148
2594
  import { readdirSync, statSync as statSync2, existsSync as existsSync4 } from "fs";
2149
2595
  function humanSize(bytes) {
2150
2596
  if (bytes < 1024) return `${bytes} B`;
@@ -2163,7 +2609,7 @@ function scanDir(dir, relBase, recursive, showHidden, entries, cap) {
2163
2609
  const fileNames = [];
2164
2610
  for (const name of names) {
2165
2611
  if (!showHidden && name.startsWith(".")) continue;
2166
- const full = join5(dir, name);
2612
+ const full = join7(dir, name);
2167
2613
  let st;
2168
2614
  try {
2169
2615
  st = statSync2(full);
@@ -2183,13 +2629,13 @@ function scanDir(dir, relBase, recursive, showHidden, entries, cap) {
2183
2629
  const rel = relBase ? `${relBase}${sep2}${name}` : name;
2184
2630
  entries.push({ kind: "dir", rel });
2185
2631
  if (recursive) {
2186
- scanDir(join5(dir, name), rel, recursive, showHidden, entries, cap);
2632
+ scanDir(join7(dir, name), rel, recursive, showHidden, entries, cap);
2187
2633
  }
2188
2634
  }
2189
2635
  for (const name of fileNames) {
2190
2636
  if (entries.length >= cap) return;
2191
2637
  const rel = relBase ? `${relBase}${sep2}${name}` : name;
2192
- const full = join5(dir, name);
2638
+ const full = join7(dir, name);
2193
2639
  let size;
2194
2640
  let mtime;
2195
2641
  try {
@@ -2480,10 +2926,10 @@ var init_TodoTools = __esm({
2480
2926
  });
2481
2927
 
2482
2928
  // ../core/src/tools/PatchFileTool.ts
2483
- import { resolve as resolve6, normalize as normalize5, relative as relative6, isAbsolute as isAbsolute6, join as join6 } from "path";
2484
- import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2929
+ import { resolve as resolve6, normalize as normalize5, relative as relative6, isAbsolute as isAbsolute6, join as join8 } from "path";
2930
+ import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
2485
2931
  import { existsSync as existsSync5 } from "fs";
2486
- import { homedir as homedir2 } from "os";
2932
+ import { homedir as homedir4 } from "os";
2487
2933
  import { randomBytes as randomBytes2 } from "crypto";
2488
2934
  var PatchFileTool;
2489
2935
  var init_PatchFileTool = __esm({
@@ -2545,7 +2991,7 @@ var init_PatchFileTool = __esm({
2545
2991
  }
2546
2992
  let originalContent;
2547
2993
  try {
2548
- originalContent = await readFile4(resolvedTarget, "utf8");
2994
+ originalContent = await readFile5(resolvedTarget, "utf8");
2549
2995
  } catch (e) {
2550
2996
  return { content: `Error reading "${args2.path}": ${e.message}`, isError: true };
2551
2997
  }
@@ -2593,8 +3039,8 @@ File preview (first 200 chars): ${JSON.stringify(preview)}`,
2593
3039
  const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
2594
3040
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2595
3041
  const suffix = randomBytes2(4).toString("hex");
2596
- const backupPath = join6(homedir2(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
2597
- await mkdir2(join6(homedir2(), ".msapling", "backups"), { recursive: true });
3042
+ const backupPath = join8(homedir4(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
3043
+ await mkdir2(join8(homedir4(), ".msapling", "backups"), { recursive: true });
2598
3044
  await writeFile2(backupPath, originalContent, "utf8");
2599
3045
  backedUpTo = backupPath;
2600
3046
  } catch {
@@ -3225,7 +3671,7 @@ ${stderr}`;
3225
3671
 
3226
3672
  // ../core/src/tools/NotebookReadTool.ts
3227
3673
  import { resolve as resolve8, normalize as normalize7, relative as relative8, isAbsolute as isAbsolute8, extname } from "path";
3228
- import { readFile as readFile5 } from "fs/promises";
3674
+ import { readFile as readFile6 } from "fs/promises";
3229
3675
  import { existsSync as existsSync6 } from "fs";
3230
3676
  function joinSource(source) {
3231
3677
  return Array.isArray(source) ? source.join("") : source ?? "";
@@ -3392,7 +3838,7 @@ var init_NotebookReadTool = __esm({
3392
3838
  }
3393
3839
  let raw;
3394
3840
  try {
3395
- raw = await readFile5(absPath, "utf8");
3841
+ raw = await readFile6(absPath, "utf8");
3396
3842
  } catch (e) {
3397
3843
  return { content: `Error: could not read file: ${e.message}`, isError: true };
3398
3844
  }
@@ -3449,10 +3895,10 @@ _(Note: notebook has ${nb.cells.length} cells; only first ${MAX_CELLS} shown.)_`
3449
3895
  });
3450
3896
 
3451
3897
  // ../core/src/tools/NotebookEditTool.ts
3452
- import { resolve as resolve9, normalize as normalize8, relative as relative9, isAbsolute as isAbsolute9, extname as extname2, join as join8 } from "path";
3453
- import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3898
+ import { resolve as resolve9, normalize as normalize8, relative as relative9, isAbsolute as isAbsolute9, extname as extname2, join as join10 } from "path";
3899
+ import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
3454
3900
  import { existsSync as existsSync7 } from "fs";
3455
- import { homedir as homedir3 } from "os";
3901
+ import { homedir as homedir5 } from "os";
3456
3902
  import { randomBytes as randomBytes3 } from "crypto";
3457
3903
  function normaliseSource(source) {
3458
3904
  if (source === "") return [];
@@ -3586,7 +4032,7 @@ var init_NotebookEditTool = __esm({
3586
4032
  }
3587
4033
  let rawJson;
3588
4034
  try {
3589
- rawJson = await readFile6(absPath, "utf8");
4035
+ rawJson = await readFile7(absPath, "utf8");
3590
4036
  } catch (e) {
3591
4037
  return { content: `Error: could not read file: ${e.message}`, isError: true };
3592
4038
  }
@@ -3632,13 +4078,13 @@ var init_NotebookEditTool = __esm({
3632
4078
  const filename = absPath.split(/[\\/]/).pop() ?? "notebook.ipynb";
3633
4079
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3634
4080
  const suffix = randomBytes3(4).toString("hex");
3635
- const backupPath = join8(
3636
- homedir3(),
4081
+ const backupPath = join10(
4082
+ homedir5(),
3637
4083
  ".msapling",
3638
4084
  "backups",
3639
4085
  `${filename}.backup-${stamp}-${suffix}.bak`
3640
4086
  );
3641
- await mkdir3(join8(homedir3(), ".msapling", "backups"), { recursive: true });
4087
+ await mkdir3(join10(homedir5(), ".msapling", "backups"), { recursive: true });
3642
4088
  await writeFile3(backupPath, rawJson, "utf8");
3643
4089
  backedUpTo = backupPath;
3644
4090
  } catch {
@@ -3708,10 +4154,10 @@ Backup: ${backedUpTo}`;
3708
4154
  });
3709
4155
 
3710
4156
  // ../core/src/tools/MultiEditFileTool.ts
3711
- import { resolve as resolve10, normalize as normalize9, relative as relative10, isAbsolute as isAbsolute10, join as join9 } from "path";
3712
- import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
4157
+ import { resolve as resolve10, normalize as normalize9, relative as relative10, isAbsolute as isAbsolute10, join as join11 } from "path";
4158
+ import { readFile as readFile8, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
3713
4159
  import { existsSync as existsSync8 } from "fs";
3714
- import { homedir as homedir4 } from "os";
4160
+ import { homedir as homedir6 } from "os";
3715
4161
  import { randomBytes as randomBytes4 } from "crypto";
3716
4162
  var MAX_EDITS, MultiEditFileTool;
3717
4163
  var init_MultiEditFileTool = __esm({
@@ -3822,7 +4268,7 @@ var init_MultiEditFileTool = __esm({
3822
4268
  }
3823
4269
  let originalContent;
3824
4270
  try {
3825
- originalContent = await readFile7(resolvedTarget, "utf8");
4271
+ originalContent = await readFile8(resolvedTarget, "utf8");
3826
4272
  } catch (e) {
3827
4273
  return {
3828
4274
  content: `Error reading "${args2.path}": ${e.message}`,
@@ -3875,13 +4321,13 @@ No changes were written (atomic: all-or-nothing).`,
3875
4321
  const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
3876
4322
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3877
4323
  const suffix = randomBytes4(4).toString("hex");
3878
- const backupPath = join9(
3879
- homedir4(),
4324
+ const backupPath = join11(
4325
+ homedir6(),
3880
4326
  ".msapling",
3881
4327
  "backups",
3882
4328
  `${filename}.backup-${stamp}-${suffix}.bak`
3883
4329
  );
3884
- await mkdir4(join9(homedir4(), ".msapling", "backups"), { recursive: true });
4330
+ await mkdir4(join11(homedir6(), ".msapling", "backups"), { recursive: true });
3885
4331
  await writeFile4(backupPath, originalContent, "utf8");
3886
4332
  backedUpTo = backupPath;
3887
4333
  } catch {
@@ -3936,20 +4382,20 @@ async function copyDir(src, dst) {
3936
4382
  }
3937
4383
  async function backupFile(absPath) {
3938
4384
  try {
3939
- const { readFile: readFile24, writeFile: writeFile13, mkdir: mkdir9 } = await import("fs/promises");
3940
- const { homedir: homedir18 } = await import("os");
3941
- const { join: join31 } = await import("path");
4385
+ const { readFile: readFile26, writeFile: writeFile13, mkdir: mkdir9 } = await import("fs/promises");
4386
+ const { homedir: homedir21 } = await import("os");
4387
+ const { join: join34 } = await import("path");
3942
4388
  const filename = absPath.split(/[\\/]/).pop() ?? "file";
3943
4389
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3944
4390
  const suffix = randomBytes5(4).toString("hex");
3945
- const backupPath = join31(
3946
- homedir18(),
4391
+ const backupPath = join34(
4392
+ homedir21(),
3947
4393
  ".msapling",
3948
4394
  "backups",
3949
4395
  `${filename}.backup-${stamp}-${suffix}.bak`
3950
4396
  );
3951
- await mkdir9(join31(homedir18(), ".msapling", "backups"), { recursive: true });
3952
- const content = await readFile24(absPath, "utf8");
4397
+ await mkdir9(join34(homedir21(), ".msapling", "backups"), { recursive: true });
4398
+ const content = await readFile26(absPath, "utf8");
3953
4399
  await writeFile13(backupPath, content, "utf8");
3954
4400
  return backupPath;
3955
4401
  } catch {
@@ -4100,7 +4546,7 @@ Overwritten destination backed up to: ${backedUpTo}`;
4100
4546
  });
4101
4547
 
4102
4548
  // ../core/src/tools/DeleteFileTool.ts
4103
- import { resolve as resolve12, normalize as normalize11, relative as relative12, isAbsolute as isAbsolute12, join as join11 } from "path";
4549
+ import { resolve as resolve12, normalize as normalize11, relative as relative12, isAbsolute as isAbsolute12, join as join13 } from "path";
4104
4550
  import { rm as rm2, stat as stat3 } from "fs/promises";
4105
4551
  import { randomBytes as randomBytes6 } from "crypto";
4106
4552
  var DeleteFileTool;
@@ -4175,14 +4621,14 @@ var init_DeleteFileTool = __esm({
4175
4621
  let backedUpTo = null;
4176
4622
  if (isFile) {
4177
4623
  try {
4178
- const { readFile: readFile24, writeFile: writeFile13, mkdir: mkdir9 } = await import("fs/promises");
4179
- const { homedir: homedir18 } = await import("os");
4180
- const existingContent = await readFile24(abs, "utf8");
4624
+ const { readFile: readFile26, writeFile: writeFile13, mkdir: mkdir9 } = await import("fs/promises");
4625
+ const { homedir: homedir21 } = await import("os");
4626
+ const existingContent = await readFile26(abs, "utf8");
4181
4627
  const filename = abs.split(/[\\/]/).pop() ?? "file";
4182
4628
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4183
4629
  const suffix = randomBytes6(4).toString("hex");
4184
- const backupPath = join11(homedir18(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
4185
- await mkdir9(join11(homedir18(), ".msapling", "backups"), { recursive: true });
4630
+ const backupPath = join13(homedir21(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
4631
+ await mkdir9(join13(homedir21(), ".msapling", "backups"), { recursive: true });
4186
4632
  await writeFile13(backupPath, existingContent, "utf8");
4187
4633
  backedUpTo = backupPath;
4188
4634
  } catch {
@@ -4731,7 +5177,7 @@ var init_Hooks = __esm({
4731
5177
  });
4732
5178
 
4733
5179
  // ../core/src/agent/ToolExecutor.ts
4734
- import { readFile as readFile8 } from "fs/promises";
5180
+ import { readFile as readFile9 } from "fs/promises";
4735
5181
  var APPROVAL_GATED, ToolExecutor;
4736
5182
  var init_ToolExecutor = __esm({
4737
5183
  "../core/src/agent/ToolExecutor.ts"() {
@@ -4982,7 +5428,7 @@ ${blocker.stderr || "(empty)"}`,
4982
5428
  }
4983
5429
  await this.voice.speak(`Executing ${toolName}`, "natural");
4984
5430
  if (toolName === "edit_file") {
4985
- const content = await readFile8(args2.path, "utf-8");
5431
+ const content = await readFile9(args2.path, "utf-8");
4986
5432
  const parentHash = await this.mdrive.getHash(content);
4987
5433
  const block = await this.mdrive.proposeRemoteEdit(
4988
5434
  args2.path,
@@ -5069,14 +5515,14 @@ var init_Safety = __esm({
5069
5515
  });
5070
5516
 
5071
5517
  // ../core/src/ProjectConfig.ts
5072
- import { homedir as homedir5 } from "os";
5073
- import { join as join12, dirname as dirname2, parse as parsePath } from "path";
5518
+ import { homedir as homedir7 } from "os";
5519
+ import { join as join14, dirname as dirname2, parse as parsePath } from "path";
5074
5520
  import { existsSync as existsSync10 } from "fs";
5075
- import { readFile as readFile9 } from "fs/promises";
5521
+ import { readFile as readFile10 } from "fs/promises";
5076
5522
  async function readIfExists(path2) {
5077
5523
  try {
5078
5524
  if (!existsSync10(path2)) return null;
5079
- const text = await readFile9(path2, "utf8");
5525
+ const text = await readFile10(path2, "utf8");
5080
5526
  return text.length > TRUNCATE_AT ? text.slice(0, TRUNCATE_AT) + "\n[...truncated]" : text;
5081
5527
  } catch {
5082
5528
  return null;
@@ -5084,7 +5530,7 @@ async function readIfExists(path2) {
5084
5530
  }
5085
5531
  async function findInDir(dir) {
5086
5532
  for (const filename of FILENAMES) {
5087
- const path2 = join12(dir, filename);
5533
+ const path2 = join14(dir, filename);
5088
5534
  const content = await readIfExists(path2);
5089
5535
  if (content !== null) {
5090
5536
  return { path: path2, filename, content };
@@ -5107,9 +5553,9 @@ async function findProjectConfig(start) {
5107
5553
  return null;
5108
5554
  }
5109
5555
  async function findUserConfig() {
5110
- const home = homedir5();
5556
+ const home = homedir7();
5111
5557
  if (!home) return null;
5112
- const userDir = join12(home, ".msapling");
5558
+ const userDir = join14(home, ".msapling");
5113
5559
  return findInDir(userDir);
5114
5560
  }
5115
5561
  function buildCombined(user, project) {
@@ -5237,6 +5683,7 @@ var init_Agent = __esm({
5237
5683
  "../core/src/agent/Agent.ts"() {
5238
5684
  "use strict";
5239
5685
  init_esm_shims();
5686
+ init_src();
5240
5687
  init_Safety();
5241
5688
  init_ProjectConfig();
5242
5689
  init_Hooks();
@@ -5400,15 +5847,19 @@ ${next}`;
5400
5847
  }
5401
5848
  const currentPrompt = queue.shift();
5402
5849
  rounds++;
5403
- const stream = this.client.streamChat({
5404
- chat_id: chatId,
5405
- prompt: currentPrompt,
5406
- model,
5407
- tools: this.executor.getToolSchemas(),
5408
- project_root: this.projectRoot,
5409
- mode: this.executor.getMode(),
5410
- ...config.combined ? { project_context: config.combined } : {}
5411
- });
5850
+ const stream = this.chatWithFallback(
5851
+ {
5852
+ chat_id: chatId,
5853
+ prompt: currentPrompt,
5854
+ model,
5855
+ tools: this.executor.getToolSchemas(),
5856
+ project_root: this.projectRoot,
5857
+ mode: this.executor.getMode(),
5858
+ ...config.combined ? { project_context: config.combined } : {}
5859
+ },
5860
+ chatId,
5861
+ currentPrompt
5862
+ );
5412
5863
  for await (const chunk of stream) {
5413
5864
  if (chunk.content) {
5414
5865
  onContent(chunk.content);
@@ -5449,6 +5900,156 @@ ${next}`;
5449
5900
  }
5450
5901
  return fullResponse;
5451
5902
  }
5903
+ /**
5904
+ * CLI-OFFLINE-01: Fallback to Ollama when backend is unreachable.
5905
+ * On network error / 5xx / ECONNREFUSED, try local Ollama.
5906
+ * If Ollama unavailable, show offline message and journal the user message only.
5907
+ */
5908
+ async *chatWithFallback(params, chatId, userMessage) {
5909
+ const journal = getJournal();
5910
+ let backendReachable = false;
5911
+ let connectionTimeout = null;
5912
+ try {
5913
+ const controller = new AbortController();
5914
+ connectionTimeout = setTimeout(() => controller.abort(), 3e3);
5915
+ const backendStream = this.client.streamChat(params);
5916
+ for await (const chunk of backendStream) {
5917
+ if (connectionTimeout) {
5918
+ clearTimeout(connectionTimeout);
5919
+ connectionTimeout = null;
5920
+ }
5921
+ backendReachable = true;
5922
+ yield chunk;
5923
+ }
5924
+ } catch (error) {
5925
+ if (connectionTimeout) {
5926
+ clearTimeout(connectionTimeout);
5927
+ connectionTimeout = null;
5928
+ }
5929
+ const isNetworkError = error?.code === "ECONNREFUSED" || error?.code === "ECONNRESET" || error?.code === "ETIMEDOUT" || error?.status >= 500 || error?.message?.includes("timeout");
5930
+ if (!isNetworkError) {
5931
+ throw error;
5932
+ }
5933
+ const localLlm = await detectLocalLlm();
5934
+ if (!localLlm) {
5935
+ const userEntry2 = {
5936
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
5937
+ chat_id: chatId,
5938
+ project_id: void 0,
5939
+ role: "user",
5940
+ content: userMessage,
5941
+ ts: Date.now(),
5942
+ source: "offline"
5943
+ };
5944
+ await journal.journalAppend(userEntry2);
5945
+ yield {
5946
+ content: "Offline \u2014 no local model available. Install Ollama (ollama.ai) or llama.cpp and pull a model.\n"
5947
+ };
5948
+ return;
5949
+ }
5950
+ const userEntry = {
5951
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
5952
+ chat_id: chatId,
5953
+ project_id: void 0,
5954
+ role: "user",
5955
+ content: userMessage,
5956
+ ts: Date.now(),
5957
+ source: "offline"
5958
+ };
5959
+ await journal.journalAppend(userEntry);
5960
+ const dialect = await localLlm.detectDialect();
5961
+ let models = await localLlm.listModels();
5962
+ let selectedModel = process.env.MSAPLING_LOCAL_LLM_MODEL || "default";
5963
+ if (models.length === 0) {
5964
+ const assistantEntry2 = {
5965
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
5966
+ chat_id: chatId,
5967
+ project_id: void 0,
5968
+ role: "assistant",
5969
+ content: `No models available. Pull one for your local LLM server.`,
5970
+ ts: Date.now(),
5971
+ model: selectedModel,
5972
+ source: `offline_${dialect}`
5973
+ };
5974
+ await journal.journalAppend(assistantEntry2);
5975
+ yield {
5976
+ content: `No models available. Pull one for your local LLM server.`
5977
+ };
5978
+ return;
5979
+ }
5980
+ if (!models.find((m) => m.name === selectedModel)) {
5981
+ selectedModel = models[0].name;
5982
+ }
5983
+ const messages = params.messages || [{ role: "user", content: userMessage }];
5984
+ let assistantContent = "";
5985
+ let promptTokens = 0;
5986
+ let completionTokens = 0;
5987
+ for await (const delta of localLlm.streamChat(selectedModel, messages)) {
5988
+ assistantContent += delta.delta;
5989
+ if (delta.done) {
5990
+ promptTokens = delta.prompt_tokens ?? 0;
5991
+ completionTokens = delta.completion_tokens ?? 0;
5992
+ }
5993
+ if (delta.delta) {
5994
+ yield { content: delta.delta };
5995
+ }
5996
+ }
5997
+ const assistantEntry = {
5998
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
5999
+ chat_id: chatId,
6000
+ project_id: void 0,
6001
+ role: "assistant",
6002
+ content: assistantContent,
6003
+ ts: Date.now(),
6004
+ model: selectedModel,
6005
+ token_count: completionTokens,
6006
+ source: `offline_${dialect}`
6007
+ };
6008
+ await journal.journalAppend(assistantEntry);
6009
+ }
6010
+ if (backendReachable) {
6011
+ const pending = await journal.journalListPending();
6012
+ if (pending.length > 0) {
6013
+ this.syncPendingMessages().catch(
6014
+ (e) => console.warn("[Offline] Background sync failed:", e)
6015
+ );
6016
+ }
6017
+ }
6018
+ }
6019
+ /**
6020
+ * Sync pending journal entries to backend in batches of 50
6021
+ */
6022
+ async syncPendingMessages() {
6023
+ const journal = getJournal();
6024
+ const pending = await journal.journalListPending();
6025
+ if (pending.length === 0) return;
6026
+ const byChat = /* @__PURE__ */ new Map();
6027
+ for (const entry of pending) {
6028
+ if (!byChat.has(entry.chat_id)) {
6029
+ byChat.set(entry.chat_id, []);
6030
+ }
6031
+ byChat.get(entry.chat_id).push(entry);
6032
+ }
6033
+ for (const [chatId, entries] of byChat.entries()) {
6034
+ for (let i = 0; i < entries.length; i += 50) {
6035
+ const batch = entries.slice(i, i + 50);
6036
+ try {
6037
+ await this.client.importChat({
6038
+ chat_id: chatId,
6039
+ messages: batch.map((e) => ({
6040
+ role: e.role,
6041
+ content: e.content,
6042
+ model: e.model
6043
+ }))
6044
+ });
6045
+ await journal.journalMarkSynced(batch.map((e) => e.id));
6046
+ } catch (e) {
6047
+ console.warn("[Offline] Sync batch failed, will retry on next connection:", e);
6048
+ break;
6049
+ }
6050
+ }
6051
+ }
6052
+ }
5452
6053
  };
5453
6054
  }
5454
6055
  });
@@ -5518,11 +6119,11 @@ var init_Mutex = __esm({
5518
6119
  * `finally` block to prevent deadlocks on throw.
5519
6120
  */
5520
6121
  acquire() {
5521
- let release2;
6122
+ let release3;
5522
6123
  const next = new Promise((resolve19) => {
5523
- release2 = resolve19;
6124
+ release3 = resolve19;
5524
6125
  });
5525
- const entry = this._queue.then(() => release2);
6126
+ const entry = this._queue.then(() => release3);
5526
6127
  this._queue = this._queue.then(() => next);
5527
6128
  return entry;
5528
6129
  }
@@ -5531,11 +6132,11 @@ var init_Mutex = __esm({
5531
6132
  * release. Returns whatever `fn` returns.
5532
6133
  */
5533
6134
  async run(fn) {
5534
- const release2 = await this.acquire();
6135
+ const release3 = await this.acquire();
5535
6136
  try {
5536
6137
  return await fn();
5537
6138
  } finally {
5538
- release2();
6139
+ release3();
5539
6140
  }
5540
6141
  }
5541
6142
  };
@@ -5543,17 +6144,17 @@ var init_Mutex = __esm({
5543
6144
  });
5544
6145
 
5545
6146
  // ../core/src/TrustStore.ts
5546
- import { join as join13 } from "path";
5547
- import { homedir as homedir6, platform as platform2 } from "os";
6147
+ import { join as join15 } from "path";
6148
+ import { homedir as homedir8, platform as platform2 } from "os";
5548
6149
  import { existsSync as existsSync11, mkdirSync } from "fs";
5549
- import { readFile as readFile10, writeFile as writeFile5, chmod } from "fs/promises";
6150
+ import { readFile as readFile11, writeFile as writeFile5, chmod } from "fs/promises";
5550
6151
  var USER_SETTINGS_PATH, TrustStore;
5551
6152
  var init_TrustStore = __esm({
5552
6153
  "../core/src/TrustStore.ts"() {
5553
6154
  "use strict";
5554
6155
  init_esm_shims();
5555
6156
  init_Mutex();
5556
- USER_SETTINGS_PATH = join13(homedir6(), ".msapling", "settings.json");
6157
+ USER_SETTINGS_PATH = join15(homedir8(), ".msapling", "settings.json");
5557
6158
  TrustStore = class {
5558
6159
  /** Current in-memory set of trusted `tool:command` keys. */
5559
6160
  trusted = /* @__PURE__ */ new Set();
@@ -5571,7 +6172,7 @@ var init_TrustStore = __esm({
5571
6172
  async readSettings() {
5572
6173
  try {
5573
6174
  if (!existsSync11(this.settingsPath)) return {};
5574
- const text = await readFile10(this.settingsPath, "utf8");
6175
+ const text = await readFile11(this.settingsPath, "utf8");
5575
6176
  if (!text.trim()) return {};
5576
6177
  return JSON.parse(text);
5577
6178
  } catch {
@@ -5583,7 +6184,7 @@ var init_TrustStore = __esm({
5583
6184
  * updating `trustedCommands`.
5584
6185
  */
5585
6186
  async writeSettings(settings) {
5586
- const dir = join13(homedir6(), ".msapling");
6187
+ const dir = join15(homedir8(), ".msapling");
5587
6188
  if (!existsSync11(dir)) mkdirSync(dir, { recursive: true });
5588
6189
  await writeFile5(this.settingsPath, JSON.stringify(settings, null, 2), "utf8");
5589
6190
  if (platform2() !== "win32") {
@@ -5669,7 +6270,7 @@ var require_polyfills = __commonJS({
5669
6270
  var constants = __require("constants");
5670
6271
  var origCwd = process.cwd;
5671
6272
  var cwd = null;
5672
- var platform4 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
6273
+ var platform5 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
5673
6274
  process.cwd = function() {
5674
6275
  if (!cwd)
5675
6276
  cwd = origCwd.call(process);
@@ -5728,7 +6329,7 @@ var require_polyfills = __commonJS({
5728
6329
  fs3.lchownSync = function() {
5729
6330
  };
5730
6331
  }
5731
- if (platform4 === "win32") {
6332
+ if (platform5 === "win32") {
5732
6333
  fs3.rename = typeof fs3.rename !== "function" ? fs3.rename : (function(fs$rename) {
5733
6334
  function rename2(from, to, cb) {
5734
6335
  var start = Date.now();
@@ -6160,8 +6761,8 @@ var require_graceful_fs = __commonJS({
6160
6761
  fs4.createReadStream = createReadStream;
6161
6762
  fs4.createWriteStream = createWriteStream;
6162
6763
  var fs$readFile = fs4.readFile;
6163
- fs4.readFile = readFile24;
6164
- function readFile24(path2, options, cb) {
6764
+ fs4.readFile = readFile26;
6765
+ function readFile26(path2, options, cb) {
6165
6766
  if (typeof options === "function")
6166
6767
  cb = options, options = null;
6167
6768
  return go$readFile(path2, options, cb);
@@ -7230,12 +7831,12 @@ var require_proper_lockfile = __commonJS({
7230
7831
  var lockfile2 = require_lockfile();
7231
7832
  var { toPromise, toSync, toSyncOptions } = require_adapter();
7232
7833
  async function lock2(file, options) {
7233
- const release2 = await toPromise(lockfile2.lock)(file, options);
7234
- return toPromise(release2);
7834
+ const release3 = await toPromise(lockfile2.lock)(file, options);
7835
+ return toPromise(release3);
7235
7836
  }
7236
7837
  function lockSync(file, options) {
7237
- const release2 = toSync(lockfile2.lock)(file, toSyncOptions(options));
7238
- return toSync(release2);
7838
+ const release3 = toSync(lockfile2.lock)(file, toSyncOptions(options));
7839
+ return toSync(release3);
7239
7840
  }
7240
7841
  function unlock2(file, options) {
7241
7842
  return toPromise(lockfile2.unlock)(file, options);
@@ -7321,10 +7922,10 @@ var require_keytar2 = __commonJS({
7321
7922
  });
7322
7923
 
7323
7924
  // ../core/src/Storage.ts
7324
- import { join as join14 } from "path";
7325
- import { homedir as homedir7 } from "os";
7925
+ import { join as join16 } from "path";
7926
+ import { homedir as homedir9 } from "os";
7326
7927
  import { chmodSync, existsSync as existsSync12, renameSync, unlinkSync, writeFileSync } from "fs";
7327
- import { mkdir as mkdir6, writeFile as writeFile6, readFile as readFile11, appendFile } from "fs/promises";
7928
+ import { mkdir as mkdir6, writeFile as writeFile6, readFile as readFile12, appendFile } from "fs/promises";
7328
7929
  import { randomBytes as randomBytes7, createHash as createHash3 } from "crypto";
7329
7930
  function hashLine(line) {
7330
7931
  return createHash3("sha256").update(line, "utf8").digest("hex");
@@ -7355,7 +7956,7 @@ var init_Storage = __esm({
7355
7956
  */
7356
7957
  _ready;
7357
7958
  constructor() {
7358
- this.baseDir = join14(homedir7(), ".msapling");
7959
+ this.baseDir = join16(homedir9(), ".msapling");
7359
7960
  this._ready = this.ensureDirs();
7360
7961
  }
7361
7962
  async ensureDirs() {
@@ -7374,7 +7975,7 @@ var init_Storage = __esm({
7374
7975
  "cache/recipes/objects"
7375
7976
  ];
7376
7977
  for (const sub of subdirs) {
7377
- const path2 = join14(this.baseDir, sub);
7978
+ const path2 = join16(this.baseDir, sub);
7378
7979
  await mkdir6(path2, { recursive: true });
7379
7980
  }
7380
7981
  if (process.platform !== "win32") {
@@ -7392,7 +7993,7 @@ var init_Storage = __esm({
7392
7993
  await this._ready;
7393
7994
  const KEYCHAIN_SERVICE = "msapling-cli";
7394
7995
  const KEYCHAIN_ACCOUNT = "auth_token";
7395
- const filePath = join14(this.baseDir, "vault", "token");
7996
+ const filePath = join16(this.baseDir, "vault", "token");
7396
7997
  try {
7397
7998
  await keytar.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, token);
7398
7999
  try {
@@ -7412,7 +8013,7 @@ var init_Storage = __esm({
7412
8013
  async loadToken() {
7413
8014
  const KEYCHAIN_SERVICE = "msapling-cli";
7414
8015
  const KEYCHAIN_ACCOUNT = "auth_token";
7415
- const filePath = join14(this.baseDir, "vault", "token");
8016
+ const filePath = join16(this.baseDir, "vault", "token");
7416
8017
  try {
7417
8018
  const keychainToken = await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
7418
8019
  if (keychainToken) {
@@ -7422,7 +8023,7 @@ var init_Storage = __esm({
7422
8023
  console.debug(`Keychain unavailable (${e instanceof Error ? e.message : String(e)}), falling back to file storage`);
7423
8024
  }
7424
8025
  if (existsSync12(filePath)) {
7425
- const text = await readFile11(filePath, "utf8");
8026
+ const text = await readFile12(filePath, "utf8");
7426
8027
  return text.trim();
7427
8028
  }
7428
8029
  return null;
@@ -7433,7 +8034,7 @@ var init_Storage = __esm({
7433
8034
  async clearToken() {
7434
8035
  const KEYCHAIN_SERVICE = "msapling-cli";
7435
8036
  const KEYCHAIN_ACCOUNT = "auth_token";
7436
- const filePath = join14(this.baseDir, "vault", "token");
8037
+ const filePath = join16(this.baseDir, "vault", "token");
7437
8038
  try {
7438
8039
  await keytar.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
7439
8040
  } catch (e) {
@@ -7472,8 +8073,8 @@ var init_Storage = __esm({
7472
8073
  async writeVaultRef(label, value) {
7473
8074
  await this._ready;
7474
8075
  const hash = createHash3("sha256").update(value, "utf8").digest("hex");
7475
- const objectPath = join14(this.baseDir, "vault", "objects", hash);
7476
- const refPath = join14(this.baseDir, "vault", "refs", label);
8076
+ const objectPath = join16(this.baseDir, "vault", "objects", hash);
8077
+ const refPath = join16(this.baseDir, "vault", "refs", label);
7477
8078
  const refTmp = `${refPath}.tmp`;
7478
8079
  await writeFile6(objectPath, value, "utf8");
7479
8080
  if (process.platform !== "win32") {
@@ -7489,12 +8090,12 @@ var init_Storage = __esm({
7489
8090
  */
7490
8091
  async readVaultRef(label) {
7491
8092
  await this._ready;
7492
- const refPath = join14(this.baseDir, "vault", "refs", label);
8093
+ const refPath = join16(this.baseDir, "vault", "refs", label);
7493
8094
  if (!existsSync12(refPath)) return null;
7494
- const hash = (await readFile11(refPath, "utf8")).trim();
7495
- const objectPath = join14(this.baseDir, "vault", "objects", hash);
8095
+ const hash = (await readFile12(refPath, "utf8")).trim();
8096
+ const objectPath = join16(this.baseDir, "vault", "objects", hash);
7496
8097
  if (!existsSync12(objectPath)) return null;
7497
- return readFile11(objectPath, "utf8");
8098
+ return readFile12(objectPath, "utf8");
7498
8099
  }
7499
8100
  // ─────────────────────────────────────────────────────────────────────────
7500
8101
  // CLI-ARCH-RECIPE-AT-HASH-URI-01 — recipe hash registry helpers
@@ -7517,8 +8118,8 @@ var init_Storage = __esm({
7517
8118
  async registerRecipe(name, content) {
7518
8119
  await this._ready;
7519
8120
  const hash = createHash3("sha256").update(content, "utf8").digest("hex");
7520
- const objectPath = join14(this.baseDir, "cache", "recipes", "objects", hash);
7521
- const indexPath = join14(this.baseDir, "cache", "recipes", "index.json");
8121
+ const objectPath = join16(this.baseDir, "cache", "recipes", "objects", hash);
8122
+ const indexPath = join16(this.baseDir, "cache", "recipes", "index.json");
7522
8123
  const indexTmp = `${indexPath}.tmp`;
7523
8124
  if (!existsSync12(objectPath)) {
7524
8125
  await writeFile6(objectPath, content, "utf8");
@@ -7526,7 +8127,7 @@ var init_Storage = __esm({
7526
8127
  let index = {};
7527
8128
  if (existsSync12(indexPath)) {
7528
8129
  try {
7529
- index = JSON.parse(await readFile11(indexPath, "utf8"));
8130
+ index = JSON.parse(await readFile12(indexPath, "utf8"));
7530
8131
  } catch {
7531
8132
  index = {};
7532
8133
  }
@@ -7542,26 +8143,26 @@ var init_Storage = __esm({
7542
8143
  */
7543
8144
  async resolveRecipe(nameOrRef) {
7544
8145
  await this._ready;
7545
- const indexPath = join14(this.baseDir, "cache", "recipes", "index.json");
8146
+ const indexPath = join16(this.baseDir, "cache", "recipes", "index.json");
7546
8147
  const atIdx = nameOrRef.indexOf("@");
7547
8148
  if (atIdx !== -1) {
7548
8149
  const hash2 = nameOrRef.slice(atIdx + 1);
7549
- const objectPath2 = join14(this.baseDir, "cache", "recipes", "objects", hash2);
8150
+ const objectPath2 = join16(this.baseDir, "cache", "recipes", "objects", hash2);
7550
8151
  if (!existsSync12(objectPath2)) return null;
7551
- return { hash: hash2, content: await readFile11(objectPath2, "utf8") };
8152
+ return { hash: hash2, content: await readFile12(objectPath2, "utf8") };
7552
8153
  }
7553
8154
  if (!existsSync12(indexPath)) return null;
7554
8155
  let index;
7555
8156
  try {
7556
- index = JSON.parse(await readFile11(indexPath, "utf8"));
8157
+ index = JSON.parse(await readFile12(indexPath, "utf8"));
7557
8158
  } catch {
7558
8159
  return null;
7559
8160
  }
7560
8161
  const hash = index[nameOrRef];
7561
8162
  if (!hash) return null;
7562
- const objectPath = join14(this.baseDir, "cache", "recipes", "objects", hash);
8163
+ const objectPath = join16(this.baseDir, "cache", "recipes", "objects", hash);
7563
8164
  if (!existsSync12(objectPath)) return null;
7564
- return { hash, content: await readFile11(objectPath, "utf8") };
8165
+ return { hash, content: await readFile12(objectPath, "utf8") };
7565
8166
  }
7566
8167
  // ─────────────────────────────────────────────────────────────────────────
7567
8168
  // CLI-ARCH-HASH-CHAIN-HISTORY-01 — append-only NDJSON history with hash chain
@@ -7583,18 +8184,18 @@ var init_Storage = __esm({
7583
8184
  */
7584
8185
  async appendHistoryEntry(content) {
7585
8186
  await this._ready;
7586
- const path2 = join14(this.baseDir, "history", "shell_history.jsonl");
8187
+ const path2 = join16(this.baseDir, "history", "shell_history.jsonl");
7587
8188
  return this.historyMutex.run(async () => {
7588
- let release2 = null;
8189
+ let release3 = null;
7589
8190
  try {
7590
8191
  if (!existsSync12(path2)) {
7591
8192
  await writeFile6(path2, "", "utf8");
7592
8193
  }
7593
- release2 = await lockfile.lock(path2, { retries: 5, retryWait: 50 });
8194
+ release3 = await lockfile.lock(path2, { retries: 5, retryWait: 50 });
7594
8195
  let prevHash = null;
7595
8196
  let seq = 1;
7596
8197
  if (existsSync12(path2)) {
7597
- const raw = (await readFile11(path2, "utf8")).trimEnd();
8198
+ const raw = (await readFile12(path2, "utf8")).trimEnd();
7598
8199
  if (raw.length > 0) {
7599
8200
  const lines = raw.split("\n");
7600
8201
  const lastLine = lines[lines.length - 1];
@@ -7616,7 +8217,7 @@ var init_Storage = __esm({
7616
8217
  await appendFile(path2, line + "\n", "utf8");
7617
8218
  return entry;
7618
8219
  } finally {
7619
- if (release2) {
8220
+ if (release3) {
7620
8221
  try {
7621
8222
  await lockfile.unlock(path2, { skipStale: true });
7622
8223
  } catch {
@@ -7633,9 +8234,9 @@ var init_Storage = __esm({
7633
8234
  */
7634
8235
  async loadHistoryEntries() {
7635
8236
  await this._ready;
7636
- const path2 = join14(this.baseDir, "history", "shell_history.jsonl");
8237
+ const path2 = join16(this.baseDir, "history", "shell_history.jsonl");
7637
8238
  if (!existsSync12(path2)) return [];
7638
- const raw = await readFile11(path2, "utf8");
8239
+ const raw = await readFile12(path2, "utf8");
7639
8240
  const entries = [];
7640
8241
  for (const line of raw.split("\n")) {
7641
8242
  if (!line.trim()) continue;
@@ -7683,11 +8284,11 @@ var init_Storage = __esm({
7683
8284
  */
7684
8285
  async saveHistory(history) {
7685
8286
  await this._ready;
7686
- const path2 = join14(this.baseDir, "history", "shell_history.json");
7687
- let release2;
8287
+ const path2 = join16(this.baseDir, "history", "shell_history.json");
8288
+ let release3;
7688
8289
  try {
7689
8290
  if (!existsSync12(path2)) writeFileSync(path2, "[]", "utf8");
7690
- release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8291
+ release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
7691
8292
  await this.historyMutex.run(async () => {
7692
8293
  const tmpPath = `${path2}.tmp`;
7693
8294
  const content = JSON.stringify(history, null, 2);
@@ -7706,7 +8307,7 @@ var init_Storage = __esm({
7706
8307
  }
7707
8308
  });
7708
8309
  } finally {
7709
- if (release2) {
8310
+ if (release3) {
7710
8311
  try {
7711
8312
  await lockfile.unlock(path2, { skipStale: true });
7712
8313
  } catch (e) {
@@ -7724,21 +8325,21 @@ var init_Storage = __esm({
7724
8325
  */
7725
8326
  async loadHistory() {
7726
8327
  await this._ready;
7727
- const path2 = join14(this.baseDir, "history", "shell_history.json");
8328
+ const path2 = join16(this.baseDir, "history", "shell_history.json");
7728
8329
  if (!existsSync12(path2)) return [];
7729
- let release2;
8330
+ let release3;
7730
8331
  try {
7731
- release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8332
+ release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
7732
8333
  return this.historyMutex.run(async () => {
7733
8334
  if (existsSync12(path2)) {
7734
- const text = await readFile11(path2, "utf8");
8335
+ const text = await readFile12(path2, "utf8");
7735
8336
  try {
7736
8337
  return JSON.parse(text);
7737
8338
  } catch (parseErr) {
7738
8339
  const filename = path2.split("/").pop() || "shell_history.json";
7739
8340
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7740
8341
  const suffix = randomBytes7(4).toString("hex");
7741
- const corruptBackupPath = join14(
8342
+ const corruptBackupPath = join16(
7742
8343
  this.baseDir,
7743
8344
  "history",
7744
8345
  `${filename}.corrupt.${stamp}-${suffix}.bak`
@@ -7755,7 +8356,7 @@ var init_Storage = __esm({
7755
8356
  return [];
7756
8357
  });
7757
8358
  } finally {
7758
- if (release2) {
8359
+ if (release3) {
7759
8360
  try {
7760
8361
  await lockfile.unlock(path2, { skipStale: true });
7761
8362
  } catch (e) {
@@ -7769,11 +8370,11 @@ var init_Storage = __esm({
7769
8370
  * R20-CLI-2: Typed with PermissionState from Sandbox.ts.
7770
8371
  */
7771
8372
  async savePermissions(permissions) {
7772
- const path2 = join14(this.baseDir, "vault", "permissions.json");
7773
- let release2;
8373
+ const path2 = join16(this.baseDir, "vault", "permissions.json");
8374
+ let release3;
7774
8375
  try {
7775
8376
  if (!existsSync12(path2)) writeFileSync(path2, "{}", "utf8");
7776
- release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8377
+ release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
7777
8378
  await this.permissionsMutex.run(async () => {
7778
8379
  const tmpPath = `${path2}.tmp`;
7779
8380
  const content = JSON.stringify(permissions, null, 2);
@@ -7792,7 +8393,7 @@ var init_Storage = __esm({
7792
8393
  }
7793
8394
  });
7794
8395
  } finally {
7795
- if (release2) {
8396
+ if (release3) {
7796
8397
  try {
7797
8398
  await lockfile.unlock(path2, { skipStale: true });
7798
8399
  } catch (e) {
@@ -7806,21 +8407,21 @@ var init_Storage = __esm({
7806
8407
  * R20-CLI-2: Typed with PermissionState from Sandbox.ts.
7807
8408
  */
7808
8409
  async loadPermissions() {
7809
- const path2 = join14(this.baseDir, "vault", "permissions.json");
8410
+ const path2 = join16(this.baseDir, "vault", "permissions.json");
7810
8411
  if (!existsSync12(path2)) return { trustedCommands: [], trustedPaths: [] };
7811
- let release2;
8412
+ let release3;
7812
8413
  try {
7813
- release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
8414
+ release3 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
7814
8415
  return this.permissionsMutex.run(async () => {
7815
8416
  if (existsSync12(path2)) {
7816
- const text = await readFile11(path2, "utf8");
8417
+ const text = await readFile12(path2, "utf8");
7817
8418
  try {
7818
8419
  return JSON.parse(text);
7819
8420
  } catch (parseErr) {
7820
8421
  const filename = path2.split("/").pop() || "permissions.json";
7821
8422
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7822
8423
  const suffix = randomBytes7(4).toString("hex");
7823
- const corruptBackupPath = join14(
8424
+ const corruptBackupPath = join16(
7824
8425
  this.baseDir,
7825
8426
  "vault",
7826
8427
  `${filename}.corrupt.${stamp}-${suffix}.bak`
@@ -7837,7 +8438,7 @@ var init_Storage = __esm({
7837
8438
  return { trustedCommands: [], trustedPaths: [] };
7838
8439
  });
7839
8440
  } finally {
7840
- if (release2) {
8441
+ if (release3) {
7841
8442
  try {
7842
8443
  await lockfile.unlock(path2, { skipStale: true });
7843
8444
  } catch (e) {
@@ -7857,7 +8458,7 @@ var init_Storage = __esm({
7857
8458
  const filename = filePath.split("/").pop() || "file";
7858
8459
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7859
8460
  const suffix = randomBytes7(4).toString("hex");
7860
- const backupPath = join14(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
8461
+ const backupPath = join16(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
7861
8462
  await writeFile6(backupPath, content, "utf8");
7862
8463
  if (process.platform !== "win32") {
7863
8464
  chmodSync(backupPath, 384);
@@ -7869,11 +8470,11 @@ var init_Storage = __esm({
7869
8470
  });
7870
8471
 
7871
8472
  // ../core/src/Settings.ts
7872
- import { homedir as homedir8 } from "os";
7873
- import { join as join15 } from "path";
8473
+ import { homedir as homedir10 } from "os";
8474
+ import { join as join17 } from "path";
7874
8475
  import { existsSync as existsSync13 } from "fs";
7875
8476
  import * as fs from "fs";
7876
- import { readFile as readFile12 } from "fs/promises";
8477
+ import { readFile as readFile13 } from "fs/promises";
7877
8478
  import { randomBytes as randomBytes8 } from "crypto";
7878
8479
  function ensureConfigDir(p) {
7879
8480
  try {
@@ -7895,7 +8496,7 @@ function ensureConfigDir(p) {
7895
8496
  async function readJson(path2) {
7896
8497
  try {
7897
8498
  if (!existsSync13(path2)) return null;
7898
- const text = await readFile12(path2, "utf8");
8499
+ const text = await readFile13(path2, "utf8");
7899
8500
  if (!text.trim()) return null;
7900
8501
  return JSON.parse(text);
7901
8502
  } catch {
@@ -7949,8 +8550,8 @@ function mergeSettings(base, override) {
7949
8550
  }
7950
8551
  async function loadSettings(cwd = process.cwd(), env = process.env, warn) {
7951
8552
  const sources = [];
7952
- const userPath = join15(homedir8() || ".", ".msapling", "settings.json");
7953
- const projectPath = join15(cwd, ".msapling", "settings.json");
8553
+ const userPath = join17(homedir10() || ".", ".msapling", "settings.json");
8554
+ const projectPath = join17(cwd, ".msapling", "settings.json");
7954
8555
  const [user, project] = await Promise.all([readJson(userPath), readJson(projectPath)]);
7955
8556
  if (user) sources.push(userPath);
7956
8557
  if (project) sources.push(projectPath);
@@ -8617,10 +9218,10 @@ var init_unlock = __esm({
8617
9218
  });
8618
9219
 
8619
9220
  // src/commands/doctor.ts
8620
- import { homedir as homedir9 } from "os";
8621
- import { join as join16 } from "path";
9221
+ import { homedir as homedir11 } from "os";
9222
+ import { join as join18 } from "path";
8622
9223
  import { existsSync as existsSync14 } from "fs";
8623
- import { readFile as readFile13 } from "fs/promises";
9224
+ import { readFile as readFile14 } from "fs/promises";
8624
9225
  async function checkApiHealth(client) {
8625
9226
  try {
8626
9227
  if (typeof client.request !== "function") {
@@ -8645,12 +9246,12 @@ async function checkAuthStatus(client) {
8645
9246
  }
8646
9247
  }
8647
9248
  async function checkSettingsFile() {
8648
- const settingsPath = join16(homedir9(), ".msapling", "settings.json");
9249
+ const settingsPath = join18(homedir11(), ".msapling", "settings.json");
8649
9250
  try {
8650
9251
  if (!existsSync14(settingsPath)) {
8651
9252
  return { ok: false, message: `Not found: ${settingsPath}` };
8652
9253
  }
8653
- const text = await readFile13(settingsPath, "utf8");
9254
+ const text = await readFile14(settingsPath, "utf8");
8654
9255
  const parsed = JSON.parse(text);
8655
9256
  return { ok: true, message: `Valid JSON (${settingsPath})`, settings: parsed };
8656
9257
  } catch (e) {
@@ -9334,7 +9935,7 @@ var init_memories = __esm({
9334
9935
  });
9335
9936
 
9336
9937
  // src/commands/mdrive.ts
9337
- import { readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
9938
+ import { readFile as readFile15, writeFile as writeFile7 } from "fs/promises";
9338
9939
  import { existsSync as existsSync15 } from "fs";
9339
9940
  import { basename, resolve as resolve14 } from "path";
9340
9941
  function formatBytes(b) {
@@ -9392,7 +9993,7 @@ var init_mdrive = __esm({
9392
9993
  context.addMessage("error", `Local file not found: ${absLocal}`);
9393
9994
  return;
9394
9995
  }
9395
- const content = await readFile14(absLocal, "utf8");
9996
+ const content = await readFile15(absLocal, "utf8");
9396
9997
  const res = await context.client.mdriveWrite(remote, content);
9397
9998
  context.addMessage("system", `Uploaded ${absLocal} \u2192 mdrive:${remote} (${formatBytes(content.length)}, hash=${res?.hash?.slice(0, 12) ?? "?"})`);
9398
9999
  return;
@@ -9485,15 +10086,15 @@ var init_clear = __esm({
9485
10086
  });
9486
10087
 
9487
10088
  // src/commands/mode.ts
9488
- import { homedir as homedir10 } from "os";
9489
- import { join as join17 } from "path";
10089
+ import { homedir as homedir12 } from "os";
10090
+ import { join as join19 } from "path";
9490
10091
  import { existsSync as existsSync16 } from "fs";
9491
- import { readFile as readFile15, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
10092
+ import { readFile as readFile16, writeFile as writeFile8, mkdir as mkdir7 } from "fs/promises";
9492
10093
  async function persistApprovalMode(mode, ttlMs) {
9493
10094
  try {
9494
10095
  let existing = {};
9495
10096
  if (existsSync16(SETTINGS_PATH)) {
9496
- const text = await readFile15(SETTINGS_PATH, "utf8");
10097
+ const text = await readFile16(SETTINGS_PATH, "utf8");
9497
10098
  if (text.trim()) {
9498
10099
  existing = JSON.parse(text);
9499
10100
  }
@@ -9504,7 +10105,7 @@ async function persistApprovalMode(mode, ttlMs) {
9504
10105
  ...ttlMs && { ttlMs }
9505
10106
  };
9506
10107
  existing.approvalMode = entry;
9507
- const settingsDir = join17(homedir10(), ".msapling");
10108
+ const settingsDir = join19(homedir12(), ".msapling");
9508
10109
  if (!existsSync16(settingsDir)) {
9509
10110
  await mkdir7(settingsDir, { recursive: true });
9510
10111
  }
@@ -9517,7 +10118,7 @@ var init_mode = __esm({
9517
10118
  "src/commands/mode.ts"() {
9518
10119
  "use strict";
9519
10120
  init_esm_shims();
9520
- SETTINGS_PATH = join17(homedir10(), ".msapling", "settings.json");
10121
+ SETTINGS_PATH = join19(homedir12(), ".msapling", "settings.json");
9521
10122
  modeCommand = {
9522
10123
  name: "mode",
9523
10124
  args: "[default|plan|acceptEdits|bypassPermissions] [...options]",
@@ -9919,7 +10520,7 @@ var init_compact = __esm({
9919
10520
  });
9920
10521
 
9921
10522
  // src/commands/init.ts
9922
- import { join as join18 } from "path";
10523
+ import { join as join20 } from "path";
9923
10524
  import { existsSync as existsSync17 } from "fs";
9924
10525
  import { writeFile as writeFile9 } from "fs/promises";
9925
10526
  var initCommand;
@@ -9934,7 +10535,7 @@ var init_init = __esm({
9934
10535
  handler: async (args2, context) => {
9935
10536
  try {
9936
10537
  const cwd = process.cwd();
9937
- const path2 = join18(cwd, "MSAPLING.md");
10538
+ const path2 = join20(cwd, "MSAPLING.md");
9938
10539
  if (existsSync17(path2)) {
9939
10540
  context.addMessage("error", "MSAPLING.md already exists in current directory.");
9940
10541
  return;
@@ -9962,7 +10563,7 @@ var init_init = __esm({
9962
10563
 
9963
10564
  // src/commands/review.ts
9964
10565
  import { existsSync as existsSync18 } from "fs";
9965
- import { readFile as readFile16 } from "fs/promises";
10566
+ import { readFile as readFile17 } from "fs/promises";
9966
10567
  var reviewCommand;
9967
10568
  var init_review = __esm({
9968
10569
  "src/commands/review.ts"() {
@@ -9982,7 +10583,7 @@ var init_review = __esm({
9982
10583
  let content = "";
9983
10584
  try {
9984
10585
  if (existsSync18(target)) {
9985
- content = await readFile16(target, "utf8");
10586
+ content = await readFile17(target, "utf8");
9986
10587
  } else {
9987
10588
  content = `Review target: ${target}`;
9988
10589
  }
@@ -10076,13 +10677,13 @@ var init_swarm = __esm({
10076
10677
  // src/commands/recipe.ts
10077
10678
  import { parse as parseYaml } from "yaml";
10078
10679
  import { existsSync as existsSync19 } from "fs";
10079
- import { readFile as readFile17 } from "fs/promises";
10080
- import { join as join19 } from "path";
10680
+ import { readFile as readFile18 } from "fs/promises";
10681
+ import { join as join21 } from "path";
10081
10682
  function findRecipe(name, cwd) {
10082
10683
  for (const dir of RECIPE_DIRS) {
10083
10684
  for (const suffix of NAME_SUFFIXES) {
10084
10685
  for (const ext of FILE_EXTS) {
10085
- const p = join19(cwd, dir, `${name}${suffix}${ext}`);
10686
+ const p = join21(cwd, dir, `${name}${suffix}${ext}`);
10086
10687
  if (existsSync19(p)) return p;
10087
10688
  }
10088
10689
  }
@@ -10142,7 +10743,7 @@ var init_recipe = __esm({
10142
10743
  let text;
10143
10744
  let recipe;
10144
10745
  try {
10145
- text = await readFile17(path2, "utf8");
10746
+ text = await readFile18(path2, "utf8");
10146
10747
  recipe = parseYaml(text);
10147
10748
  } catch (e) {
10148
10749
  context.addMessage("error", `Failed to load ${path2}: ${e.message}`);
@@ -10197,8 +10798,8 @@ ${rendered}` : rendered;
10197
10798
 
10198
10799
  // src/commands/skill.ts
10199
10800
  import { existsSync as existsSync20, readdirSync as readdirSync2, statSync as statSync5 } from "fs";
10200
- import { readFile as readFile18 } from "fs/promises";
10201
- import { join as join20, resolve as resolve15 } from "path";
10801
+ import { readFile as readFile19 } from "fs/promises";
10802
+ import { join as join22, resolve as resolve15 } from "path";
10202
10803
  function findSkillsRoot(cwd) {
10203
10804
  for (const candidate of SKILLS_DIRS) {
10204
10805
  const full = resolve15(cwd, candidate);
@@ -10215,7 +10816,7 @@ function listAllSkills(root) {
10215
10816
  return out;
10216
10817
  }
10217
10818
  for (const domain of domains) {
10218
- const dir = join20(root, domain);
10819
+ const dir = join22(root, domain);
10219
10820
  let s;
10220
10821
  try {
10221
10822
  s = statSync5(dir);
@@ -10231,7 +10832,7 @@ function listAllSkills(root) {
10231
10832
  }
10232
10833
  for (const f of files) {
10233
10834
  if (!f.endsWith(".md")) continue;
10234
- out.push({ domain, name: f.slice(0, -3), path: join20(dir, f) });
10835
+ out.push({ domain, name: f.slice(0, -3), path: join22(dir, f) });
10235
10836
  }
10236
10837
  }
10237
10838
  return out.sort(
@@ -10296,7 +10897,7 @@ var init_skill = __esm({
10296
10897
  }
10297
10898
  let body;
10298
10899
  try {
10299
- body = await readFile18(skill.path, "utf8");
10900
+ body = await readFile19(skill.path, "utf8");
10300
10901
  } catch (e) {
10301
10902
  context.addMessage("error", `Failed to load skill ${skill.path}: ${e.message}`);
10302
10903
  return;
@@ -10320,8 +10921,8 @@ ${prompt4}`;
10320
10921
  });
10321
10922
 
10322
10923
  // src/commands/benchmark.ts
10323
- import { homedir as homedir11 } from "os";
10324
- import { join as join21 } from "path";
10924
+ import { homedir as homedir13 } from "os";
10925
+ import { join as join23 } from "path";
10325
10926
  import { mkdirSync as mkdirSync4 } from "fs";
10326
10927
  import * as fs2 from "fs";
10327
10928
  function parseArgs(args2) {
@@ -10441,10 +11042,10 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
10441
11042
  `[HW at run time: CPU ${hwAtEnd.cpuPct}% / RAM ${hwAtEnd.ramPct}% | ${hw.ramGiB} GiB RAM, ${hw.cores} cores]`
10442
11043
  );
10443
11044
  try {
10444
- const dir = join21(homedir11(), ".msapling", "benchmarks");
11045
+ const dir = join23(homedir13(), ".msapling", "benchmarks");
10445
11046
  mkdirSync4(dir, { recursive: true });
10446
11047
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
10447
- const file = join21(dir, `${ts}.json`);
11048
+ const file = join23(dir, `${ts}.json`);
10448
11049
  const run = {
10449
11050
  ts: (/* @__PURE__ */ new Date()).toISOString(),
10450
11051
  rounds,
@@ -10690,22 +11291,22 @@ var init_theme = __esm({
10690
11291
  });
10691
11292
 
10692
11293
  // src/commands/theme.ts
10693
- import { join as join22 } from "path";
10694
- import { homedir as homedir12 } from "os";
11294
+ import { join as join24 } from "path";
11295
+ import { homedir as homedir14 } from "os";
10695
11296
  import { existsSync as existsSync21 } from "fs";
10696
- import { readFile as readFile19, writeFile as writeFile10 } from "fs/promises";
11297
+ import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
10697
11298
  async function persistTheme(storage, themeName) {
10698
- const settingsPath = join22(homedir12(), ".msapling", "settings.json");
11299
+ const settingsPath = join24(homedir14(), ".msapling", "settings.json");
10699
11300
  let existing = {};
10700
11301
  try {
10701
11302
  if (existsSync21(settingsPath)) {
10702
- const text = await readFile19(settingsPath, "utf8");
11303
+ const text = await readFile20(settingsPath, "utf8");
10703
11304
  if (text.trim()) existing = JSON.parse(text);
10704
11305
  }
10705
11306
  } catch {
10706
11307
  }
10707
11308
  existing["theme"] = themeName;
10708
- ensureConfigDir(join22(homedir12(), ".msapling"));
11309
+ ensureConfigDir(join24(homedir14(), ".msapling"));
10709
11310
  await writeFile10(settingsPath, JSON.stringify(existing, null, 2), "utf8");
10710
11311
  }
10711
11312
  var VALID_THEMES, themeCommand;
@@ -10777,7 +11378,7 @@ var init_version = __esm({
10777
11378
  description: "Show version information for CLI and core packages",
10778
11379
  category: "debug",
10779
11380
  handler: async (_args, context) => {
10780
- const cliVersion = true ? "2.3.6-beta.28" : "(dev)";
11381
+ const cliVersion = true ? "2.3.6-beta.30" : "(dev)";
10781
11382
  const coreVersion = true ? "2.3.2" : "(dev)";
10782
11383
  const runtime = process.version;
10783
11384
  context.addMessage("system", "MSapling Version Info");
@@ -10786,7 +11387,7 @@ var init_version = __esm({
10786
11387
  context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
10787
11388
  context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
10788
11389
  try {
10789
- const ts = "2026-05-29T17:03:26.902Z";
11390
+ const ts = "2026-05-29T20:13:59.523Z";
10790
11391
  if (ts && ts !== "__BUILD_TIMESTAMP__") {
10791
11392
  context.addMessage("system", row2("Build Timestamp", ts));
10792
11393
  }
@@ -10799,15 +11400,15 @@ var init_version = __esm({
10799
11400
  });
10800
11401
 
10801
11402
  // src/commands/feedback.ts
10802
- import { join as join23 } from "path";
11403
+ import { join as join25 } from "path";
10803
11404
  import { existsSync as existsSync22 } from "fs";
10804
- import { readFile as readFile20 } from "fs/promises";
11405
+ import { readFile as readFile21 } from "fs/promises";
10805
11406
  async function readCliVersion() {
10806
11407
  try {
10807
11408
  const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
10808
- const pkgPath = join23(baseDir, "..", "..", "package.json");
11409
+ const pkgPath = join25(baseDir, "..", "..", "package.json");
10809
11410
  if (!existsSync22(pkgPath)) return "unknown";
10810
- const text = await readFile20(pkgPath, "utf8");
11411
+ const text = await readFile21(pkgPath, "utf8");
10811
11412
  const json = JSON.parse(text);
10812
11413
  return json.version ?? "unknown";
10813
11414
  } catch {
@@ -10847,8 +11448,8 @@ var init_feedback = __esm({
10847
11448
  });
10848
11449
 
10849
11450
  // src/commands/export.ts
10850
- import { homedir as homedir13 } from "os";
10851
- import { join as join24 } from "path";
11451
+ import { homedir as homedir15 } from "os";
11452
+ import { join as join26 } from "path";
10852
11453
  import { writeFile as writeFile11, mkdir as mkdir8 } from "fs/promises";
10853
11454
  function formatTimestamp(date) {
10854
11455
  return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
@@ -10898,10 +11499,10 @@ var init_export = __esm({
10898
11499
  let outputPath;
10899
11500
  let content;
10900
11501
  if (arg === "" || arg === "json") {
10901
- outputPath = join24(homedir13(), `msapling-export-${timestamp}.json`);
11502
+ outputPath = join26(homedir15(), `msapling-export-${timestamp}.json`);
10902
11503
  content = buildJsonExport(history);
10903
11504
  } else if (arg === "markdown" || arg === "md") {
10904
- outputPath = join24(homedir13(), `msapling-export-${timestamp}.md`);
11505
+ outputPath = join26(homedir15(), `msapling-export-${timestamp}.md`);
10905
11506
  content = buildMarkdownExport(history);
10906
11507
  } else {
10907
11508
  outputPath = arg;
@@ -10913,7 +11514,7 @@ var init_export = __esm({
10913
11514
  }
10914
11515
  }
10915
11516
  try {
10916
- const dir = join24(outputPath, "..");
11517
+ const dir = join26(outputPath, "..");
10917
11518
  await mkdir8(dir, { recursive: true });
10918
11519
  await writeFile11(outputPath, content, "utf8");
10919
11520
  context.addMessage("system", `Exported to: ${outputPath}`);
@@ -11108,17 +11709,17 @@ var init_plan = __esm({
11108
11709
  });
11109
11710
 
11110
11711
  // src/commands/note.ts
11111
- import { homedir as homedir14 } from "os";
11112
- import { join as join25 } from "path";
11712
+ import { homedir as homedir16 } from "os";
11713
+ import { join as join27 } from "path";
11113
11714
  import { existsSync as existsSync23 } from "fs";
11114
- import { readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
11715
+ import { readFile as readFile22, writeFile as writeFile12 } from "fs/promises";
11115
11716
  function getNotesFilePath() {
11116
- return join25(homedir14(), ".msapling", "notes.json");
11717
+ return join27(homedir16(), ".msapling", "notes.json");
11117
11718
  }
11118
11719
  async function readNotes(filePath = getNotesFilePath()) {
11119
11720
  try {
11120
11721
  if (!existsSync23(filePath)) return [];
11121
- const raw = await readFile21(filePath, "utf8");
11722
+ const raw = await readFile22(filePath, "utf8");
11122
11723
  const parsed = JSON.parse(raw);
11123
11724
  if (!Array.isArray(parsed)) return [];
11124
11725
  return parsed;
@@ -11127,7 +11728,7 @@ async function readNotes(filePath = getNotesFilePath()) {
11127
11728
  }
11128
11729
  }
11129
11730
  async function writeNotes(notes, filePath = getNotesFilePath()) {
11130
- const dir = join25(homedir14(), ".msapling");
11731
+ const dir = join27(homedir16(), ".msapling");
11131
11732
  ensureConfigDir(dir);
11132
11733
  await writeFile12(filePath, JSON.stringify(notes, null, 2), "utf8");
11133
11734
  }
@@ -11273,14 +11874,14 @@ var init_todo = __esm({
11273
11874
  });
11274
11875
 
11275
11876
  // src/commands/outputStyle.ts
11276
- import { homedir as homedir15 } from "os";
11277
- import { join as join26, basename as basename2, extname as extname3 } from "path";
11877
+ import { homedir as homedir17 } from "os";
11878
+ import { join as join28, basename as basename2, extname as extname3 } from "path";
11278
11879
  import { existsSync as existsSync24, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
11279
11880
  function stylesDir() {
11280
- return join26(homedir15(), ".msapling", "output-styles");
11881
+ return join28(homedir17(), ".msapling", "output-styles");
11281
11882
  }
11282
11883
  function activeFile() {
11283
- return join26(stylesDir(), ".active");
11884
+ return join28(stylesDir(), ".active");
11284
11885
  }
11285
11886
  function parseStyleFile(text) {
11286
11887
  const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
@@ -11305,7 +11906,7 @@ function listUserStyles() {
11305
11906
  const out = [];
11306
11907
  for (const entry of readdirSync3(dir)) {
11307
11908
  if (extname3(entry).toLowerCase() !== ".md") continue;
11308
- const full = join26(dir, entry);
11909
+ const full = join28(dir, entry);
11309
11910
  try {
11310
11911
  const text = readFileSync(full, "utf8");
11311
11912
  const { description, body } = parseStyleFile(text);
@@ -11355,7 +11956,7 @@ function createUserStyle(name, description, body) {
11355
11956
  }
11356
11957
  const dir = stylesDir();
11357
11958
  if (!existsSync24(dir)) mkdirSync5(dir, { recursive: true });
11358
- const target = join26(dir, `${name}.md`);
11959
+ const target = join28(dir, `${name}.md`);
11359
11960
  const frontmatter = `---
11360
11961
  description: ${description.replace(/\n/g, " ")}
11361
11962
  ---
@@ -12027,6 +12628,442 @@ var init_webhook = __esm({
12027
12628
  }
12028
12629
  });
12029
12630
 
12631
+ // src/commands/sync.ts
12632
+ var syncCommand;
12633
+ var init_sync = __esm({
12634
+ "src/commands/sync.ts"() {
12635
+ "use strict";
12636
+ init_esm_shims();
12637
+ init_src();
12638
+ syncCommand = {
12639
+ name: "sync",
12640
+ description: "Manage offline message sync (status|push)",
12641
+ args: "[status|push]",
12642
+ category: "config",
12643
+ handler: async (args2, context) => {
12644
+ const subcommand = (args2[0] || "status").toLowerCase();
12645
+ const journal = getJournal();
12646
+ switch (subcommand) {
12647
+ case "status": {
12648
+ const { pending, total } = await journal.journalCount();
12649
+ context.addMessage("assistant", [
12650
+ `Offline messages journal:`,
12651
+ ` Total: ${total}`,
12652
+ ` Pending sync: ${pending}`,
12653
+ ` Synced: ${total - pending}`,
12654
+ ...pending > 0 ? [``, `Run /sync push to sync pending messages to the backend.`] : []
12655
+ ].join("\n"));
12656
+ break;
12657
+ }
12658
+ case "push": {
12659
+ const pending = await journal.journalListPending();
12660
+ if (pending.length === 0) {
12661
+ context.addMessage("assistant", "No pending messages to sync.");
12662
+ break;
12663
+ }
12664
+ context.addMessage("assistant", `Syncing ${pending.length} message(s)...`);
12665
+ const byChat = /* @__PURE__ */ new Map();
12666
+ for (const entry of pending) {
12667
+ if (!byChat.has(entry.chat_id)) {
12668
+ byChat.set(entry.chat_id, []);
12669
+ }
12670
+ byChat.get(entry.chat_id).push(entry);
12671
+ }
12672
+ let synced = 0;
12673
+ let failed = 0;
12674
+ for (const [chatId, entries] of byChat.entries()) {
12675
+ for (let i = 0; i < entries.length; i += 50) {
12676
+ const batch = entries.slice(i, i + 50);
12677
+ try {
12678
+ await context.client.importChat({
12679
+ chat_id: chatId,
12680
+ messages: batch.map((e) => ({
12681
+ role: e.role,
12682
+ content: e.content,
12683
+ model: e.model
12684
+ }))
12685
+ });
12686
+ await journal.journalMarkSynced(batch.map((e) => e.id));
12687
+ synced += batch.length;
12688
+ } catch (e) {
12689
+ context.addMessage(
12690
+ "error",
12691
+ `Error syncing batch from chat ${chatId}: ${String(e).slice(0, 100)}`
12692
+ );
12693
+ failed += batch.length;
12694
+ }
12695
+ }
12696
+ }
12697
+ context.addMessage("assistant", `Sync complete: ${synced} synced, ${failed} failed`);
12698
+ break;
12699
+ }
12700
+ default:
12701
+ context.addMessage("error", `Unknown sync subcommand: ${subcommand}
12702
+ Usage: /sync [status|push]`);
12703
+ }
12704
+ }
12705
+ };
12706
+ }
12707
+ });
12708
+
12709
+ // ../core/src/diagnostics/specs.ts
12710
+ import { cpus as cpus2, totalmem as totalmem2, freemem as freemem2, platform as platform3, arch as arch2 } from "os";
12711
+ import { execSync } from "child_process";
12712
+ async function collectSpecs() {
12713
+ const cpuList = cpus2();
12714
+ const totalMemBytes = totalmem2();
12715
+ const freeMemBytes = freemem2();
12716
+ const specs = {
12717
+ cpu: {
12718
+ cores: cpuList.length,
12719
+ model: cpuList[0]?.model ?? "Unknown",
12720
+ speed: cpuList[0]?.speed ?? 0
12721
+ // MHz -> convert below
12722
+ },
12723
+ memory: {
12724
+ totalGB: Math.round(totalMemBytes / 1024 ** 3 * 10) / 10,
12725
+ freeGB: Math.round(freeMemBytes / 1024 ** 3 * 10) / 10
12726
+ },
12727
+ gpu: [],
12728
+ disk: {
12729
+ freeGB: 0,
12730
+ totalGB: 0
12731
+ },
12732
+ platform: platform3(),
12733
+ arch: arch2(),
12734
+ nodeVersion: process.version.slice(1)
12735
+ // Remove 'v' prefix
12736
+ };
12737
+ specs.cpu.speed = Math.round(specs.cpu.speed / 1e3 * 10) / 10;
12738
+ try {
12739
+ const { statfs } = await import("fs/promises");
12740
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "/";
12741
+ const stat5 = await statfs(homeDir);
12742
+ specs.disk.freeGB = Math.round(stat5.bavail * stat5.bsize / 1024 ** 3 * 10) / 10;
12743
+ specs.disk.totalGB = Math.round(stat5.blocks * stat5.bsize / 1024 ** 3 * 10) / 10;
12744
+ } catch {
12745
+ specs.disk.freeGB = -1;
12746
+ specs.disk.totalGB = -1;
12747
+ }
12748
+ specs.gpu = await detectGpu();
12749
+ return specs;
12750
+ }
12751
+ async function detectGpu() {
12752
+ const currentPlatform = platform3();
12753
+ if (currentPlatform === "win32") {
12754
+ return detectGpuWindows();
12755
+ } else if (currentPlatform === "darwin") {
12756
+ return detectGpuMacOS();
12757
+ } else {
12758
+ return detectGpuLinux();
12759
+ }
12760
+ }
12761
+ function detectGpuWindows() {
12762
+ try {
12763
+ const output = execSync(
12764
+ 'powershell -Command "Get-CimInstance Win32_VideoController | Select-Object Name, AdapterRAM"',
12765
+ { timeout: 2e3, encoding: "utf-8" }
12766
+ );
12767
+ const gpus = [];
12768
+ const lines = output.split("\n");
12769
+ for (const line of lines) {
12770
+ const parts = line.trim().split(/\s{2,}/);
12771
+ if (parts[0] && parts[0] !== "Name") {
12772
+ const memBytes = parseInt(parts[1] || "0");
12773
+ gpus.push({
12774
+ name: parts[0],
12775
+ memoryGB: memBytes > 0 ? Math.round(memBytes / 1024 ** 3 * 10) / 10 : void 0
12776
+ });
12777
+ }
12778
+ }
12779
+ return gpus;
12780
+ } catch {
12781
+ return [];
12782
+ }
12783
+ }
12784
+ function detectGpuMacOS() {
12785
+ try {
12786
+ const output = execSync("system_profiler SPDisplaysDataType -json", {
12787
+ timeout: 2e3,
12788
+ encoding: "utf-8"
12789
+ });
12790
+ const data = JSON.parse(output);
12791
+ const gpus = [];
12792
+ const displays = data.SPDisplaysDataType || [];
12793
+ for (const display of displays) {
12794
+ const chips = display["sppci_model_name"];
12795
+ if (chips) {
12796
+ gpus.push({ name: chips });
12797
+ }
12798
+ }
12799
+ return gpus;
12800
+ } catch {
12801
+ return [];
12802
+ }
12803
+ }
12804
+ function detectGpuLinux() {
12805
+ const gpus = [];
12806
+ try {
12807
+ const output = execSync(
12808
+ "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader",
12809
+ { timeout: 2e3, encoding: "utf-8" }
12810
+ );
12811
+ const lines = output.trim().split("\n");
12812
+ for (const line of lines) {
12813
+ const [name, mem] = line.split(",");
12814
+ const memGB = mem ? Math.round(parseInt(mem) / 1024 * 10) / 10 : void 0;
12815
+ gpus.push({ name: name.trim(), memoryGB: memGB });
12816
+ }
12817
+ return gpus;
12818
+ } catch {
12819
+ }
12820
+ try {
12821
+ const output = execSync("rocm-smi --showproductname", {
12822
+ timeout: 2e3,
12823
+ encoding: "utf-8"
12824
+ });
12825
+ const lines = output.trim().split("\n");
12826
+ for (const line of lines) {
12827
+ if (line.includes("GPU")) {
12828
+ const match = line.match(/:\s*(.+)/);
12829
+ if (match) gpus.push({ name: match[1].trim() });
12830
+ }
12831
+ }
12832
+ return gpus;
12833
+ } catch {
12834
+ }
12835
+ try {
12836
+ const output = execSync("lspci | grep -i vga", {
12837
+ timeout: 2e3,
12838
+ encoding: "utf-8"
12839
+ });
12840
+ const lines = output.trim().split("\n");
12841
+ for (const line of lines) {
12842
+ const match = line.match(/:\s*(.+)/);
12843
+ if (match) gpus.push({ name: match[1].trim() });
12844
+ }
12845
+ return gpus;
12846
+ } catch {
12847
+ return [];
12848
+ }
12849
+ }
12850
+ var init_specs = __esm({
12851
+ "../core/src/diagnostics/specs.ts"() {
12852
+ "use strict";
12853
+ init_esm_shims();
12854
+ }
12855
+ });
12856
+
12857
+ // ../core/src/governor/ResourceGovernor.ts
12858
+ import { freemem as freemem3 } from "os";
12859
+ import { homedir as homedir18 } from "os";
12860
+ import { readFile as readFile23 } from "fs/promises";
12861
+ import { join as join29 } from "path";
12862
+ function determineTier(specs) {
12863
+ const memGB = specs.memory.totalGB;
12864
+ const cores = specs.cpu.cores;
12865
+ if (memGB < 8 || cores < 4) return "T1";
12866
+ if (memGB < 16 || cores < 8) return "T2";
12867
+ if (memGB < 32 || cores < 16) return "T3";
12868
+ return "T4";
12869
+ }
12870
+ function recommendLimits(specs) {
12871
+ const tier = determineTier(specs);
12872
+ return tierTable[tier];
12873
+ }
12874
+ async function readConfigOverrides() {
12875
+ try {
12876
+ const configPath = join29(homedir18(), ".msapling", "config.json");
12877
+ const content = await readFile23(configPath, "utf-8");
12878
+ const config = JSON.parse(content);
12879
+ return config.limits ?? null;
12880
+ } catch {
12881
+ return null;
12882
+ }
12883
+ }
12884
+ async function createResourceGovernor(specs) {
12885
+ const recommended = recommendLimits(specs);
12886
+ const overrides = await readConfigOverrides();
12887
+ return new ResourceGovernor(recommended, overrides ?? void 0);
12888
+ }
12889
+ var tierTable, ResourceGovernor;
12890
+ var init_ResourceGovernor = __esm({
12891
+ "../core/src/governor/ResourceGovernor.ts"() {
12892
+ "use strict";
12893
+ init_esm_shims();
12894
+ tierTable = {
12895
+ T1: { maxAgents: 1, maxParallelTools: 1, maxFileWatchers: 50, localLlmTier: "3B" },
12896
+ T2: { maxAgents: 2, maxParallelTools: 4, maxFileWatchers: 200, localLlmTier: "7B" },
12897
+ T3: { maxAgents: 4, maxParallelTools: 8, maxFileWatchers: 500, localLlmTier: "13B" },
12898
+ T4: { maxAgents: 8, maxParallelTools: 16, maxFileWatchers: 2e3, localLlmTier: "32B+" }
12899
+ };
12900
+ ResourceGovernor = class {
12901
+ activeAgents = 0;
12902
+ maxAgents;
12903
+ activeTool = 0;
12904
+ maxParallelTools;
12905
+ maxFileWatchers;
12906
+ memoryWarningShown = false;
12907
+ memoryCheckInterval = null;
12908
+ minFreeMemGB = 1.5;
12909
+ constructor(limits, overrides) {
12910
+ this.maxAgents = overrides?.maxAgents ?? limits.maxAgents;
12911
+ this.maxParallelTools = overrides?.maxParallelTools ?? limits.maxParallelTools;
12912
+ this.maxFileWatchers = overrides?.maxFileWatchers ?? limits.maxFileWatchers;
12913
+ this.startMemoryMonitor();
12914
+ }
12915
+ /**
12916
+ * Monitor free memory every 5s; block new acquires if below threshold
12917
+ */
12918
+ startMemoryMonitor() {
12919
+ this.memoryCheckInterval = setInterval(() => {
12920
+ const freeMemBytes = freemem3();
12921
+ const freeMemGB = freeMemBytes / 1024 ** 3;
12922
+ if (freeMemGB < this.minFreeMemGB && !this.memoryWarningShown) {
12923
+ console.warn(
12924
+ `[ResourceGovernor] Low memory: ${freeMemGB.toFixed(2)}GB free (threshold: ${this.minFreeMemGB}GB)`
12925
+ );
12926
+ this.memoryWarningShown = true;
12927
+ } else if (freeMemGB >= this.minFreeMemGB) {
12928
+ this.memoryWarningShown = false;
12929
+ }
12930
+ }, 5e3);
12931
+ }
12932
+ /**
12933
+ * Attempt to acquire an agent slot
12934
+ */
12935
+ acquireAgent() {
12936
+ const freeMemGB = freemem3() / 1024 ** 3;
12937
+ if (freeMemGB < this.minFreeMemGB) return false;
12938
+ if (this.activeAgents >= this.maxAgents) return false;
12939
+ this.activeAgents++;
12940
+ return true;
12941
+ }
12942
+ /**
12943
+ * Release an agent slot
12944
+ */
12945
+ releaseAgent() {
12946
+ this.activeAgents = Math.max(0, this.activeAgents - 1);
12947
+ }
12948
+ /**
12949
+ * Attempt to acquire a tool execution slot
12950
+ */
12951
+ acquireTool() {
12952
+ const freeMemGB = freemem3() / 1024 ** 3;
12953
+ if (freeMemGB < this.minFreeMemGB) return false;
12954
+ if (this.activeTool >= this.maxParallelTools) return false;
12955
+ this.activeTool++;
12956
+ return true;
12957
+ }
12958
+ /**
12959
+ * Release a tool execution slot
12960
+ */
12961
+ releaseTool() {
12962
+ this.activeTool = Math.max(0, this.activeTool - 1);
12963
+ }
12964
+ /**
12965
+ * Get current active counts
12966
+ */
12967
+ getStatus() {
12968
+ return {
12969
+ activeAgents: this.activeAgents,
12970
+ activeTool: this.activeTool,
12971
+ maxAgents: this.maxAgents,
12972
+ maxParallelTools: this.maxParallelTools
12973
+ };
12974
+ }
12975
+ /**
12976
+ * Clean up monitor interval
12977
+ */
12978
+ destroy() {
12979
+ if (this.memoryCheckInterval) {
12980
+ clearInterval(this.memoryCheckInterval);
12981
+ this.memoryCheckInterval = null;
12982
+ }
12983
+ }
12984
+ };
12985
+ }
12986
+ });
12987
+
12988
+ // src/commands/diag.ts
12989
+ async function handler(args2, ctx) {
12990
+ const jsonMode = args2.includes("json");
12991
+ try {
12992
+ const specs = await collectSpecs();
12993
+ const recommended = recommendLimits(specs);
12994
+ const tier = determineTier(specs);
12995
+ const governor = await createResourceGovernor(specs);
12996
+ const status = governor.getStatus();
12997
+ governor.destroy();
12998
+ if (jsonMode) {
12999
+ const output = {
13000
+ specs,
13001
+ recommended,
13002
+ tier,
13003
+ status
13004
+ };
13005
+ console.log(JSON.stringify(output, null, 2));
13006
+ } else {
13007
+ console.log("\n=== System Diagnostics ===\n");
13008
+ console.log("CPU:");
13009
+ console.log(` Cores: ${specs.cpu.cores}`);
13010
+ console.log(` Model: ${specs.cpu.model}`);
13011
+ console.log(` Speed: ${specs.cpu.speed} GHz`);
13012
+ console.log("\nMemory:");
13013
+ console.log(` Total: ${specs.memory.totalGB} GB`);
13014
+ console.log(` Free: ${specs.memory.freeGB} GB`);
13015
+ console.log("\nGPU:");
13016
+ if (specs.gpu.length === 0) {
13017
+ console.log(" None detected");
13018
+ } else {
13019
+ for (const gpu of specs.gpu) {
13020
+ console.log(` ${gpu.name}${gpu.memoryGB ? ` (${gpu.memoryGB} GB)` : ""}`);
13021
+ }
13022
+ }
13023
+ console.log("\nDisk:");
13024
+ if (specs.disk.totalGB > 0) {
13025
+ console.log(` Total: ${specs.disk.totalGB} GB`);
13026
+ console.log(` Free: ${specs.disk.freeGB} GB`);
13027
+ } else {
13028
+ console.log(" Unavailable");
13029
+ }
13030
+ console.log("\nPlatform:");
13031
+ console.log(` OS: ${specs.platform}`);
13032
+ console.log(` Arch: ${specs.arch}`);
13033
+ console.log(` Node: ${specs.nodeVersion}`);
13034
+ console.log("\n=== Resource Recommendations ===\n");
13035
+ console.log(`Tier: ${tier}`);
13036
+ console.log(` Max Agents: ${recommended.maxAgents}`);
13037
+ console.log(` Max Parallel Tools: ${recommended.maxParallelTools}`);
13038
+ console.log(` Max File Watchers: ${recommended.maxFileWatchers}`);
13039
+ console.log(` Local LLM Tier: ${recommended.localLlmTier}`);
13040
+ console.log("\n=== Current Active ===\n");
13041
+ console.log(`Active Agents: ${status.activeAgents}/${status.maxAgents}`);
13042
+ console.log(`Active Tools: ${status.activeTool}/${status.maxParallelTools}`);
13043
+ console.log("\n");
13044
+ }
13045
+ } catch (error) {
13046
+ ctx.addMessage("error", `[diag] Error: ${error instanceof Error ? error.message : error}`);
13047
+ }
13048
+ }
13049
+ var diagCommand;
13050
+ var init_diag = __esm({
13051
+ "src/commands/diag.ts"() {
13052
+ "use strict";
13053
+ init_esm_shims();
13054
+ init_specs();
13055
+ init_ResourceGovernor();
13056
+ diagCommand = {
13057
+ name: "diag",
13058
+ aliases: ["diagnostics"],
13059
+ description: "Display system specs and resource limits",
13060
+ category: "debug",
13061
+ args: "[json]",
13062
+ handler
13063
+ };
13064
+ }
13065
+ });
13066
+
12030
13067
  // src/commands/index.ts
12031
13068
  var commands_exports = {};
12032
13069
  __export(commands_exports, {
@@ -12090,6 +13127,8 @@ var init_commands = __esm({
12090
13127
  init_mfa();
12091
13128
  init_mcp();
12092
13129
  init_webhook();
13130
+ init_sync();
13131
+ init_diag();
12093
13132
  commands = [
12094
13133
  loginCommand,
12095
13134
  unlockCommand,
@@ -12139,7 +13178,9 @@ var init_commands = __esm({
12139
13178
  mlineageCommand,
12140
13179
  mfaCommand,
12141
13180
  mcpCommand,
12142
- webhookCommand
13181
+ webhookCommand,
13182
+ syncCommand,
13183
+ diagCommand
12143
13184
  ];
12144
13185
  }
12145
13186
  });
@@ -12210,15 +13251,15 @@ __export(exec_exports, {
12210
13251
  runExec: () => runExec
12211
13252
  });
12212
13253
  import { existsSync as existsSync26 } from "fs";
12213
- import { readFile as readFile23 } from "fs/promises";
12214
- import { homedir as homedir16 } from "os";
12215
- import { join as join27 } from "path";
13254
+ import { readFile as readFile25 } from "fs/promises";
13255
+ import { homedir as homedir19 } from "os";
13256
+ import { join as join30 } from "path";
12216
13257
  async function loadPersistedSettings() {
12217
13258
  const out = { mode: "default", theme: null };
12218
13259
  try {
12219
- const p = join27(homedir16(), ".msapling", "settings.json");
13260
+ const p = join30(homedir19(), ".msapling", "settings.json");
12220
13261
  if (!existsSync26(p)) return out;
12221
- const raw = JSON.parse(await readFile23(p, "utf8"));
13262
+ const raw = JSON.parse(await readFile25(p, "utf8"));
12222
13263
  const parsed = parseApprovalMode(raw, Date.now());
12223
13264
  if (parsed.kind === "ok") out.mode = parsed.mode;
12224
13265
  const themeRaw = raw?.theme;
@@ -12455,13 +13496,13 @@ var init_format = __esm({
12455
13496
  // src/commands/billing/open-browser.ts
12456
13497
  import { spawn as spawn10 } from "child_process";
12457
13498
  async function openBrowser(url) {
12458
- const platform4 = process.platform;
13499
+ const platform5 = process.platform;
12459
13500
  let cmd;
12460
13501
  let args2;
12461
- if (platform4 === "win32") {
13502
+ if (platform5 === "win32") {
12462
13503
  cmd = "cmd";
12463
13504
  args2 = ["/c", "start", "", url];
12464
- } else if (platform4 === "darwin") {
13505
+ } else if (platform5 === "darwin") {
12465
13506
  cmd = "open";
12466
13507
  args2 = [url];
12467
13508
  } else {
@@ -13093,8 +14134,8 @@ var doctor_exports = {};
13093
14134
  __export(doctor_exports, {
13094
14135
  runDoctor: () => runDoctor
13095
14136
  });
13096
- import { homedir as homedir17, platform as platform3 } from "os";
13097
- import { join as join28 } from "path";
14137
+ import { homedir as homedir20, platform as platform4 } from "os";
14138
+ import { join as join31 } from "path";
13098
14139
  import { existsSync as existsSync27, statSync as statSync6, accessSync } from "fs";
13099
14140
  import { readdir as readdir3 } from "fs/promises";
13100
14141
  import { exec } from "child_process";
@@ -13118,7 +14159,7 @@ async function checkNodeVersion() {
13118
14159
  };
13119
14160
  }
13120
14161
  async function checkConfigDir() {
13121
- const configDir = join28(homedir17(), ".msapling");
14162
+ const configDir = join31(homedir20(), ".msapling");
13122
14163
  if (!existsSync27(configDir)) {
13123
14164
  return {
13124
14165
  name: "Config directory",
@@ -13136,7 +14177,7 @@ async function checkConfigDir() {
13136
14177
  remediation: `rm "${configDir}" && mkdir -p "${configDir}"`
13137
14178
  };
13138
14179
  }
13139
- if (platform3() !== "win32") {
14180
+ if (platform4() !== "win32") {
13140
14181
  const mode = stats.mode & 511;
13141
14182
  const safe = (mode & 63) === 0;
13142
14183
  if (!safe) {
@@ -13182,7 +14223,7 @@ async function checkKeytar() {
13182
14223
  }
13183
14224
  async function checkPathConflicts() {
13184
14225
  const pathEnv = process.env.PATH || "";
13185
- const paths = pathEnv.split(platform3() === "win32" ? ";" : ":");
14226
+ const paths = pathEnv.split(platform4() === "win32" ? ";" : ":");
13186
14227
  const conflicts = [];
13187
14228
  for (const dir of paths) {
13188
14229
  if (!dir || !existsSync27(dir)) continue;
@@ -13190,7 +14231,7 @@ async function checkPathConflicts() {
13190
14231
  const files = await readdir3(dir);
13191
14232
  for (const file of files) {
13192
14233
  if (file === "msapling" || file === "msapling.exe" || file === "msapling.py") {
13193
- const fullPath = join28(dir, file);
14234
+ const fullPath = join31(dir, file);
13194
14235
  conflicts.push(fullPath);
13195
14236
  }
13196
14237
  }
@@ -13290,11 +14331,11 @@ async function checkTokenValidity() {
13290
14331
  }
13291
14332
  }
13292
14333
  async function checkOsSpecific() {
13293
- if (platform3() === "win32") {
14334
+ if (platform4() === "win32") {
13294
14335
  try {
13295
- const configDir = join28(homedir17(), ".msapling");
14336
+ const configDir = join31(homedir20(), ".msapling");
13296
14337
  const longPath = "A".repeat(260);
13297
- const testPath = join28(configDir, longPath);
14338
+ const testPath = join31(configDir, longPath);
13298
14339
  try {
13299
14340
  accessSync(configDir);
13300
14341
  } catch {
@@ -13319,14 +14360,14 @@ async function checkOsSpecific() {
13319
14360
  };
13320
14361
  }
13321
14362
  }
13322
- if (platform3() === "darwin") {
14363
+ if (platform4() === "darwin") {
13323
14364
  return {
13324
14365
  name: "OS-specific (macOS)",
13325
14366
  status: "PASS",
13326
14367
  message: "macOS detected"
13327
14368
  };
13328
14369
  }
13329
- if (platform3() === "linux") {
14370
+ if (platform4() === "linux") {
13330
14371
  try {
13331
14372
  await import("keytar");
13332
14373
  return {
@@ -13346,7 +14387,7 @@ async function checkOsSpecific() {
13346
14387
  return {
13347
14388
  name: "OS-specific",
13348
14389
  status: "PASS",
13349
- message: `${platform3()} detected`
14390
+ message: `${platform4()} detected`
13350
14391
  };
13351
14392
  }
13352
14393
  function formatCheckResult(result, maxLabelWidth) {
@@ -14956,7 +15997,7 @@ __export(server_exports, {
14956
15997
  runStdioWithRegistry: () => runStdioWithRegistry
14957
15998
  });
14958
15999
  import { readdirSync as readdirSync4, readFileSync as readFileSync2, statSync as statSync7 } from "fs";
14959
- import { join as join29, relative as relative14, resolve as resolve18 } from "path";
16000
+ import { join as join32, relative as relative14, resolve as resolve18 } from "path";
14960
16001
  function asResult2(text, isError = false) {
14961
16002
  return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
14962
16003
  }
@@ -14976,7 +16017,7 @@ function buildFileTree(root, maxFiles) {
14976
16017
  for (const name of entries) {
14977
16018
  if (out.length >= maxFiles) break;
14978
16019
  if (SKIP_DIRS2.has(name)) continue;
14979
- const full = join29(dir, name);
16020
+ const full = join32(dir, name);
14980
16021
  let s;
14981
16022
  try {
14982
16023
  s = statSync7(full);
@@ -15742,7 +16783,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
15742
16783
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
15743
16784
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
15744
16785
  "\u25CF MSapling CLI v",
15745
- "2.3.6-beta.28"
16786
+ "2.3.6-beta.30"
15746
16787
  ] }),
15747
16788
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
15748
16789
  ] });
@@ -16266,9 +17307,9 @@ ${prompt4}` : prompt4;
16266
17307
  const filePath = mention.slice(1);
16267
17308
  try {
16268
17309
  const { existsSync: existsSync28 } = await import("fs");
16269
- const { readFile: readFile24 } = await import("fs/promises");
17310
+ const { readFile: readFile26 } = await import("fs/promises");
16270
17311
  if (existsSync28(filePath)) {
16271
- const content = await readFile24(filePath, "utf8");
17312
+ const content = await readFile26(filePath, "utf8");
16272
17313
  const MAX_LEN = 32768;
16273
17314
  const truncated = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n...[TRUNCATED]" : content;
16274
17315
  finalCmd += `
@@ -16320,7 +17361,7 @@ ${finalCmd}`;
16320
17361
  init_esm_shims();
16321
17362
  init_src3();
16322
17363
  init_parseApprovalMode();
16323
- import { readFile as readFile22 } from "fs/promises";
17364
+ import { readFile as readFile24 } from "fs/promises";
16324
17365
  import { existsSync as existsSync25 } from "fs";
16325
17366
  async function initSession(ctx) {
16326
17367
  try {
@@ -16335,11 +17376,11 @@ async function initSession(ctx) {
16335
17376
  ctx.setShellEscapeEnabled(settings.shellEscapeEnabled !== false);
16336
17377
  }
16337
17378
  try {
16338
- const { homedir: homedir18 } = await import("os");
16339
- const { join: join31 } = await import("path");
16340
- const userSettingsPath = join31(homedir18(), ".msapling", "settings.json");
17379
+ const { homedir: homedir21 } = await import("os");
17380
+ const { join: join34 } = await import("path");
17381
+ const userSettingsPath = join34(homedir21(), ".msapling", "settings.json");
16341
17382
  if (existsSync25(userSettingsPath)) {
16342
- const userText = await readFile22(userSettingsPath, "utf8");
17383
+ const userText = await readFile24(userSettingsPath, "utf8");
16343
17384
  let parsed;
16344
17385
  try {
16345
17386
  parsed = JSON.parse(userText);
@@ -16644,12 +17685,12 @@ var App = ({ compact: compact2 = false }) => {
16644
17685
  init_esm_shims();
16645
17686
  import { readFileSync as readFileSync3 } from "fs";
16646
17687
  import { fileURLToPath as fileURLToPath2 } from "url";
16647
- import { dirname as dirname3, join as join30 } from "path";
17688
+ import { dirname as dirname3, join as join33 } from "path";
16648
17689
  function readCliVersion2() {
16649
17690
  const here = dirname3(fileURLToPath2(import.meta.url));
16650
17691
  for (const rel of ["../package.json", "../../package.json"]) {
16651
17692
  try {
16652
- const pkg = JSON.parse(readFileSync3(join30(here, rel), "utf8"));
17693
+ const pkg = JSON.parse(readFileSync3(join33(here, rel), "utf8"));
16653
17694
  if (pkg.name && pkg.version) {
16654
17695
  return { name: pkg.name, version: pkg.version };
16655
17696
  }
@@ -16735,11 +17776,11 @@ function handleCliArgs(args2) {
16735
17776
  }
16736
17777
  if (args2[0] === "mcp" && args2[1] === "serve") {
16737
17778
  (async () => {
16738
- const { MSaplingClient: MSaplingClient2 } = await Promise.resolve().then(() => (init_src(), src_exports));
17779
+ const { MSaplingClient: MSaplingClient3 } = await Promise.resolve().then(() => (init_src(), src_exports));
16739
17780
  const { runStdio: runStdio2 } = await Promise.resolve().then(() => (init_server(), server_exports));
16740
17781
  const apiUrl = process.env.MSAPLING_API_URL;
16741
17782
  const token = process.env.MSAPLING_TOKEN;
16742
- const client = new MSaplingClient2({ apiUrl, token });
17783
+ const client = new MSaplingClient3({ apiUrl, token });
16743
17784
  await runStdio2(client);
16744
17785
  })().catch((e) => {
16745
17786
  process.stderr.write(`[msapling-mcp] fatal: ${e?.message ?? e}