@mtreeai/msapling-cli 2.3.6-beta.1 → 2.3.6-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2069 -493
- package/package.json +55 -56
- package/LICENSE +0 -24
package/dist/index.js
CHANGED
|
@@ -79,6 +79,11 @@ var init_src = __esm({
|
|
|
79
79
|
MSaplingClient = class {
|
|
80
80
|
apiUrl;
|
|
81
81
|
token;
|
|
82
|
+
// CLI-AUTH-CSRF-01: simple cookie jar so we can implement the backend's
|
|
83
|
+
// double-submit CSRF pattern (msapling_csrftoken cookie ↔ X-CSRF-Token header).
|
|
84
|
+
// Without this, every authenticated POST/PATCH/DELETE returns 403
|
|
85
|
+
// CSRF_COOKIE_MISSING even with a valid Bearer token.
|
|
86
|
+
cookies = /* @__PURE__ */ new Map();
|
|
82
87
|
constructor(options = {}) {
|
|
83
88
|
this.apiUrl = (options.apiUrl || "https://api.msapling.com").replace(/\/$/, "");
|
|
84
89
|
this.token = options.token || null;
|
|
@@ -89,12 +94,38 @@ var init_src = __esm({
|
|
|
89
94
|
getApiUrl() {
|
|
90
95
|
return this.apiUrl;
|
|
91
96
|
}
|
|
97
|
+
serializeCookies() {
|
|
98
|
+
return Array.from(this.cookies.entries()).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
99
|
+
}
|
|
100
|
+
updateCookiesFromResponse(response) {
|
|
101
|
+
const setCookieArr = response.headers.getSetCookie?.() ?? [];
|
|
102
|
+
for (const line of setCookieArr) {
|
|
103
|
+
const firstSemi = line.indexOf(";");
|
|
104
|
+
const pair = firstSemi >= 0 ? line.slice(0, firstSemi) : line;
|
|
105
|
+
const eq = pair.indexOf("=");
|
|
106
|
+
if (eq <= 0) continue;
|
|
107
|
+
const name = pair.slice(0, eq).trim();
|
|
108
|
+
const value = pair.slice(eq + 1).trim();
|
|
109
|
+
if (!name || !value) continue;
|
|
110
|
+
this.cookies.set(name, value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
92
113
|
async request(path2, options = {}) {
|
|
93
114
|
const headers = new Headers(options.headers);
|
|
94
115
|
if (this.token) {
|
|
95
116
|
headers.set("Authorization", `Bearer ${this.token}`);
|
|
96
117
|
}
|
|
97
|
-
headers.
|
|
118
|
+
if (!headers.has("Content-Type")) {
|
|
119
|
+
headers.set("Content-Type", "application/json");
|
|
120
|
+
}
|
|
121
|
+
if (this.cookies.size > 0) {
|
|
122
|
+
headers.set("Cookie", this.serializeCookies());
|
|
123
|
+
}
|
|
124
|
+
const method = (options.method || "GET").toUpperCase();
|
|
125
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
126
|
+
const csrf = this.cookies.get("msapling_csrftoken");
|
|
127
|
+
if (csrf) headers.set("X-CSRF-Token", csrf);
|
|
128
|
+
}
|
|
98
129
|
const controller = new AbortController();
|
|
99
130
|
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
100
131
|
try {
|
|
@@ -106,6 +137,7 @@ var init_src = __esm({
|
|
|
106
137
|
keepalive: true
|
|
107
138
|
});
|
|
108
139
|
clearTimeout(timeout);
|
|
140
|
+
this.updateCookiesFromResponse(response);
|
|
109
141
|
if (!response.ok) {
|
|
110
142
|
let detail = "Unknown error";
|
|
111
143
|
let code = "unknown";
|
|
@@ -272,7 +304,11 @@ var init_src = __esm({
|
|
|
272
304
|
try {
|
|
273
305
|
const parts = token.split(".");
|
|
274
306
|
if (parts.length !== 3) return null;
|
|
275
|
-
|
|
307
|
+
let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
308
|
+
const padLen = (4 - b64.length % 4) % 4;
|
|
309
|
+
b64 += "=".repeat(padLen);
|
|
310
|
+
const decoded = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("binary");
|
|
311
|
+
const payload = JSON.parse(decoded);
|
|
276
312
|
return payload;
|
|
277
313
|
} catch {
|
|
278
314
|
return null;
|
|
@@ -280,8 +316,19 @@ var init_src = __esm({
|
|
|
280
316
|
}
|
|
281
317
|
/**
|
|
282
318
|
* Login with email and password.
|
|
283
|
-
*
|
|
284
|
-
*
|
|
319
|
+
*
|
|
320
|
+
* The JWT `mfa` claim is the *session* MFA state ("has this session completed
|
|
321
|
+
* TOTP yet"), not "TOTP is required". Treating `mfa:false` as TOTP-required
|
|
322
|
+
* was wrong: it blocked every user without 2FA enabled, including all
|
|
323
|
+
* guest/free accounts and the live backend which doesn't enforce TOTP yet.
|
|
324
|
+
*
|
|
325
|
+
* New contract: accept the token as fully usable. If the backend enforces
|
|
326
|
+
* MFA on a subsequent protected endpoint, that endpoint will reply with
|
|
327
|
+
* `mfa_required` and the caller can step up via verifyLoginTotp(). Until
|
|
328
|
+
* the backend exposes an explicit `mfa_required` field on the login
|
|
329
|
+
* response, we don't pre-emptively branch.
|
|
330
|
+
*
|
|
331
|
+
* On 401: throws MSaplingError with code 'invalid_credentials'.
|
|
285
332
|
*/
|
|
286
333
|
async loginEmailPassword(email, password) {
|
|
287
334
|
const params = new URLSearchParams();
|
|
@@ -299,12 +346,9 @@ var init_src = __esm({
|
|
|
299
346
|
signal: controller.signal
|
|
300
347
|
});
|
|
301
348
|
clearTimeout(timeout);
|
|
349
|
+
this.updateCookiesFromResponse(response);
|
|
302
350
|
if (response.ok) {
|
|
303
351
|
const data = await response.json();
|
|
304
|
-
const payload = this.decodeJwtPayload(data.access_token);
|
|
305
|
-
if (payload && payload.mfa === false) {
|
|
306
|
-
return { kind: "totp_required", partialToken: data.access_token };
|
|
307
|
-
}
|
|
308
352
|
return { kind: "success", token: data.access_token };
|
|
309
353
|
}
|
|
310
354
|
if (response.status === 401) {
|
|
@@ -326,6 +370,42 @@ var init_src = __esm({
|
|
|
326
370
|
clearTimeout(timeout);
|
|
327
371
|
}
|
|
328
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Sign in as a guest. Backend issues a guest-scoped JWT with limited tier
|
|
375
|
+
* capabilities. No prior credentials required.
|
|
376
|
+
*/
|
|
377
|
+
async loginGuest() {
|
|
378
|
+
const controller = new AbortController();
|
|
379
|
+
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
380
|
+
try {
|
|
381
|
+
const response = await fetch(`${this.apiUrl}/auth/guest`, {
|
|
382
|
+
method: "POST",
|
|
383
|
+
headers: { "Content-Type": "application/json" },
|
|
384
|
+
body: "{}",
|
|
385
|
+
signal: controller.signal
|
|
386
|
+
});
|
|
387
|
+
clearTimeout(timeout);
|
|
388
|
+
this.updateCookiesFromResponse(response);
|
|
389
|
+
if (response.ok) {
|
|
390
|
+
const data = await response.json();
|
|
391
|
+
return { kind: "success", token: data.access_token };
|
|
392
|
+
}
|
|
393
|
+
let detail = "Guest login failed";
|
|
394
|
+
try {
|
|
395
|
+
const err = await response.json();
|
|
396
|
+
detail = err.detail || detail;
|
|
397
|
+
} catch (e) {
|
|
398
|
+
}
|
|
399
|
+
throw new MSaplingError(detail, response.status, "guest_login_failed");
|
|
400
|
+
} catch (e) {
|
|
401
|
+
if (e.name === "AbortError") {
|
|
402
|
+
throw new MSaplingError("Request timed out after 30s.", 408, "timeout");
|
|
403
|
+
}
|
|
404
|
+
throw e;
|
|
405
|
+
} finally {
|
|
406
|
+
clearTimeout(timeout);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
329
409
|
/**
|
|
330
410
|
* Verify TOTP code during login.
|
|
331
411
|
* Requires a partial token from a prior /auth/login call when MFA is enabled.
|
|
@@ -335,16 +415,21 @@ var init_src = __esm({
|
|
|
335
415
|
const controller = new AbortController();
|
|
336
416
|
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
337
417
|
try {
|
|
338
|
-
const
|
|
418
|
+
const csrf = this.cookies.get("msapling_csrftoken");
|
|
419
|
+
const verifyHeaders = {
|
|
420
|
+
"Authorization": `Bearer ${this.token}`,
|
|
421
|
+
"Content-Type": "application/json"
|
|
422
|
+
};
|
|
423
|
+
if (this.cookies.size > 0) verifyHeaders["Cookie"] = this.serializeCookies();
|
|
424
|
+
if (csrf) verifyHeaders["X-CSRF-Token"] = csrf;
|
|
425
|
+
const response = await fetch(`${this.apiUrl}/api/mfa/login-verify`, {
|
|
339
426
|
method: "POST",
|
|
340
|
-
headers:
|
|
341
|
-
"Authorization": `Bearer ${this.token}`,
|
|
342
|
-
"Content-Type": "application/json"
|
|
343
|
-
},
|
|
427
|
+
headers: verifyHeaders,
|
|
344
428
|
body: JSON.stringify({ code }),
|
|
345
429
|
signal: controller.signal
|
|
346
430
|
});
|
|
347
431
|
clearTimeout(timeout);
|
|
432
|
+
this.updateCookiesFromResponse(response);
|
|
348
433
|
if (response.ok) {
|
|
349
434
|
const data = await response.json();
|
|
350
435
|
return { kind: "success", token: data.access_token };
|
|
@@ -374,8 +459,9 @@ var init_src = __esm({
|
|
|
374
459
|
*/
|
|
375
460
|
async chatOnce(prompt, model, chatId) {
|
|
376
461
|
let acc = "";
|
|
377
|
-
for await (const chunk of this.streamChat({ prompt, model, chat_id: chatId })) {
|
|
378
|
-
if (chunk.
|
|
462
|
+
for await (const chunk of this.streamChat({ content: prompt, model, chat_id: chatId ?? "" })) {
|
|
463
|
+
if (chunk.delta) acc += chunk.delta;
|
|
464
|
+
else if (chunk.content) acc += chunk.content;
|
|
379
465
|
}
|
|
380
466
|
return acc;
|
|
381
467
|
}
|
|
@@ -455,6 +541,33 @@ var init_src = __esm({
|
|
|
455
541
|
});
|
|
456
542
|
return data.hash;
|
|
457
543
|
}
|
|
544
|
+
async getActiveTasks() {
|
|
545
|
+
return this.request("/api/task-monitor/active");
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Wakeup Management (LAB-CHAT-PARITY-04)
|
|
549
|
+
*/
|
|
550
|
+
async listWakeups(params = {}) {
|
|
551
|
+
const queryParams = new URLSearchParams(params);
|
|
552
|
+
return this.request(`/api/wakeups/?${queryParams.toString()}`);
|
|
553
|
+
}
|
|
554
|
+
async createWakeup(data) {
|
|
555
|
+
return this.request("/api/wakeups/", {
|
|
556
|
+
method: "POST",
|
|
557
|
+
body: JSON.stringify(data)
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
async cancelWakeup(id) {
|
|
561
|
+
await this.request(`/api/wakeups/${id}`, {
|
|
562
|
+
method: "DELETE"
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Orchestration & Fleet Status
|
|
567
|
+
*/
|
|
568
|
+
async getFleetStatus() {
|
|
569
|
+
return this.request("/api/orchestration/status");
|
|
570
|
+
}
|
|
458
571
|
async proposeEdit(params) {
|
|
459
572
|
return await this.request("/api/mdrive/ram/blocks/propose", {
|
|
460
573
|
method: "POST",
|
|
@@ -462,6 +575,9 @@ var init_src = __esm({
|
|
|
462
575
|
});
|
|
463
576
|
}
|
|
464
577
|
async *streamChat(params) {
|
|
578
|
+
if (!params.frame_id) {
|
|
579
|
+
params = { ...params, frame_id: "earth_surface" };
|
|
580
|
+
}
|
|
465
581
|
const controller = new AbortController();
|
|
466
582
|
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
467
583
|
try {
|
|
@@ -487,22 +603,34 @@ var init_src = __esm({
|
|
|
487
603
|
if (!response.body) throw new Error("No response body");
|
|
488
604
|
const reader = response.body.getReader();
|
|
489
605
|
const decoder = new TextDecoder();
|
|
606
|
+
let pending = "";
|
|
607
|
+
const yieldLine = function* (raw) {
|
|
608
|
+
const line = raw.replace(/\r$/, "").trim();
|
|
609
|
+
if (!line) return;
|
|
610
|
+
try {
|
|
611
|
+
yield JSON.parse(line);
|
|
612
|
+
} catch {
|
|
613
|
+
if (line.startsWith("{")) {
|
|
614
|
+
console.warn(`[Stream] Dropped malformed JSON: ${line}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
490
618
|
while (true) {
|
|
491
619
|
const { done, value } = await reader.read();
|
|
492
620
|
if (done) break;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
} catch (e) {
|
|
500
|
-
if (line.startsWith("{")) {
|
|
501
|
-
console.warn(`[Stream] Dropped partial JSON: ${line}`);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
621
|
+
pending += decoder.decode(value, { stream: true });
|
|
622
|
+
let nlIdx;
|
|
623
|
+
while ((nlIdx = pending.indexOf("\n")) !== -1) {
|
|
624
|
+
const raw = pending.slice(0, nlIdx);
|
|
625
|
+
pending = pending.slice(nlIdx + 1);
|
|
626
|
+
yield* yieldLine(raw);
|
|
504
627
|
}
|
|
505
628
|
}
|
|
629
|
+
pending += decoder.decode();
|
|
630
|
+
if (pending.length > 0) {
|
|
631
|
+
yield* yieldLine(pending);
|
|
632
|
+
pending = "";
|
|
633
|
+
}
|
|
506
634
|
} catch (e) {
|
|
507
635
|
if (e.name === "AbortError") {
|
|
508
636
|
throw new MSaplingError("Request timed out after 30s.", 408, "timeout");
|
|
@@ -657,6 +785,7 @@ import { resolve as resolve2, normalize as normalize2, relative as relative2, is
|
|
|
657
785
|
import { writeFile, readFile as readFile2, mkdir } from "fs/promises";
|
|
658
786
|
import { existsSync as existsSync2 } from "fs";
|
|
659
787
|
import { homedir } from "os";
|
|
788
|
+
import { randomBytes } from "crypto";
|
|
660
789
|
var MAX_CONTENT_BYTES, WriteFileTool;
|
|
661
790
|
var init_WriteFileTool = __esm({
|
|
662
791
|
"../core/src/tools/WriteFileTool.ts"() {
|
|
@@ -732,7 +861,9 @@ var init_WriteFileTool = __esm({
|
|
|
732
861
|
if (existsSync2(resolvedTarget)) {
|
|
733
862
|
const existingContent = await readFile2(resolvedTarget, "utf8");
|
|
734
863
|
const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
|
|
735
|
-
const
|
|
864
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
865
|
+
const suffix = randomBytes(4).toString("hex");
|
|
866
|
+
const backupPath = join2(homedir(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
736
867
|
await mkdir(join2(homedir(), ".msapling", "backups"), { recursive: true });
|
|
737
868
|
await writeFile(backupPath, existingContent, "utf8");
|
|
738
869
|
backedUpTo = backupPath;
|
|
@@ -1028,7 +1159,7 @@ var init_RunCommandTool = __esm({
|
|
|
1028
1159
|
this.activeCommands++;
|
|
1029
1160
|
return;
|
|
1030
1161
|
}
|
|
1031
|
-
return new Promise((
|
|
1162
|
+
return new Promise((resolve18) => this.queue.push(resolve18));
|
|
1032
1163
|
}
|
|
1033
1164
|
static releaseLock() {
|
|
1034
1165
|
if (this.queue.length > 0) {
|
|
@@ -1107,9 +1238,9 @@ var init_RunCommandTool = __esm({
|
|
|
1107
1238
|
const chunks = { stdout: [], stderr: [] };
|
|
1108
1239
|
proc.stdout?.on("data", (chunk) => chunks.stdout.push(chunk));
|
|
1109
1240
|
proc.stderr?.on("data", (chunk) => chunks.stderr.push(chunk));
|
|
1110
|
-
const exitCode = await new Promise((
|
|
1111
|
-
proc.on("exit", (code) =>
|
|
1112
|
-
proc.on("error", () =>
|
|
1241
|
+
const exitCode = await new Promise((resolve18) => {
|
|
1242
|
+
proc.on("exit", (code) => resolve18(code ?? 1));
|
|
1243
|
+
proc.on("error", () => resolve18(1));
|
|
1113
1244
|
});
|
|
1114
1245
|
const stdout = Buffer.concat(chunks.stdout).toString("utf-8");
|
|
1115
1246
|
const stderr = Buffer.concat(chunks.stderr).toString("utf-8");
|
|
@@ -1220,9 +1351,9 @@ var init_src2 = __esm({
|
|
|
1220
1351
|
const messages = this.parser.parse(value);
|
|
1221
1352
|
for (const msg of messages) {
|
|
1222
1353
|
if (msg.id !== void 0 && this.pendingRequests.has(Number(msg.id))) {
|
|
1223
|
-
const
|
|
1224
|
-
if (
|
|
1225
|
-
|
|
1354
|
+
const resolve18 = this.pendingRequests.get(Number(msg.id));
|
|
1355
|
+
if (resolve18) {
|
|
1356
|
+
resolve18(msg.result || msg.error);
|
|
1226
1357
|
this.pendingRequests.delete(Number(msg.id));
|
|
1227
1358
|
}
|
|
1228
1359
|
}
|
|
@@ -1237,8 +1368,8 @@ var init_src2 = __esm({
|
|
|
1237
1368
|
const message = `Content-Length: ${Buffer.byteLength(content, "utf8")}\r
|
|
1238
1369
|
\r
|
|
1239
1370
|
${content}`;
|
|
1240
|
-
return new Promise((
|
|
1241
|
-
this.pendingRequests.set(id,
|
|
1371
|
+
return new Promise((resolve18) => {
|
|
1372
|
+
this.pendingRequests.set(id, resolve18);
|
|
1242
1373
|
this.process.stdin.write(message);
|
|
1243
1374
|
this.process.stdin.flush();
|
|
1244
1375
|
});
|
|
@@ -1370,9 +1501,9 @@ var init_SubShellTool = __esm({
|
|
|
1370
1501
|
}
|
|
1371
1502
|
throw e;
|
|
1372
1503
|
}
|
|
1373
|
-
await new Promise((
|
|
1374
|
-
proc.on("exit", () =>
|
|
1375
|
-
proc.on("error", () =>
|
|
1504
|
+
await new Promise((resolve18) => {
|
|
1505
|
+
proc.on("exit", () => resolve18());
|
|
1506
|
+
proc.on("error", () => resolve18());
|
|
1376
1507
|
});
|
|
1377
1508
|
return { content: `Successfully launched separate window for ${args2.worker_id}` };
|
|
1378
1509
|
}
|
|
@@ -1455,13 +1586,13 @@ async function findRg() {
|
|
|
1455
1586
|
const candidates = ["rg", "C:\\Program Files\\ripgrep\\rg.exe"];
|
|
1456
1587
|
for (const bin of candidates) {
|
|
1457
1588
|
try {
|
|
1458
|
-
const exited = await new Promise((
|
|
1589
|
+
const exited = await new Promise((resolve18) => {
|
|
1459
1590
|
try {
|
|
1460
1591
|
const p = spawn4(bin, ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1461
|
-
p.on("error", () =>
|
|
1462
|
-
p.on("exit", (code) =>
|
|
1592
|
+
p.on("error", () => resolve18(null));
|
|
1593
|
+
p.on("exit", (code) => resolve18(code));
|
|
1463
1594
|
} catch {
|
|
1464
|
-
|
|
1595
|
+
resolve18(null);
|
|
1465
1596
|
}
|
|
1466
1597
|
});
|
|
1467
1598
|
if (exited === 0) return bin;
|
|
@@ -1471,7 +1602,7 @@ async function findRg() {
|
|
|
1471
1602
|
return null;
|
|
1472
1603
|
}
|
|
1473
1604
|
function runRg(bin, args2) {
|
|
1474
|
-
return new Promise((
|
|
1605
|
+
return new Promise((resolve18) => {
|
|
1475
1606
|
const p = spawn4(bin, args2, { stdio: ["ignore", "pipe", "pipe"] });
|
|
1476
1607
|
let stdout = "";
|
|
1477
1608
|
let stderr = "";
|
|
@@ -1482,10 +1613,10 @@ function runRg(bin, args2) {
|
|
|
1482
1613
|
stderr += d.toString("utf8");
|
|
1483
1614
|
});
|
|
1484
1615
|
p.on("error", (e) => {
|
|
1485
|
-
|
|
1616
|
+
resolve18({ stdout, stderr: stderr + (e?.message ?? ""), exitCode: -1 });
|
|
1486
1617
|
});
|
|
1487
1618
|
p.on("exit", (code) => {
|
|
1488
|
-
|
|
1619
|
+
resolve18({ stdout, stderr, exitCode: code });
|
|
1489
1620
|
});
|
|
1490
1621
|
});
|
|
1491
1622
|
}
|
|
@@ -2037,6 +2168,7 @@ import { resolve as resolve6, normalize as normalize5, relative as relative6, is
|
|
|
2037
2168
|
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
2038
2169
|
import { existsSync as existsSync5 } from "fs";
|
|
2039
2170
|
import { homedir as homedir2 } from "os";
|
|
2171
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
2040
2172
|
var PatchFileTool;
|
|
2041
2173
|
var init_PatchFileTool = __esm({
|
|
2042
2174
|
"../core/src/tools/PatchFileTool.ts"() {
|
|
@@ -2143,7 +2275,9 @@ File preview (first 200 chars): ${JSON.stringify(preview)}`,
|
|
|
2143
2275
|
let backedUpTo = null;
|
|
2144
2276
|
try {
|
|
2145
2277
|
const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
|
|
2146
|
-
const
|
|
2278
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2279
|
+
const suffix = randomBytes2(4).toString("hex");
|
|
2280
|
+
const backupPath = join6(homedir2(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
2147
2281
|
await mkdir2(join6(homedir2(), ".msapling", "backups"), { recursive: true });
|
|
2148
2282
|
await writeFile2(backupPath, originalContent, "utf8");
|
|
2149
2283
|
backedUpTo = backupPath;
|
|
@@ -2734,12 +2868,12 @@ Command: ${command}`,
|
|
|
2734
2868
|
proc.stdout?.on("data", (chunk) => chunks.stdout.push(chunk));
|
|
2735
2869
|
proc.stderr?.on("data", (chunk) => chunks.stderr.push(chunk));
|
|
2736
2870
|
const timeoutPromise = new Promise(
|
|
2737
|
-
(
|
|
2871
|
+
(resolve18) => setTimeout(() => resolve18("timeout"), timeoutMs)
|
|
2738
2872
|
);
|
|
2739
2873
|
const processPromise = (async () => {
|
|
2740
|
-
const exitCode2 = await new Promise((
|
|
2741
|
-
proc.on("exit", (code) =>
|
|
2742
|
-
proc.on("error", () =>
|
|
2874
|
+
const exitCode2 = await new Promise((resolve18) => {
|
|
2875
|
+
proc.on("exit", (code) => resolve18(code ?? 1));
|
|
2876
|
+
proc.on("error", () => resolve18(1));
|
|
2743
2877
|
});
|
|
2744
2878
|
const stdout2 = Buffer.concat(chunks.stdout).toString("utf-8");
|
|
2745
2879
|
const stderr2 = Buffer.concat(chunks.stderr).toString("utf-8");
|
|
@@ -3003,6 +3137,7 @@ import { resolve as resolve9, normalize as normalize8, relative as relative9, is
|
|
|
3003
3137
|
import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
3004
3138
|
import { existsSync as existsSync7 } from "fs";
|
|
3005
3139
|
import { homedir as homedir3 } from "os";
|
|
3140
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
3006
3141
|
function normaliseSource(source) {
|
|
3007
3142
|
if (source === "") return [];
|
|
3008
3143
|
const lines = source.split("\n");
|
|
@@ -3179,11 +3314,13 @@ var init_NotebookEditTool = __esm({
|
|
|
3179
3314
|
let backedUpTo = null;
|
|
3180
3315
|
try {
|
|
3181
3316
|
const filename = absPath.split(/[\\/]/).pop() ?? "notebook.ipynb";
|
|
3317
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3318
|
+
const suffix = randomBytes3(4).toString("hex");
|
|
3182
3319
|
const backupPath = join8(
|
|
3183
3320
|
homedir3(),
|
|
3184
3321
|
".msapling",
|
|
3185
3322
|
"backups",
|
|
3186
|
-
`${filename}
|
|
3323
|
+
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
3187
3324
|
);
|
|
3188
3325
|
await mkdir3(join8(homedir3(), ".msapling", "backups"), { recursive: true });
|
|
3189
3326
|
await writeFile3(backupPath, rawJson, "utf8");
|
|
@@ -3246,7 +3383,7 @@ var init_NotebookEditTool = __esm({
|
|
|
3246
3383
|
File: ${absPath}`;
|
|
3247
3384
|
if (backedUpTo) {
|
|
3248
3385
|
summary += `
|
|
3249
|
-
|
|
3386
|
+
Backup: ${backedUpTo}`;
|
|
3250
3387
|
}
|
|
3251
3388
|
return { content: summary };
|
|
3252
3389
|
}
|
|
@@ -3259,6 +3396,7 @@ import { resolve as resolve10, normalize as normalize9, relative as relative10,
|
|
|
3259
3396
|
import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
3260
3397
|
import { existsSync as existsSync8 } from "fs";
|
|
3261
3398
|
import { homedir as homedir4 } from "os";
|
|
3399
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
3262
3400
|
var MAX_EDITS, MultiEditFileTool;
|
|
3263
3401
|
var init_MultiEditFileTool = __esm({
|
|
3264
3402
|
"../core/src/tools/MultiEditFileTool.ts"() {
|
|
@@ -3419,11 +3557,13 @@ No changes were written (atomic: all-or-nothing).`,
|
|
|
3419
3557
|
let backedUpTo = null;
|
|
3420
3558
|
try {
|
|
3421
3559
|
const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
|
|
3560
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3561
|
+
const suffix = randomBytes4(4).toString("hex");
|
|
3422
3562
|
const backupPath = join9(
|
|
3423
3563
|
homedir4(),
|
|
3424
3564
|
".msapling",
|
|
3425
3565
|
"backups",
|
|
3426
|
-
`${filename}
|
|
3566
|
+
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
3427
3567
|
);
|
|
3428
3568
|
await mkdir4(join9(homedir4(), ".msapling", "backups"), { recursive: true });
|
|
3429
3569
|
await writeFile4(backupPath, originalContent, "utf8");
|
|
@@ -3456,6 +3596,7 @@ Pre-edit content backed up to: ${backedUpTo}`;
|
|
|
3456
3596
|
import { resolve as resolve11, normalize as normalize10, relative as relative11, isAbsolute as isAbsolute11, dirname } from "path";
|
|
3457
3597
|
import { rename, mkdir as mkdir5, copyFile, rm, stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
3458
3598
|
import { existsSync as existsSync9, statSync as statSync3 } from "fs";
|
|
3599
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
3459
3600
|
function containedPath(p, root) {
|
|
3460
3601
|
const abs = isAbsolute11(p) ? normalize10(p) : resolve11(root, p.trim());
|
|
3461
3602
|
const rel = relative11(root, abs);
|
|
@@ -3479,17 +3620,19 @@ async function copyDir(src, dst) {
|
|
|
3479
3620
|
}
|
|
3480
3621
|
async function backupFile(absPath) {
|
|
3481
3622
|
try {
|
|
3482
|
-
const { readFile: readFile23, writeFile: writeFile12, mkdir:
|
|
3483
|
-
const { homedir:
|
|
3484
|
-
const { join:
|
|
3623
|
+
const { readFile: readFile23, writeFile: writeFile12, mkdir: mkdir9 } = await import("fs/promises");
|
|
3624
|
+
const { homedir: homedir17 } = await import("os");
|
|
3625
|
+
const { join: join31 } = await import("path");
|
|
3485
3626
|
const filename = absPath.split(/[\\/]/).pop() ?? "file";
|
|
3486
|
-
const
|
|
3487
|
-
|
|
3627
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3628
|
+
const suffix = randomBytes5(4).toString("hex");
|
|
3629
|
+
const backupPath = join31(
|
|
3630
|
+
homedir17(),
|
|
3488
3631
|
".msapling",
|
|
3489
3632
|
"backups",
|
|
3490
|
-
`${filename}
|
|
3633
|
+
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
3491
3634
|
);
|
|
3492
|
-
await
|
|
3635
|
+
await mkdir9(join31(homedir17(), ".msapling", "backups"), { recursive: true });
|
|
3493
3636
|
const content = await readFile23(absPath, "utf8");
|
|
3494
3637
|
await writeFile12(backupPath, content, "utf8");
|
|
3495
3638
|
return backupPath;
|
|
@@ -3641,8 +3784,9 @@ Overwritten destination backed up to: ${backedUpTo}`;
|
|
|
3641
3784
|
});
|
|
3642
3785
|
|
|
3643
3786
|
// ../core/src/tools/DeleteFileTool.ts
|
|
3644
|
-
import { resolve as resolve12, normalize as normalize11, relative as relative12, isAbsolute as isAbsolute12, join as
|
|
3787
|
+
import { resolve as resolve12, normalize as normalize11, relative as relative12, isAbsolute as isAbsolute12, join as join11 } from "path";
|
|
3645
3788
|
import { rm as rm2, stat as stat3 } from "fs/promises";
|
|
3789
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
3646
3790
|
var DeleteFileTool;
|
|
3647
3791
|
var init_DeleteFileTool = __esm({
|
|
3648
3792
|
"../core/src/tools/DeleteFileTool.ts"() {
|
|
@@ -3715,12 +3859,14 @@ var init_DeleteFileTool = __esm({
|
|
|
3715
3859
|
let backedUpTo = null;
|
|
3716
3860
|
if (isFile) {
|
|
3717
3861
|
try {
|
|
3718
|
-
const { readFile: readFile23, writeFile: writeFile12, mkdir:
|
|
3719
|
-
const { homedir:
|
|
3862
|
+
const { readFile: readFile23, writeFile: writeFile12, mkdir: mkdir9 } = await import("fs/promises");
|
|
3863
|
+
const { homedir: homedir17 } = await import("os");
|
|
3720
3864
|
const existingContent = await readFile23(abs, "utf8");
|
|
3721
3865
|
const filename = abs.split(/[\\/]/).pop() ?? "file";
|
|
3722
|
-
const
|
|
3723
|
-
|
|
3866
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3867
|
+
const suffix = randomBytes6(4).toString("hex");
|
|
3868
|
+
const backupPath = join11(homedir17(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
3869
|
+
await mkdir9(join11(homedir17(), ".msapling", "backups"), { recursive: true });
|
|
3724
3870
|
await writeFile12(backupPath, existingContent, "utf8");
|
|
3725
3871
|
backedUpTo = backupPath;
|
|
3726
3872
|
} catch {
|
|
@@ -3744,7 +3890,7 @@ var init_DeleteFileTool = __esm({
|
|
|
3744
3890
|
let summary = `Deleted ${kind}: ${abs}`;
|
|
3745
3891
|
if (backedUpTo) {
|
|
3746
3892
|
summary += `
|
|
3747
|
-
|
|
3893
|
+
Backup: ${backedUpTo}`;
|
|
3748
3894
|
}
|
|
3749
3895
|
return { content: summary };
|
|
3750
3896
|
}
|
|
@@ -3942,12 +4088,81 @@ var init_Sandbox = __esm({
|
|
|
3942
4088
|
}
|
|
3943
4089
|
return { status: "safe", hash };
|
|
3944
4090
|
}
|
|
3945
|
-
|
|
3946
|
-
|
|
4091
|
+
/**
|
|
4092
|
+
* CLI-AUDIT-AUTO-44: curate PATH for sandboxed subprocesses.
|
|
4093
|
+
*
|
|
4094
|
+
* Previously `getRestrictedEnv` forwarded `process.env.PATH` verbatim. That
|
|
4095
|
+
* means any directory injected onto the user's PATH (npm-global, ~/.local/bin,
|
|
4096
|
+
* a malicious package's postinstall step adding /tmp/evil) inherits straight
|
|
4097
|
+
* into every sandboxed tool spawn. Containment is then trivially defeated:
|
|
4098
|
+
* "git" might resolve to /tmp/evil/git.
|
|
4099
|
+
*
|
|
4100
|
+
* Strategy:
|
|
4101
|
+
* - Default to a small allowlist of canonical system dirs (matches POSIX
|
|
4102
|
+
* `/etc/login.defs` style + common Windows system roots).
|
|
4103
|
+
* - Filter the caller's PATH against the allowlist, preserving order so
|
|
4104
|
+
* that legitimate /usr/local/bin/git still wins over /usr/bin/git.
|
|
4105
|
+
* - Drop world-writable / home-relative entries (`/tmp`, `~`, `.`, ``).
|
|
4106
|
+
* - Caller can override with an explicit `pathOverride` argument (e.g.
|
|
4107
|
+
* boot-time validation, tests).
|
|
4108
|
+
*/
|
|
4109
|
+
static SAFE_PATH_ENTRIES = /* @__PURE__ */ new Set([
|
|
4110
|
+
// POSIX
|
|
4111
|
+
"/usr/local/sbin",
|
|
4112
|
+
"/usr/local/bin",
|
|
4113
|
+
"/usr/sbin",
|
|
4114
|
+
"/usr/bin",
|
|
4115
|
+
"/sbin",
|
|
4116
|
+
"/bin",
|
|
4117
|
+
// macOS extras
|
|
4118
|
+
"/opt/homebrew/bin",
|
|
4119
|
+
"/opt/homebrew/sbin",
|
|
4120
|
+
"/opt/local/bin",
|
|
4121
|
+
// Windows
|
|
4122
|
+
"C:\\Windows\\System32",
|
|
4123
|
+
"C:\\Windows",
|
|
4124
|
+
"C:\\Windows\\System32\\Wbem",
|
|
4125
|
+
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0"
|
|
4126
|
+
]);
|
|
4127
|
+
/**
|
|
4128
|
+
* Decide whether a single PATH entry is acceptable inside the sandbox.
|
|
4129
|
+
* Exposed (static) for unit testing.
|
|
4130
|
+
*/
|
|
4131
|
+
static isSafePathEntry(entry) {
|
|
4132
|
+
if (!entry) return false;
|
|
4133
|
+
const e = entry.trim();
|
|
4134
|
+
if (!e) return false;
|
|
4135
|
+
if (e === "." || e === "..") return false;
|
|
4136
|
+
if (e.startsWith("~")) return false;
|
|
4137
|
+
if (e.startsWith("/tmp") || e.startsWith("/var/tmp")) return false;
|
|
4138
|
+
if (process.platform === "win32") {
|
|
4139
|
+
const lower = e.toLowerCase();
|
|
4140
|
+
for (const safe of _Sandbox.SAFE_PATH_ENTRIES) {
|
|
4141
|
+
if (safe.toLowerCase() === lower) return true;
|
|
4142
|
+
}
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
return _Sandbox.SAFE_PATH_ENTRIES.has(e);
|
|
4146
|
+
}
|
|
4147
|
+
/**
|
|
4148
|
+
* Build a curated PATH from the host environment, retaining only entries that
|
|
4149
|
+
* pass `isSafePathEntry`. Order is preserved. If nothing matches, fall back
|
|
4150
|
+
* to a minimal platform default so subprocess spawn never sees an empty PATH.
|
|
4151
|
+
*/
|
|
4152
|
+
static curatePath(rawPath) {
|
|
4153
|
+
const sep3 = process.platform === "win32" ? ";" : ":";
|
|
4154
|
+
const entries = (rawPath ?? "").split(sep3);
|
|
4155
|
+
const kept = entries.filter((e) => _Sandbox.isSafePathEntry(e));
|
|
4156
|
+
if (kept.length > 0) return kept.join(sep3);
|
|
4157
|
+
return process.platform === "win32" ? "C:\\Windows\\System32;C:\\Windows" : "/usr/local/bin:/usr/bin:/bin";
|
|
4158
|
+
}
|
|
4159
|
+
getRestrictedEnv(pathOverride) {
|
|
4160
|
+
const safeKeys = ["LANG", "LC_ALL", "NODE_ENV", "BUN_ENV"];
|
|
3947
4161
|
const filteredEnv = {};
|
|
3948
4162
|
for (const key of safeKeys) {
|
|
3949
4163
|
if (process.env[key]) filteredEnv[key] = process.env[key];
|
|
3950
4164
|
}
|
|
4165
|
+
filteredEnv["PATH"] = pathOverride ?? _Sandbox.curatePath(process.env.PATH);
|
|
3951
4166
|
filteredEnv["MSAPLING_SANDBOX"] = "true";
|
|
3952
4167
|
return filteredEnv;
|
|
3953
4168
|
}
|
|
@@ -3991,10 +4206,10 @@ var init_Voice = __esm({
|
|
|
3991
4206
|
`;
|
|
3992
4207
|
try {
|
|
3993
4208
|
if (process.platform === "win32") {
|
|
3994
|
-
await new Promise((
|
|
4209
|
+
await new Promise((resolve18, reject) => {
|
|
3995
4210
|
try {
|
|
3996
4211
|
const proc = spawn6("powershell", ["-Command", psCommand]);
|
|
3997
|
-
proc.on("exit", () =>
|
|
4212
|
+
proc.on("exit", () => resolve18());
|
|
3998
4213
|
proc.on("error", reject);
|
|
3999
4214
|
} catch (e) {
|
|
4000
4215
|
reject(e);
|
|
@@ -4108,7 +4323,7 @@ function matches(entry, ctx) {
|
|
|
4108
4323
|
async function runOne(entry, ctx) {
|
|
4109
4324
|
const timeoutMs = entry.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
4110
4325
|
const command = entry.command;
|
|
4111
|
-
return new Promise((
|
|
4326
|
+
return new Promise((resolve18) => {
|
|
4112
4327
|
const isWindows = process.platform === "win32";
|
|
4113
4328
|
const child = spawn7(isWindows ? "cmd.exe" : "sh", isWindows ? ["/c", command] : ["-c", command], {
|
|
4114
4329
|
cwd: ctx.cwd ?? process.cwd(),
|
|
@@ -4123,7 +4338,7 @@ async function runOne(entry, ctx) {
|
|
|
4123
4338
|
settled = true;
|
|
4124
4339
|
clearTimeout(killer);
|
|
4125
4340
|
const blocked = !!entry.blocking && (exitCode === null || exitCode !== 0);
|
|
4126
|
-
|
|
4341
|
+
resolve18({ command, exitCode, stdout, stderr, timedOut, blocked });
|
|
4127
4342
|
};
|
|
4128
4343
|
const killer = setTimeout(() => {
|
|
4129
4344
|
timedOut = true;
|
|
@@ -4371,6 +4586,35 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
4371
4586
|
};
|
|
4372
4587
|
}
|
|
4373
4588
|
}
|
|
4589
|
+
if (args2.path) {
|
|
4590
|
+
const check = this.sandbox.isPathSafe(args2.path);
|
|
4591
|
+
if (!check.safe) {
|
|
4592
|
+
await this.voice.speak(`Security block detected`, "urgent");
|
|
4593
|
+
const staleKey = `${toolName}:${(args2.path || "").trim()}`;
|
|
4594
|
+
if (this.trustStore?.has(staleKey)) {
|
|
4595
|
+
this.trustStore.delete(staleKey).catch(() => {
|
|
4596
|
+
});
|
|
4597
|
+
}
|
|
4598
|
+
return { content: `Security Block: ${check.reason}`, isError: true };
|
|
4599
|
+
}
|
|
4600
|
+
}
|
|
4601
|
+
if (toolName === "run_command" || toolName === "bash_command") {
|
|
4602
|
+
if (!args2.command) {
|
|
4603
|
+
return { content: `Error: ${toolName} requires a command argument`, isError: true };
|
|
4604
|
+
}
|
|
4605
|
+
const analysis = this.sandbox.analyzeCommand(args2.command);
|
|
4606
|
+
if (analysis.status === "blocked") {
|
|
4607
|
+
const staleCmdKey = toolName === "bash_command" ? `bash_command:${(args2.command || "").trim().replace(/\s+/g, " ")}${args2.cwd ? `:cwd=${args2.cwd}` : ""}` : `run_command:${(args2.command || "").trim()}`;
|
|
4608
|
+
if (this.trustStore?.has(staleCmdKey)) {
|
|
4609
|
+
this.trustStore.delete(staleCmdKey).catch(() => {
|
|
4610
|
+
});
|
|
4611
|
+
}
|
|
4612
|
+
return { content: `Security Block: ${analysis.reason}`, isError: true };
|
|
4613
|
+
}
|
|
4614
|
+
if (analysis.status === "dangerous" && this.mode !== "bypassPermissions") {
|
|
4615
|
+
return { content: `Dangerous Command Blocked: ${analysis.reason}. Use manual approval or trust hash ${analysis.hash}.`, isError: true };
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4374
4618
|
if (this.needsApproval(toolName) && this.approvalCallback) {
|
|
4375
4619
|
let cmdKey = "";
|
|
4376
4620
|
if (toolName === "run_command") {
|
|
@@ -4382,9 +4626,6 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
4382
4626
|
} else {
|
|
4383
4627
|
cmdKey = `${toolName}:${(args2.path || args2.instruction || "").trim()}`;
|
|
4384
4628
|
}
|
|
4385
|
-
if ((toolName === "run_command" || toolName === "bash_command") && !args2.command) {
|
|
4386
|
-
return { content: `Error: ${toolName} requires a command argument`, isError: true };
|
|
4387
|
-
}
|
|
4388
4629
|
const alreadyTrusted = this.trustStore ? this.trustStore.has(cmdKey) : this.sessionTrust.has(cmdKey);
|
|
4389
4630
|
if (!alreadyTrusted) {
|
|
4390
4631
|
const decision = await this.approvalCallback({
|
|
@@ -4431,22 +4672,6 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
4431
4672
|
Please approve the diff in the UI to sync this change locally.`
|
|
4432
4673
|
};
|
|
4433
4674
|
}
|
|
4434
|
-
if (args2.path) {
|
|
4435
|
-
const check = this.sandbox.isPathSafe(args2.path);
|
|
4436
|
-
if (!check.safe) {
|
|
4437
|
-
await this.voice.speak(`Security block detected`, "urgent");
|
|
4438
|
-
return { content: `Security Block: ${check.reason}`, isError: true };
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
if (toolName === "run_command" || toolName === "bash_command") {
|
|
4442
|
-
const analysis = this.sandbox.analyzeCommand(args2.command);
|
|
4443
|
-
if (analysis.status === "blocked") {
|
|
4444
|
-
return { content: `Security Block: ${analysis.reason}`, isError: true };
|
|
4445
|
-
}
|
|
4446
|
-
if (analysis.status === "dangerous" && this.mode !== "bypassPermissions") {
|
|
4447
|
-
return { content: `Dangerous Command Blocked: ${analysis.reason}. Use manual approval or trust hash ${analysis.hash}.`, isError: true };
|
|
4448
|
-
}
|
|
4449
|
-
}
|
|
4450
4675
|
const result = await tool.execute(args2, projectRoot);
|
|
4451
4676
|
if (this.hooks) {
|
|
4452
4677
|
this.hooks.fire({
|
|
@@ -4519,7 +4744,7 @@ var init_Safety = __esm({
|
|
|
4519
4744
|
|
|
4520
4745
|
// ../core/src/ProjectConfig.ts
|
|
4521
4746
|
import { homedir as homedir5 } from "os";
|
|
4522
|
-
import { join as
|
|
4747
|
+
import { join as join12, dirname as dirname2, parse as parsePath } from "path";
|
|
4523
4748
|
import { existsSync as existsSync10 } from "fs";
|
|
4524
4749
|
import { readFile as readFile9 } from "fs/promises";
|
|
4525
4750
|
async function readIfExists(path2) {
|
|
@@ -4533,7 +4758,7 @@ async function readIfExists(path2) {
|
|
|
4533
4758
|
}
|
|
4534
4759
|
async function findInDir(dir) {
|
|
4535
4760
|
for (const filename of FILENAMES) {
|
|
4536
|
-
const path2 =
|
|
4761
|
+
const path2 = join12(dir, filename);
|
|
4537
4762
|
const content = await readIfExists(path2);
|
|
4538
4763
|
if (content !== null) {
|
|
4539
4764
|
return { path: path2, filename, content };
|
|
@@ -4558,7 +4783,7 @@ async function findProjectConfig(start) {
|
|
|
4558
4783
|
async function findUserConfig() {
|
|
4559
4784
|
const home = homedir5();
|
|
4560
4785
|
if (!home) return null;
|
|
4561
|
-
const userDir =
|
|
4786
|
+
const userDir = join12(home, ".msapling");
|
|
4562
4787
|
return findInDir(userDir);
|
|
4563
4788
|
}
|
|
4564
4789
|
function buildCombined(user, project) {
|
|
@@ -4968,8 +5193,8 @@ var init_Mutex = __esm({
|
|
|
4968
5193
|
*/
|
|
4969
5194
|
acquire() {
|
|
4970
5195
|
let release2;
|
|
4971
|
-
const next = new Promise((
|
|
4972
|
-
release2 =
|
|
5196
|
+
const next = new Promise((resolve18) => {
|
|
5197
|
+
release2 = resolve18;
|
|
4973
5198
|
});
|
|
4974
5199
|
const entry = this._queue.then(() => release2);
|
|
4975
5200
|
this._queue = this._queue.then(() => next);
|
|
@@ -4992,17 +5217,17 @@ var init_Mutex = __esm({
|
|
|
4992
5217
|
});
|
|
4993
5218
|
|
|
4994
5219
|
// ../core/src/TrustStore.ts
|
|
4995
|
-
import { join as
|
|
4996
|
-
import { homedir as homedir6 } from "os";
|
|
5220
|
+
import { join as join13 } from "path";
|
|
5221
|
+
import { homedir as homedir6, platform as platform2 } from "os";
|
|
4997
5222
|
import { existsSync as existsSync11, mkdirSync } from "fs";
|
|
4998
|
-
import { readFile as readFile10, writeFile as writeFile5 } from "fs/promises";
|
|
5223
|
+
import { readFile as readFile10, writeFile as writeFile5, chmod } from "fs/promises";
|
|
4999
5224
|
var USER_SETTINGS_PATH, TrustStore;
|
|
5000
5225
|
var init_TrustStore = __esm({
|
|
5001
5226
|
"../core/src/TrustStore.ts"() {
|
|
5002
5227
|
"use strict";
|
|
5003
5228
|
init_esm_shims();
|
|
5004
5229
|
init_Mutex();
|
|
5005
|
-
USER_SETTINGS_PATH =
|
|
5230
|
+
USER_SETTINGS_PATH = join13(homedir6(), ".msapling", "settings.json");
|
|
5006
5231
|
TrustStore = class {
|
|
5007
5232
|
/** Current in-memory set of trusted `tool:command` keys. */
|
|
5008
5233
|
trusted = /* @__PURE__ */ new Set();
|
|
@@ -5032,9 +5257,15 @@ var init_TrustStore = __esm({
|
|
|
5032
5257
|
* updating `trustedCommands`.
|
|
5033
5258
|
*/
|
|
5034
5259
|
async writeSettings(settings) {
|
|
5035
|
-
const dir =
|
|
5260
|
+
const dir = join13(homedir6(), ".msapling");
|
|
5036
5261
|
if (!existsSync11(dir)) mkdirSync(dir, { recursive: true });
|
|
5037
5262
|
await writeFile5(this.settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
5263
|
+
if (platform2() !== "win32") {
|
|
5264
|
+
try {
|
|
5265
|
+
await chmod(this.settingsPath, 384);
|
|
5266
|
+
} catch {
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5038
5269
|
}
|
|
5039
5270
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
5040
5271
|
/**
|
|
@@ -5112,7 +5343,7 @@ var require_polyfills = __commonJS({
|
|
|
5112
5343
|
var constants = __require("constants");
|
|
5113
5344
|
var origCwd = process.cwd;
|
|
5114
5345
|
var cwd = null;
|
|
5115
|
-
var
|
|
5346
|
+
var platform4 = process.env.GRACEFUL_FS_PLATFORM || process.platform;
|
|
5116
5347
|
process.cwd = function() {
|
|
5117
5348
|
if (!cwd)
|
|
5118
5349
|
cwd = origCwd.call(process);
|
|
@@ -5132,54 +5363,54 @@ var require_polyfills = __commonJS({
|
|
|
5132
5363
|
}
|
|
5133
5364
|
var chdir;
|
|
5134
5365
|
module.exports = patch;
|
|
5135
|
-
function patch(
|
|
5366
|
+
function patch(fs3) {
|
|
5136
5367
|
if (constants.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
|
|
5137
|
-
patchLchmod(
|
|
5138
|
-
}
|
|
5139
|
-
if (!
|
|
5140
|
-
patchLutimes(
|
|
5141
|
-
}
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
if (
|
|
5161
|
-
|
|
5368
|
+
patchLchmod(fs3);
|
|
5369
|
+
}
|
|
5370
|
+
if (!fs3.lutimes) {
|
|
5371
|
+
patchLutimes(fs3);
|
|
5372
|
+
}
|
|
5373
|
+
fs3.chown = chownFix(fs3.chown);
|
|
5374
|
+
fs3.fchown = chownFix(fs3.fchown);
|
|
5375
|
+
fs3.lchown = chownFix(fs3.lchown);
|
|
5376
|
+
fs3.chmod = chmodFix(fs3.chmod);
|
|
5377
|
+
fs3.fchmod = chmodFix(fs3.fchmod);
|
|
5378
|
+
fs3.lchmod = chmodFix(fs3.lchmod);
|
|
5379
|
+
fs3.chownSync = chownFixSync(fs3.chownSync);
|
|
5380
|
+
fs3.fchownSync = chownFixSync(fs3.fchownSync);
|
|
5381
|
+
fs3.lchownSync = chownFixSync(fs3.lchownSync);
|
|
5382
|
+
fs3.chmodSync = chmodFixSync(fs3.chmodSync);
|
|
5383
|
+
fs3.fchmodSync = chmodFixSync(fs3.fchmodSync);
|
|
5384
|
+
fs3.lchmodSync = chmodFixSync(fs3.lchmodSync);
|
|
5385
|
+
fs3.stat = statFix(fs3.stat);
|
|
5386
|
+
fs3.fstat = statFix(fs3.fstat);
|
|
5387
|
+
fs3.lstat = statFix(fs3.lstat);
|
|
5388
|
+
fs3.statSync = statFixSync(fs3.statSync);
|
|
5389
|
+
fs3.fstatSync = statFixSync(fs3.fstatSync);
|
|
5390
|
+
fs3.lstatSync = statFixSync(fs3.lstatSync);
|
|
5391
|
+
if (fs3.chmod && !fs3.lchmod) {
|
|
5392
|
+
fs3.lchmod = function(path2, mode, cb) {
|
|
5162
5393
|
if (cb) process.nextTick(cb);
|
|
5163
5394
|
};
|
|
5164
|
-
|
|
5395
|
+
fs3.lchmodSync = function() {
|
|
5165
5396
|
};
|
|
5166
5397
|
}
|
|
5167
|
-
if (
|
|
5168
|
-
|
|
5398
|
+
if (fs3.chown && !fs3.lchown) {
|
|
5399
|
+
fs3.lchown = function(path2, uid, gid, cb) {
|
|
5169
5400
|
if (cb) process.nextTick(cb);
|
|
5170
5401
|
};
|
|
5171
|
-
|
|
5402
|
+
fs3.lchownSync = function() {
|
|
5172
5403
|
};
|
|
5173
5404
|
}
|
|
5174
|
-
if (
|
|
5175
|
-
|
|
5405
|
+
if (platform4 === "win32") {
|
|
5406
|
+
fs3.rename = typeof fs3.rename !== "function" ? fs3.rename : (function(fs$rename) {
|
|
5176
5407
|
function rename2(from, to, cb) {
|
|
5177
5408
|
var start = Date.now();
|
|
5178
5409
|
var backoff = 0;
|
|
5179
5410
|
fs$rename(from, to, function CB(er) {
|
|
5180
5411
|
if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() - start < 6e4) {
|
|
5181
5412
|
setTimeout(function() {
|
|
5182
|
-
|
|
5413
|
+
fs3.stat(to, function(stater, st) {
|
|
5183
5414
|
if (stater && stater.code === "ENOENT")
|
|
5184
5415
|
fs$rename(from, to, CB);
|
|
5185
5416
|
else
|
|
@@ -5195,9 +5426,9 @@ var require_polyfills = __commonJS({
|
|
|
5195
5426
|
}
|
|
5196
5427
|
if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
|
|
5197
5428
|
return rename2;
|
|
5198
|
-
})(
|
|
5429
|
+
})(fs3.rename);
|
|
5199
5430
|
}
|
|
5200
|
-
|
|
5431
|
+
fs3.read = typeof fs3.read !== "function" ? fs3.read : (function(fs$read) {
|
|
5201
5432
|
function read(fd, buffer, offset, length, position, callback_) {
|
|
5202
5433
|
var callback;
|
|
5203
5434
|
if (callback_ && typeof callback_ === "function") {
|
|
@@ -5205,22 +5436,22 @@ var require_polyfills = __commonJS({
|
|
|
5205
5436
|
callback = function(er, _, __) {
|
|
5206
5437
|
if (er && er.code === "EAGAIN" && eagCounter < 10) {
|
|
5207
5438
|
eagCounter++;
|
|
5208
|
-
return fs$read.call(
|
|
5439
|
+
return fs$read.call(fs3, fd, buffer, offset, length, position, callback);
|
|
5209
5440
|
}
|
|
5210
5441
|
callback_.apply(this, arguments);
|
|
5211
5442
|
};
|
|
5212
5443
|
}
|
|
5213
|
-
return fs$read.call(
|
|
5444
|
+
return fs$read.call(fs3, fd, buffer, offset, length, position, callback);
|
|
5214
5445
|
}
|
|
5215
5446
|
if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read);
|
|
5216
5447
|
return read;
|
|
5217
|
-
})(
|
|
5218
|
-
|
|
5448
|
+
})(fs3.read);
|
|
5449
|
+
fs3.readSync = typeof fs3.readSync !== "function" ? fs3.readSync : /* @__PURE__ */ (function(fs$readSync) {
|
|
5219
5450
|
return function(fd, buffer, offset, length, position) {
|
|
5220
5451
|
var eagCounter = 0;
|
|
5221
5452
|
while (true) {
|
|
5222
5453
|
try {
|
|
5223
|
-
return fs$readSync.call(
|
|
5454
|
+
return fs$readSync.call(fs3, fd, buffer, offset, length, position);
|
|
5224
5455
|
} catch (er) {
|
|
5225
5456
|
if (er.code === "EAGAIN" && eagCounter < 10) {
|
|
5226
5457
|
eagCounter++;
|
|
@@ -5230,10 +5461,10 @@ var require_polyfills = __commonJS({
|
|
|
5230
5461
|
}
|
|
5231
5462
|
}
|
|
5232
5463
|
};
|
|
5233
|
-
})(
|
|
5234
|
-
function patchLchmod(
|
|
5235
|
-
|
|
5236
|
-
|
|
5464
|
+
})(fs3.readSync);
|
|
5465
|
+
function patchLchmod(fs4) {
|
|
5466
|
+
fs4.lchmod = function(path2, mode, callback) {
|
|
5467
|
+
fs4.open(
|
|
5237
5468
|
path2,
|
|
5238
5469
|
constants.O_WRONLY | constants.O_SYMLINK,
|
|
5239
5470
|
mode,
|
|
@@ -5242,80 +5473,80 @@ var require_polyfills = __commonJS({
|
|
|
5242
5473
|
if (callback) callback(err);
|
|
5243
5474
|
return;
|
|
5244
5475
|
}
|
|
5245
|
-
|
|
5246
|
-
|
|
5476
|
+
fs4.fchmod(fd, mode, function(err2) {
|
|
5477
|
+
fs4.close(fd, function(err22) {
|
|
5247
5478
|
if (callback) callback(err2 || err22);
|
|
5248
5479
|
});
|
|
5249
5480
|
});
|
|
5250
5481
|
}
|
|
5251
5482
|
);
|
|
5252
5483
|
};
|
|
5253
|
-
|
|
5254
|
-
var fd =
|
|
5484
|
+
fs4.lchmodSync = function(path2, mode) {
|
|
5485
|
+
var fd = fs4.openSync(path2, constants.O_WRONLY | constants.O_SYMLINK, mode);
|
|
5255
5486
|
var threw = true;
|
|
5256
5487
|
var ret;
|
|
5257
5488
|
try {
|
|
5258
|
-
ret =
|
|
5489
|
+
ret = fs4.fchmodSync(fd, mode);
|
|
5259
5490
|
threw = false;
|
|
5260
5491
|
} finally {
|
|
5261
5492
|
if (threw) {
|
|
5262
5493
|
try {
|
|
5263
|
-
|
|
5494
|
+
fs4.closeSync(fd);
|
|
5264
5495
|
} catch (er) {
|
|
5265
5496
|
}
|
|
5266
5497
|
} else {
|
|
5267
|
-
|
|
5498
|
+
fs4.closeSync(fd);
|
|
5268
5499
|
}
|
|
5269
5500
|
}
|
|
5270
5501
|
return ret;
|
|
5271
5502
|
};
|
|
5272
5503
|
}
|
|
5273
|
-
function patchLutimes(
|
|
5274
|
-
if (constants.hasOwnProperty("O_SYMLINK") &&
|
|
5275
|
-
|
|
5276
|
-
|
|
5504
|
+
function patchLutimes(fs4) {
|
|
5505
|
+
if (constants.hasOwnProperty("O_SYMLINK") && fs4.futimes) {
|
|
5506
|
+
fs4.lutimes = function(path2, at, mt, cb) {
|
|
5507
|
+
fs4.open(path2, constants.O_SYMLINK, function(er, fd) {
|
|
5277
5508
|
if (er) {
|
|
5278
5509
|
if (cb) cb(er);
|
|
5279
5510
|
return;
|
|
5280
5511
|
}
|
|
5281
|
-
|
|
5282
|
-
|
|
5512
|
+
fs4.futimes(fd, at, mt, function(er2) {
|
|
5513
|
+
fs4.close(fd, function(er22) {
|
|
5283
5514
|
if (cb) cb(er2 || er22);
|
|
5284
5515
|
});
|
|
5285
5516
|
});
|
|
5286
5517
|
});
|
|
5287
5518
|
};
|
|
5288
|
-
|
|
5289
|
-
var fd =
|
|
5519
|
+
fs4.lutimesSync = function(path2, at, mt) {
|
|
5520
|
+
var fd = fs4.openSync(path2, constants.O_SYMLINK);
|
|
5290
5521
|
var ret;
|
|
5291
5522
|
var threw = true;
|
|
5292
5523
|
try {
|
|
5293
|
-
ret =
|
|
5524
|
+
ret = fs4.futimesSync(fd, at, mt);
|
|
5294
5525
|
threw = false;
|
|
5295
5526
|
} finally {
|
|
5296
5527
|
if (threw) {
|
|
5297
5528
|
try {
|
|
5298
|
-
|
|
5529
|
+
fs4.closeSync(fd);
|
|
5299
5530
|
} catch (er) {
|
|
5300
5531
|
}
|
|
5301
5532
|
} else {
|
|
5302
|
-
|
|
5533
|
+
fs4.closeSync(fd);
|
|
5303
5534
|
}
|
|
5304
5535
|
}
|
|
5305
5536
|
return ret;
|
|
5306
5537
|
};
|
|
5307
|
-
} else if (
|
|
5308
|
-
|
|
5538
|
+
} else if (fs4.futimes) {
|
|
5539
|
+
fs4.lutimes = function(_a, _b, _c, cb) {
|
|
5309
5540
|
if (cb) process.nextTick(cb);
|
|
5310
5541
|
};
|
|
5311
|
-
|
|
5542
|
+
fs4.lutimesSync = function() {
|
|
5312
5543
|
};
|
|
5313
5544
|
}
|
|
5314
5545
|
}
|
|
5315
5546
|
function chmodFix(orig) {
|
|
5316
5547
|
if (!orig) return orig;
|
|
5317
5548
|
return function(target, mode, cb) {
|
|
5318
|
-
return orig.call(
|
|
5549
|
+
return orig.call(fs3, target, mode, function(er) {
|
|
5319
5550
|
if (chownErOk(er)) er = null;
|
|
5320
5551
|
if (cb) cb.apply(this, arguments);
|
|
5321
5552
|
});
|
|
@@ -5325,7 +5556,7 @@ var require_polyfills = __commonJS({
|
|
|
5325
5556
|
if (!orig) return orig;
|
|
5326
5557
|
return function(target, mode) {
|
|
5327
5558
|
try {
|
|
5328
|
-
return orig.call(
|
|
5559
|
+
return orig.call(fs3, target, mode);
|
|
5329
5560
|
} catch (er) {
|
|
5330
5561
|
if (!chownErOk(er)) throw er;
|
|
5331
5562
|
}
|
|
@@ -5334,7 +5565,7 @@ var require_polyfills = __commonJS({
|
|
|
5334
5565
|
function chownFix(orig) {
|
|
5335
5566
|
if (!orig) return orig;
|
|
5336
5567
|
return function(target, uid, gid, cb) {
|
|
5337
|
-
return orig.call(
|
|
5568
|
+
return orig.call(fs3, target, uid, gid, function(er) {
|
|
5338
5569
|
if (chownErOk(er)) er = null;
|
|
5339
5570
|
if (cb) cb.apply(this, arguments);
|
|
5340
5571
|
});
|
|
@@ -5344,7 +5575,7 @@ var require_polyfills = __commonJS({
|
|
|
5344
5575
|
if (!orig) return orig;
|
|
5345
5576
|
return function(target, uid, gid) {
|
|
5346
5577
|
try {
|
|
5347
|
-
return orig.call(
|
|
5578
|
+
return orig.call(fs3, target, uid, gid);
|
|
5348
5579
|
} catch (er) {
|
|
5349
5580
|
if (!chownErOk(er)) throw er;
|
|
5350
5581
|
}
|
|
@@ -5364,13 +5595,13 @@ var require_polyfills = __commonJS({
|
|
|
5364
5595
|
}
|
|
5365
5596
|
if (cb) cb.apply(this, arguments);
|
|
5366
5597
|
}
|
|
5367
|
-
return options ? orig.call(
|
|
5598
|
+
return options ? orig.call(fs3, target, options, callback) : orig.call(fs3, target, callback);
|
|
5368
5599
|
};
|
|
5369
5600
|
}
|
|
5370
5601
|
function statFixSync(orig) {
|
|
5371
5602
|
if (!orig) return orig;
|
|
5372
5603
|
return function(target, options) {
|
|
5373
|
-
var stats = options ? orig.call(
|
|
5604
|
+
var stats = options ? orig.call(fs3, target, options) : orig.call(fs3, target);
|
|
5374
5605
|
if (stats) {
|
|
5375
5606
|
if (stats.uid < 0) stats.uid += 4294967296;
|
|
5376
5607
|
if (stats.gid < 0) stats.gid += 4294967296;
|
|
@@ -5401,7 +5632,7 @@ var require_legacy_streams = __commonJS({
|
|
|
5401
5632
|
init_esm_shims();
|
|
5402
5633
|
var Stream = __require("stream").Stream;
|
|
5403
5634
|
module.exports = legacy;
|
|
5404
|
-
function legacy(
|
|
5635
|
+
function legacy(fs3) {
|
|
5405
5636
|
return {
|
|
5406
5637
|
ReadStream,
|
|
5407
5638
|
WriteStream
|
|
@@ -5444,7 +5675,7 @@ var require_legacy_streams = __commonJS({
|
|
|
5444
5675
|
});
|
|
5445
5676
|
return;
|
|
5446
5677
|
}
|
|
5447
|
-
|
|
5678
|
+
fs3.open(this.path, this.flags, this.mode, function(err, fd) {
|
|
5448
5679
|
if (err) {
|
|
5449
5680
|
self.emit("error", err);
|
|
5450
5681
|
self.readable = false;
|
|
@@ -5483,7 +5714,7 @@ var require_legacy_streams = __commonJS({
|
|
|
5483
5714
|
this.busy = false;
|
|
5484
5715
|
this._queue = [];
|
|
5485
5716
|
if (this.fd === null) {
|
|
5486
|
-
this._open =
|
|
5717
|
+
this._open = fs3.open;
|
|
5487
5718
|
this._queue.push([this._open, this.path, this.flags, this.mode, void 0]);
|
|
5488
5719
|
this.flush();
|
|
5489
5720
|
}
|
|
@@ -5521,7 +5752,7 @@ var require_graceful_fs = __commonJS({
|
|
|
5521
5752
|
"../../node_modules/.bun/graceful-fs@4.2.11/node_modules/graceful-fs/graceful-fs.js"(exports, module) {
|
|
5522
5753
|
"use strict";
|
|
5523
5754
|
init_esm_shims();
|
|
5524
|
-
var
|
|
5755
|
+
var fs3 = __require("fs");
|
|
5525
5756
|
var polyfills = require_polyfills();
|
|
5526
5757
|
var legacy = require_legacy_streams();
|
|
5527
5758
|
var clone = require_clone();
|
|
@@ -5553,12 +5784,12 @@ var require_graceful_fs = __commonJS({
|
|
|
5553
5784
|
m = "GFS4: " + m.split(/\n/).join("\nGFS4: ");
|
|
5554
5785
|
console.error(m);
|
|
5555
5786
|
};
|
|
5556
|
-
if (!
|
|
5787
|
+
if (!fs3[gracefulQueue]) {
|
|
5557
5788
|
queue = global[gracefulQueue] || [];
|
|
5558
|
-
publishQueue(
|
|
5559
|
-
|
|
5789
|
+
publishQueue(fs3, queue);
|
|
5790
|
+
fs3.close = (function(fs$close) {
|
|
5560
5791
|
function close(fd, cb) {
|
|
5561
|
-
return fs$close.call(
|
|
5792
|
+
return fs$close.call(fs3, fd, function(err) {
|
|
5562
5793
|
if (!err) {
|
|
5563
5794
|
resetQueue();
|
|
5564
5795
|
}
|
|
@@ -5570,40 +5801,40 @@ var require_graceful_fs = __commonJS({
|
|
|
5570
5801
|
value: fs$close
|
|
5571
5802
|
});
|
|
5572
5803
|
return close;
|
|
5573
|
-
})(
|
|
5574
|
-
|
|
5804
|
+
})(fs3.close);
|
|
5805
|
+
fs3.closeSync = (function(fs$closeSync) {
|
|
5575
5806
|
function closeSync(fd) {
|
|
5576
|
-
fs$closeSync.apply(
|
|
5807
|
+
fs$closeSync.apply(fs3, arguments);
|
|
5577
5808
|
resetQueue();
|
|
5578
5809
|
}
|
|
5579
5810
|
Object.defineProperty(closeSync, previousSymbol, {
|
|
5580
5811
|
value: fs$closeSync
|
|
5581
5812
|
});
|
|
5582
5813
|
return closeSync;
|
|
5583
|
-
})(
|
|
5814
|
+
})(fs3.closeSync);
|
|
5584
5815
|
if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
|
|
5585
5816
|
process.on("exit", function() {
|
|
5586
|
-
debug(
|
|
5587
|
-
__require("assert").equal(
|
|
5817
|
+
debug(fs3[gracefulQueue]);
|
|
5818
|
+
__require("assert").equal(fs3[gracefulQueue].length, 0);
|
|
5588
5819
|
});
|
|
5589
5820
|
}
|
|
5590
5821
|
}
|
|
5591
5822
|
var queue;
|
|
5592
5823
|
if (!global[gracefulQueue]) {
|
|
5593
|
-
publishQueue(global,
|
|
5594
|
-
}
|
|
5595
|
-
module.exports = patch(clone(
|
|
5596
|
-
if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !
|
|
5597
|
-
module.exports = patch(
|
|
5598
|
-
|
|
5599
|
-
}
|
|
5600
|
-
function patch(
|
|
5601
|
-
polyfills(
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
var fs$readFile =
|
|
5606
|
-
|
|
5824
|
+
publishQueue(global, fs3[gracefulQueue]);
|
|
5825
|
+
}
|
|
5826
|
+
module.exports = patch(clone(fs3));
|
|
5827
|
+
if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs3.__patched) {
|
|
5828
|
+
module.exports = patch(fs3);
|
|
5829
|
+
fs3.__patched = true;
|
|
5830
|
+
}
|
|
5831
|
+
function patch(fs4) {
|
|
5832
|
+
polyfills(fs4);
|
|
5833
|
+
fs4.gracefulify = patch;
|
|
5834
|
+
fs4.createReadStream = createReadStream;
|
|
5835
|
+
fs4.createWriteStream = createWriteStream;
|
|
5836
|
+
var fs$readFile = fs4.readFile;
|
|
5837
|
+
fs4.readFile = readFile23;
|
|
5607
5838
|
function readFile23(path2, options, cb) {
|
|
5608
5839
|
if (typeof options === "function")
|
|
5609
5840
|
cb = options, options = null;
|
|
@@ -5619,8 +5850,8 @@ var require_graceful_fs = __commonJS({
|
|
|
5619
5850
|
});
|
|
5620
5851
|
}
|
|
5621
5852
|
}
|
|
5622
|
-
var fs$writeFile =
|
|
5623
|
-
|
|
5853
|
+
var fs$writeFile = fs4.writeFile;
|
|
5854
|
+
fs4.writeFile = writeFile12;
|
|
5624
5855
|
function writeFile12(path2, data, options, cb) {
|
|
5625
5856
|
if (typeof options === "function")
|
|
5626
5857
|
cb = options, options = null;
|
|
@@ -5636,10 +5867,10 @@ var require_graceful_fs = __commonJS({
|
|
|
5636
5867
|
});
|
|
5637
5868
|
}
|
|
5638
5869
|
}
|
|
5639
|
-
var fs$appendFile =
|
|
5870
|
+
var fs$appendFile = fs4.appendFile;
|
|
5640
5871
|
if (fs$appendFile)
|
|
5641
|
-
|
|
5642
|
-
function
|
|
5872
|
+
fs4.appendFile = appendFile2;
|
|
5873
|
+
function appendFile2(path2, data, options, cb) {
|
|
5643
5874
|
if (typeof options === "function")
|
|
5644
5875
|
cb = options, options = null;
|
|
5645
5876
|
return go$appendFile(path2, data, options, cb);
|
|
@@ -5654,9 +5885,9 @@ var require_graceful_fs = __commonJS({
|
|
|
5654
5885
|
});
|
|
5655
5886
|
}
|
|
5656
5887
|
}
|
|
5657
|
-
var fs$copyFile =
|
|
5888
|
+
var fs$copyFile = fs4.copyFile;
|
|
5658
5889
|
if (fs$copyFile)
|
|
5659
|
-
|
|
5890
|
+
fs4.copyFile = copyFile2;
|
|
5660
5891
|
function copyFile2(src, dest, flags, cb) {
|
|
5661
5892
|
if (typeof flags === "function") {
|
|
5662
5893
|
cb = flags;
|
|
@@ -5674,10 +5905,10 @@ var require_graceful_fs = __commonJS({
|
|
|
5674
5905
|
});
|
|
5675
5906
|
}
|
|
5676
5907
|
}
|
|
5677
|
-
var fs$readdir =
|
|
5678
|
-
|
|
5908
|
+
var fs$readdir = fs4.readdir;
|
|
5909
|
+
fs4.readdir = readdir5;
|
|
5679
5910
|
var noReaddirOptionVersions = /^v[0-5]\./;
|
|
5680
|
-
function
|
|
5911
|
+
function readdir5(path2, options, cb) {
|
|
5681
5912
|
if (typeof options === "function")
|
|
5682
5913
|
cb = options, options = null;
|
|
5683
5914
|
var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path3, options2, cb2, startTime) {
|
|
@@ -5716,21 +5947,21 @@ var require_graceful_fs = __commonJS({
|
|
|
5716
5947
|
}
|
|
5717
5948
|
}
|
|
5718
5949
|
if (process.version.substr(0, 4) === "v0.8") {
|
|
5719
|
-
var legStreams = legacy(
|
|
5950
|
+
var legStreams = legacy(fs4);
|
|
5720
5951
|
ReadStream = legStreams.ReadStream;
|
|
5721
5952
|
WriteStream = legStreams.WriteStream;
|
|
5722
5953
|
}
|
|
5723
|
-
var fs$ReadStream =
|
|
5954
|
+
var fs$ReadStream = fs4.ReadStream;
|
|
5724
5955
|
if (fs$ReadStream) {
|
|
5725
5956
|
ReadStream.prototype = Object.create(fs$ReadStream.prototype);
|
|
5726
5957
|
ReadStream.prototype.open = ReadStream$open;
|
|
5727
5958
|
}
|
|
5728
|
-
var fs$WriteStream =
|
|
5959
|
+
var fs$WriteStream = fs4.WriteStream;
|
|
5729
5960
|
if (fs$WriteStream) {
|
|
5730
5961
|
WriteStream.prototype = Object.create(fs$WriteStream.prototype);
|
|
5731
5962
|
WriteStream.prototype.open = WriteStream$open;
|
|
5732
5963
|
}
|
|
5733
|
-
Object.defineProperty(
|
|
5964
|
+
Object.defineProperty(fs4, "ReadStream", {
|
|
5734
5965
|
get: function() {
|
|
5735
5966
|
return ReadStream;
|
|
5736
5967
|
},
|
|
@@ -5740,7 +5971,7 @@ var require_graceful_fs = __commonJS({
|
|
|
5740
5971
|
enumerable: true,
|
|
5741
5972
|
configurable: true
|
|
5742
5973
|
});
|
|
5743
|
-
Object.defineProperty(
|
|
5974
|
+
Object.defineProperty(fs4, "WriteStream", {
|
|
5744
5975
|
get: function() {
|
|
5745
5976
|
return WriteStream;
|
|
5746
5977
|
},
|
|
@@ -5751,7 +5982,7 @@ var require_graceful_fs = __commonJS({
|
|
|
5751
5982
|
configurable: true
|
|
5752
5983
|
});
|
|
5753
5984
|
var FileReadStream = ReadStream;
|
|
5754
|
-
Object.defineProperty(
|
|
5985
|
+
Object.defineProperty(fs4, "FileReadStream", {
|
|
5755
5986
|
get: function() {
|
|
5756
5987
|
return FileReadStream;
|
|
5757
5988
|
},
|
|
@@ -5762,7 +5993,7 @@ var require_graceful_fs = __commonJS({
|
|
|
5762
5993
|
configurable: true
|
|
5763
5994
|
});
|
|
5764
5995
|
var FileWriteStream = WriteStream;
|
|
5765
|
-
Object.defineProperty(
|
|
5996
|
+
Object.defineProperty(fs4, "FileWriteStream", {
|
|
5766
5997
|
get: function() {
|
|
5767
5998
|
return FileWriteStream;
|
|
5768
5999
|
},
|
|
@@ -5811,13 +6042,13 @@ var require_graceful_fs = __commonJS({
|
|
|
5811
6042
|
});
|
|
5812
6043
|
}
|
|
5813
6044
|
function createReadStream(path2, options) {
|
|
5814
|
-
return new
|
|
6045
|
+
return new fs4.ReadStream(path2, options);
|
|
5815
6046
|
}
|
|
5816
6047
|
function createWriteStream(path2, options) {
|
|
5817
|
-
return new
|
|
6048
|
+
return new fs4.WriteStream(path2, options);
|
|
5818
6049
|
}
|
|
5819
|
-
var fs$open =
|
|
5820
|
-
|
|
6050
|
+
var fs$open = fs4.open;
|
|
6051
|
+
fs4.open = open;
|
|
5821
6052
|
function open(path2, flags, mode, cb) {
|
|
5822
6053
|
if (typeof mode === "function")
|
|
5823
6054
|
cb = mode, mode = null;
|
|
@@ -5833,20 +6064,20 @@ var require_graceful_fs = __commonJS({
|
|
|
5833
6064
|
});
|
|
5834
6065
|
}
|
|
5835
6066
|
}
|
|
5836
|
-
return
|
|
6067
|
+
return fs4;
|
|
5837
6068
|
}
|
|
5838
6069
|
function enqueue(elem) {
|
|
5839
6070
|
debug("ENQUEUE", elem[0].name, elem[1]);
|
|
5840
|
-
|
|
6071
|
+
fs3[gracefulQueue].push(elem);
|
|
5841
6072
|
retry();
|
|
5842
6073
|
}
|
|
5843
6074
|
var retryTimer;
|
|
5844
6075
|
function resetQueue() {
|
|
5845
6076
|
var now = Date.now();
|
|
5846
|
-
for (var i = 0; i <
|
|
5847
|
-
if (
|
|
5848
|
-
|
|
5849
|
-
|
|
6077
|
+
for (var i = 0; i < fs3[gracefulQueue].length; ++i) {
|
|
6078
|
+
if (fs3[gracefulQueue][i].length > 2) {
|
|
6079
|
+
fs3[gracefulQueue][i][3] = now;
|
|
6080
|
+
fs3[gracefulQueue][i][4] = now;
|
|
5850
6081
|
}
|
|
5851
6082
|
}
|
|
5852
6083
|
retry();
|
|
@@ -5854,9 +6085,9 @@ var require_graceful_fs = __commonJS({
|
|
|
5854
6085
|
function retry() {
|
|
5855
6086
|
clearTimeout(retryTimer);
|
|
5856
6087
|
retryTimer = void 0;
|
|
5857
|
-
if (
|
|
6088
|
+
if (fs3[gracefulQueue].length === 0)
|
|
5858
6089
|
return;
|
|
5859
|
-
var elem =
|
|
6090
|
+
var elem = fs3[gracefulQueue].shift();
|
|
5860
6091
|
var fn = elem[0];
|
|
5861
6092
|
var args2 = elem[1];
|
|
5862
6093
|
var err = elem[2];
|
|
@@ -5878,7 +6109,7 @@ var require_graceful_fs = __commonJS({
|
|
|
5878
6109
|
debug("RETRY", fn.name, args2);
|
|
5879
6110
|
fn.apply(null, args2.concat([startTime]));
|
|
5880
6111
|
} else {
|
|
5881
|
-
|
|
6112
|
+
fs3[gracefulQueue].push(elem);
|
|
5882
6113
|
}
|
|
5883
6114
|
}
|
|
5884
6115
|
if (retryTimer === void 0) {
|
|
@@ -6324,10 +6555,10 @@ var require_mtime_precision = __commonJS({
|
|
|
6324
6555
|
"use strict";
|
|
6325
6556
|
init_esm_shims();
|
|
6326
6557
|
var cacheSymbol = /* @__PURE__ */ Symbol();
|
|
6327
|
-
function probe(file,
|
|
6328
|
-
const cachedPrecision =
|
|
6558
|
+
function probe(file, fs3, callback) {
|
|
6559
|
+
const cachedPrecision = fs3[cacheSymbol];
|
|
6329
6560
|
if (cachedPrecision) {
|
|
6330
|
-
return
|
|
6561
|
+
return fs3.stat(file, (err, stat5) => {
|
|
6331
6562
|
if (err) {
|
|
6332
6563
|
return callback(err);
|
|
6333
6564
|
}
|
|
@@ -6335,16 +6566,16 @@ var require_mtime_precision = __commonJS({
|
|
|
6335
6566
|
});
|
|
6336
6567
|
}
|
|
6337
6568
|
const mtime = new Date(Math.ceil(Date.now() / 1e3) * 1e3 + 5);
|
|
6338
|
-
|
|
6569
|
+
fs3.utimes(file, mtime, mtime, (err) => {
|
|
6339
6570
|
if (err) {
|
|
6340
6571
|
return callback(err);
|
|
6341
6572
|
}
|
|
6342
|
-
|
|
6573
|
+
fs3.stat(file, (err2, stat5) => {
|
|
6343
6574
|
if (err2) {
|
|
6344
6575
|
return callback(err2);
|
|
6345
6576
|
}
|
|
6346
6577
|
const precision = stat5.mtime.getTime() % 1e3 === 0 ? "s" : "ms";
|
|
6347
|
-
Object.defineProperty(
|
|
6578
|
+
Object.defineProperty(fs3, cacheSymbol, { value: precision });
|
|
6348
6579
|
callback(null, stat5.mtime, precision);
|
|
6349
6580
|
});
|
|
6350
6581
|
});
|
|
@@ -6367,7 +6598,7 @@ var require_lockfile = __commonJS({
|
|
|
6367
6598
|
"use strict";
|
|
6368
6599
|
init_esm_shims();
|
|
6369
6600
|
var path2 = __require("path");
|
|
6370
|
-
var
|
|
6601
|
+
var fs3 = require_graceful_fs();
|
|
6371
6602
|
var retry = require_retry2();
|
|
6372
6603
|
var onExit = require_signal_exit();
|
|
6373
6604
|
var mtimePrecision = require_mtime_precision();
|
|
@@ -6498,7 +6729,7 @@ var require_lockfile = __commonJS({
|
|
|
6498
6729
|
update: null,
|
|
6499
6730
|
realpath: true,
|
|
6500
6731
|
retries: 0,
|
|
6501
|
-
fs,
|
|
6732
|
+
fs: fs3,
|
|
6502
6733
|
onCompromised: (err) => {
|
|
6503
6734
|
throw err;
|
|
6504
6735
|
},
|
|
@@ -6542,7 +6773,7 @@ var require_lockfile = __commonJS({
|
|
|
6542
6773
|
}
|
|
6543
6774
|
function unlock2(file, options, callback) {
|
|
6544
6775
|
options = {
|
|
6545
|
-
fs,
|
|
6776
|
+
fs: fs3,
|
|
6546
6777
|
realpath: true,
|
|
6547
6778
|
...options
|
|
6548
6779
|
};
|
|
@@ -6564,7 +6795,7 @@ var require_lockfile = __commonJS({
|
|
|
6564
6795
|
options = {
|
|
6565
6796
|
stale: 1e4,
|
|
6566
6797
|
realpath: true,
|
|
6567
|
-
fs,
|
|
6798
|
+
fs: fs3,
|
|
6568
6799
|
...options
|
|
6569
6800
|
};
|
|
6570
6801
|
options.stale = Math.max(options.stale || 0, 2e3);
|
|
@@ -6604,16 +6835,16 @@ var require_adapter = __commonJS({
|
|
|
6604
6835
|
"../../node_modules/.bun/proper-lockfile@4.1.2/node_modules/proper-lockfile/lib/adapter.js"(exports, module) {
|
|
6605
6836
|
"use strict";
|
|
6606
6837
|
init_esm_shims();
|
|
6607
|
-
var
|
|
6608
|
-
function createSyncFs(
|
|
6838
|
+
var fs3 = require_graceful_fs();
|
|
6839
|
+
function createSyncFs(fs4) {
|
|
6609
6840
|
const methods = ["mkdir", "realpath", "stat", "rmdir", "utimes"];
|
|
6610
|
-
const newFs = { ...
|
|
6841
|
+
const newFs = { ...fs4 };
|
|
6611
6842
|
methods.forEach((method) => {
|
|
6612
6843
|
newFs[method] = (...args2) => {
|
|
6613
6844
|
const callback = args2.pop();
|
|
6614
6845
|
let ret;
|
|
6615
6846
|
try {
|
|
6616
|
-
ret =
|
|
6847
|
+
ret = fs4[`${method}Sync`](...args2);
|
|
6617
6848
|
} catch (err) {
|
|
6618
6849
|
return callback(err);
|
|
6619
6850
|
}
|
|
@@ -6623,12 +6854,12 @@ var require_adapter = __commonJS({
|
|
|
6623
6854
|
return newFs;
|
|
6624
6855
|
}
|
|
6625
6856
|
function toPromise(method) {
|
|
6626
|
-
return (...args2) => new Promise((
|
|
6857
|
+
return (...args2) => new Promise((resolve18, reject) => {
|
|
6627
6858
|
args2.push((err, result) => {
|
|
6628
6859
|
if (err) {
|
|
6629
6860
|
reject(err);
|
|
6630
6861
|
} else {
|
|
6631
|
-
|
|
6862
|
+
resolve18(result);
|
|
6632
6863
|
}
|
|
6633
6864
|
});
|
|
6634
6865
|
method(...args2);
|
|
@@ -6651,7 +6882,7 @@ var require_adapter = __commonJS({
|
|
|
6651
6882
|
}
|
|
6652
6883
|
function toSyncOptions(options) {
|
|
6653
6884
|
options = { ...options };
|
|
6654
|
-
options.fs = createSyncFs(options.fs ||
|
|
6885
|
+
options.fs = createSyncFs(options.fs || fs3);
|
|
6655
6886
|
if (typeof options.retries === "number" && options.retries > 0 || options.retries && typeof options.retries.retries === "number" && options.retries.retries > 0) {
|
|
6656
6887
|
throw Object.assign(new Error("Cannot use retries with the sync api"), { code: "ESYNC" });
|
|
6657
6888
|
}
|
|
@@ -6712,7 +6943,7 @@ var init_keytar = __esm({
|
|
|
6712
6943
|
|
|
6713
6944
|
// node-file:<repo>\node_modules\.bun\keytar@7.9.0\node_modules\keytar\build\Release\keytar.node
|
|
6714
6945
|
var require_keytar = __commonJS({
|
|
6715
|
-
"node-file:
|
|
6946
|
+
"node-file:F:\\MForest\\projects\\MSapling_CLI\\node_modules\\.bun\\keytar@7.9.0\\node_modules\\keytar\\build\\Release\\keytar.node"(exports, module) {
|
|
6716
6947
|
"use strict";
|
|
6717
6948
|
init_esm_shims();
|
|
6718
6949
|
init_keytar();
|
|
@@ -6764,10 +6995,14 @@ var require_keytar2 = __commonJS({
|
|
|
6764
6995
|
});
|
|
6765
6996
|
|
|
6766
6997
|
// ../core/src/Storage.ts
|
|
6767
|
-
import { join as
|
|
6998
|
+
import { join as join14 } from "path";
|
|
6768
6999
|
import { homedir as homedir7 } from "os";
|
|
6769
|
-
import { chmodSync, existsSync as existsSync12, renameSync, unlinkSync } from "fs";
|
|
6770
|
-
import { mkdir as mkdir6, writeFile as writeFile6, readFile as readFile11 } from "fs/promises";
|
|
7000
|
+
import { chmodSync, existsSync as existsSync12, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
7001
|
+
import { mkdir as mkdir6, writeFile as writeFile6, readFile as readFile11, appendFile } from "fs/promises";
|
|
7002
|
+
import { randomBytes as randomBytes7, createHash as createHash3 } from "crypto";
|
|
7003
|
+
function hashLine(line) {
|
|
7004
|
+
return createHash3("sha256").update(line, "utf8").digest("hex");
|
|
7005
|
+
}
|
|
6771
7006
|
var lockfile, keytar, StorageManager;
|
|
6772
7007
|
var init_Storage = __esm({
|
|
6773
7008
|
"../core/src/Storage.ts"() {
|
|
@@ -6794,15 +7029,26 @@ var init_Storage = __esm({
|
|
|
6794
7029
|
*/
|
|
6795
7030
|
_ready;
|
|
6796
7031
|
constructor() {
|
|
6797
|
-
this.baseDir =
|
|
7032
|
+
this.baseDir = join14(homedir7(), ".msapling");
|
|
6798
7033
|
this._ready = this.ensureDirs();
|
|
6799
7034
|
}
|
|
6800
7035
|
async ensureDirs() {
|
|
6801
7036
|
try {
|
|
6802
7037
|
await mkdir6(this.baseDir, { recursive: true });
|
|
6803
|
-
const subdirs = [
|
|
7038
|
+
const subdirs = [
|
|
7039
|
+
"history",
|
|
7040
|
+
"backups",
|
|
7041
|
+
"cache",
|
|
7042
|
+
// CLI-ARCH-CONTENT-HASH-VAULT-01: content-addressed vault layout
|
|
7043
|
+
"vault",
|
|
7044
|
+
"vault/objects",
|
|
7045
|
+
"vault/refs",
|
|
7046
|
+
// CLI-ARCH-RECIPE-AT-HASH-URI-01: recipe hash cache
|
|
7047
|
+
"cache/recipes",
|
|
7048
|
+
"cache/recipes/objects"
|
|
7049
|
+
];
|
|
6804
7050
|
for (const sub of subdirs) {
|
|
6805
|
-
const path2 =
|
|
7051
|
+
const path2 = join14(this.baseDir, sub);
|
|
6806
7052
|
await mkdir6(path2, { recursive: true });
|
|
6807
7053
|
}
|
|
6808
7054
|
if (process.platform !== "win32") {
|
|
@@ -6820,7 +7066,7 @@ var init_Storage = __esm({
|
|
|
6820
7066
|
await this._ready;
|
|
6821
7067
|
const KEYCHAIN_SERVICE = "msapling-cli";
|
|
6822
7068
|
const KEYCHAIN_ACCOUNT = "auth_token";
|
|
6823
|
-
const filePath =
|
|
7069
|
+
const filePath = join14(this.baseDir, "vault", "token");
|
|
6824
7070
|
try {
|
|
6825
7071
|
await keytar.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, token);
|
|
6826
7072
|
try {
|
|
@@ -6840,7 +7086,7 @@ var init_Storage = __esm({
|
|
|
6840
7086
|
async loadToken() {
|
|
6841
7087
|
const KEYCHAIN_SERVICE = "msapling-cli";
|
|
6842
7088
|
const KEYCHAIN_ACCOUNT = "auth_token";
|
|
6843
|
-
const filePath =
|
|
7089
|
+
const filePath = join14(this.baseDir, "vault", "token");
|
|
6844
7090
|
try {
|
|
6845
7091
|
const keychainToken = await keytar.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
6846
7092
|
if (keychainToken) {
|
|
@@ -6861,7 +7107,7 @@ var init_Storage = __esm({
|
|
|
6861
7107
|
async clearToken() {
|
|
6862
7108
|
const KEYCHAIN_SERVICE = "msapling-cli";
|
|
6863
7109
|
const KEYCHAIN_ACCOUNT = "auth_token";
|
|
6864
|
-
const filePath =
|
|
7110
|
+
const filePath = join14(this.baseDir, "vault", "token");
|
|
6865
7111
|
try {
|
|
6866
7112
|
await keytar.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
6867
7113
|
} catch (e) {
|
|
@@ -6869,13 +7115,237 @@ var init_Storage = __esm({
|
|
|
6869
7115
|
}
|
|
6870
7116
|
try {
|
|
6871
7117
|
if (existsSync12(filePath)) {
|
|
6872
|
-
const
|
|
6873
|
-
await
|
|
7118
|
+
const fs3 = await import("fs/promises");
|
|
7119
|
+
await fs3.unlink(filePath);
|
|
6874
7120
|
}
|
|
6875
7121
|
} catch (e) {
|
|
6876
7122
|
console.warn(`Failed to delete token file: ${e}`);
|
|
6877
7123
|
}
|
|
6878
7124
|
}
|
|
7125
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7126
|
+
// CLI-ARCH-CONTENT-HASH-VAULT-01 — content-addressed vault helpers
|
|
7127
|
+
//
|
|
7128
|
+
// Every vault value is a content-addressed blob stored under
|
|
7129
|
+
// ~/.msapling/vault/objects/<sha256-hex>
|
|
7130
|
+
// A human-readable ref file under
|
|
7131
|
+
// ~/.msapling/vault/refs/<label>
|
|
7132
|
+
// holds the sha256-hex of the current value.
|
|
7133
|
+
//
|
|
7134
|
+
// Updating a value: write new object → atomically rewrite ref.
|
|
7135
|
+
// Old objects are GC'd separately (retention window TBD — see roadmap).
|
|
7136
|
+
// The keytar account name stores the sha256-hex so the plaintext label
|
|
7137
|
+
// remains opaque on disk (label → hash is in keytar; hash → blob is on disk).
|
|
7138
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7139
|
+
/**
|
|
7140
|
+
* Write a value into the content-addressed object store and atomically
|
|
7141
|
+
* update the named ref. Returns the sha256 hex of the stored object.
|
|
7142
|
+
*
|
|
7143
|
+
* WAL-PATTERN: write to objects/<hash> (idempotent — same content = same
|
|
7144
|
+
* path), then rename refs/<label>.tmp → refs/<label>.
|
|
7145
|
+
*/
|
|
7146
|
+
async writeVaultRef(label, value) {
|
|
7147
|
+
await this._ready;
|
|
7148
|
+
const hash = createHash3("sha256").update(value, "utf8").digest("hex");
|
|
7149
|
+
const objectPath = join14(this.baseDir, "vault", "objects", hash);
|
|
7150
|
+
const refPath = join14(this.baseDir, "vault", "refs", label);
|
|
7151
|
+
const refTmp = `${refPath}.tmp`;
|
|
7152
|
+
await writeFile6(objectPath, value, "utf8");
|
|
7153
|
+
if (process.platform !== "win32") {
|
|
7154
|
+
chmodSync(objectPath, 384);
|
|
7155
|
+
}
|
|
7156
|
+
await writeFile6(refTmp, hash, "utf8");
|
|
7157
|
+
renameSync(refTmp, refPath);
|
|
7158
|
+
return hash;
|
|
7159
|
+
}
|
|
7160
|
+
/**
|
|
7161
|
+
* Read a vault value by label. Returns null if the ref or its object is
|
|
7162
|
+
* missing (bootstrap / first-run case).
|
|
7163
|
+
*/
|
|
7164
|
+
async readVaultRef(label) {
|
|
7165
|
+
await this._ready;
|
|
7166
|
+
const refPath = join14(this.baseDir, "vault", "refs", label);
|
|
7167
|
+
if (!existsSync12(refPath)) return null;
|
|
7168
|
+
const hash = (await readFile11(refPath, "utf8")).trim();
|
|
7169
|
+
const objectPath = join14(this.baseDir, "vault", "objects", hash);
|
|
7170
|
+
if (!existsSync12(objectPath)) return null;
|
|
7171
|
+
return readFile11(objectPath, "utf8");
|
|
7172
|
+
}
|
|
7173
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7174
|
+
// CLI-ARCH-RECIPE-AT-HASH-URI-01 — recipe hash registry helpers
|
|
7175
|
+
//
|
|
7176
|
+
// On first load of a recipe file, its content is sha256-hashed and the
|
|
7177
|
+
// mapping { name → hash } is stored in
|
|
7178
|
+
// ~/.msapling/cache/recipes/index.json
|
|
7179
|
+
// The immutable blob is written to
|
|
7180
|
+
// ~/.msapling/cache/recipes/objects/<sha256>
|
|
7181
|
+
//
|
|
7182
|
+
// Invocations reference recipes as `recipe@<sha256>` URIs. The /recipe
|
|
7183
|
+
// command resolves a bare name through the index and logs the hash for
|
|
7184
|
+
// traceability.
|
|
7185
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7186
|
+
/**
|
|
7187
|
+
* Register a recipe file in the local hash index. Idempotent — if the
|
|
7188
|
+
* content hash already exists the call is a no-op (returns existing hash).
|
|
7189
|
+
* Returns the sha256 hex for use in log messages / URI construction.
|
|
7190
|
+
*/
|
|
7191
|
+
async registerRecipe(name, content) {
|
|
7192
|
+
await this._ready;
|
|
7193
|
+
const hash = createHash3("sha256").update(content, "utf8").digest("hex");
|
|
7194
|
+
const objectPath = join14(this.baseDir, "cache", "recipes", "objects", hash);
|
|
7195
|
+
const indexPath = join14(this.baseDir, "cache", "recipes", "index.json");
|
|
7196
|
+
const indexTmp = `${indexPath}.tmp`;
|
|
7197
|
+
if (!existsSync12(objectPath)) {
|
|
7198
|
+
await writeFile6(objectPath, content, "utf8");
|
|
7199
|
+
}
|
|
7200
|
+
let index = {};
|
|
7201
|
+
if (existsSync12(indexPath)) {
|
|
7202
|
+
try {
|
|
7203
|
+
index = JSON.parse(await readFile11(indexPath, "utf8"));
|
|
7204
|
+
} catch {
|
|
7205
|
+
index = {};
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
index[name] = hash;
|
|
7209
|
+
await writeFile6(indexTmp, JSON.stringify(index, null, 2), "utf8");
|
|
7210
|
+
renameSync(indexTmp, indexPath);
|
|
7211
|
+
return hash;
|
|
7212
|
+
}
|
|
7213
|
+
/**
|
|
7214
|
+
* Resolve a recipe name or `recipe@<hash>` URI to its cached content.
|
|
7215
|
+
* Returns { hash, content } or null if not found in the local cache.
|
|
7216
|
+
*/
|
|
7217
|
+
async resolveRecipe(nameOrRef) {
|
|
7218
|
+
await this._ready;
|
|
7219
|
+
const indexPath = join14(this.baseDir, "cache", "recipes", "index.json");
|
|
7220
|
+
const atIdx = nameOrRef.indexOf("@");
|
|
7221
|
+
if (atIdx !== -1) {
|
|
7222
|
+
const hash2 = nameOrRef.slice(atIdx + 1);
|
|
7223
|
+
const objectPath2 = join14(this.baseDir, "cache", "recipes", "objects", hash2);
|
|
7224
|
+
if (!existsSync12(objectPath2)) return null;
|
|
7225
|
+
return { hash: hash2, content: await readFile11(objectPath2, "utf8") };
|
|
7226
|
+
}
|
|
7227
|
+
if (!existsSync12(indexPath)) return null;
|
|
7228
|
+
let index;
|
|
7229
|
+
try {
|
|
7230
|
+
index = JSON.parse(await readFile11(indexPath, "utf8"));
|
|
7231
|
+
} catch {
|
|
7232
|
+
return null;
|
|
7233
|
+
}
|
|
7234
|
+
const hash = index[nameOrRef];
|
|
7235
|
+
if (!hash) return null;
|
|
7236
|
+
const objectPath = join14(this.baseDir, "cache", "recipes", "objects", hash);
|
|
7237
|
+
if (!existsSync12(objectPath)) return null;
|
|
7238
|
+
return { hash, content: await readFile11(objectPath, "utf8") };
|
|
7239
|
+
}
|
|
7240
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7241
|
+
// CLI-ARCH-HASH-CHAIN-HISTORY-01 — append-only NDJSON history with hash chain
|
|
7242
|
+
//
|
|
7243
|
+
// Shell history is stored as an append-only NDJSON file
|
|
7244
|
+
// ~/.msapling/history/shell_history.jsonl
|
|
7245
|
+
// Each entry carries a `prev_hash` field so tampering is detectable.
|
|
7246
|
+
// `msapling --verify-history` re-hashes the chain and reports breaks.
|
|
7247
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7248
|
+
/**
|
|
7249
|
+
* Append a single command to the hash-chain history.
|
|
7250
|
+
* Uses lockfile + in-process mutex as a critical section around:
|
|
7251
|
+
* 1. Load last entry to compute prev_hash
|
|
7252
|
+
* 2. Build + serialize new entry
|
|
7253
|
+
* 3. Append to shell_history.jsonl
|
|
7254
|
+
*
|
|
7255
|
+
* WAL-PATTERN note: append-only log uses appendFile, not rename — the
|
|
7256
|
+
* lockfile is the write-serialisation primitive here.
|
|
7257
|
+
*/
|
|
7258
|
+
async appendHistoryEntry(content) {
|
|
7259
|
+
await this._ready;
|
|
7260
|
+
const path2 = join14(this.baseDir, "history", "shell_history.jsonl");
|
|
7261
|
+
return this.historyMutex.run(async () => {
|
|
7262
|
+
let release2 = null;
|
|
7263
|
+
try {
|
|
7264
|
+
if (!existsSync12(path2)) {
|
|
7265
|
+
await writeFile6(path2, "", "utf8");
|
|
7266
|
+
}
|
|
7267
|
+
release2 = await lockfile.lock(path2, { retries: 5, retryWait: 50 });
|
|
7268
|
+
let prevHash = null;
|
|
7269
|
+
let seq = 1;
|
|
7270
|
+
if (existsSync12(path2)) {
|
|
7271
|
+
const raw = (await readFile11(path2, "utf8")).trimEnd();
|
|
7272
|
+
if (raw.length > 0) {
|
|
7273
|
+
const lines = raw.split("\n");
|
|
7274
|
+
const lastLine = lines[lines.length - 1];
|
|
7275
|
+
try {
|
|
7276
|
+
const last = JSON.parse(lastLine);
|
|
7277
|
+
seq = last.seq + 1;
|
|
7278
|
+
prevHash = hashLine(lastLine);
|
|
7279
|
+
} catch {
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
const entry = {
|
|
7284
|
+
seq,
|
|
7285
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7286
|
+
content,
|
|
7287
|
+
prev_hash: prevHash
|
|
7288
|
+
};
|
|
7289
|
+
const line = JSON.stringify(entry);
|
|
7290
|
+
await appendFile(path2, line + "\n", "utf8");
|
|
7291
|
+
return entry;
|
|
7292
|
+
} finally {
|
|
7293
|
+
if (release2) {
|
|
7294
|
+
try {
|
|
7295
|
+
await lockfile.unlock(path2, { skipStale: true });
|
|
7296
|
+
} catch {
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7299
|
+
}
|
|
7300
|
+
});
|
|
7301
|
+
}
|
|
7302
|
+
/**
|
|
7303
|
+
* Load all history entries from shell_history.jsonl.
|
|
7304
|
+
* Skips malformed lines (logs a warning for each).
|
|
7305
|
+
*
|
|
7306
|
+
* // TODO: implement tail-read for large history files to avoid loading entire file into memory
|
|
7307
|
+
*/
|
|
7308
|
+
async loadHistoryEntries() {
|
|
7309
|
+
await this._ready;
|
|
7310
|
+
const path2 = join14(this.baseDir, "history", "shell_history.jsonl");
|
|
7311
|
+
if (!existsSync12(path2)) return [];
|
|
7312
|
+
const raw = await readFile11(path2, "utf8");
|
|
7313
|
+
const entries = [];
|
|
7314
|
+
for (const line of raw.split("\n")) {
|
|
7315
|
+
if (!line.trim()) continue;
|
|
7316
|
+
try {
|
|
7317
|
+
entries.push(JSON.parse(line));
|
|
7318
|
+
} catch {
|
|
7319
|
+
console.warn(`[history] Skipped malformed NDJSON line: ${line.slice(0, 80)}`);
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
return entries;
|
|
7323
|
+
}
|
|
7324
|
+
/**
|
|
7325
|
+
* Verify the hash chain of shell_history.jsonl.
|
|
7326
|
+
* Returns { ok: true } if intact, or { ok: false, breaks: [...] } listing
|
|
7327
|
+
* each break as { seq, expected, actual }.
|
|
7328
|
+
*
|
|
7329
|
+
* Used by `msapling --verify-history`.
|
|
7330
|
+
*/
|
|
7331
|
+
async verifyHistory() {
|
|
7332
|
+
const entries = await this.loadHistoryEntries();
|
|
7333
|
+
const breaks = [];
|
|
7334
|
+
let prevLine = null;
|
|
7335
|
+
for (const entry of entries) {
|
|
7336
|
+
if (prevLine !== null) {
|
|
7337
|
+
const expected = hashLine(prevLine);
|
|
7338
|
+
const actual = entry.prev_hash ?? "";
|
|
7339
|
+
if (expected !== actual) {
|
|
7340
|
+
breaks.push({ seq: entry.seq, expected, actual });
|
|
7341
|
+
}
|
|
7342
|
+
} else if (entry.prev_hash !== null) {
|
|
7343
|
+
breaks.push({ seq: entry.seq, expected: "null", actual: entry.prev_hash });
|
|
7344
|
+
}
|
|
7345
|
+
prevLine = JSON.stringify(entry);
|
|
7346
|
+
}
|
|
7347
|
+
return breaks.length === 0 ? { ok: true } : { ok: false, breaks };
|
|
7348
|
+
}
|
|
6879
7349
|
/**
|
|
6880
7350
|
* SYNC-01: Serialised history write with file-based locking.
|
|
6881
7351
|
* Uses proper-lockfile to coordinate writes across multiple terminal instances
|
|
@@ -6886,10 +7356,12 @@ var init_Storage = __esm({
|
|
|
6886
7356
|
* 2. Partial writes don't corrupt the JSON (atomic rename)
|
|
6887
7357
|
*/
|
|
6888
7358
|
async saveHistory(history) {
|
|
6889
|
-
|
|
7359
|
+
await this._ready;
|
|
7360
|
+
const path2 = join14(this.baseDir, "history", "shell_history.json");
|
|
6890
7361
|
let release2;
|
|
6891
7362
|
try {
|
|
6892
|
-
|
|
7363
|
+
if (!existsSync12(path2)) writeFileSync(path2, "[]", "utf8");
|
|
7364
|
+
release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
6893
7365
|
await this.historyMutex.run(async () => {
|
|
6894
7366
|
const tmpPath = `${path2}.tmp`;
|
|
6895
7367
|
const content = JSON.stringify(history, null, 2);
|
|
@@ -6899,8 +7371,8 @@ var init_Storage = __esm({
|
|
|
6899
7371
|
} catch (e) {
|
|
6900
7372
|
try {
|
|
6901
7373
|
if (existsSync12(tmpPath)) {
|
|
6902
|
-
const
|
|
6903
|
-
await
|
|
7374
|
+
const fs3 = await import("fs/promises");
|
|
7375
|
+
await fs3.unlink(tmpPath);
|
|
6904
7376
|
}
|
|
6905
7377
|
} catch {
|
|
6906
7378
|
}
|
|
@@ -6921,16 +7393,38 @@ var init_Storage = __esm({
|
|
|
6921
7393
|
* SYNC-01: Serialised history read with file-based locking.
|
|
6922
7394
|
* Reads are gated behind the file lock so a read that overlaps with an
|
|
6923
7395
|
* in-progress write from another process always sees a complete, valid JSON.
|
|
7396
|
+
*
|
|
7397
|
+
* // TODO: implement tail-read for large history files to avoid loading entire file into memory
|
|
6924
7398
|
*/
|
|
6925
7399
|
async loadHistory() {
|
|
6926
|
-
|
|
7400
|
+
await this._ready;
|
|
7401
|
+
const path2 = join14(this.baseDir, "history", "shell_history.json");
|
|
7402
|
+
if (!existsSync12(path2)) return [];
|
|
6927
7403
|
let release2;
|
|
6928
7404
|
try {
|
|
6929
|
-
release2 = await lockfile.lock(path2, { retries: 5, retryWait: 50 });
|
|
7405
|
+
release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
6930
7406
|
return this.historyMutex.run(async () => {
|
|
6931
7407
|
if (existsSync12(path2)) {
|
|
6932
7408
|
const text = await readFile11(path2, "utf8");
|
|
6933
|
-
|
|
7409
|
+
try {
|
|
7410
|
+
return JSON.parse(text);
|
|
7411
|
+
} catch (parseErr) {
|
|
7412
|
+
const filename = path2.split("/").pop() || "shell_history.json";
|
|
7413
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7414
|
+
const suffix = randomBytes7(4).toString("hex");
|
|
7415
|
+
const corruptBackupPath = join14(
|
|
7416
|
+
this.baseDir,
|
|
7417
|
+
"history",
|
|
7418
|
+
`${filename}.corrupt.${stamp}-${suffix}.bak`
|
|
7419
|
+
);
|
|
7420
|
+
try {
|
|
7421
|
+
renameSync(path2, corruptBackupPath);
|
|
7422
|
+
console.warn(`History file was corrupt; backed up to ${corruptBackupPath}`);
|
|
7423
|
+
} catch (backupErr) {
|
|
7424
|
+
console.error(`Failed to backup corrupt history file: ${backupErr}`);
|
|
7425
|
+
}
|
|
7426
|
+
return [];
|
|
7427
|
+
}
|
|
6934
7428
|
}
|
|
6935
7429
|
return [];
|
|
6936
7430
|
});
|
|
@@ -6946,12 +7440,14 @@ var init_Storage = __esm({
|
|
|
6946
7440
|
}
|
|
6947
7441
|
/**
|
|
6948
7442
|
* SYNC-01: Serialised permissions write with file-based locking.
|
|
7443
|
+
* R20-CLI-2: Typed with PermissionState from Sandbox.ts.
|
|
6949
7444
|
*/
|
|
6950
7445
|
async savePermissions(permissions) {
|
|
6951
|
-
const path2 =
|
|
7446
|
+
const path2 = join14(this.baseDir, "vault", "permissions.json");
|
|
6952
7447
|
let release2;
|
|
6953
7448
|
try {
|
|
6954
|
-
|
|
7449
|
+
if (!existsSync12(path2)) writeFileSync(path2, "{}", "utf8");
|
|
7450
|
+
release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
6955
7451
|
await this.permissionsMutex.run(async () => {
|
|
6956
7452
|
const tmpPath = `${path2}.tmp`;
|
|
6957
7453
|
const content = JSON.stringify(permissions, null, 2);
|
|
@@ -6961,8 +7457,8 @@ var init_Storage = __esm({
|
|
|
6961
7457
|
} catch (e) {
|
|
6962
7458
|
try {
|
|
6963
7459
|
if (existsSync12(tmpPath)) {
|
|
6964
|
-
const
|
|
6965
|
-
await
|
|
7460
|
+
const fs3 = await import("fs/promises");
|
|
7461
|
+
await fs3.unlink(tmpPath);
|
|
6966
7462
|
}
|
|
6967
7463
|
} catch {
|
|
6968
7464
|
}
|
|
@@ -6981,16 +7477,36 @@ var init_Storage = __esm({
|
|
|
6981
7477
|
}
|
|
6982
7478
|
/**
|
|
6983
7479
|
* SYNC-01: Serialised permissions read with file-based locking.
|
|
7480
|
+
* R20-CLI-2: Typed with PermissionState from Sandbox.ts.
|
|
6984
7481
|
*/
|
|
6985
7482
|
async loadPermissions() {
|
|
6986
|
-
const path2 =
|
|
7483
|
+
const path2 = join14(this.baseDir, "vault", "permissions.json");
|
|
7484
|
+
if (!existsSync12(path2)) return { trustedCommands: [], trustedPaths: [] };
|
|
6987
7485
|
let release2;
|
|
6988
7486
|
try {
|
|
6989
|
-
release2 = await lockfile.lock(path2, { retries: 5, retryWait: 50 });
|
|
7487
|
+
release2 = await lockfile.lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
6990
7488
|
return this.permissionsMutex.run(async () => {
|
|
6991
7489
|
if (existsSync12(path2)) {
|
|
6992
7490
|
const text = await readFile11(path2, "utf8");
|
|
6993
|
-
|
|
7491
|
+
try {
|
|
7492
|
+
return JSON.parse(text);
|
|
7493
|
+
} catch (parseErr) {
|
|
7494
|
+
const filename = path2.split("/").pop() || "permissions.json";
|
|
7495
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7496
|
+
const suffix = randomBytes7(4).toString("hex");
|
|
7497
|
+
const corruptBackupPath = join14(
|
|
7498
|
+
this.baseDir,
|
|
7499
|
+
"vault",
|
|
7500
|
+
`${filename}.corrupt.${stamp}-${suffix}.bak`
|
|
7501
|
+
);
|
|
7502
|
+
try {
|
|
7503
|
+
renameSync(path2, corruptBackupPath);
|
|
7504
|
+
console.warn(`Permissions file was corrupt; backed up to ${corruptBackupPath}`);
|
|
7505
|
+
} catch (backupErr) {
|
|
7506
|
+
console.error(`Failed to backup corrupt permissions file: ${backupErr}`);
|
|
7507
|
+
}
|
|
7508
|
+
return { trustedCommands: [], trustedPaths: [] };
|
|
7509
|
+
}
|
|
6994
7510
|
}
|
|
6995
7511
|
return { trustedCommands: [], trustedPaths: [] };
|
|
6996
7512
|
});
|
|
@@ -7008,11 +7524,14 @@ var init_Storage = __esm({
|
|
|
7008
7524
|
* Backup a file before AI edit.
|
|
7009
7525
|
* CLI-R11-STORAGE-03: backup artifacts are written with restrictive 0600
|
|
7010
7526
|
* permissions so restored content is not readable by other OS users.
|
|
7527
|
+
* CLI-ARCH-NO-EPOCH-IN-KEYS-FU-01: uses ISO timestamp + random suffix
|
|
7528
|
+
* instead of epoch milliseconds for better traceability and collision avoidance.
|
|
7011
7529
|
*/
|
|
7012
7530
|
async backup(filePath, content) {
|
|
7013
7531
|
const filename = filePath.split("/").pop() || "file";
|
|
7014
|
-
const
|
|
7015
|
-
const
|
|
7532
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7533
|
+
const suffix = randomBytes7(4).toString("hex");
|
|
7534
|
+
const backupPath = join14(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
7016
7535
|
await writeFile6(backupPath, content, "utf8");
|
|
7017
7536
|
if (process.platform !== "win32") {
|
|
7018
7537
|
chmodSync(backupPath, 384);
|
|
@@ -7025,9 +7544,28 @@ var init_Storage = __esm({
|
|
|
7025
7544
|
|
|
7026
7545
|
// ../core/src/Settings.ts
|
|
7027
7546
|
import { homedir as homedir8 } from "os";
|
|
7028
|
-
import { join as
|
|
7547
|
+
import { join as join15 } from "path";
|
|
7029
7548
|
import { existsSync as existsSync13 } from "fs";
|
|
7549
|
+
import * as fs from "fs";
|
|
7030
7550
|
import { readFile as readFile12 } from "fs/promises";
|
|
7551
|
+
import { randomBytes as randomBytes8 } from "crypto";
|
|
7552
|
+
function ensureConfigDir(p) {
|
|
7553
|
+
try {
|
|
7554
|
+
fs.mkdirSync(p, { recursive: true, mode: 448 });
|
|
7555
|
+
} catch (e) {
|
|
7556
|
+
if (e.code === "EEXIST" || e.code === "ENOTDIR") {
|
|
7557
|
+
const stat5 = fs.statSync(p);
|
|
7558
|
+
if (!stat5.isDirectory()) {
|
|
7559
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7560
|
+
const suffix = randomBytes8(4).toString("hex");
|
|
7561
|
+
fs.renameSync(p, `${p}.broken-${stamp}-${suffix}`);
|
|
7562
|
+
fs.mkdirSync(p, { recursive: true, mode: 448 });
|
|
7563
|
+
}
|
|
7564
|
+
} else {
|
|
7565
|
+
throw e;
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7031
7569
|
async function readJson(path2) {
|
|
7032
7570
|
try {
|
|
7033
7571
|
if (!existsSync13(path2)) return null;
|
|
@@ -7085,8 +7623,8 @@ function mergeSettings(base, override) {
|
|
|
7085
7623
|
}
|
|
7086
7624
|
async function loadSettings(cwd = process.cwd(), env = process.env, warn) {
|
|
7087
7625
|
const sources = [];
|
|
7088
|
-
const userPath =
|
|
7089
|
-
const projectPath =
|
|
7626
|
+
const userPath = join15(homedir8() || ".", ".msapling", "settings.json");
|
|
7627
|
+
const projectPath = join15(cwd, ".msapling", "settings.json");
|
|
7090
7628
|
const [user, project] = await Promise.all([readJson(userPath), readJson(projectPath)]);
|
|
7091
7629
|
if (user) sources.push(userPath);
|
|
7092
7630
|
if (project) sources.push(projectPath);
|
|
@@ -7249,7 +7787,7 @@ var init_client = __esm({
|
|
|
7249
7787
|
if (!this.proc) throw new MCPClientError(`MCP server "${this.name}" not started`);
|
|
7250
7788
|
const id = this.nextId++;
|
|
7251
7789
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
7252
|
-
return new Promise((
|
|
7790
|
+
return new Promise((resolve18, reject) => {
|
|
7253
7791
|
const timer = setTimeout(() => {
|
|
7254
7792
|
this.pending.delete(id);
|
|
7255
7793
|
reject(new MCPClientError(`MCP request ${method} timed out after ${timeoutMs}ms`));
|
|
@@ -7257,7 +7795,7 @@ var init_client = __esm({
|
|
|
7257
7795
|
this.pending.set(id, {
|
|
7258
7796
|
resolve: (v) => {
|
|
7259
7797
|
clearTimeout(timer);
|
|
7260
|
-
|
|
7798
|
+
resolve18(v);
|
|
7261
7799
|
},
|
|
7262
7800
|
reject: (e) => {
|
|
7263
7801
|
clearTimeout(timer);
|
|
@@ -7284,7 +7822,7 @@ var init_client = __esm({
|
|
|
7284
7822
|
if (!this.proc?.stdout) return;
|
|
7285
7823
|
const stdout = this.proc.stdout;
|
|
7286
7824
|
const decoder = new TextDecoder();
|
|
7287
|
-
return new Promise((
|
|
7825
|
+
return new Promise((resolve18) => {
|
|
7288
7826
|
stdout.on("data", (chunk) => {
|
|
7289
7827
|
this.buffer += decoder.decode(chunk, { stream: true });
|
|
7290
7828
|
let idx;
|
|
@@ -7295,8 +7833,8 @@ var init_client = __esm({
|
|
|
7295
7833
|
this.handleFrame(line);
|
|
7296
7834
|
}
|
|
7297
7835
|
});
|
|
7298
|
-
stdout.on("end", () =>
|
|
7299
|
-
stdout.on("error", () =>
|
|
7836
|
+
stdout.on("end", () => resolve18());
|
|
7837
|
+
stdout.on("error", () => resolve18());
|
|
7300
7838
|
});
|
|
7301
7839
|
}
|
|
7302
7840
|
handleFrame(line) {
|
|
@@ -7383,7 +7921,7 @@ var init_Swarm = __esm({
|
|
|
7383
7921
|
* Execute a parallel swarm with shadow auditing and optional sub-shell popups.
|
|
7384
7922
|
*/
|
|
7385
7923
|
async execute(tasks, projectRoot) {
|
|
7386
|
-
const
|
|
7924
|
+
const promises2 = tasks.map(async (task) => {
|
|
7387
7925
|
const audit = await this.shadow.verifyAction(task.prompt, `Swarm Task: ${task.name}`);
|
|
7388
7926
|
if (!audit.approved) {
|
|
7389
7927
|
return { taskId: task.id, workerId: "none", response: "", status: "blocked", auditNote: audit.reasoning };
|
|
@@ -7405,7 +7943,7 @@ var init_Swarm = __esm({
|
|
|
7405
7943
|
return { taskId: task.id, workerId: task.id, response: e.message, status: "failed" };
|
|
7406
7944
|
}
|
|
7407
7945
|
});
|
|
7408
|
-
return Promise.all(
|
|
7946
|
+
return Promise.all(promises2);
|
|
7409
7947
|
}
|
|
7410
7948
|
/**
|
|
7411
7949
|
* SYNTHESIS: Use a high-reasoning model to merge the safe swarm results.
|
|
@@ -7470,29 +8008,34 @@ function isEmailAddress(str) {
|
|
|
7470
8008
|
function isTokenLike(str) {
|
|
7471
8009
|
return str.length >= 20 && /^[A-Za-z0-9_\-\.]+$/.test(str);
|
|
7472
8010
|
}
|
|
8011
|
+
function setRawModeGuarded(stdin, mode) {
|
|
8012
|
+
try {
|
|
8013
|
+
if (typeof stdin.setRawMode === "function") {
|
|
8014
|
+
stdin.setRawMode(mode);
|
|
8015
|
+
}
|
|
8016
|
+
} catch (e) {
|
|
8017
|
+
}
|
|
8018
|
+
}
|
|
7473
8019
|
async function promptPassword(prompt) {
|
|
7474
|
-
return new Promise((
|
|
8020
|
+
return new Promise((resolve18) => {
|
|
7475
8021
|
const stdin = process.stdin;
|
|
7476
8022
|
const stdout = process.stdout;
|
|
7477
8023
|
stdout.write(prompt);
|
|
7478
|
-
const wasRaw = stdin.isRaw;
|
|
7479
|
-
|
|
7480
|
-
stdin.setRawMode(true);
|
|
7481
|
-
} catch (e) {
|
|
7482
|
-
}
|
|
8024
|
+
const wasRaw = stdin.isRaw ?? false;
|
|
8025
|
+
setRawModeGuarded(stdin, true);
|
|
7483
8026
|
let password = "";
|
|
7484
8027
|
const onData = (chunk) => {
|
|
7485
8028
|
const char = chunk.toString();
|
|
7486
8029
|
if (char === "\n" || char === "\r") {
|
|
7487
|
-
stdin
|
|
8030
|
+
setRawModeGuarded(stdin, wasRaw);
|
|
7488
8031
|
stdin.removeListener("data", onData);
|
|
7489
8032
|
stdout.write("\n");
|
|
7490
|
-
|
|
8033
|
+
resolve18(password);
|
|
7491
8034
|
} else if (char === "") {
|
|
7492
|
-
stdin
|
|
8035
|
+
setRawModeGuarded(stdin, wasRaw);
|
|
7493
8036
|
stdin.removeListener("data", onData);
|
|
7494
8037
|
stdout.write("\n");
|
|
7495
|
-
|
|
8038
|
+
resolve18("");
|
|
7496
8039
|
} else if (char === "\x7F" || char === "\b") {
|
|
7497
8040
|
password = password.slice(0, -1);
|
|
7498
8041
|
} else if (char >= " " && char <= "~") {
|
|
@@ -7502,40 +8045,6 @@ async function promptPassword(prompt) {
|
|
|
7502
8045
|
stdin.on("data", onData);
|
|
7503
8046
|
});
|
|
7504
8047
|
}
|
|
7505
|
-
async function promptTotp(prompt) {
|
|
7506
|
-
return new Promise((resolve17) => {
|
|
7507
|
-
const stdin = process.stdin;
|
|
7508
|
-
const stdout = process.stdout;
|
|
7509
|
-
stdout.write(prompt);
|
|
7510
|
-
const wasRaw = stdin.isRaw;
|
|
7511
|
-
try {
|
|
7512
|
-
stdin.setRawMode(true);
|
|
7513
|
-
} catch (e) {
|
|
7514
|
-
}
|
|
7515
|
-
let code = "";
|
|
7516
|
-
const onData = (chunk) => {
|
|
7517
|
-
const char = chunk.toString();
|
|
7518
|
-
if (char === "\n" || char === "\r") {
|
|
7519
|
-
stdin.setRawMode(wasRaw);
|
|
7520
|
-
stdin.removeListener("data", onData);
|
|
7521
|
-
stdout.write("\n");
|
|
7522
|
-
resolve17(code);
|
|
7523
|
-
} else if (char === "") {
|
|
7524
|
-
stdin.setRawMode(wasRaw);
|
|
7525
|
-
stdin.removeListener("data", onData);
|
|
7526
|
-
stdout.write("\n");
|
|
7527
|
-
resolve17("");
|
|
7528
|
-
} else if (char === "\x7F" || char === "\b") {
|
|
7529
|
-
code = code.slice(0, -1);
|
|
7530
|
-
} else if (/^\d$/.test(char)) {
|
|
7531
|
-
if (code.length < 6) {
|
|
7532
|
-
code += char;
|
|
7533
|
-
}
|
|
7534
|
-
}
|
|
7535
|
-
};
|
|
7536
|
-
stdin.on("data", onData);
|
|
7537
|
-
});
|
|
7538
|
-
}
|
|
7539
8048
|
async function loginWithEmailPassword(email, context) {
|
|
7540
8049
|
context.addMessage("system", `Logging in as ${email}...`);
|
|
7541
8050
|
let password = "";
|
|
@@ -7552,40 +8061,26 @@ async function loginWithEmailPassword(email, context) {
|
|
|
7552
8061
|
try {
|
|
7553
8062
|
const result = await context.client.loginEmailPassword(email, password);
|
|
7554
8063
|
password = "";
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
totpCode = await promptTotp("Enter your 6-digit code: ");
|
|
7560
|
-
} catch (err) {
|
|
7561
|
-
context.addMessage("error", `TOTP prompt failed: ${err}`);
|
|
7562
|
-
return;
|
|
7563
|
-
}
|
|
7564
|
-
if (!totpCode || totpCode.length !== 6) {
|
|
7565
|
-
context.addMessage("error", "Invalid TOTP code (must be 6 digits).");
|
|
7566
|
-
return;
|
|
7567
|
-
}
|
|
7568
|
-
context.client.setToken(result.partialToken);
|
|
7569
|
-
try {
|
|
7570
|
-
const totpResult = await context.client.verifyLoginTotp(totpCode);
|
|
7571
|
-
await context.storage.saveToken(totpResult.token);
|
|
7572
|
-
context.client.setToken(totpResult.token);
|
|
7573
|
-
await context.refreshOverview();
|
|
7574
|
-
context.addMessage("system", `Logged in as ${email} (2FA verified).`);
|
|
7575
|
-
} catch (err) {
|
|
7576
|
-
context.client.setToken(null);
|
|
7577
|
-
context.addMessage("error", `TOTP verification failed: ${err}`);
|
|
7578
|
-
}
|
|
7579
|
-
} else if (result.kind === "success") {
|
|
7580
|
-
await context.storage.saveToken(result.token);
|
|
7581
|
-
context.client.setToken(result.token);
|
|
7582
|
-
await context.refreshOverview();
|
|
7583
|
-
context.addMessage("system", `Logged in as ${email}.`);
|
|
7584
|
-
}
|
|
8064
|
+
await context.storage.saveToken(result.token);
|
|
8065
|
+
context.client.setToken(result.token);
|
|
8066
|
+
await context.refreshOverview();
|
|
8067
|
+
context.addMessage("system", `Logged in as ${email}.`);
|
|
7585
8068
|
} catch (err) {
|
|
7586
8069
|
context.addMessage("error", `Login failed: ${err}`);
|
|
7587
8070
|
}
|
|
7588
8071
|
}
|
|
8072
|
+
async function loginAsGuest(context) {
|
|
8073
|
+
context.addMessage("system", "Signing in as guest...");
|
|
8074
|
+
try {
|
|
8075
|
+
const result = await context.client.loginGuest();
|
|
8076
|
+
await context.storage.saveToken(result.token);
|
|
8077
|
+
context.client.setToken(result.token);
|
|
8078
|
+
await context.refreshOverview();
|
|
8079
|
+
context.addMessage("system", "Signed in as guest (limited tier).");
|
|
8080
|
+
} catch (err) {
|
|
8081
|
+
context.addMessage("error", `Guest login failed: ${err}`);
|
|
8082
|
+
}
|
|
8083
|
+
}
|
|
7589
8084
|
async function loginWithGithubDevice(context) {
|
|
7590
8085
|
context.addMessage("system", "Starting GitHub device flow...");
|
|
7591
8086
|
let deviceResp;
|
|
@@ -7609,11 +8104,11 @@ async function loginWithGithubDevice(context) {
|
|
|
7609
8104
|
`Open ${verification_uri} in your browser and enter code: ${user_code}
|
|
7610
8105
|
(Waiting for authorization \u2014 expires in ${expires_in}s)`
|
|
7611
8106
|
);
|
|
7612
|
-
|
|
8107
|
+
let pollMs = (interval + 1) * 1e3;
|
|
7613
8108
|
const deadline = Date.now() + expires_in * 1e3;
|
|
7614
8109
|
let githubToken = null;
|
|
7615
8110
|
while (Date.now() < deadline) {
|
|
7616
|
-
await new Promise((
|
|
8111
|
+
await new Promise((resolve18) => setTimeout(resolve18, pollMs));
|
|
7617
8112
|
let tokenResp;
|
|
7618
8113
|
try {
|
|
7619
8114
|
tokenResp = await fetch(GITHUB_TOKEN_URL, {
|
|
@@ -7636,7 +8131,8 @@ async function loginWithGithubDevice(context) {
|
|
|
7636
8131
|
}
|
|
7637
8132
|
if (tokenData.error === "authorization_pending") continue;
|
|
7638
8133
|
if (tokenData.error === "slow_down") {
|
|
7639
|
-
|
|
8134
|
+
pollMs += 5e3;
|
|
8135
|
+
await new Promise((resolve18) => setTimeout(resolve18, 5e3));
|
|
7640
8136
|
continue;
|
|
7641
8137
|
}
|
|
7642
8138
|
context.addMessage("system", `GitHub auth error: ${tokenData.error_description || tokenData.error}`);
|
|
@@ -7676,11 +8172,11 @@ var init_login = __esm({
|
|
|
7676
8172
|
init_esm_shims();
|
|
7677
8173
|
GITHUB_CLIENT_ID = "Ov23liOA9yKFLUEEVY3G";
|
|
7678
8174
|
GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
7679
|
-
GITHUB_TOKEN_URL = "https://github.com/oauth/access_token";
|
|
8175
|
+
GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
7680
8176
|
loginCommand = {
|
|
7681
8177
|
name: "login",
|
|
7682
|
-
args: "[email|github|token]",
|
|
7683
|
-
description: "Authenticate: /login your@email.com | /login github | /login <token>",
|
|
8178
|
+
args: "[email|github|guest|token]",
|
|
8179
|
+
description: "Authenticate: /login your@email.com | /login github | /login guest | /login <token>",
|
|
7684
8180
|
category: "auth",
|
|
7685
8181
|
handler: async (args2, context) => {
|
|
7686
8182
|
const arg = args2[0];
|
|
@@ -7690,12 +8186,15 @@ var init_login = __esm({
|
|
|
7690
8186
|
`Usage:
|
|
7691
8187
|
/login your@email.com Sign in with email + password
|
|
7692
8188
|
/login github GitHub device flow
|
|
8189
|
+
/login guest Anonymous guest session (limited tier)
|
|
7693
8190
|
/login <token> Paste an API token from msapling.com \u2192 Settings \u2192 API Keys`
|
|
7694
8191
|
);
|
|
7695
8192
|
return;
|
|
7696
8193
|
}
|
|
7697
8194
|
if (arg === "github") {
|
|
7698
8195
|
await loginWithGithubDevice(context);
|
|
8196
|
+
} else if (arg === "guest") {
|
|
8197
|
+
await loginAsGuest(context);
|
|
7699
8198
|
} else if (isEmailAddress(arg)) {
|
|
7700
8199
|
await loginWithEmailPassword(arg, context);
|
|
7701
8200
|
} else if (isTokenLike(arg)) {
|
|
@@ -7706,7 +8205,7 @@ var init_login = __esm({
|
|
|
7706
8205
|
} else {
|
|
7707
8206
|
context.addMessage(
|
|
7708
8207
|
"error",
|
|
7709
|
-
`Unrecognized argument: "${arg}". Expected email, 'github', or token string.`
|
|
8208
|
+
`Unrecognized argument: "${arg}". Expected email, 'github', 'guest', or token string.`
|
|
7710
8209
|
);
|
|
7711
8210
|
}
|
|
7712
8211
|
}
|
|
@@ -7745,7 +8244,7 @@ var init_unlock = __esm({
|
|
|
7745
8244
|
|
|
7746
8245
|
// src/commands/doctor.ts
|
|
7747
8246
|
import { homedir as homedir9 } from "os";
|
|
7748
|
-
import { join as
|
|
8247
|
+
import { join as join16 } from "path";
|
|
7749
8248
|
import { existsSync as existsSync14 } from "fs";
|
|
7750
8249
|
import { readFile as readFile13 } from "fs/promises";
|
|
7751
8250
|
async function checkApiHealth(client) {
|
|
@@ -7772,7 +8271,7 @@ async function checkAuthStatus(client) {
|
|
|
7772
8271
|
}
|
|
7773
8272
|
}
|
|
7774
8273
|
async function checkSettingsFile() {
|
|
7775
|
-
const settingsPath =
|
|
8274
|
+
const settingsPath = join16(homedir9(), ".msapling", "settings.json");
|
|
7776
8275
|
try {
|
|
7777
8276
|
if (!existsSync14(settingsPath)) {
|
|
7778
8277
|
return { ok: false, message: `Not found: ${settingsPath}` };
|
|
@@ -8018,7 +8517,7 @@ var init_clear = __esm({
|
|
|
8018
8517
|
|
|
8019
8518
|
// src/commands/mode.ts
|
|
8020
8519
|
import { homedir as homedir10 } from "os";
|
|
8021
|
-
import { join as
|
|
8520
|
+
import { join as join17 } from "path";
|
|
8022
8521
|
import { existsSync as existsSync15 } from "fs";
|
|
8023
8522
|
import { readFile as readFile14, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
8024
8523
|
async function persistApprovalMode(mode, ttlMs) {
|
|
@@ -8036,7 +8535,7 @@ async function persistApprovalMode(mode, ttlMs) {
|
|
|
8036
8535
|
...ttlMs && { ttlMs }
|
|
8037
8536
|
};
|
|
8038
8537
|
existing.approvalMode = entry;
|
|
8039
|
-
const settingsDir =
|
|
8538
|
+
const settingsDir = join17(homedir10(), ".msapling");
|
|
8040
8539
|
if (!existsSync15(settingsDir)) {
|
|
8041
8540
|
await mkdir7(settingsDir, { recursive: true });
|
|
8042
8541
|
}
|
|
@@ -8049,7 +8548,7 @@ var init_mode = __esm({
|
|
|
8049
8548
|
"src/commands/mode.ts"() {
|
|
8050
8549
|
"use strict";
|
|
8051
8550
|
init_esm_shims();
|
|
8052
|
-
SETTINGS_PATH =
|
|
8551
|
+
SETTINGS_PATH = join17(homedir10(), ".msapling", "settings.json");
|
|
8053
8552
|
modeCommand = {
|
|
8054
8553
|
name: "mode",
|
|
8055
8554
|
args: "[default|plan|acceptEdits|bypassPermissions] [...options]",
|
|
@@ -8323,7 +8822,7 @@ var init_compact = __esm({
|
|
|
8323
8822
|
});
|
|
8324
8823
|
|
|
8325
8824
|
// src/commands/init.ts
|
|
8326
|
-
import { join as
|
|
8825
|
+
import { join as join18 } from "path";
|
|
8327
8826
|
import { existsSync as existsSync16 } from "fs";
|
|
8328
8827
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
8329
8828
|
var initCommand;
|
|
@@ -8338,7 +8837,7 @@ var init_init = __esm({
|
|
|
8338
8837
|
handler: async (args2, context) => {
|
|
8339
8838
|
try {
|
|
8340
8839
|
const cwd = process.cwd();
|
|
8341
|
-
const path2 =
|
|
8840
|
+
const path2 = join18(cwd, "MSAPLING.md");
|
|
8342
8841
|
if (existsSync16(path2)) {
|
|
8343
8842
|
context.addMessage("error", "MSAPLING.md already exists in current directory.");
|
|
8344
8843
|
return;
|
|
@@ -8481,12 +8980,12 @@ var init_swarm = __esm({
|
|
|
8481
8980
|
import { parse as parseYaml } from "yaml";
|
|
8482
8981
|
import { existsSync as existsSync18 } from "fs";
|
|
8483
8982
|
import { readFile as readFile16 } from "fs/promises";
|
|
8484
|
-
import { join as
|
|
8983
|
+
import { join as join19 } from "path";
|
|
8485
8984
|
function findRecipe(name, cwd) {
|
|
8486
8985
|
for (const dir of RECIPE_DIRS) {
|
|
8487
8986
|
for (const suffix of NAME_SUFFIXES) {
|
|
8488
8987
|
for (const ext of FILE_EXTS) {
|
|
8489
|
-
const p =
|
|
8988
|
+
const p = join19(cwd, dir, `${name}${suffix}${ext}`);
|
|
8490
8989
|
if (existsSync18(p)) return p;
|
|
8491
8990
|
}
|
|
8492
8991
|
}
|
|
@@ -8509,6 +9008,7 @@ var init_recipe = __esm({
|
|
|
8509
9008
|
"src/commands/recipe.ts"() {
|
|
8510
9009
|
"use strict";
|
|
8511
9010
|
init_esm_shims();
|
|
9011
|
+
init_src3();
|
|
8512
9012
|
RECIPE_DIRS = [".msapling/recipes", ".claude/recipes", ".gemini/recipes"];
|
|
8513
9013
|
NAME_SUFFIXES = ["", "-workflow"];
|
|
8514
9014
|
FILE_EXTS = [".yaml", ".yml"];
|
|
@@ -8542,14 +9042,21 @@ var init_recipe = __esm({
|
|
|
8542
9042
|
context.addMessage("system", `Usage: /recipe ${name} <prompt> (the prompt fills $SELECTION / $PROMPT in step templates)`);
|
|
8543
9043
|
return;
|
|
8544
9044
|
}
|
|
9045
|
+
let text;
|
|
8545
9046
|
let recipe;
|
|
8546
9047
|
try {
|
|
8547
|
-
|
|
9048
|
+
text = await readFile16(path2, "utf8");
|
|
8548
9049
|
recipe = parseYaml(text);
|
|
8549
9050
|
} catch (e) {
|
|
8550
9051
|
context.addMessage("error", `Failed to load ${path2}: ${e.message}`);
|
|
8551
9052
|
return;
|
|
8552
9053
|
}
|
|
9054
|
+
try {
|
|
9055
|
+
const storage = new StorageManager();
|
|
9056
|
+
const hash = await storage.registerRecipe(name, text);
|
|
9057
|
+
context.addMessage("system", `[recipe] Resolved ${name} \u2192 recipe@${hash.slice(0, 12)}\u2026`);
|
|
9058
|
+
} catch {
|
|
9059
|
+
}
|
|
8553
9060
|
const steps = recipe.steps ?? [];
|
|
8554
9061
|
if (steps.length === 0) {
|
|
8555
9062
|
context.addMessage("system", `Recipe '${name}' has no steps.`);
|
|
@@ -8592,13 +9099,13 @@ ${rendered}` : rendered;
|
|
|
8592
9099
|
});
|
|
8593
9100
|
|
|
8594
9101
|
// src/commands/skill.ts
|
|
8595
|
-
import { existsSync as existsSync19, readdirSync as readdirSync2, statSync as
|
|
9102
|
+
import { existsSync as existsSync19, readdirSync as readdirSync2, statSync as statSync5 } from "fs";
|
|
8596
9103
|
import { readFile as readFile17 } from "fs/promises";
|
|
8597
|
-
import { join as
|
|
9104
|
+
import { join as join20, resolve as resolve14 } from "path";
|
|
8598
9105
|
function findSkillsRoot(cwd) {
|
|
8599
9106
|
for (const candidate of SKILLS_DIRS) {
|
|
8600
9107
|
const full = resolve14(cwd, candidate);
|
|
8601
|
-
if (existsSync19(full) &&
|
|
9108
|
+
if (existsSync19(full) && statSync5(full).isDirectory()) return full;
|
|
8602
9109
|
}
|
|
8603
9110
|
return null;
|
|
8604
9111
|
}
|
|
@@ -8611,10 +9118,10 @@ function listAllSkills(root) {
|
|
|
8611
9118
|
return out;
|
|
8612
9119
|
}
|
|
8613
9120
|
for (const domain of domains) {
|
|
8614
|
-
const dir =
|
|
9121
|
+
const dir = join20(root, domain);
|
|
8615
9122
|
let s;
|
|
8616
9123
|
try {
|
|
8617
|
-
s =
|
|
9124
|
+
s = statSync5(dir);
|
|
8618
9125
|
} catch {
|
|
8619
9126
|
continue;
|
|
8620
9127
|
}
|
|
@@ -8627,7 +9134,7 @@ function listAllSkills(root) {
|
|
|
8627
9134
|
}
|
|
8628
9135
|
for (const f of files) {
|
|
8629
9136
|
if (!f.endsWith(".md")) continue;
|
|
8630
|
-
out.push({ domain, name: f.slice(0, -3), path:
|
|
9137
|
+
out.push({ domain, name: f.slice(0, -3), path: join20(dir, f) });
|
|
8631
9138
|
}
|
|
8632
9139
|
}
|
|
8633
9140
|
return out.sort(
|
|
@@ -8717,8 +9224,9 @@ ${prompt}`;
|
|
|
8717
9224
|
|
|
8718
9225
|
// src/commands/benchmark.ts
|
|
8719
9226
|
import { homedir as homedir11 } from "os";
|
|
8720
|
-
import { join as
|
|
8721
|
-
import { mkdirSync as
|
|
9227
|
+
import { join as join21 } from "path";
|
|
9228
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
9229
|
+
import * as fs2 from "fs";
|
|
8722
9230
|
function parseArgs(args2) {
|
|
8723
9231
|
let models = null;
|
|
8724
9232
|
let rounds = 1;
|
|
@@ -8833,10 +9341,10 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
|
|
|
8833
9341
|
`[HW at run time: CPU ${hwAtEnd.cpuPct}% / RAM ${hwAtEnd.ramPct}% | ${hw.ramGiB} GiB RAM, ${hw.cores} cores]`
|
|
8834
9342
|
);
|
|
8835
9343
|
try {
|
|
8836
|
-
const dir =
|
|
8837
|
-
|
|
9344
|
+
const dir = join21(homedir11(), ".msapling", "benchmarks");
|
|
9345
|
+
mkdirSync4(dir, { recursive: true });
|
|
8838
9346
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
|
|
8839
|
-
const file =
|
|
9347
|
+
const file = join21(dir, `${ts}.json`);
|
|
8840
9348
|
const run = {
|
|
8841
9349
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8842
9350
|
rounds,
|
|
@@ -8846,7 +9354,7 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
|
|
|
8846
9354
|
hw_end: hwAtEnd,
|
|
8847
9355
|
results
|
|
8848
9356
|
};
|
|
8849
|
-
|
|
9357
|
+
await fs2.promises.writeFile(file, JSON.stringify(run, null, 2));
|
|
8850
9358
|
context.addMessage("system", `Results saved to ${file}`);
|
|
8851
9359
|
} catch (e) {
|
|
8852
9360
|
context.addMessage("system", `Warning: could not save results \u2014 ${e?.message ?? e}`);
|
|
@@ -9082,12 +9590,12 @@ var init_theme = __esm({
|
|
|
9082
9590
|
});
|
|
9083
9591
|
|
|
9084
9592
|
// src/commands/theme.ts
|
|
9085
|
-
import { join as
|
|
9593
|
+
import { join as join22 } from "path";
|
|
9086
9594
|
import { homedir as homedir12 } from "os";
|
|
9087
9595
|
import { existsSync as existsSync20 } from "fs";
|
|
9088
|
-
import { readFile as readFile18, writeFile as writeFile9
|
|
9596
|
+
import { readFile as readFile18, writeFile as writeFile9 } from "fs/promises";
|
|
9089
9597
|
async function persistTheme(storage, themeName) {
|
|
9090
|
-
const settingsPath =
|
|
9598
|
+
const settingsPath = join22(homedir12(), ".msapling", "settings.json");
|
|
9091
9599
|
let existing = {};
|
|
9092
9600
|
try {
|
|
9093
9601
|
if (existsSync20(settingsPath)) {
|
|
@@ -9097,7 +9605,7 @@ async function persistTheme(storage, themeName) {
|
|
|
9097
9605
|
} catch {
|
|
9098
9606
|
}
|
|
9099
9607
|
existing["theme"] = themeName;
|
|
9100
|
-
|
|
9608
|
+
ensureConfigDir(join22(homedir12(), ".msapling"));
|
|
9101
9609
|
await writeFile9(settingsPath, JSON.stringify(existing, null, 2), "utf8");
|
|
9102
9610
|
}
|
|
9103
9611
|
var VALID_THEMES, themeCommand;
|
|
@@ -9106,6 +9614,7 @@ var init_theme2 = __esm({
|
|
|
9106
9614
|
"use strict";
|
|
9107
9615
|
init_esm_shims();
|
|
9108
9616
|
init_theme();
|
|
9617
|
+
init_src3();
|
|
9109
9618
|
VALID_THEMES = ["diamond", "onyx", "mono"];
|
|
9110
9619
|
themeCommand = {
|
|
9111
9620
|
name: "theme",
|
|
@@ -9153,7 +9662,7 @@ Available themes: ${VALID_THEMES.join(", ")}`);
|
|
|
9153
9662
|
});
|
|
9154
9663
|
|
|
9155
9664
|
// src/commands/version.ts
|
|
9156
|
-
import { join as
|
|
9665
|
+
import { join as join23 } from "path";
|
|
9157
9666
|
import { existsSync as existsSync21 } from "fs";
|
|
9158
9667
|
import { readFile as readFile19 } from "fs/promises";
|
|
9159
9668
|
function row2(label, value) {
|
|
@@ -9182,8 +9691,8 @@ var init_version = __esm({
|
|
|
9182
9691
|
category: "debug",
|
|
9183
9692
|
handler: async (_args, context) => {
|
|
9184
9693
|
const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
9185
|
-
const cliPkgPath =
|
|
9186
|
-
const corePkgPath =
|
|
9694
|
+
const cliPkgPath = join23(baseDir, "..", "..", "package.json");
|
|
9695
|
+
const corePkgPath = join23(baseDir, "..", "..", "..", "core", "package.json");
|
|
9187
9696
|
const [cliVersion, coreVersion] = await Promise.all([
|
|
9188
9697
|
readPackageVersion(cliPkgPath),
|
|
9189
9698
|
readPackageVersion(corePkgPath)
|
|
@@ -9208,13 +9717,13 @@ var init_version = __esm({
|
|
|
9208
9717
|
});
|
|
9209
9718
|
|
|
9210
9719
|
// src/commands/feedback.ts
|
|
9211
|
-
import { join as
|
|
9720
|
+
import { join as join24 } from "path";
|
|
9212
9721
|
import { existsSync as existsSync22 } from "fs";
|
|
9213
9722
|
import { readFile as readFile20 } from "fs/promises";
|
|
9214
9723
|
async function readCliVersion() {
|
|
9215
9724
|
try {
|
|
9216
9725
|
const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
9217
|
-
const pkgPath =
|
|
9726
|
+
const pkgPath = join24(baseDir, "..", "..", "package.json");
|
|
9218
9727
|
if (!existsSync22(pkgPath)) return "unknown";
|
|
9219
9728
|
const text = await readFile20(pkgPath, "utf8");
|
|
9220
9729
|
const json = JSON.parse(text);
|
|
@@ -9257,8 +9766,8 @@ var init_feedback = __esm({
|
|
|
9257
9766
|
|
|
9258
9767
|
// src/commands/export.ts
|
|
9259
9768
|
import { homedir as homedir13 } from "os";
|
|
9260
|
-
import { join as
|
|
9261
|
-
import { writeFile as writeFile10, mkdir as
|
|
9769
|
+
import { join as join25 } from "path";
|
|
9770
|
+
import { writeFile as writeFile10, mkdir as mkdir8 } from "fs/promises";
|
|
9262
9771
|
function formatTimestamp(date) {
|
|
9263
9772
|
return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
9264
9773
|
}
|
|
@@ -9307,10 +9816,10 @@ var init_export = __esm({
|
|
|
9307
9816
|
let outputPath;
|
|
9308
9817
|
let content;
|
|
9309
9818
|
if (arg === "" || arg === "json") {
|
|
9310
|
-
outputPath =
|
|
9819
|
+
outputPath = join25(homedir13(), `msapling-export-${timestamp}.json`);
|
|
9311
9820
|
content = buildJsonExport(history);
|
|
9312
9821
|
} else if (arg === "markdown" || arg === "md") {
|
|
9313
|
-
outputPath =
|
|
9822
|
+
outputPath = join25(homedir13(), `msapling-export-${timestamp}.md`);
|
|
9314
9823
|
content = buildMarkdownExport(history);
|
|
9315
9824
|
} else {
|
|
9316
9825
|
outputPath = arg;
|
|
@@ -9322,8 +9831,8 @@ var init_export = __esm({
|
|
|
9322
9831
|
}
|
|
9323
9832
|
}
|
|
9324
9833
|
try {
|
|
9325
|
-
const dir =
|
|
9326
|
-
await
|
|
9834
|
+
const dir = join25(outputPath, "..");
|
|
9835
|
+
await mkdir8(dir, { recursive: true });
|
|
9327
9836
|
await writeFile10(outputPath, content, "utf8");
|
|
9328
9837
|
context.addMessage("system", `Exported to: ${outputPath}`);
|
|
9329
9838
|
} catch (e) {
|
|
@@ -9518,11 +10027,11 @@ var init_plan = __esm({
|
|
|
9518
10027
|
|
|
9519
10028
|
// src/commands/note.ts
|
|
9520
10029
|
import { homedir as homedir14 } from "os";
|
|
9521
|
-
import { join as
|
|
9522
|
-
import {
|
|
10030
|
+
import { join as join26 } from "path";
|
|
10031
|
+
import { existsSync as existsSync23 } from "fs";
|
|
9523
10032
|
import { readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
9524
10033
|
function getNotesFilePath() {
|
|
9525
|
-
return
|
|
10034
|
+
return join26(homedir14(), ".msapling", "notes.json");
|
|
9526
10035
|
}
|
|
9527
10036
|
async function readNotes(filePath = getNotesFilePath()) {
|
|
9528
10037
|
try {
|
|
@@ -9536,8 +10045,8 @@ async function readNotes(filePath = getNotesFilePath()) {
|
|
|
9536
10045
|
}
|
|
9537
10046
|
}
|
|
9538
10047
|
async function writeNotes(notes, filePath = getNotesFilePath()) {
|
|
9539
|
-
const dir =
|
|
9540
|
-
|
|
10048
|
+
const dir = join26(homedir14(), ".msapling");
|
|
10049
|
+
ensureConfigDir(dir);
|
|
9541
10050
|
await writeFile11(filePath, JSON.stringify(notes, null, 2), "utf8");
|
|
9542
10051
|
}
|
|
9543
10052
|
function formatTimestamp2(iso) {
|
|
@@ -9681,6 +10190,238 @@ var init_todo = __esm({
|
|
|
9681
10190
|
}
|
|
9682
10191
|
});
|
|
9683
10192
|
|
|
10193
|
+
// src/commands/outputStyle.ts
|
|
10194
|
+
import { homedir as homedir15 } from "os";
|
|
10195
|
+
import { join as join27, basename, extname as extname3 } from "path";
|
|
10196
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
10197
|
+
function stylesDir() {
|
|
10198
|
+
return join27(homedir15(), ".msapling", "output-styles");
|
|
10199
|
+
}
|
|
10200
|
+
function activeFile() {
|
|
10201
|
+
return join27(stylesDir(), ".active");
|
|
10202
|
+
}
|
|
10203
|
+
function parseStyleFile(text) {
|
|
10204
|
+
const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
10205
|
+
if (!fm) {
|
|
10206
|
+
const lines = text.split(/\r?\n/);
|
|
10207
|
+
for (const line of lines) {
|
|
10208
|
+
if (!line.trim()) continue;
|
|
10209
|
+
const desc = line.replace(/^#\s+/, "").trim();
|
|
10210
|
+
return { description: desc || "(user style)", body: text.trim() };
|
|
10211
|
+
}
|
|
10212
|
+
return { description: "(user style)", body: text.trim() };
|
|
10213
|
+
}
|
|
10214
|
+
const meta = fm[1];
|
|
10215
|
+
const body = text.slice(fm[0].length).trim();
|
|
10216
|
+
const descMatch = meta.match(/^description:\s*(.+)$/m);
|
|
10217
|
+
const description = descMatch ? descMatch[1].trim().replace(/^['"]|['"]$/g, "") : "(user style)";
|
|
10218
|
+
return { description, body };
|
|
10219
|
+
}
|
|
10220
|
+
function listUserStyles() {
|
|
10221
|
+
const dir = stylesDir();
|
|
10222
|
+
if (!existsSync24(dir)) return [];
|
|
10223
|
+
const out = [];
|
|
10224
|
+
for (const entry of readdirSync3(dir)) {
|
|
10225
|
+
if (extname3(entry).toLowerCase() !== ".md") continue;
|
|
10226
|
+
const full = join27(dir, entry);
|
|
10227
|
+
try {
|
|
10228
|
+
const text = readFileSync(full, "utf8");
|
|
10229
|
+
const { description, body } = parseStyleFile(text);
|
|
10230
|
+
out.push({
|
|
10231
|
+
name: basename(entry, ".md"),
|
|
10232
|
+
description,
|
|
10233
|
+
body,
|
|
10234
|
+
source: "user",
|
|
10235
|
+
path: full
|
|
10236
|
+
});
|
|
10237
|
+
} catch {
|
|
10238
|
+
}
|
|
10239
|
+
}
|
|
10240
|
+
return out;
|
|
10241
|
+
}
|
|
10242
|
+
function listStyles() {
|
|
10243
|
+
const user = listUserStyles();
|
|
10244
|
+
const userNames = new Set(user.map((s) => s.name));
|
|
10245
|
+
const builtins = BUILTIN_STYLES.filter((s) => !userNames.has(s.name));
|
|
10246
|
+
return [...builtins, ...user];
|
|
10247
|
+
}
|
|
10248
|
+
function findStyle(name) {
|
|
10249
|
+
return listStyles().find((s) => s.name === name) ?? null;
|
|
10250
|
+
}
|
|
10251
|
+
function getActiveStyleName() {
|
|
10252
|
+
try {
|
|
10253
|
+
const f = activeFile();
|
|
10254
|
+
if (!existsSync24(f)) return "default";
|
|
10255
|
+
return readFileSync(f, "utf8").trim() || "default";
|
|
10256
|
+
} catch {
|
|
10257
|
+
return "default";
|
|
10258
|
+
}
|
|
10259
|
+
}
|
|
10260
|
+
function setActiveStyleName(name) {
|
|
10261
|
+
const dir = stylesDir();
|
|
10262
|
+
if (!existsSync24(dir)) mkdirSync5(dir, { recursive: true });
|
|
10263
|
+
writeFileSync2(activeFile(), `${name}
|
|
10264
|
+
`, "utf8");
|
|
10265
|
+
}
|
|
10266
|
+
function getActiveStyle() {
|
|
10267
|
+
const name = getActiveStyleName();
|
|
10268
|
+
return findStyle(name) ?? BUILTIN_STYLES[0];
|
|
10269
|
+
}
|
|
10270
|
+
function createUserStyle(name, description, body) {
|
|
10271
|
+
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(name)) {
|
|
10272
|
+
throw new Error(`Invalid style name "${name}" \u2014 use letters, digits, _ and - only.`);
|
|
10273
|
+
}
|
|
10274
|
+
const dir = stylesDir();
|
|
10275
|
+
if (!existsSync24(dir)) mkdirSync5(dir, { recursive: true });
|
|
10276
|
+
const target = join27(dir, `${name}.md`);
|
|
10277
|
+
const frontmatter = `---
|
|
10278
|
+
description: ${description.replace(/\n/g, " ")}
|
|
10279
|
+
---
|
|
10280
|
+
|
|
10281
|
+
`;
|
|
10282
|
+
writeFileSync2(target, frontmatter + body.trim() + "\n", "utf8");
|
|
10283
|
+
return target;
|
|
10284
|
+
}
|
|
10285
|
+
var BUILTIN_STYLES, outputStyleCommand;
|
|
10286
|
+
var init_outputStyle = __esm({
|
|
10287
|
+
"src/commands/outputStyle.ts"() {
|
|
10288
|
+
"use strict";
|
|
10289
|
+
init_esm_shims();
|
|
10290
|
+
BUILTIN_STYLES = [
|
|
10291
|
+
{
|
|
10292
|
+
name: "default",
|
|
10293
|
+
description: "Default MSapling behavior \u2014 no extra system prefix.",
|
|
10294
|
+
body: "",
|
|
10295
|
+
source: "builtin",
|
|
10296
|
+
path: null
|
|
10297
|
+
},
|
|
10298
|
+
{
|
|
10299
|
+
name: "concise",
|
|
10300
|
+
description: "Terse, code-first answers; minimal prose.",
|
|
10301
|
+
body: "You are MSapling in concise mode. Keep every response as short as possible.\nPrefer code blocks, bullet lists, and direct answers. Skip preamble, recap,\nand offers to elaborate unless explicitly asked.",
|
|
10302
|
+
source: "builtin",
|
|
10303
|
+
path: null
|
|
10304
|
+
},
|
|
10305
|
+
{
|
|
10306
|
+
name: "explanatory",
|
|
10307
|
+
description: "Walk through reasoning and trade-offs before the answer.",
|
|
10308
|
+
body: "You are MSapling in explanatory mode. Briefly walk through your reasoning,\npoint out trade-offs, and cite the relevant file or doc before giving the\nfinal answer. Aim for 2\u20133 short paragraphs, then a clear conclusion.",
|
|
10309
|
+
source: "builtin",
|
|
10310
|
+
path: null
|
|
10311
|
+
},
|
|
10312
|
+
{
|
|
10313
|
+
name: "learning",
|
|
10314
|
+
description: "Teach-by-doing: explain concepts, ask checkpoint questions.",
|
|
10315
|
+
body: "You are MSapling in learning mode. Assume the user is new to the topic.\nExplain key concepts in plain language, surface common pitfalls, and end\nwith a one-sentence comprehension check the user can answer or skip.",
|
|
10316
|
+
source: "builtin",
|
|
10317
|
+
path: null
|
|
10318
|
+
}
|
|
10319
|
+
];
|
|
10320
|
+
outputStyleCommand = {
|
|
10321
|
+
name: "output-style",
|
|
10322
|
+
aliases: ["style"],
|
|
10323
|
+
args: "[list|use <name>|new <name> <description>]",
|
|
10324
|
+
description: "List, switch, or create assistant output styles",
|
|
10325
|
+
category: "model",
|
|
10326
|
+
handler: (args2, context) => {
|
|
10327
|
+
const sub = (args2[0] ?? "list").toLowerCase();
|
|
10328
|
+
if (sub === "list" || sub === "ls") {
|
|
10329
|
+
const styles = listStyles();
|
|
10330
|
+
const active = getActiveStyleName();
|
|
10331
|
+
context.addMessage("system", "Output Styles:");
|
|
10332
|
+
for (const s of styles) {
|
|
10333
|
+
const marker = s.name === active ? "*" : " ";
|
|
10334
|
+
const tag = s.source === "builtin" ? "[builtin]" : "[user]";
|
|
10335
|
+
context.addMessage("system", ` ${marker} ${s.name.padEnd(14)} ${tag} ${s.description}`);
|
|
10336
|
+
}
|
|
10337
|
+
context.addMessage("system", `Active: ${active}`);
|
|
10338
|
+
return;
|
|
10339
|
+
}
|
|
10340
|
+
if (sub === "use") {
|
|
10341
|
+
const name = args2[1];
|
|
10342
|
+
if (!name) {
|
|
10343
|
+
context.addMessage("error", "Usage: /output-style use <name>");
|
|
10344
|
+
return;
|
|
10345
|
+
}
|
|
10346
|
+
const style = findStyle(name);
|
|
10347
|
+
if (!style) {
|
|
10348
|
+
context.addMessage("error", `Unknown style: ${name}. Try /output-style list.`);
|
|
10349
|
+
return;
|
|
10350
|
+
}
|
|
10351
|
+
setActiveStyleName(name);
|
|
10352
|
+
context.addMessage("system", `Active output style: ${name} \u2014 ${style.description}`);
|
|
10353
|
+
return;
|
|
10354
|
+
}
|
|
10355
|
+
if (sub === "new" || sub === "create") {
|
|
10356
|
+
const name = args2[1];
|
|
10357
|
+
const description = args2.slice(2).join(" ");
|
|
10358
|
+
if (!name) {
|
|
10359
|
+
context.addMessage("error", "Usage: /output-style new <name> <description>");
|
|
10360
|
+
return;
|
|
10361
|
+
}
|
|
10362
|
+
try {
|
|
10363
|
+
const path2 = createUserStyle(name, description || `User style: ${name}`, "");
|
|
10364
|
+
context.addMessage(
|
|
10365
|
+
"system",
|
|
10366
|
+
`Created ${path2} \u2014 edit the file to define the system prompt body, then /output-style use ${name}`
|
|
10367
|
+
);
|
|
10368
|
+
} catch (e) {
|
|
10369
|
+
context.addMessage("error", e.message ?? String(e));
|
|
10370
|
+
}
|
|
10371
|
+
return;
|
|
10372
|
+
}
|
|
10373
|
+
if (sub === "show") {
|
|
10374
|
+
const style = getActiveStyle();
|
|
10375
|
+
context.addMessage("system", `Active style: ${style.name} (${style.source})`);
|
|
10376
|
+
context.addMessage("system", `Description: ${style.description}`);
|
|
10377
|
+
if (style.body) {
|
|
10378
|
+
context.addMessage("system", "\u2500\u2500\u2500 body \u2500\u2500\u2500");
|
|
10379
|
+
context.addMessage("system", style.body);
|
|
10380
|
+
} else {
|
|
10381
|
+
context.addMessage("system", "(no system-prompt body \u2014 default behavior)");
|
|
10382
|
+
}
|
|
10383
|
+
return;
|
|
10384
|
+
}
|
|
10385
|
+
context.addMessage(
|
|
10386
|
+
"error",
|
|
10387
|
+
`Unknown subcommand: ${sub}. Try /output-style list|use|new|show`
|
|
10388
|
+
);
|
|
10389
|
+
}
|
|
10390
|
+
};
|
|
10391
|
+
}
|
|
10392
|
+
});
|
|
10393
|
+
|
|
10394
|
+
// src/commands/totp.ts
|
|
10395
|
+
var totpCommand;
|
|
10396
|
+
var init_totp = __esm({
|
|
10397
|
+
"src/commands/totp.ts"() {
|
|
10398
|
+
"use strict";
|
|
10399
|
+
init_esm_shims();
|
|
10400
|
+
totpCommand = {
|
|
10401
|
+
name: "totp",
|
|
10402
|
+
args: "<code>",
|
|
10403
|
+
description: "Step up the current session with a 6-digit TOTP code",
|
|
10404
|
+
category: "auth",
|
|
10405
|
+
handler: async (args2, context) => {
|
|
10406
|
+
const code = (args2[0] || "").trim();
|
|
10407
|
+
if (!/^\d{6}$/.test(code)) {
|
|
10408
|
+
context.addMessage("error", "Usage: /totp <6-digit code>");
|
|
10409
|
+
return;
|
|
10410
|
+
}
|
|
10411
|
+
try {
|
|
10412
|
+
const result = await context.client.verifyLoginTotp(code);
|
|
10413
|
+
await context.storage.saveToken(result.token);
|
|
10414
|
+
context.client.setToken(result.token);
|
|
10415
|
+
await context.refreshOverview();
|
|
10416
|
+
context.addMessage("system", "TOTP verified \u2014 session now fully authenticated.");
|
|
10417
|
+
} catch (err) {
|
|
10418
|
+
context.addMessage("error", `TOTP verification failed: ${err}`);
|
|
10419
|
+
}
|
|
10420
|
+
}
|
|
10421
|
+
};
|
|
10422
|
+
}
|
|
10423
|
+
});
|
|
10424
|
+
|
|
9684
10425
|
// src/commands/index.ts
|
|
9685
10426
|
var commands_exports = {};
|
|
9686
10427
|
__export(commands_exports, {
|
|
@@ -9727,6 +10468,8 @@ var init_commands = __esm({
|
|
|
9727
10468
|
init_plan();
|
|
9728
10469
|
init_note();
|
|
9729
10470
|
init_todo();
|
|
10471
|
+
init_outputStyle();
|
|
10472
|
+
init_totp();
|
|
9730
10473
|
commands = [
|
|
9731
10474
|
loginCommand,
|
|
9732
10475
|
unlockCommand,
|
|
@@ -9758,11 +10501,481 @@ var init_commands = __esm({
|
|
|
9758
10501
|
shortcutsCommand,
|
|
9759
10502
|
planCommand,
|
|
9760
10503
|
noteCommand,
|
|
9761
|
-
todoCommand
|
|
10504
|
+
todoCommand,
|
|
10505
|
+
outputStyleCommand,
|
|
10506
|
+
totpCommand
|
|
9762
10507
|
];
|
|
9763
10508
|
}
|
|
9764
10509
|
});
|
|
9765
10510
|
|
|
10511
|
+
// src/runtime/exec.ts
|
|
10512
|
+
var exec_exports = {};
|
|
10513
|
+
__export(exec_exports, {
|
|
10514
|
+
runExec: () => runExec
|
|
10515
|
+
});
|
|
10516
|
+
async function runExec(rawInput) {
|
|
10517
|
+
const trimmed = (rawInput || "").trim();
|
|
10518
|
+
if (!trimmed) {
|
|
10519
|
+
process.stderr.write("msapling --exec: empty command\n");
|
|
10520
|
+
return 2;
|
|
10521
|
+
}
|
|
10522
|
+
const parts = trimmed.split(/\s+/);
|
|
10523
|
+
const name = parts[0];
|
|
10524
|
+
const args2 = parts.slice(1);
|
|
10525
|
+
const cmd = findCommand(name);
|
|
10526
|
+
if (!cmd) {
|
|
10527
|
+
process.stderr.write(`msapling --exec: unknown command "${name}"
|
|
10528
|
+
`);
|
|
10529
|
+
return 2;
|
|
10530
|
+
}
|
|
10531
|
+
const storage = new StorageManager();
|
|
10532
|
+
const token = await storage.loadToken().catch(() => null);
|
|
10533
|
+
const client = new MSaplingClient({
|
|
10534
|
+
apiUrl: process.env.MSAPLING_API_URL,
|
|
10535
|
+
token: token || void 0
|
|
10536
|
+
});
|
|
10537
|
+
const stdout = process.stdout;
|
|
10538
|
+
let mode = "default";
|
|
10539
|
+
let model = process.env.MSAPLING_MODEL || "google/gemini-2.0-flash-001";
|
|
10540
|
+
let projectId = null;
|
|
10541
|
+
let activeChatId = null;
|
|
10542
|
+
const context = {
|
|
10543
|
+
client,
|
|
10544
|
+
storage,
|
|
10545
|
+
activeChatId,
|
|
10546
|
+
setActiveChatId: (id) => {
|
|
10547
|
+
activeChatId = id;
|
|
10548
|
+
},
|
|
10549
|
+
addMessage: (role, content) => {
|
|
10550
|
+
stdout.write(`[${role}] ${content}
|
|
10551
|
+
`);
|
|
10552
|
+
},
|
|
10553
|
+
clearHistory: () => {
|
|
10554
|
+
},
|
|
10555
|
+
setMode: (m) => {
|
|
10556
|
+
mode = m;
|
|
10557
|
+
},
|
|
10558
|
+
getMode: () => mode,
|
|
10559
|
+
setModel: (m) => {
|
|
10560
|
+
model = m;
|
|
10561
|
+
},
|
|
10562
|
+
getModel: () => model,
|
|
10563
|
+
setProjectId: (id) => {
|
|
10564
|
+
projectId = id;
|
|
10565
|
+
},
|
|
10566
|
+
getProjectId: () => projectId,
|
|
10567
|
+
lastCost: 0,
|
|
10568
|
+
sessionCost: 0,
|
|
10569
|
+
refreshOverview: async () => {
|
|
10570
|
+
},
|
|
10571
|
+
exit: () => {
|
|
10572
|
+
}
|
|
10573
|
+
};
|
|
10574
|
+
try {
|
|
10575
|
+
await cmd.handler(args2, context);
|
|
10576
|
+
return 0;
|
|
10577
|
+
} catch (e) {
|
|
10578
|
+
process.stderr.write(`msapling --exec: error: ${e?.message ?? e}
|
|
10579
|
+
`);
|
|
10580
|
+
return 1;
|
|
10581
|
+
}
|
|
10582
|
+
}
|
|
10583
|
+
var init_exec = __esm({
|
|
10584
|
+
"src/runtime/exec.ts"() {
|
|
10585
|
+
"use strict";
|
|
10586
|
+
init_esm_shims();
|
|
10587
|
+
init_src();
|
|
10588
|
+
init_src3();
|
|
10589
|
+
init_commands();
|
|
10590
|
+
}
|
|
10591
|
+
});
|
|
10592
|
+
|
|
10593
|
+
// src/runtime/doctorRedact.ts
|
|
10594
|
+
function redactSecrets(text) {
|
|
10595
|
+
let out = text;
|
|
10596
|
+
for (const [re, replacement] of PATTERNS) {
|
|
10597
|
+
out = out.replace(re, replacement);
|
|
10598
|
+
}
|
|
10599
|
+
return out;
|
|
10600
|
+
}
|
|
10601
|
+
var PATTERNS;
|
|
10602
|
+
var init_doctorRedact = __esm({
|
|
10603
|
+
"src/runtime/doctorRedact.ts"() {
|
|
10604
|
+
"use strict";
|
|
10605
|
+
init_esm_shims();
|
|
10606
|
+
PATTERNS = [
|
|
10607
|
+
// Legacy key=value pairs (from original doctor.ts redactSecrets).
|
|
10608
|
+
[/token[=:]\s*['"]?[a-zA-Z0-9_.-]+['"]?/gi, "token=***"],
|
|
10609
|
+
[/password[=:]\s*['"]?[a-zA-Z0-9_.-]+['"]?/gi, "password=***"],
|
|
10610
|
+
[/api[_-]?key[=:]\s*['"]?[a-zA-Z0-9_.-]+['"]?/gi, "api_key=***"],
|
|
10611
|
+
[/secret[=:]\s*['"]?[a-zA-Z0-9_.-]+['"]?/gi, "secret=***"],
|
|
10612
|
+
[/MSAPLING_TOKEN=.*/gi, "MSAPLING_TOKEN=***"],
|
|
10613
|
+
[/MSAPLING_API_KEY=.*/gi, "MSAPLING_API_KEY=***"],
|
|
10614
|
+
// Bearer tokens (case-insensitive).
|
|
10615
|
+
[/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer ***"],
|
|
10616
|
+
// GitHub PATs — keep the prefix so the credential class is identifiable.
|
|
10617
|
+
[/\bghp_[A-Za-z0-9]{20,}/g, "ghp_***"],
|
|
10618
|
+
[/\bghs_[A-Za-z0-9]{20,}/g, "ghs_***"],
|
|
10619
|
+
[/\bgho_[A-Za-z0-9]{20,}/g, "gho_***"],
|
|
10620
|
+
[/\bghu_[A-Za-z0-9]{20,}/g, "ghu_***"],
|
|
10621
|
+
[/\bghr_[A-Za-z0-9]{20,}/g, "ghr_***"],
|
|
10622
|
+
[/\bgithub_pat_[A-Za-z0-9_]{20,}/g, "github_pat_***"],
|
|
10623
|
+
// AWS access key IDs.
|
|
10624
|
+
[/\bAKIA[0-9A-Z]{16}\b/g, "AKIA***"],
|
|
10625
|
+
[/\bASIA[0-9A-Z]{16}\b/g, "ASIA***"],
|
|
10626
|
+
// Slack tokens (xoxa/xoxb/xoxp/xoxr/xoxs/xoxe-...).
|
|
10627
|
+
[/\bxox[abpres]-[A-Za-z0-9-]{10,}/gi, "xox-***"],
|
|
10628
|
+
// Anthropic + OpenAI style. Anthropic first to preserve "sk-ant-" prefix.
|
|
10629
|
+
[/\bsk-ant-[A-Za-z0-9_-]{20,}/g, "sk-ant-***"],
|
|
10630
|
+
[/\bsk-[A-Za-z0-9_-]{20,}/g, "sk-***"],
|
|
10631
|
+
// JWT-ish payload: three base64url segments separated by dots, header starts eyJ.
|
|
10632
|
+
[/\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}/g, "***jwt***"]
|
|
10633
|
+
];
|
|
10634
|
+
}
|
|
10635
|
+
});
|
|
10636
|
+
|
|
10637
|
+
// src/runtime/doctor.ts
|
|
10638
|
+
var doctor_exports = {};
|
|
10639
|
+
__export(doctor_exports, {
|
|
10640
|
+
runDoctor: () => runDoctor
|
|
10641
|
+
});
|
|
10642
|
+
import { homedir as homedir16, platform as platform3 } from "os";
|
|
10643
|
+
import { join as join28 } from "path";
|
|
10644
|
+
import { existsSync as existsSync26, statSync as statSync6, accessSync } from "fs";
|
|
10645
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
10646
|
+
import { exec } from "child_process";
|
|
10647
|
+
import { promisify } from "util";
|
|
10648
|
+
async function checkNodeVersion() {
|
|
10649
|
+
const version = process.version;
|
|
10650
|
+
const match = version.match(/v(\d+)/);
|
|
10651
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
10652
|
+
if (major >= 18) {
|
|
10653
|
+
return {
|
|
10654
|
+
name: "Node version",
|
|
10655
|
+
status: "PASS",
|
|
10656
|
+
message: `${version} (>=18)`
|
|
10657
|
+
};
|
|
10658
|
+
}
|
|
10659
|
+
return {
|
|
10660
|
+
name: "Node version",
|
|
10661
|
+
status: "FAIL",
|
|
10662
|
+
message: `${version} (requires >=18)`,
|
|
10663
|
+
remediation: `Upgrade Node.js to v18+ from https://nodejs.org/`
|
|
10664
|
+
};
|
|
10665
|
+
}
|
|
10666
|
+
async function checkConfigDir() {
|
|
10667
|
+
const configDir = join28(homedir16(), ".msapling");
|
|
10668
|
+
if (!existsSync26(configDir)) {
|
|
10669
|
+
return {
|
|
10670
|
+
name: "Config directory",
|
|
10671
|
+
status: "WARN",
|
|
10672
|
+
message: `${configDir} does not exist`,
|
|
10673
|
+
remediation: `mkdir -p "${configDir}" && chmod 700 "${configDir}"`
|
|
10674
|
+
};
|
|
10675
|
+
}
|
|
10676
|
+
const stats = statSync6(configDir);
|
|
10677
|
+
if (!stats.isDirectory()) {
|
|
10678
|
+
return {
|
|
10679
|
+
name: "Config directory",
|
|
10680
|
+
status: "FAIL",
|
|
10681
|
+
message: `${configDir} exists but is not a directory`,
|
|
10682
|
+
remediation: `rm "${configDir}" && mkdir -p "${configDir}"`
|
|
10683
|
+
};
|
|
10684
|
+
}
|
|
10685
|
+
if (platform3() !== "win32") {
|
|
10686
|
+
const mode = stats.mode & 511;
|
|
10687
|
+
const safe = (mode & 63) === 0;
|
|
10688
|
+
if (!safe) {
|
|
10689
|
+
return {
|
|
10690
|
+
name: "Config directory",
|
|
10691
|
+
status: "WARN",
|
|
10692
|
+
message: `${configDir} has overly permissive perms (${(mode & 511).toString(8)})`,
|
|
10693
|
+
remediation: `chmod 700 "${configDir}"`
|
|
10694
|
+
};
|
|
10695
|
+
}
|
|
10696
|
+
}
|
|
10697
|
+
return {
|
|
10698
|
+
name: "Config directory",
|
|
10699
|
+
status: "PASS",
|
|
10700
|
+
message: `${configDir} exists and is readable`
|
|
10701
|
+
};
|
|
10702
|
+
}
|
|
10703
|
+
async function checkKeytar() {
|
|
10704
|
+
try {
|
|
10705
|
+
const keytar2 = await import("keytar");
|
|
10706
|
+
if (keytar2 && typeof keytar2.getPassword === "function") {
|
|
10707
|
+
return {
|
|
10708
|
+
name: "Keytar native binary",
|
|
10709
|
+
status: "PASS",
|
|
10710
|
+
message: "Keytar loadable and functional"
|
|
10711
|
+
};
|
|
10712
|
+
}
|
|
10713
|
+
} catch (e) {
|
|
10714
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
10715
|
+
return {
|
|
10716
|
+
name: "Keytar native binary",
|
|
10717
|
+
status: "WARN",
|
|
10718
|
+
message: `Keytar unavailable: ${msg}`,
|
|
10719
|
+
remediation: `Token will be stored in plaintext. Run 'npm rebuild keytar' or 'bun install --force' to rebuild native bindings.`
|
|
10720
|
+
};
|
|
10721
|
+
}
|
|
10722
|
+
return {
|
|
10723
|
+
name: "Keytar native binary",
|
|
10724
|
+
status: "WARN",
|
|
10725
|
+
message: "Keytar check inconclusive",
|
|
10726
|
+
remediation: `Run 'npm rebuild keytar' or 'bun install --force' to rebuild native bindings.`
|
|
10727
|
+
};
|
|
10728
|
+
}
|
|
10729
|
+
async function checkPathConflicts() {
|
|
10730
|
+
const pathEnv = process.env.PATH || "";
|
|
10731
|
+
const paths = pathEnv.split(platform3() === "win32" ? ";" : ":");
|
|
10732
|
+
const conflicts = [];
|
|
10733
|
+
for (const dir of paths) {
|
|
10734
|
+
if (!dir || !existsSync26(dir)) continue;
|
|
10735
|
+
try {
|
|
10736
|
+
const files = await readdir3(dir);
|
|
10737
|
+
for (const file of files) {
|
|
10738
|
+
if (file === "msapling" || file === "msapling.exe" || file === "msapling.py") {
|
|
10739
|
+
const fullPath = join28(dir, file);
|
|
10740
|
+
conflicts.push(fullPath);
|
|
10741
|
+
}
|
|
10742
|
+
}
|
|
10743
|
+
} catch {
|
|
10744
|
+
}
|
|
10745
|
+
}
|
|
10746
|
+
if (conflicts.length === 0) {
|
|
10747
|
+
return {
|
|
10748
|
+
name: "PATH conflicts",
|
|
10749
|
+
status: "PASS",
|
|
10750
|
+
message: "No conflicting msapling binaries found"
|
|
10751
|
+
};
|
|
10752
|
+
}
|
|
10753
|
+
if (conflicts.length === 1) {
|
|
10754
|
+
return {
|
|
10755
|
+
name: "PATH conflicts",
|
|
10756
|
+
status: "PASS",
|
|
10757
|
+
message: `Single msapling binary found: ${conflicts[0]}`
|
|
10758
|
+
};
|
|
10759
|
+
}
|
|
10760
|
+
return {
|
|
10761
|
+
name: "PATH conflicts",
|
|
10762
|
+
status: "WARN",
|
|
10763
|
+
message: `Multiple msapling binaries found in PATH`,
|
|
10764
|
+
remediation: `Found: ${conflicts.join(", ")}. Remove stale copies (especially .py or .exe).`
|
|
10765
|
+
};
|
|
10766
|
+
}
|
|
10767
|
+
async function checkNetworkReach() {
|
|
10768
|
+
const apiUrl = process.env.MSAPLING_API_URL || "https://api.msapling.com";
|
|
10769
|
+
const timeout = 5e3;
|
|
10770
|
+
try {
|
|
10771
|
+
const controller = new AbortController();
|
|
10772
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
10773
|
+
const response = await fetch(`${apiUrl}/api/health`, {
|
|
10774
|
+
method: "HEAD",
|
|
10775
|
+
signal: controller.signal
|
|
10776
|
+
});
|
|
10777
|
+
clearTimeout(timeoutId);
|
|
10778
|
+
if (response.ok) {
|
|
10779
|
+
return {
|
|
10780
|
+
name: "Network reach",
|
|
10781
|
+
status: "PASS",
|
|
10782
|
+
message: `${apiUrl} reachable`
|
|
10783
|
+
};
|
|
10784
|
+
}
|
|
10785
|
+
return {
|
|
10786
|
+
name: "Network reach",
|
|
10787
|
+
status: "WARN",
|
|
10788
|
+
message: `${apiUrl} returned status ${response.status}`,
|
|
10789
|
+
remediation: `Check your network connection or API server status.`
|
|
10790
|
+
};
|
|
10791
|
+
} catch (e) {
|
|
10792
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
10793
|
+
return {
|
|
10794
|
+
name: "Network reach",
|
|
10795
|
+
status: "WARN",
|
|
10796
|
+
message: `Cannot reach ${apiUrl}: ${msg}`,
|
|
10797
|
+
remediation: `Check your network connection and firewall settings.`
|
|
10798
|
+
};
|
|
10799
|
+
}
|
|
10800
|
+
}
|
|
10801
|
+
async function checkTokenValidity() {
|
|
10802
|
+
try {
|
|
10803
|
+
const keytar2 = await import("keytar");
|
|
10804
|
+
const KEYCHAIN_SERVICE = "msapling-cli";
|
|
10805
|
+
const KEYCHAIN_ACCOUNT = "auth_token";
|
|
10806
|
+
const token = await keytar2.getPassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
10807
|
+
if (!token) {
|
|
10808
|
+
return {
|
|
10809
|
+
name: "Token validity",
|
|
10810
|
+
status: "WARN",
|
|
10811
|
+
message: "No token stored in keychain",
|
|
10812
|
+
remediation: `Run 'msapling login' to authenticate.`
|
|
10813
|
+
};
|
|
10814
|
+
}
|
|
10815
|
+
if (typeof token === "string" && token.length > 10) {
|
|
10816
|
+
return {
|
|
10817
|
+
name: "Token validity",
|
|
10818
|
+
status: "PASS",
|
|
10819
|
+
message: `Token loaded (${token.length} chars)`
|
|
10820
|
+
};
|
|
10821
|
+
}
|
|
10822
|
+
return {
|
|
10823
|
+
name: "Token validity",
|
|
10824
|
+
status: "WARN",
|
|
10825
|
+
message: "Token present but appears invalid",
|
|
10826
|
+
remediation: `Run 'msapling login' to re-authenticate.`
|
|
10827
|
+
};
|
|
10828
|
+
} catch (e) {
|
|
10829
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
10830
|
+
return {
|
|
10831
|
+
name: "Token validity",
|
|
10832
|
+
status: "WARN",
|
|
10833
|
+
message: `Cannot check token: ${msg}`,
|
|
10834
|
+
remediation: `Run 'msapling login' to authenticate.`
|
|
10835
|
+
};
|
|
10836
|
+
}
|
|
10837
|
+
}
|
|
10838
|
+
async function checkOsSpecific() {
|
|
10839
|
+
if (platform3() === "win32") {
|
|
10840
|
+
try {
|
|
10841
|
+
const configDir = join28(homedir16(), ".msapling");
|
|
10842
|
+
const longPath = "A".repeat(260);
|
|
10843
|
+
const testPath = join28(configDir, longPath);
|
|
10844
|
+
try {
|
|
10845
|
+
accessSync(configDir);
|
|
10846
|
+
} catch {
|
|
10847
|
+
return {
|
|
10848
|
+
name: "OS-specific (Windows)",
|
|
10849
|
+
status: "WARN",
|
|
10850
|
+
message: "Cannot verify Windows long-path support",
|
|
10851
|
+
remediation: `Enable LongPathsEnabled in Registry or use 'fsutil 8dot3name set C: 0'.`
|
|
10852
|
+
};
|
|
10853
|
+
}
|
|
10854
|
+
return {
|
|
10855
|
+
name: "OS-specific (Windows)",
|
|
10856
|
+
status: "PASS",
|
|
10857
|
+
message: "Windows long-path support accessible"
|
|
10858
|
+
};
|
|
10859
|
+
} catch (e) {
|
|
10860
|
+
return {
|
|
10861
|
+
name: "OS-specific (Windows)",
|
|
10862
|
+
status: "WARN",
|
|
10863
|
+
message: "Windows long-path check failed",
|
|
10864
|
+
remediation: `Enable LongPathsEnabled in Registry or use 'fsutil 8dot3name set C: 0'.`
|
|
10865
|
+
};
|
|
10866
|
+
}
|
|
10867
|
+
}
|
|
10868
|
+
if (platform3() === "darwin") {
|
|
10869
|
+
return {
|
|
10870
|
+
name: "OS-specific (macOS)",
|
|
10871
|
+
status: "PASS",
|
|
10872
|
+
message: "macOS detected"
|
|
10873
|
+
};
|
|
10874
|
+
}
|
|
10875
|
+
if (platform3() === "linux") {
|
|
10876
|
+
try {
|
|
10877
|
+
await import("keytar");
|
|
10878
|
+
return {
|
|
10879
|
+
name: "OS-specific (Linux)",
|
|
10880
|
+
status: "PASS",
|
|
10881
|
+
message: "libsecret/keytar dependencies available"
|
|
10882
|
+
};
|
|
10883
|
+
} catch (e) {
|
|
10884
|
+
return {
|
|
10885
|
+
name: "OS-specific (Linux)",
|
|
10886
|
+
status: "WARN",
|
|
10887
|
+
message: "libsecret may not be installed",
|
|
10888
|
+
remediation: `Install libsecret: sudo apt-get install libsecret-1-dev (Ubuntu/Debian) or dnf install libsecret-devel (Fedora)`
|
|
10889
|
+
};
|
|
10890
|
+
}
|
|
10891
|
+
}
|
|
10892
|
+
return {
|
|
10893
|
+
name: "OS-specific",
|
|
10894
|
+
status: "PASS",
|
|
10895
|
+
message: `${platform3()} detected`
|
|
10896
|
+
};
|
|
10897
|
+
}
|
|
10898
|
+
function formatCheckResult(result, maxLabelWidth) {
|
|
10899
|
+
const indicator2 = result.status === "PASS" ? "\u2713" : result.status === "WARN" ? "\u26A0" : "\u2717";
|
|
10900
|
+
const paddedLabel = result.name.padEnd(maxLabelWidth);
|
|
10901
|
+
const statusStr = `[${indicator2} ${result.status}]`.padEnd(10);
|
|
10902
|
+
let output = ` ${paddedLabel} ${statusStr} ${result.message}`;
|
|
10903
|
+
if (result.remediation) {
|
|
10904
|
+
output += `
|
|
10905
|
+
\u2192 ${result.remediation}`;
|
|
10906
|
+
}
|
|
10907
|
+
return output;
|
|
10908
|
+
}
|
|
10909
|
+
function dumpEnv(debug) {
|
|
10910
|
+
if (!debug) return [];
|
|
10911
|
+
const lines = [];
|
|
10912
|
+
lines.push("");
|
|
10913
|
+
lines.push("\u2500".repeat(60));
|
|
10914
|
+
lines.push("Environment variables:");
|
|
10915
|
+
lines.push("\u2500".repeat(60));
|
|
10916
|
+
const env = { ...process.env };
|
|
10917
|
+
const keys = Object.keys(env).sort();
|
|
10918
|
+
for (const key of keys) {
|
|
10919
|
+
const value = env[key] || "";
|
|
10920
|
+
const redacted = redactSecrets(value);
|
|
10921
|
+
lines.push(`${key}=${redacted}`);
|
|
10922
|
+
}
|
|
10923
|
+
return lines;
|
|
10924
|
+
}
|
|
10925
|
+
async function runDoctor(debug = false) {
|
|
10926
|
+
const output = [];
|
|
10927
|
+
output.push("");
|
|
10928
|
+
output.push("MSapling Doctor \u2014 Health Check");
|
|
10929
|
+
output.push("\u2500".repeat(60));
|
|
10930
|
+
const checks = [];
|
|
10931
|
+
checks.push(await checkNodeVersion());
|
|
10932
|
+
checks.push(await checkConfigDir());
|
|
10933
|
+
checks.push(await checkKeytar());
|
|
10934
|
+
checks.push(await checkPathConflicts());
|
|
10935
|
+
checks.push(await checkNetworkReach());
|
|
10936
|
+
checks.push(await checkTokenValidity());
|
|
10937
|
+
checks.push(await checkOsSpecific());
|
|
10938
|
+
const maxLabelWidth = Math.max(...checks.map((c) => c.name.length));
|
|
10939
|
+
for (const check of checks) {
|
|
10940
|
+
output.push(formatCheckResult(check, maxLabelWidth));
|
|
10941
|
+
}
|
|
10942
|
+
output.push("");
|
|
10943
|
+
output.push("\u2500".repeat(60));
|
|
10944
|
+
const failCount = checks.filter((c) => c.status === "FAIL").length;
|
|
10945
|
+
const warnCount = checks.filter((c) => c.status === "WARN").length;
|
|
10946
|
+
if (failCount === 0 && warnCount === 0) {
|
|
10947
|
+
output.push("All checks passed! \u2713");
|
|
10948
|
+
} else {
|
|
10949
|
+
const msgs = [];
|
|
10950
|
+
if (failCount > 0) msgs.push(`${failCount} failed`);
|
|
10951
|
+
if (warnCount > 0) msgs.push(`${warnCount} warning(s)`);
|
|
10952
|
+
output.push(`Status: ${msgs.join(", ")}`);
|
|
10953
|
+
}
|
|
10954
|
+
output.push("");
|
|
10955
|
+
if (debug) {
|
|
10956
|
+
output.push(...dumpEnv(true));
|
|
10957
|
+
output.push("");
|
|
10958
|
+
}
|
|
10959
|
+
const rendered = output.join("\n");
|
|
10960
|
+
console.log(rendered);
|
|
10961
|
+
return {
|
|
10962
|
+
exitCode: failCount > 0 ? 1 : 0,
|
|
10963
|
+
checks,
|
|
10964
|
+
failCount,
|
|
10965
|
+
warnCount,
|
|
10966
|
+
rendered
|
|
10967
|
+
};
|
|
10968
|
+
}
|
|
10969
|
+
var execAsync;
|
|
10970
|
+
var init_doctor2 = __esm({
|
|
10971
|
+
"src/runtime/doctor.ts"() {
|
|
10972
|
+
"use strict";
|
|
10973
|
+
init_esm_shims();
|
|
10974
|
+
init_doctorRedact();
|
|
10975
|
+
execAsync = promisify(exec);
|
|
10976
|
+
}
|
|
10977
|
+
});
|
|
10978
|
+
|
|
9766
10979
|
// ../../node_modules/.bun/diff@9.0.0/node_modules/diff/libesm/diff/base.js
|
|
9767
10980
|
var Diff;
|
|
9768
10981
|
var init_base = __esm({
|
|
@@ -9849,13 +11062,13 @@ var init_base = __esm({
|
|
|
9849
11062
|
editLength++;
|
|
9850
11063
|
};
|
|
9851
11064
|
if (callback) {
|
|
9852
|
-
(function
|
|
11065
|
+
(function exec2() {
|
|
9853
11066
|
setTimeout(function() {
|
|
9854
11067
|
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
|
9855
11068
|
return callback(void 0);
|
|
9856
11069
|
}
|
|
9857
11070
|
if (!execEditLength()) {
|
|
9858
|
-
|
|
11071
|
+
exec2();
|
|
9859
11072
|
}
|
|
9860
11073
|
}, 0);
|
|
9861
11074
|
})();
|
|
@@ -10909,7 +12122,7 @@ async function mergeToolRegistries(localTools, client) {
|
|
|
10909
12122
|
return Array.from(merged.values());
|
|
10910
12123
|
}
|
|
10911
12124
|
for (const backendTool of registry.tools) {
|
|
10912
|
-
const tier = backendTool.tier_required === "free" || backendTool.tier_required === "pro" ? backendTool.tier_required : "
|
|
12125
|
+
const tier = backendTool.tier_required === "free" || backendTool.tier_required === "pro" || backendTool.tier_required === "lifetime" || backendTool.tier_required === "enterprise" ? backendTool.tier_required : "enterprise";
|
|
10913
12126
|
if (!merged.has(backendTool.name)) {
|
|
10914
12127
|
merged.set(backendTool.name, {
|
|
10915
12128
|
name: backendTool.name,
|
|
@@ -10939,17 +12152,17 @@ var init_registry_merger = __esm({
|
|
|
10939
12152
|
|
|
10940
12153
|
// ../core/src/mcp/local_tools.ts
|
|
10941
12154
|
import { spawn as spawn10 } from "child_process";
|
|
10942
|
-
import { readdir as
|
|
10943
|
-
import { resolve as
|
|
12155
|
+
import { readdir as readdir4, stat as stat4, realpath as realpath2 } from "fs/promises";
|
|
12156
|
+
import { resolve as resolve16 } from "path";
|
|
10944
12157
|
function asResult(text, isError = false) {
|
|
10945
12158
|
return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
|
|
10946
12159
|
}
|
|
10947
12160
|
async function runCommand(command, cwd) {
|
|
10948
|
-
return new Promise((
|
|
12161
|
+
return new Promise((resolve18) => {
|
|
10949
12162
|
let p;
|
|
10950
12163
|
const timeout = setTimeout(() => {
|
|
10951
12164
|
if (p) p.kill();
|
|
10952
|
-
|
|
12165
|
+
resolve18({ stdout: "", stderr: "Command timed out after 30s", exit_code: -1 });
|
|
10953
12166
|
}, 3e4);
|
|
10954
12167
|
try {
|
|
10955
12168
|
p = spawn10("sh", ["-c", command], {
|
|
@@ -10967,15 +12180,15 @@ async function runCommand(command, cwd) {
|
|
|
10967
12180
|
});
|
|
10968
12181
|
p.on("error", (e) => {
|
|
10969
12182
|
clearTimeout(timeout);
|
|
10970
|
-
|
|
12183
|
+
resolve18({ stdout, stderr: stderr + (e?.message ?? ""), exit_code: -1 });
|
|
10971
12184
|
});
|
|
10972
12185
|
p.on("exit", (code) => {
|
|
10973
12186
|
clearTimeout(timeout);
|
|
10974
|
-
|
|
12187
|
+
resolve18({ stdout, stderr, exit_code: code });
|
|
10975
12188
|
});
|
|
10976
12189
|
} catch (e) {
|
|
10977
12190
|
clearTimeout(timeout);
|
|
10978
|
-
|
|
12191
|
+
resolve18({
|
|
10979
12192
|
stdout: "",
|
|
10980
12193
|
stderr: e?.message ?? "Failed to spawn process",
|
|
10981
12194
|
exit_code: -1
|
|
@@ -10985,7 +12198,7 @@ async function runCommand(command, cwd) {
|
|
|
10985
12198
|
}
|
|
10986
12199
|
async function listDirectory(path2, maxEntries = 100) {
|
|
10987
12200
|
try {
|
|
10988
|
-
const entries = await
|
|
12201
|
+
const entries = await readdir4(path2, { withFileTypes: true });
|
|
10989
12202
|
const result = [];
|
|
10990
12203
|
for (const entry of entries.slice(0, maxEntries)) {
|
|
10991
12204
|
const item = {
|
|
@@ -11042,7 +12255,7 @@ async function callLocalTool(name, args2, projectRoot) {
|
|
|
11042
12255
|
const command = String(args2.command ?? "");
|
|
11043
12256
|
let cwd = projectRoot;
|
|
11044
12257
|
if (args2.cwd) {
|
|
11045
|
-
cwd =
|
|
12258
|
+
cwd = resolve16(projectRoot, String(args2.cwd));
|
|
11046
12259
|
try {
|
|
11047
12260
|
const resolvedCwd = await realpath2(cwd);
|
|
11048
12261
|
const resolvedRoot = await realpath2(projectRoot);
|
|
@@ -11079,7 +12292,7 @@ ${res.stderr}`
|
|
|
11079
12292
|
return asResult("path is required", true);
|
|
11080
12293
|
}
|
|
11081
12294
|
try {
|
|
11082
|
-
const resolvedPath = await realpath2(
|
|
12295
|
+
const resolvedPath = await realpath2(resolve16(projectRoot, pathArg));
|
|
11083
12296
|
const resolvedRoot = await realpath2(projectRoot);
|
|
11084
12297
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
11085
12298
|
return asResult("Error: path attempts to escape project root", true);
|
|
@@ -11098,7 +12311,7 @@ ${res.stderr}`
|
|
|
11098
12311
|
}
|
|
11099
12312
|
case "local_glob": {
|
|
11100
12313
|
const pattern = String(args2.pattern ?? "");
|
|
11101
|
-
let cwd = args2.cwd ?
|
|
12314
|
+
let cwd = args2.cwd ? resolve16(projectRoot, String(args2.cwd)) : projectRoot;
|
|
11102
12315
|
if (!pattern) {
|
|
11103
12316
|
return asResult("pattern is required", true);
|
|
11104
12317
|
}
|
|
@@ -11127,7 +12340,7 @@ ${res.stderr}`
|
|
|
11127
12340
|
}
|
|
11128
12341
|
if (path2) {
|
|
11129
12342
|
try {
|
|
11130
|
-
const resolvedPath = await realpath2(
|
|
12343
|
+
const resolvedPath = await realpath2(resolve16(projectRoot, path2));
|
|
11131
12344
|
const resolvedRoot = await realpath2(projectRoot);
|
|
11132
12345
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
11133
12346
|
return asResult("Error: path attempts to escape project root", true);
|
|
@@ -11147,7 +12360,7 @@ ${res.stderr}`
|
|
|
11147
12360
|
return asResult("cwd is required", true);
|
|
11148
12361
|
}
|
|
11149
12362
|
try {
|
|
11150
|
-
const resolvedCwd = await realpath2(
|
|
12363
|
+
const resolvedCwd = await realpath2(resolve16(projectRoot, cwdArg));
|
|
11151
12364
|
const resolvedRoot = await realpath2(projectRoot);
|
|
11152
12365
|
if (!resolvedCwd.startsWith(resolvedRoot)) {
|
|
11153
12366
|
return asResult("Error: cwd attempts to escape project root", true);
|
|
@@ -11173,7 +12386,7 @@ ${status.porcelain || "(clean)"}`
|
|
|
11173
12386
|
return asResult("cwd is required", true);
|
|
11174
12387
|
}
|
|
11175
12388
|
try {
|
|
11176
|
-
const resolvedCwd = await realpath2(
|
|
12389
|
+
const resolvedCwd = await realpath2(resolve16(projectRoot, cwdArg));
|
|
11177
12390
|
const resolvedRoot = await realpath2(projectRoot);
|
|
11178
12391
|
if (!resolvedCwd.startsWith(resolvedRoot)) {
|
|
11179
12392
|
return asResult("Error: cwd attempts to escape project root", true);
|
|
@@ -11288,8 +12501,8 @@ __export(server_exports, {
|
|
|
11288
12501
|
runStdio: () => runStdio,
|
|
11289
12502
|
runStdioWithRegistry: () => runStdioWithRegistry
|
|
11290
12503
|
});
|
|
11291
|
-
import { readdirSync as
|
|
11292
|
-
import { join as
|
|
12504
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync2, statSync as statSync7 } from "fs";
|
|
12505
|
+
import { join as join29, relative as relative14, resolve as resolve17 } from "path";
|
|
11293
12506
|
function asResult2(text, isError = false) {
|
|
11294
12507
|
return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
|
|
11295
12508
|
}
|
|
@@ -11302,17 +12515,17 @@ function buildFileTree(root, maxFiles) {
|
|
|
11302
12515
|
const dir = queue.shift();
|
|
11303
12516
|
let entries;
|
|
11304
12517
|
try {
|
|
11305
|
-
entries =
|
|
12518
|
+
entries = readdirSync4(dir);
|
|
11306
12519
|
} catch {
|
|
11307
12520
|
continue;
|
|
11308
12521
|
}
|
|
11309
12522
|
for (const name of entries) {
|
|
11310
12523
|
if (out.length >= maxFiles) break;
|
|
11311
12524
|
if (SKIP_DIRS2.has(name)) continue;
|
|
11312
|
-
const full =
|
|
12525
|
+
const full = join29(dir, name);
|
|
11313
12526
|
let s;
|
|
11314
12527
|
try {
|
|
11315
|
-
s =
|
|
12528
|
+
s = statSync7(full);
|
|
11316
12529
|
} catch {
|
|
11317
12530
|
continue;
|
|
11318
12531
|
}
|
|
@@ -11333,7 +12546,7 @@ function readFilesAsContext(root, files, maxKB) {
|
|
|
11333
12546
|
for (const f of files) {
|
|
11334
12547
|
let body;
|
|
11335
12548
|
try {
|
|
11336
|
-
body =
|
|
12549
|
+
body = readFileSync2(f, "utf8");
|
|
11337
12550
|
} catch {
|
|
11338
12551
|
continue;
|
|
11339
12552
|
}
|
|
@@ -11347,11 +12560,37 @@ ${body}
|
|
|
11347
12560
|
}
|
|
11348
12561
|
return parts.join("\n\n");
|
|
11349
12562
|
}
|
|
12563
|
+
function installDrainHandlers(server) {
|
|
12564
|
+
const abort = new AbortController();
|
|
12565
|
+
let drainStarted = false;
|
|
12566
|
+
const onSignal = (sig) => {
|
|
12567
|
+
if (drainStarted) return;
|
|
12568
|
+
drainStarted = true;
|
|
12569
|
+
process.stderr.write(`[mcp-server] received ${sig}, starting graceful drain
|
|
12570
|
+
`);
|
|
12571
|
+
server.drain().then((forcedResponses) => {
|
|
12572
|
+
for (const resp of forcedResponses) {
|
|
12573
|
+
try {
|
|
12574
|
+
process.stdout.write(JSON.stringify(resp) + "\n");
|
|
12575
|
+
} catch {
|
|
12576
|
+
}
|
|
12577
|
+
}
|
|
12578
|
+
process.stderr.write("[mcp-server] drain complete, exiting\n");
|
|
12579
|
+
abort.abort();
|
|
12580
|
+
process.exit(0);
|
|
12581
|
+
});
|
|
12582
|
+
};
|
|
12583
|
+
process.on("SIGTERM", () => onSignal("SIGTERM"));
|
|
12584
|
+
process.on("SIGINT", () => onSignal("SIGINT"));
|
|
12585
|
+
return abort;
|
|
12586
|
+
}
|
|
11350
12587
|
async function runStdio(client) {
|
|
11351
12588
|
const server = new MCPServer(client);
|
|
12589
|
+
const abort = installDrainHandlers(server);
|
|
11352
12590
|
let buffer = "";
|
|
11353
12591
|
const decoder = new TextDecoder();
|
|
11354
12592
|
for await (const chunk of process.stdin) {
|
|
12593
|
+
if (abort.signal.aborted) break;
|
|
11355
12594
|
buffer += decoder.decode(chunk, { stream: true });
|
|
11356
12595
|
let idx;
|
|
11357
12596
|
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
@@ -11371,9 +12610,11 @@ async function runStdio(client) {
|
|
|
11371
12610
|
}
|
|
11372
12611
|
async function runStdioWithRegistry(client, registryClient) {
|
|
11373
12612
|
const server = new MCPServer(client, registryClient || client);
|
|
12613
|
+
const abort = installDrainHandlers(server);
|
|
11374
12614
|
let buffer = "";
|
|
11375
12615
|
const decoder = new TextDecoder();
|
|
11376
12616
|
for await (const chunk of process.stdin) {
|
|
12617
|
+
if (abort.signal.aborted) break;
|
|
11377
12618
|
buffer += decoder.decode(chunk, { stream: true });
|
|
11378
12619
|
let idx;
|
|
11379
12620
|
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
@@ -11391,7 +12632,7 @@ async function runStdioWithRegistry(client, registryClient) {
|
|
|
11391
12632
|
}
|
|
11392
12633
|
}
|
|
11393
12634
|
}
|
|
11394
|
-
var TOOLS, PROTOCOL_VERSION2, PRO_TIERS2, TIER_CACHE_TTL_MS, SAFE_PATH_RE, MCPServer;
|
|
12635
|
+
var AP4_MAX_BLOB_BYTES, TOOLS, PROTOCOL_VERSION2, PRO_TIERS2, TIER_CACHE_TTL_MS, DRAIN_TIMEOUT_MS, SAFE_PATH_RE, MCPServer;
|
|
11395
12636
|
var init_server = __esm({
|
|
11396
12637
|
"../core/src/mcp/server.ts"() {
|
|
11397
12638
|
"use strict";
|
|
@@ -11400,6 +12641,7 @@ var init_server = __esm({
|
|
|
11400
12641
|
init_libesm();
|
|
11401
12642
|
init_registry_merger();
|
|
11402
12643
|
init_local_tools();
|
|
12644
|
+
AP4_MAX_BLOB_BYTES = 4 * 1024;
|
|
11403
12645
|
TOOLS = [
|
|
11404
12646
|
{
|
|
11405
12647
|
name: "msapling_chat",
|
|
@@ -11518,11 +12760,57 @@ var init_server = __esm({
|
|
|
11518
12760
|
tier: "free",
|
|
11519
12761
|
inputSchema: { type: "object", properties: {} }
|
|
11520
12762
|
},
|
|
12763
|
+
{
|
|
12764
|
+
name: "msapling_list_wakeups",
|
|
12765
|
+
description: "[Pro] List pending AI wake-ups/scheduled tasks for your account.",
|
|
12766
|
+
tier: "pro",
|
|
12767
|
+
inputSchema: {
|
|
12768
|
+
type: "object",
|
|
12769
|
+
properties: {
|
|
12770
|
+
status: { type: "string", enum: ["pending", "fired", "cancelled", "expired"], description: "Filter by status" },
|
|
12771
|
+
session_id: { type: "string", description: "Filter by chat session ID" }
|
|
12772
|
+
}
|
|
12773
|
+
}
|
|
12774
|
+
},
|
|
12775
|
+
{
|
|
12776
|
+
name: "msapling_schedule_wakeup",
|
|
12777
|
+
description: "[Pro] Schedule a future AI task or follow-up wakeup.",
|
|
12778
|
+
tier: "pro",
|
|
12779
|
+
inputSchema: {
|
|
12780
|
+
type: "object",
|
|
12781
|
+
required: ["delay_seconds", "reason"],
|
|
12782
|
+
properties: {
|
|
12783
|
+
delay_seconds: { type: "number", description: "Delay in seconds from now" },
|
|
12784
|
+
reason: { type: "string", description: "Short description of what the AI should do" },
|
|
12785
|
+
prompt: { type: "string", description: "Specific prompt to process when the wakeup fires" },
|
|
12786
|
+
session_id: { type: "string", description: "Associate with an existing chat session" }
|
|
12787
|
+
}
|
|
12788
|
+
}
|
|
12789
|
+
},
|
|
12790
|
+
{
|
|
12791
|
+
name: "msapling_cancel_wakeup",
|
|
12792
|
+
description: "[Pro] Cancel a pending AI wakeup.",
|
|
12793
|
+
tier: "pro",
|
|
12794
|
+
inputSchema: {
|
|
12795
|
+
type: "object",
|
|
12796
|
+
required: ["id"],
|
|
12797
|
+
properties: {
|
|
12798
|
+
id: { type: "string", description: "The wakeup UUID to cancel" }
|
|
12799
|
+
}
|
|
12800
|
+
}
|
|
12801
|
+
},
|
|
12802
|
+
{
|
|
12803
|
+
name: "msapling_fleet_status",
|
|
12804
|
+
description: "[Pro] View status of your remote agent fleet and active tasks.",
|
|
12805
|
+
tier: "pro",
|
|
12806
|
+
inputSchema: { type: "object", properties: {} }
|
|
12807
|
+
},
|
|
11521
12808
|
...LOCAL_TOOLS
|
|
11522
12809
|
];
|
|
11523
12810
|
PROTOCOL_VERSION2 = "2024-11-05";
|
|
11524
12811
|
PRO_TIERS2 = /* @__PURE__ */ new Set(["pro", "monthly", "lifetime", "enterprise", "admin", "superadmin"]);
|
|
11525
12812
|
TIER_CACHE_TTL_MS = 6e4;
|
|
12813
|
+
DRAIN_TIMEOUT_MS = 5e3;
|
|
11526
12814
|
SAFE_PATH_RE = /^[A-Za-z0-9_./\-\\: ]+$/;
|
|
11527
12815
|
MCPServer = class {
|
|
11528
12816
|
constructor(client, backendClient) {
|
|
@@ -11533,6 +12821,83 @@ var init_server = __esm({
|
|
|
11533
12821
|
backendClient;
|
|
11534
12822
|
tierCache = null;
|
|
11535
12823
|
registryCache = null;
|
|
12824
|
+
/**
|
|
12825
|
+
* CLIENT-CLI-16: When true, the server is shutting down. New tool calls
|
|
12826
|
+
* are rejected with a JSON-RPC error; only in-flight calls are allowed
|
|
12827
|
+
* to finish.
|
|
12828
|
+
*/
|
|
12829
|
+
_isDraining = false;
|
|
12830
|
+
/**
|
|
12831
|
+
* CLIENT-CLI-16: Set of in-flight tool call IDs. Used during draining to
|
|
12832
|
+
* wait for completion before closing the transport. Capped at
|
|
12833
|
+
* MAX_INFLIGHT_TOOLS.
|
|
12834
|
+
*/
|
|
12835
|
+
_inflightCalls = /* @__PURE__ */ new Map();
|
|
12836
|
+
/**
|
|
12837
|
+
* CLIENT-CLI-16: Resolved when all in-flight calls complete during drain,
|
|
12838
|
+
* or when the drain timeout fires.
|
|
12839
|
+
*/
|
|
12840
|
+
_drainResolve = null;
|
|
12841
|
+
/** CLIENT-CLI-16: Whether the server is currently draining. */
|
|
12842
|
+
get isDraining() {
|
|
12843
|
+
return this._isDraining;
|
|
12844
|
+
}
|
|
12845
|
+
/**
|
|
12846
|
+
* CLIENT-CLI-16: Enter draining mode. Returns a promise that resolves when
|
|
12847
|
+
* all in-flight calls complete or DRAIN_TIMEOUT_MS elapses, whichever
|
|
12848
|
+
* comes first.
|
|
12849
|
+
*
|
|
12850
|
+
* After the promise resolves, any remaining in-flight calls have been
|
|
12851
|
+
* forcefully closed with error responses (written to stdout by the caller).
|
|
12852
|
+
*/
|
|
12853
|
+
async drain() {
|
|
12854
|
+
this._isDraining = true;
|
|
12855
|
+
const inflight = this._inflightCalls.size;
|
|
12856
|
+
process.stderr.write(
|
|
12857
|
+
`[mcp-server] draining: ${inflight} in-flight tool call(s)
|
|
12858
|
+
`
|
|
12859
|
+
);
|
|
12860
|
+
if (inflight === 0) {
|
|
12861
|
+
return [];
|
|
12862
|
+
}
|
|
12863
|
+
const forcedResponses = await new Promise((resolve18) => {
|
|
12864
|
+
this._drainResolve = () => resolve18([]);
|
|
12865
|
+
setTimeout(() => {
|
|
12866
|
+
this._drainResolve = null;
|
|
12867
|
+
const remaining = Array.from(this._inflightCalls.values());
|
|
12868
|
+
if (remaining.length === 0) {
|
|
12869
|
+
resolve18([]);
|
|
12870
|
+
return;
|
|
12871
|
+
}
|
|
12872
|
+
process.stderr.write(
|
|
12873
|
+
`[mcp-server] drain timeout: force-closing ${remaining.length} call(s)
|
|
12874
|
+
`
|
|
12875
|
+
);
|
|
12876
|
+
const errorResponses = remaining.map((call) => ({
|
|
12877
|
+
jsonrpc: "2.0",
|
|
12878
|
+
id: call.id,
|
|
12879
|
+
error: {
|
|
12880
|
+
code: -32e3,
|
|
12881
|
+
message: `Server shutdown: tool call "${call.name}" timed out after ${DRAIN_TIMEOUT_MS}ms`
|
|
12882
|
+
}
|
|
12883
|
+
}));
|
|
12884
|
+
this._inflightCalls.clear();
|
|
12885
|
+
resolve18(errorResponses);
|
|
12886
|
+
}, DRAIN_TIMEOUT_MS);
|
|
12887
|
+
});
|
|
12888
|
+
return forcedResponses;
|
|
12889
|
+
}
|
|
12890
|
+
/**
|
|
12891
|
+
* CLIENT-CLI-16: Called when an in-flight tool call completes during drain.
|
|
12892
|
+
* If all calls are done, resolves the drain promise.
|
|
12893
|
+
*/
|
|
12894
|
+
_completeInflight(id) {
|
|
12895
|
+
this._inflightCalls.delete(id);
|
|
12896
|
+
if (this._isDraining && this._inflightCalls.size === 0 && this._drainResolve) {
|
|
12897
|
+
this._drainResolve();
|
|
12898
|
+
this._drainResolve = null;
|
|
12899
|
+
}
|
|
12900
|
+
}
|
|
11536
12901
|
/**
|
|
11537
12902
|
* Fetch the caller's tier (Pro vs free) and cache it briefly. We avoid a
|
|
11538
12903
|
* `me()` call per `tools/list` because Claude Code calls tools/list often
|
|
@@ -11564,6 +12929,25 @@ var init_server = __esm({
|
|
|
11564
12929
|
invalidateTierCache() {
|
|
11565
12930
|
this.tierCache = null;
|
|
11566
12931
|
}
|
|
12932
|
+
/**
|
|
12933
|
+
* CLI-R12-20260507-TIER-02: Map tier name to numeric level for comparison.
|
|
12934
|
+
* Higher level = more restrictive/expensive.
|
|
12935
|
+
* free (1) < pro (5) < lifetime (10) < enterprise (100)
|
|
12936
|
+
*/
|
|
12937
|
+
tierLevel(tier) {
|
|
12938
|
+
switch (String(tier).toLowerCase()) {
|
|
12939
|
+
case "free":
|
|
12940
|
+
return 1;
|
|
12941
|
+
case "pro":
|
|
12942
|
+
return 5;
|
|
12943
|
+
case "lifetime":
|
|
12944
|
+
return 10;
|
|
12945
|
+
case "enterprise":
|
|
12946
|
+
return 100;
|
|
12947
|
+
default:
|
|
12948
|
+
return 100;
|
|
12949
|
+
}
|
|
12950
|
+
}
|
|
11567
12951
|
/**
|
|
11568
12952
|
* Fetch and cache the merged tool registry (local + backend).
|
|
11569
12953
|
* Called on first tools/list request. Cached for session lifetime.
|
|
@@ -11601,30 +12985,62 @@ var init_server = __esm({
|
|
|
11601
12985
|
return { jsonrpc: "2.0", id, result: { tools: wireFormat } };
|
|
11602
12986
|
}
|
|
11603
12987
|
case "tools/call": {
|
|
12988
|
+
if (this._isDraining) {
|
|
12989
|
+
return {
|
|
12990
|
+
jsonrpc: "2.0",
|
|
12991
|
+
id,
|
|
12992
|
+
error: {
|
|
12993
|
+
code: -32e3,
|
|
12994
|
+
message: "Server shutting down: not accepting new tool calls"
|
|
12995
|
+
}
|
|
12996
|
+
};
|
|
12997
|
+
}
|
|
11604
12998
|
const name = req.params?.name;
|
|
11605
12999
|
const args2 = req.params?.arguments ?? {};
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
} catch (e) {
|
|
11612
|
-
const msg = e instanceof MSaplingError ? `[backend ${e.status ?? "?"} ${e.code ?? ""}] ${e.message}` : e?.message ?? "tool call failed";
|
|
11613
|
-
return { jsonrpc: "2.0", id, result: asResult2(msg, true) };
|
|
13000
|
+
this._inflightCalls.set(id, {
|
|
13001
|
+
id,
|
|
13002
|
+
name: name ?? "unknown",
|
|
13003
|
+
startedAt: Date.now(),
|
|
13004
|
+
resolve: () => {
|
|
11614
13005
|
}
|
|
11615
|
-
}
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
13006
|
+
});
|
|
13007
|
+
try {
|
|
13008
|
+
const localTool = TOOLS.find((t) => t.name === name);
|
|
13009
|
+
if (localTool) {
|
|
13010
|
+
try {
|
|
13011
|
+
const result = await this.callTool(name, args2);
|
|
13012
|
+
const resp = { jsonrpc: "2.0", id, result };
|
|
13013
|
+
return resp;
|
|
13014
|
+
} catch (e) {
|
|
13015
|
+
const msg = e instanceof MSaplingError ? `[backend ${e.status ?? "?"} ${e.code ?? ""}] ${e.message}` : e?.message ?? "tool call failed";
|
|
13016
|
+
return { jsonrpc: "2.0", id, result: asResult2(msg, true) };
|
|
13017
|
+
}
|
|
13018
|
+
}
|
|
13019
|
+
const merged = await this.getMergedRegistry();
|
|
13020
|
+
const backendTool = merged.find((t) => t.name === name && t.runs_on === "backend");
|
|
13021
|
+
if (backendTool) {
|
|
13022
|
+
const isPro = await this.getIsProCached();
|
|
13023
|
+
const toolTierLevel = this.tierLevel(backendTool.tier);
|
|
13024
|
+
const userTierLevel = isPro ? 5 : 1;
|
|
13025
|
+
if (toolTierLevel > userTierLevel) {
|
|
13026
|
+
return {
|
|
13027
|
+
jsonrpc: "2.0",
|
|
13028
|
+
id,
|
|
13029
|
+
result: asResult2(`Access denied: tool "${name}" requires ${backendTool.tier} tier`, true)
|
|
13030
|
+
};
|
|
13031
|
+
}
|
|
13032
|
+
try {
|
|
13033
|
+
const result = await this.invokeBackendTool(name, args2);
|
|
13034
|
+
return { jsonrpc: "2.0", id, result };
|
|
13035
|
+
} catch (e) {
|
|
13036
|
+
const msg = e instanceof MSaplingError ? `[backend ${e.status ?? "?"} ${e.code ?? ""}] ${e.message}` : e?.message ?? "tool call failed";
|
|
13037
|
+
return { jsonrpc: "2.0", id, result: asResult2(msg, true) };
|
|
13038
|
+
}
|
|
11625
13039
|
}
|
|
13040
|
+
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown tool: ${name}` } };
|
|
13041
|
+
} finally {
|
|
13042
|
+
this._completeInflight(id);
|
|
11626
13043
|
}
|
|
11627
|
-
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown tool: ${name}` } };
|
|
11628
13044
|
}
|
|
11629
13045
|
case "shutdown":
|
|
11630
13046
|
return { jsonrpc: "2.0", id, result: {} };
|
|
@@ -11675,12 +13091,30 @@ ${r.response ?? ""}`;
|
|
|
11675
13091
|
return asResult2(text);
|
|
11676
13092
|
}
|
|
11677
13093
|
case "msapling_diff": {
|
|
13094
|
+
const oldContent = String(args2.old_content ?? "");
|
|
13095
|
+
const newContent = String(args2.new_content ?? "");
|
|
13096
|
+
const oldBytes = Buffer.byteLength(oldContent, "utf8");
|
|
13097
|
+
const newBytes = Buffer.byteLength(newContent, "utf8");
|
|
13098
|
+
if (oldBytes > AP4_MAX_BLOB_BYTES || newBytes > AP4_MAX_BLOB_BYTES) {
|
|
13099
|
+
return asResult2(
|
|
13100
|
+
`AP-4: Blobs exceed 4 KB (old=${oldBytes}B, new=${newBytes}B). Use the backend endpoint: POST /api/projects/:id/diff`,
|
|
13101
|
+
true
|
|
13102
|
+
);
|
|
13103
|
+
}
|
|
11678
13104
|
const filename = String(args2.filename ?? "file");
|
|
11679
|
-
const patch = createPatch(filename,
|
|
13105
|
+
const patch = createPatch(filename, oldContent, newContent, "", "");
|
|
11680
13106
|
return asResult2(patch);
|
|
11681
13107
|
}
|
|
11682
13108
|
case "msapling_apply_diff": {
|
|
11683
|
-
const
|
|
13109
|
+
const originalContent = String(args2.original_content ?? "");
|
|
13110
|
+
const originalBytes = Buffer.byteLength(originalContent, "utf8");
|
|
13111
|
+
if (originalBytes > AP4_MAX_BLOB_BYTES) {
|
|
13112
|
+
return asResult2(
|
|
13113
|
+
`AP-4: Blob exceeds 4 KB (${originalBytes}B). Use the backend endpoint: POST /api/projects/:id/apply`,
|
|
13114
|
+
true
|
|
13115
|
+
);
|
|
13116
|
+
}
|
|
13117
|
+
const applied = applyPatch(originalContent, String(args2.diff_text ?? ""));
|
|
11684
13118
|
if (applied === false) {
|
|
11685
13119
|
return asResult2("Diff did not apply cleanly (hunks rejected).", true);
|
|
11686
13120
|
}
|
|
@@ -11726,7 +13160,7 @@ ${r.response ?? ""}`;
|
|
|
11726
13160
|
return asResult2(JSON.stringify(result));
|
|
11727
13161
|
}
|
|
11728
13162
|
case "msapling_project_context": {
|
|
11729
|
-
const root =
|
|
13163
|
+
const root = resolve17(String(args2.path ?? "."));
|
|
11730
13164
|
const maxFiles = Number.isFinite(args2.max_files) ? Number(args2.max_files) : 30;
|
|
11731
13165
|
const maxKB = Number.isFinite(args2.max_file_size_kb) ? Number(args2.max_file_size_kb) : 50;
|
|
11732
13166
|
const files = buildFileTree(root, maxFiles);
|
|
@@ -11777,6 +13211,36 @@ ${text}`);
|
|
|
11777
13211
|
return asResult2(`Available models (${models.length} total, showing first 30):
|
|
11778
13212
|
${lines.join("\n")}`);
|
|
11779
13213
|
}
|
|
13214
|
+
case "msapling_list_wakeups": {
|
|
13215
|
+
const isPro = await this.getIsProCached();
|
|
13216
|
+
if (!isPro) return asResult2("msapling_list_wakeups requires a Pro subscription.", true);
|
|
13217
|
+
const data = await this.client.listWakeups(args2);
|
|
13218
|
+
return asResult2(JSON.stringify(data, null, 2));
|
|
13219
|
+
}
|
|
13220
|
+
case "msapling_schedule_wakeup": {
|
|
13221
|
+
const isPro = await this.getIsProCached();
|
|
13222
|
+
if (!isPro) return asResult2("msapling_schedule_wakeup requires a Pro subscription.", true);
|
|
13223
|
+
const data = await this.client.createWakeup({
|
|
13224
|
+
delay_seconds: Number(args2.delay_seconds),
|
|
13225
|
+
reason: String(args2.reason ?? ""),
|
|
13226
|
+
prompt: args2.prompt ? String(args2.prompt) : void 0,
|
|
13227
|
+
session_id: args2.session_id ? String(args2.session_id) : void 0
|
|
13228
|
+
});
|
|
13229
|
+
return asResult2(JSON.stringify(data, null, 2));
|
|
13230
|
+
}
|
|
13231
|
+
case "msapling_cancel_wakeup": {
|
|
13232
|
+
const isPro = await this.getIsProCached();
|
|
13233
|
+
if (!isPro) return asResult2("msapling_cancel_wakeup requires a Pro subscription.", true);
|
|
13234
|
+
await this.client.cancelWakeup(String(args2.id));
|
|
13235
|
+
return asResult2("Wakeup cancelled");
|
|
13236
|
+
}
|
|
13237
|
+
case "msapling_fleet_status": {
|
|
13238
|
+
const isPro = await this.getIsProCached();
|
|
13239
|
+
if (!isPro) return asResult2("msapling_fleet_status requires a Pro subscription.", true);
|
|
13240
|
+
const status = await this.client.getFleetStatus();
|
|
13241
|
+
const tasks = await this.client.getActiveTasks();
|
|
13242
|
+
return asResult2(JSON.stringify({ status, tasks }, null, 2));
|
|
13243
|
+
}
|
|
11780
13244
|
// Local MCP tools (filesystem, shell, git)
|
|
11781
13245
|
case "local_run_command":
|
|
11782
13246
|
case "local_list_directory":
|
|
@@ -11822,7 +13286,10 @@ init_esm_shims();
|
|
|
11822
13286
|
import { Box, Text } from "ink";
|
|
11823
13287
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11824
13288
|
var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
|
|
11825
|
-
/* @__PURE__ */
|
|
13289
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
|
|
13290
|
+
"\u25CF MSapling CLI v",
|
|
13291
|
+
"2.3.6-beta.11"
|
|
13292
|
+
] }),
|
|
11826
13293
|
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
|
|
11827
13294
|
] });
|
|
11828
13295
|
|
|
@@ -12308,9 +13775,9 @@ ${prompt}` : prompt;
|
|
|
12308
13775
|
if (proc.stderr) proc.stderr.on("data", (chunk) => {
|
|
12309
13776
|
stderr += chunk.toString();
|
|
12310
13777
|
});
|
|
12311
|
-
await new Promise((
|
|
13778
|
+
await new Promise((resolve18, reject) => {
|
|
12312
13779
|
proc.on("close", (code) => {
|
|
12313
|
-
if (code === 0 || code === null)
|
|
13780
|
+
if (code === 0 || code === null) resolve18();
|
|
12314
13781
|
else reject(new Error(`Process exited with code ${code}`));
|
|
12315
13782
|
});
|
|
12316
13783
|
proc.on("error", reject);
|
|
@@ -12337,9 +13804,9 @@ ${prompt}` : prompt;
|
|
|
12337
13804
|
for (const mention of fileMentions) {
|
|
12338
13805
|
const filePath = mention.slice(1);
|
|
12339
13806
|
try {
|
|
12340
|
-
const { existsSync:
|
|
13807
|
+
const { existsSync: existsSync27 } = await import("fs");
|
|
12341
13808
|
const { readFile: readFile23 } = await import("fs/promises");
|
|
12342
|
-
if (
|
|
13809
|
+
if (existsSync27(filePath)) {
|
|
12343
13810
|
const content = await readFile23(filePath, "utf8");
|
|
12344
13811
|
const MAX_LEN = 32768;
|
|
12345
13812
|
const truncated = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n...[TRUNCATED]" : content;
|
|
@@ -12392,7 +13859,66 @@ ${finalCmd}`;
|
|
|
12392
13859
|
init_esm_shims();
|
|
12393
13860
|
init_src3();
|
|
12394
13861
|
import { readFile as readFile22 } from "fs/promises";
|
|
12395
|
-
import { existsSync as
|
|
13862
|
+
import { existsSync as existsSync25 } from "fs";
|
|
13863
|
+
|
|
13864
|
+
// src/state/parseApprovalMode.ts
|
|
13865
|
+
init_esm_shims();
|
|
13866
|
+
var VALID_MODES = [
|
|
13867
|
+
"default",
|
|
13868
|
+
"plan",
|
|
13869
|
+
"acceptEdits",
|
|
13870
|
+
"bypassPermissions"
|
|
13871
|
+
];
|
|
13872
|
+
function parseApprovalMode(raw, now) {
|
|
13873
|
+
if (!raw || typeof raw !== "object") {
|
|
13874
|
+
return { kind: "invalid", error: "settings file is not a JSON object" };
|
|
13875
|
+
}
|
|
13876
|
+
const block = raw.approvalMode;
|
|
13877
|
+
if (block === void 0) return { kind: "absent" };
|
|
13878
|
+
if (typeof block === "string") {
|
|
13879
|
+
if (!VALID_MODES.includes(block)) {
|
|
13880
|
+
return { kind: "invalid", error: `unknown approvalMode "${block}"` };
|
|
13881
|
+
}
|
|
13882
|
+
return { kind: "ok", mode: block };
|
|
13883
|
+
}
|
|
13884
|
+
if (typeof block !== "object" || block === null) {
|
|
13885
|
+
return {
|
|
13886
|
+
kind: "invalid",
|
|
13887
|
+
error: `approvalMode must be a string or object, got ${typeof block}`
|
|
13888
|
+
};
|
|
13889
|
+
}
|
|
13890
|
+
const obj = block;
|
|
13891
|
+
const mode = obj.mode;
|
|
13892
|
+
if (typeof mode !== "string" || !VALID_MODES.includes(mode)) {
|
|
13893
|
+
return { kind: "invalid", error: `approvalMode.mode invalid: ${JSON.stringify(mode)}` };
|
|
13894
|
+
}
|
|
13895
|
+
if (obj.ttlMs === void 0 && obj.timestamp === void 0) {
|
|
13896
|
+
return { kind: "ok", mode };
|
|
13897
|
+
}
|
|
13898
|
+
if (typeof obj.ttlMs !== "number" || !Number.isFinite(obj.ttlMs) || obj.ttlMs <= 0) {
|
|
13899
|
+
return {
|
|
13900
|
+
kind: "invalid",
|
|
13901
|
+
error: `approvalMode.ttlMs must be a positive number, got ${JSON.stringify(obj.ttlMs)}`
|
|
13902
|
+
};
|
|
13903
|
+
}
|
|
13904
|
+
if (typeof obj.timestamp !== "number" || !Number.isFinite(obj.timestamp) || obj.timestamp <= 0) {
|
|
13905
|
+
return {
|
|
13906
|
+
kind: "invalid",
|
|
13907
|
+
error: `approvalMode.timestamp must be a positive number, got ${JSON.stringify(obj.timestamp)}`
|
|
13908
|
+
};
|
|
13909
|
+
}
|
|
13910
|
+
if (mode !== "bypassPermissions") {
|
|
13911
|
+
return { kind: "ok", mode };
|
|
13912
|
+
}
|
|
13913
|
+
const age = now - obj.timestamp;
|
|
13914
|
+
if (age > obj.ttlMs) {
|
|
13915
|
+
return { kind: "expired", mode: "default", agedMs: age };
|
|
13916
|
+
}
|
|
13917
|
+
const remainingMs = obj.ttlMs - age;
|
|
13918
|
+
return { kind: "ok", mode: "bypassPermissions", ttl: { remainingMs } };
|
|
13919
|
+
}
|
|
13920
|
+
|
|
13921
|
+
// src/state/initSession.ts
|
|
12396
13922
|
async function initSession(ctx) {
|
|
12397
13923
|
try {
|
|
12398
13924
|
const { settings } = await loadSettings(
|
|
@@ -12406,38 +13932,40 @@ async function initSession(ctx) {
|
|
|
12406
13932
|
ctx.setShellEscapeEnabled(settings.shellEscapeEnabled !== false);
|
|
12407
13933
|
}
|
|
12408
13934
|
try {
|
|
12409
|
-
const { homedir:
|
|
12410
|
-
const { join:
|
|
12411
|
-
const userSettingsPath =
|
|
12412
|
-
if (
|
|
13935
|
+
const { homedir: homedir17 } = await import("os");
|
|
13936
|
+
const { join: join31 } = await import("path");
|
|
13937
|
+
const userSettingsPath = join31(homedir17(), ".msapling", "settings.json");
|
|
13938
|
+
if (existsSync25(userSettingsPath)) {
|
|
12413
13939
|
const userText = await readFile22(userSettingsPath, "utf8");
|
|
12414
|
-
|
|
12415
|
-
|
|
13940
|
+
let parsed;
|
|
13941
|
+
try {
|
|
13942
|
+
parsed = JSON.parse(userText);
|
|
13943
|
+
} catch (e) {
|
|
13944
|
+
ctx.addMessage("system", `\u26A0 ~/.msapling/settings.json is not valid JSON: ${e.message}. Using default mode.`);
|
|
13945
|
+
parsed = {};
|
|
13946
|
+
}
|
|
13947
|
+
const result = parseApprovalMode(parsed, Date.now());
|
|
12416
13948
|
let modeToApply = "default";
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
13949
|
+
switch (result.kind) {
|
|
13950
|
+
case "absent":
|
|
13951
|
+
break;
|
|
13952
|
+
case "invalid":
|
|
13953
|
+
ctx.addMessage("system", `\u26A0 Persisted approvalMode invalid (${result.error}). Reverting to default mode.`);
|
|
13954
|
+
break;
|
|
13955
|
+
case "expired":
|
|
13956
|
+
ctx.addMessage("system", "\u26A0 bypassPermissions TTL expired. Reverting to default mode.");
|
|
13957
|
+
break;
|
|
13958
|
+
case "ok":
|
|
13959
|
+
modeToApply = result.mode;
|
|
13960
|
+
if (result.mode === "bypassPermissions") {
|
|
13961
|
+
if (result.ttl) {
|
|
13962
|
+
const mins = (result.ttl.remainingMs / 1e3 / 60).toFixed(1);
|
|
13963
|
+
ctx.addMessage("system", `\u26A0 bypassPermissions active (expires in ~${mins} min). Use /mode default to re-enable controls.`);
|
|
12427
13964
|
} else {
|
|
12428
|
-
|
|
12429
|
-
const remaining = ttlMs - age;
|
|
12430
|
-
ctx.addMessage("system", `\u26A0 bypassPermissions active (expires in ~${(remaining / 1e3 / 60).toFixed(1)} min). Use /mode default to re-enable controls.`);
|
|
13965
|
+
ctx.addMessage("system", "\u26A0 bypassPermissions active (no TTL). Use /mode default to re-enable controls.");
|
|
12431
13966
|
}
|
|
12432
|
-
} else {
|
|
12433
|
-
modeToApply = mode;
|
|
12434
13967
|
}
|
|
12435
|
-
|
|
12436
|
-
} else if (typeof approvalMode === "string" && validModes.includes(approvalMode)) {
|
|
12437
|
-
modeToApply = approvalMode;
|
|
12438
|
-
if (modeToApply === "bypassPermissions") {
|
|
12439
|
-
ctx.addMessage("system", "\u26A0 bypassPermissions active (no TTL). Use /mode default to re-enable controls.");
|
|
12440
|
-
}
|
|
13968
|
+
break;
|
|
12441
13969
|
}
|
|
12442
13970
|
ctx.setMode(modeToApply);
|
|
12443
13971
|
}
|
|
@@ -12500,8 +14028,8 @@ var App = ({ compact: compact2 = false }) => {
|
|
|
12500
14028
|
const storage = useRef(new StorageManager()).current;
|
|
12501
14029
|
const client = useRef(new MSaplingClient()).current;
|
|
12502
14030
|
const requestApproval = useCallback((request) => {
|
|
12503
|
-
return new Promise((
|
|
12504
|
-
setPendingApproval({ request, resolve:
|
|
14031
|
+
return new Promise((resolve18) => {
|
|
14032
|
+
setPendingApproval({ request, resolve: resolve18 });
|
|
12505
14033
|
});
|
|
12506
14034
|
}, []);
|
|
12507
14035
|
const agent = useRef(new Agent(client, process.cwd(), requestApproval)).current;
|
|
@@ -12703,21 +14231,69 @@ var App = ({ compact: compact2 = false }) => {
|
|
|
12703
14231
|
|
|
12704
14232
|
// src/runtime/bootstrap.ts
|
|
12705
14233
|
init_esm_shims();
|
|
14234
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
14235
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
14236
|
+
import { dirname as dirname3, join as join30 } from "path";
|
|
14237
|
+
function readCliVersion2() {
|
|
14238
|
+
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
14239
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
14240
|
+
try {
|
|
14241
|
+
const pkg = JSON.parse(readFileSync3(join30(here, rel), "utf8"));
|
|
14242
|
+
if (pkg.name && pkg.version) {
|
|
14243
|
+
return { name: pkg.name, version: pkg.version };
|
|
14244
|
+
}
|
|
14245
|
+
} catch {
|
|
14246
|
+
}
|
|
14247
|
+
}
|
|
14248
|
+
return { name: "@mtreeai/msapling-cli", version: "unknown" };
|
|
14249
|
+
}
|
|
14250
|
+
var CLI_PKG = readCliVersion2();
|
|
12706
14251
|
function handleCliArgs(args2) {
|
|
12707
14252
|
if (args2.includes("--version") || args2.includes("-v")) {
|
|
12708
|
-
console.log(
|
|
14253
|
+
console.log(`${CLI_PKG.name} ${CLI_PKG.version}`);
|
|
12709
14254
|
process.exit(0);
|
|
12710
14255
|
}
|
|
12711
14256
|
if (args2.includes("--help") || args2.includes("-h")) {
|
|
12712
14257
|
console.log("msapling \u2014 MSapling CLI (React/Ink)");
|
|
12713
14258
|
console.log("Usage: msapling start interactive REPL");
|
|
12714
14259
|
console.log(" msapling --compact start REPL in compact mode (no footer, thin separators)");
|
|
14260
|
+
console.log(' msapling --exec "<cmd>" run one slash command non-interactively and exit');
|
|
12715
14261
|
console.log(" msapling mcp serve run as MCP stdio server (Claude Code / Cursor / Windsurf integration)");
|
|
14262
|
+
console.log(" msapling doctor run diagnostic health checks");
|
|
14263
|
+
console.log(" msapling doctor --debug run doctor with full environment dump");
|
|
12716
14264
|
console.log(" msapling --version print version and exit");
|
|
12717
14265
|
console.log(" msapling --help print this message");
|
|
12718
14266
|
console.log("Inside the REPL: type /help for slash-command help.");
|
|
12719
14267
|
process.exit(0);
|
|
12720
14268
|
}
|
|
14269
|
+
const execIdx = args2.findIndex((a) => a === "--exec" || a.startsWith("--exec="));
|
|
14270
|
+
if (execIdx >= 0) {
|
|
14271
|
+
const eqArg = args2[execIdx];
|
|
14272
|
+
const cmdStr = eqArg.startsWith("--exec=") ? eqArg.slice("--exec=".length) : args2[execIdx + 1] ?? "";
|
|
14273
|
+
(async () => {
|
|
14274
|
+
const { runExec: runExec2 } = await Promise.resolve().then(() => (init_exec(), exec_exports));
|
|
14275
|
+
const exitCode = await runExec2(cmdStr);
|
|
14276
|
+
process.exit(exitCode);
|
|
14277
|
+
})().catch((e) => {
|
|
14278
|
+
process.stderr.write(`[msapling-exec] fatal: ${e?.message ?? e}
|
|
14279
|
+
`);
|
|
14280
|
+
process.exit(1);
|
|
14281
|
+
});
|
|
14282
|
+
return false;
|
|
14283
|
+
}
|
|
14284
|
+
if (args2[0] === "doctor") {
|
|
14285
|
+
(async () => {
|
|
14286
|
+
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor2(), doctor_exports));
|
|
14287
|
+
const debug = args2.includes("--debug");
|
|
14288
|
+
const result = await runDoctor2(debug);
|
|
14289
|
+
process.exit(result.exitCode);
|
|
14290
|
+
})().catch((e) => {
|
|
14291
|
+
process.stderr.write(`[msapling-doctor] fatal: ${e?.message ?? e}
|
|
14292
|
+
`);
|
|
14293
|
+
process.exit(1);
|
|
14294
|
+
});
|
|
14295
|
+
return false;
|
|
14296
|
+
}
|
|
12721
14297
|
if (args2[0] === "mcp" && args2[1] === "serve") {
|
|
12722
14298
|
(async () => {
|
|
12723
14299
|
const { MSaplingClient: MSaplingClient2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|