@freesyntax/notch-cli 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,11 +8,14 @@ import {
8
8
  autoCompress,
9
9
  estimateTokens
10
10
  } from "./chunk-MWM5TFY4.js";
11
+ import {
12
+ __require
13
+ } from "./chunk-3RG5ZIWI.js";
11
14
 
12
15
  // src/index.ts
13
16
  import { Command } from "commander";
14
- import chalk9 from "chalk";
15
- import ora from "ora";
17
+ import chalk26 from "chalk";
18
+ import ora4 from "ora";
16
19
  import * as readline from "readline";
17
20
  import * as nodePath from "path";
18
21
 
@@ -863,7 +866,7 @@ var GITHUB_API = "https://api.github.com";
863
866
  function getToken() {
864
867
  return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
865
868
  }
866
- async function ghFetch(path19, opts2 = {}) {
869
+ async function ghFetch(path25, opts2 = {}) {
867
870
  const token = getToken();
868
871
  const headers = {
869
872
  "Accept": "application/vnd.github+json",
@@ -871,7 +874,7 @@ async function ghFetch(path19, opts2 = {}) {
871
874
  ...opts2.headers ?? {}
872
875
  };
873
876
  if (token) headers["Authorization"] = `Bearer ${token}`;
874
- return fetch(`${GITHUB_API}${path19}`, { ...opts2, headers });
877
+ return fetch(`${GITHUB_API}${path25}`, { ...opts2, headers });
875
878
  }
876
879
  var parameters9 = z9.object({
877
880
  action: z9.enum([
@@ -1028,24 +1031,516 @@ var githubTool = {
1028
1031
  execute
1029
1032
  };
1030
1033
 
1034
+ // src/tools/lsp.ts
1035
+ import { execSync as execSync3, execFileSync } from "child_process";
1036
+ import fs6 from "fs/promises";
1037
+ import path7 from "path";
1038
+ import { z as z10 } from "zod";
1039
+ var parameters10 = z10.object({
1040
+ action: z10.enum(["definition", "references", "diagnostics", "hover"]).describe("LSP action: definition (go-to-def), references (find usages), diagnostics (type errors), hover (type info)"),
1041
+ file: z10.string().describe("File path (relative or absolute)"),
1042
+ line: z10.number().optional().describe("Line number (1-based) \u2014 required for definition, references, hover"),
1043
+ character: z10.number().optional().describe("Column number (0-based) \u2014 required for definition, references, hover"),
1044
+ symbol: z10.string().optional().describe("Symbol name to search for (alternative to line/character)")
1045
+ });
1046
+ function detectLanguage(filePath) {
1047
+ const ext = path7.extname(filePath).toLowerCase();
1048
+ switch (ext) {
1049
+ case ".ts":
1050
+ case ".tsx":
1051
+ case ".js":
1052
+ case ".jsx":
1053
+ case ".mjs":
1054
+ case ".cjs":
1055
+ return { lang: "typescript", server: "typescript-language-server", tsconfig: true };
1056
+ case ".py":
1057
+ return { lang: "python", server: "pyright", tsconfig: false };
1058
+ case ".go":
1059
+ return { lang: "go", server: "gopls", tsconfig: false };
1060
+ case ".rs":
1061
+ return { lang: "rust", server: "rust-analyzer", tsconfig: false };
1062
+ default:
1063
+ return { lang: "unknown", server: null, tsconfig: false };
1064
+ }
1065
+ }
1066
+ function isAvailable(cmd) {
1067
+ try {
1068
+ const which = process.platform === "win32" ? "where" : "which";
1069
+ execFileSync(which, [cmd], { stdio: "pipe", timeout: 3e3 });
1070
+ return true;
1071
+ } catch {
1072
+ return false;
1073
+ }
1074
+ }
1075
+ async function tsDiagnostics(filePath, cwd) {
1076
+ try {
1077
+ const result = execSync3(
1078
+ `npx tsc --noEmit --pretty false "${filePath}" 2>&1`,
1079
+ { cwd, encoding: "utf-8", timeout: 3e4 }
1080
+ );
1081
+ return result.trim() || "No type errors found.";
1082
+ } catch (err) {
1083
+ const output = err.stdout || err.stderr || "";
1084
+ if (output.includes("error TS")) {
1085
+ const lines = output.split("\n").filter((l) => l.includes("error TS"));
1086
+ return lines.slice(0, 20).join("\n") || "No type errors found.";
1087
+ }
1088
+ try {
1089
+ const result = execSync3(
1090
+ `npx tsc --noEmit --pretty false 2>&1 | head -30`,
1091
+ { cwd, encoding: "utf-8", timeout: 3e4, shell: true }
1092
+ );
1093
+ return result.trim() || "No type errors found.";
1094
+ } catch (err2) {
1095
+ const out = err2.stdout || err2.stderr || "";
1096
+ const errLines = out.split("\n").filter((l) => l.includes("error TS"));
1097
+ return errLines.slice(0, 20).join("\n") || "Could not run type checker.";
1098
+ }
1099
+ }
1100
+ }
1101
+ async function findDefinition(symbol, cwd) {
1102
+ const patterns = [
1103
+ `(function|const|let|var|class|interface|type|enum)\\s+${symbol}\\b`,
1104
+ `export\\s+(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${symbol}\\b`,
1105
+ `def\\s+${symbol}\\s*\\(`,
1106
+ // Python
1107
+ `func\\s+${symbol}\\s*\\(`,
1108
+ // Go
1109
+ `fn\\s+${symbol}\\s*[<(]`
1110
+ // Rust
1111
+ ];
1112
+ const combinedPattern = patterns.join("|");
1113
+ try {
1114
+ const result = execSync3(
1115
+ `rg --no-heading --line-number -e "${combinedPattern}" --type-add "code:*.{ts,tsx,js,jsx,py,go,rs}" --type code -m 10`,
1116
+ { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 }
1117
+ );
1118
+ return result.trim() || `No definition found for "${symbol}"`;
1119
+ } catch {
1120
+ try {
1121
+ const result = execSync3(
1122
+ `grep -rn -E "${combinedPattern}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.py" --include="*.go" --include="*.rs" . | head -10`,
1123
+ { cwd, encoding: "utf-8", timeout: 1e4, shell: true, maxBuffer: 1024 * 1024 }
1124
+ );
1125
+ return result.trim() || `No definition found for "${symbol}"`;
1126
+ } catch {
1127
+ return `No definition found for "${symbol}"`;
1128
+ }
1129
+ }
1130
+ }
1131
+ async function findReferences(symbol, cwd) {
1132
+ try {
1133
+ const result = execSync3(
1134
+ `rg --no-heading --line-number -w "${symbol}" --type-add "code:*.{ts,tsx,js,jsx,py,go,rs}" --type code -m 30`,
1135
+ { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 }
1136
+ );
1137
+ const lines = result.trim().split("\n");
1138
+ return `Found ${lines.length} reference(s) for "${symbol}":
1139
+
1140
+ ${lines.join("\n")}`;
1141
+ } catch {
1142
+ try {
1143
+ const result = execSync3(
1144
+ `grep -rn -w "${symbol}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.py" --include="*.go" --include="*.rs" . | head -30`,
1145
+ { cwd, encoding: "utf-8", timeout: 1e4, shell: true, maxBuffer: 1024 * 1024 }
1146
+ );
1147
+ const lines = result.trim().split("\n");
1148
+ return `Found ${lines.length} reference(s) for "${symbol}":
1149
+
1150
+ ${lines.join("\n")}`;
1151
+ } catch {
1152
+ return `No references found for "${symbol}"`;
1153
+ }
1154
+ }
1155
+ }
1156
+ async function getHoverInfo(symbol, filePath, line, cwd) {
1157
+ try {
1158
+ const absPath = path7.isAbsolute(filePath) ? filePath : path7.resolve(cwd, filePath);
1159
+ const content = await fs6.readFile(absPath, "utf-8");
1160
+ const lines = content.split("\n");
1161
+ const startLine = Math.max(0, line - 3);
1162
+ const endLine = Math.min(lines.length, line + 2);
1163
+ const context = lines.slice(startLine, endLine).map((l, i) => `${startLine + i + 1}: ${l}`).join("\n");
1164
+ const def = await findDefinition(symbol, cwd);
1165
+ return `## Context at ${filePath}:${line}
1166
+ \`\`\`
1167
+ ${context}
1168
+ \`\`\`
1169
+
1170
+ ## Definition
1171
+ ${def}`;
1172
+ } catch (err) {
1173
+ return `Could not get hover info: ${err.message}`;
1174
+ }
1175
+ }
1176
+ async function getDiagnostics(filePath, cwd) {
1177
+ const absPath = path7.isAbsolute(filePath) ? filePath : path7.resolve(cwd, filePath);
1178
+ const { lang } = detectLanguage(absPath);
1179
+ switch (lang) {
1180
+ case "typescript":
1181
+ return tsDiagnostics(absPath, cwd);
1182
+ case "python": {
1183
+ if (isAvailable("pyright")) {
1184
+ try {
1185
+ const result = execFileSync("pyright", [absPath], {
1186
+ cwd,
1187
+ encoding: "utf-8",
1188
+ timeout: 3e4
1189
+ });
1190
+ return result.trim() || "No errors found.";
1191
+ } catch (err) {
1192
+ return err.stdout || err.stderr || "Pyright check failed.";
1193
+ }
1194
+ }
1195
+ try {
1196
+ execFileSync("python", ["-m", "py_compile", absPath], {
1197
+ cwd,
1198
+ encoding: "utf-8",
1199
+ timeout: 1e4
1200
+ });
1201
+ return "No syntax errors found.";
1202
+ } catch (err) {
1203
+ return err.stderr || err.stdout || "Syntax check failed.";
1204
+ }
1205
+ }
1206
+ case "go": {
1207
+ try {
1208
+ const result = execFileSync("go", ["vet", absPath], {
1209
+ cwd,
1210
+ encoding: "utf-8",
1211
+ timeout: 15e3
1212
+ });
1213
+ return result.trim() || "No issues found.";
1214
+ } catch (err) {
1215
+ return err.stderr || err.stdout || "Go vet failed.";
1216
+ }
1217
+ }
1218
+ case "rust": {
1219
+ try {
1220
+ const result = execSync3(`cargo check --message-format short 2>&1 | head -30`, {
1221
+ cwd,
1222
+ encoding: "utf-8",
1223
+ timeout: 6e4,
1224
+ shell: true
1225
+ });
1226
+ return result.trim() || "No issues found.";
1227
+ } catch (err) {
1228
+ return err.stderr || err.stdout || "Cargo check failed.";
1229
+ }
1230
+ }
1231
+ default:
1232
+ return `No language server available for ${lang} files. Use grep to search for symbols.`;
1233
+ }
1234
+ }
1235
+ var lspTool = {
1236
+ name: "lsp",
1237
+ description: "Code intelligence: find definitions, references, type errors, and hover info. Uses TypeScript compiler, pyright, gopls, or grep-based heuristics. Actions: definition (go-to-def), references (find usages), diagnostics (type errors), hover (type info). Provide either line/character position or a symbol name.",
1238
+ parameters: parameters10,
1239
+ async execute(params, ctx) {
1240
+ const filePath = params.file;
1241
+ const absPath = path7.isAbsolute(filePath) ? filePath : path7.resolve(ctx.cwd, filePath);
1242
+ try {
1243
+ switch (params.action) {
1244
+ case "definition": {
1245
+ const symbol = params.symbol || await extractSymbol(absPath, params.line, params.character);
1246
+ if (!symbol) {
1247
+ return { content: "Provide a symbol name or line/character position.", isError: true };
1248
+ }
1249
+ const result = await findDefinition(symbol, ctx.cwd);
1250
+ return { content: result };
1251
+ }
1252
+ case "references": {
1253
+ const symbol = params.symbol || await extractSymbol(absPath, params.line, params.character);
1254
+ if (!symbol) {
1255
+ return { content: "Provide a symbol name or line/character position.", isError: true };
1256
+ }
1257
+ const result = await findReferences(symbol, ctx.cwd);
1258
+ return { content: result };
1259
+ }
1260
+ case "diagnostics": {
1261
+ const result = await getDiagnostics(filePath, ctx.cwd);
1262
+ return { content: result };
1263
+ }
1264
+ case "hover": {
1265
+ const symbol = params.symbol || await extractSymbol(absPath, params.line, params.character);
1266
+ if (!symbol) {
1267
+ return { content: "Provide a symbol name or line/character position.", isError: true };
1268
+ }
1269
+ const result = await getHoverInfo(symbol, filePath, params.line ?? 1, ctx.cwd);
1270
+ return { content: result };
1271
+ }
1272
+ default:
1273
+ return { content: `Unknown action: ${params.action}`, isError: true };
1274
+ }
1275
+ } catch (err) {
1276
+ return { content: `LSP error: ${err.message}`, isError: true };
1277
+ }
1278
+ }
1279
+ };
1280
+ async function extractSymbol(filePath, line, character) {
1281
+ if (!line) return null;
1282
+ try {
1283
+ const content = await fs6.readFile(filePath, "utf-8");
1284
+ const lines = content.split("\n");
1285
+ const lineText = lines[line - 1];
1286
+ if (!lineText) return null;
1287
+ const col = character ?? 0;
1288
+ const wordRegex = /[a-zA-Z_$][a-zA-Z0-9_$]*/g;
1289
+ let match;
1290
+ while ((match = wordRegex.exec(lineText)) !== null) {
1291
+ const start = match.index;
1292
+ const end = start + match[0].length;
1293
+ if (col >= start && col <= end) {
1294
+ return match[0];
1295
+ }
1296
+ }
1297
+ const firstMatch = lineText.match(/[a-zA-Z_$][a-zA-Z0-9_$]*/);
1298
+ return firstMatch?.[0] ?? null;
1299
+ } catch {
1300
+ return null;
1301
+ }
1302
+ }
1303
+
1304
+ // src/tools/notebook.ts
1305
+ import fs7 from "fs/promises";
1306
+ import path8 from "path";
1307
+ import { z as z11 } from "zod";
1308
+ var parameters11 = z11.object({
1309
+ action: z11.enum(["read", "edit", "add", "remove"]).describe("Action: read (view cells), edit (modify cell source), add (insert new cell), remove (delete cell)"),
1310
+ path: z11.string().describe("Path to the .ipynb notebook file"),
1311
+ cell_index: z11.number().optional().describe("Cell index (0-based) \u2014 for edit/remove actions"),
1312
+ cell_type: z11.enum(["code", "markdown", "raw"]).optional().describe("Cell type \u2014 for add action"),
1313
+ content: z11.string().optional().describe("New cell content \u2014 for edit/add actions"),
1314
+ insert_after: z11.number().optional().describe("Insert after this cell index \u2014 for add action (-1 for beginning)")
1315
+ });
1316
+ function formatNotebook(nb, filePath) {
1317
+ const parts = [
1318
+ `# ${path8.basename(filePath)}`,
1319
+ `# ${nb.cells.length} cells | nbformat ${nb.nbformat}.${nb.nbformat_minor}`,
1320
+ ""
1321
+ ];
1322
+ for (let i = 0; i < nb.cells.length; i++) {
1323
+ const cell = nb.cells[i];
1324
+ const source = cell.source.join("");
1325
+ const header = cell.cell_type === "code" ? `[${i}] Code (exec: ${cell.execution_count ?? "-"})` : `[${i}] ${cell.cell_type.charAt(0).toUpperCase() + cell.cell_type.slice(1)}`;
1326
+ parts.push(`--- ${header} ---`);
1327
+ parts.push(source);
1328
+ if (cell.cell_type === "code" && cell.outputs && cell.outputs.length > 0) {
1329
+ parts.push(" Output:");
1330
+ for (const output of cell.outputs) {
1331
+ if (output.text) {
1332
+ parts.push(" " + output.text.join("").slice(0, 500));
1333
+ } else if (output.data?.["text/plain"]) {
1334
+ parts.push(" " + output.data["text/plain"].join("").slice(0, 500));
1335
+ } else if (output.ename) {
1336
+ parts.push(` Error: ${output.ename}: ${output.evalue}`);
1337
+ }
1338
+ }
1339
+ }
1340
+ parts.push("");
1341
+ }
1342
+ return parts.join("\n");
1343
+ }
1344
+ var notebookTool = {
1345
+ name: "notebook",
1346
+ description: "Read and edit Jupyter .ipynb notebook files. Actions: read (view all cells with outputs), edit (modify a cell), add (insert a new cell), remove (delete a cell).",
1347
+ parameters: parameters11,
1348
+ async execute(params, ctx) {
1349
+ const filePath = path8.isAbsolute(params.path) ? params.path : path8.resolve(ctx.cwd, params.path);
1350
+ try {
1351
+ switch (params.action) {
1352
+ case "read": {
1353
+ const raw = await fs7.readFile(filePath, "utf-8");
1354
+ const nb = JSON.parse(raw);
1355
+ return { content: formatNotebook(nb, filePath) };
1356
+ }
1357
+ case "edit": {
1358
+ if (params.cell_index === void 0) {
1359
+ return { content: "cell_index is required for edit action.", isError: true };
1360
+ }
1361
+ if (params.content === void 0) {
1362
+ return { content: "content is required for edit action.", isError: true };
1363
+ }
1364
+ const raw = await fs7.readFile(filePath, "utf-8");
1365
+ const nb = JSON.parse(raw);
1366
+ if (params.cell_index < 0 || params.cell_index >= nb.cells.length) {
1367
+ return { content: `Cell index ${params.cell_index} out of range (0-${nb.cells.length - 1}).`, isError: true };
1368
+ }
1369
+ const lines = params.content.split("\n").map(
1370
+ (l, i, arr) => i < arr.length - 1 ? l + "\n" : l
1371
+ );
1372
+ nb.cells[params.cell_index].source = lines;
1373
+ await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
1374
+ return { content: `Cell [${params.cell_index}] updated in ${path8.basename(filePath)}.` };
1375
+ }
1376
+ case "add": {
1377
+ if (params.content === void 0) {
1378
+ return { content: "content is required for add action.", isError: true };
1379
+ }
1380
+ const raw = await fs7.readFile(filePath, "utf-8");
1381
+ const nb = JSON.parse(raw);
1382
+ const cellType = params.cell_type ?? "code";
1383
+ const lines = params.content.split("\n").map(
1384
+ (l, i, arr) => i < arr.length - 1 ? l + "\n" : l
1385
+ );
1386
+ const newCell = {
1387
+ cell_type: cellType,
1388
+ source: lines,
1389
+ metadata: {},
1390
+ ...cellType === "code" ? { outputs: [], execution_count: null } : {}
1391
+ };
1392
+ const insertIdx = params.insert_after !== void 0 ? params.insert_after + 1 : nb.cells.length;
1393
+ nb.cells.splice(insertIdx, 0, newCell);
1394
+ await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
1395
+ return { content: `Added ${cellType} cell at index [${insertIdx}] in ${path8.basename(filePath)}.` };
1396
+ }
1397
+ case "remove": {
1398
+ if (params.cell_index === void 0) {
1399
+ return { content: "cell_index is required for remove action.", isError: true };
1400
+ }
1401
+ const raw = await fs7.readFile(filePath, "utf-8");
1402
+ const nb = JSON.parse(raw);
1403
+ if (params.cell_index < 0 || params.cell_index >= nb.cells.length) {
1404
+ return { content: `Cell index ${params.cell_index} out of range (0-${nb.cells.length - 1}).`, isError: true };
1405
+ }
1406
+ const removed = nb.cells.splice(params.cell_index, 1)[0];
1407
+ await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
1408
+ return { content: `Removed ${removed.cell_type} cell [${params.cell_index}] from ${path8.basename(filePath)}. ${nb.cells.length} cells remaining.` };
1409
+ }
1410
+ default:
1411
+ return { content: `Unknown action: ${params.action}`, isError: true };
1412
+ }
1413
+ } catch (err) {
1414
+ return { content: `Notebook error: ${err.message}`, isError: true };
1415
+ }
1416
+ }
1417
+ };
1418
+
1419
+ // src/tools/task.ts
1420
+ import { z as z12 } from "zod";
1421
+ import chalk from "chalk";
1422
+ var tasks = [];
1423
+ var nextId = 1;
1424
+ var parameters12 = z12.object({
1425
+ action: z12.enum(["create", "update", "list", "get"]).describe("Action: create (new task), update (change status/subject), list (show all), get (show one)"),
1426
+ subject: z12.string().optional().describe("Task title \u2014 for create action"),
1427
+ description: z12.string().optional().describe("Task details \u2014 for create action"),
1428
+ task_id: z12.number().int().positive().optional().describe("Task ID \u2014 for update/get actions"),
1429
+ status: z12.enum(["pending", "in_progress", "completed", "failed"]).optional().describe("New status \u2014 for update action")
1430
+ });
1431
+ function formatTask(t) {
1432
+ const icons = {
1433
+ pending: "\u25CB",
1434
+ in_progress: "\u25D0",
1435
+ completed: "\u25CF",
1436
+ failed: "\u2717"
1437
+ };
1438
+ const colors = {
1439
+ pending: chalk.gray,
1440
+ in_progress: chalk.yellow,
1441
+ completed: chalk.green,
1442
+ failed: chalk.red
1443
+ };
1444
+ return `${colors[t.status](icons[t.status])} #${t.id} ${colors[t.status](t.subject)} [${t.status}]`;
1445
+ }
1446
+ function formatTaskList() {
1447
+ if (tasks.length === 0) return "No tasks.";
1448
+ return tasks.map(formatTask).join("\n");
1449
+ }
1450
+ var taskTool = {
1451
+ name: "task",
1452
+ description: "Manage tasks within the current session. Use this to break complex work into tracked steps. Actions: create (new task), update (change status), list (show all tasks), get (show one task). Mark tasks as in_progress when starting and completed when done.",
1453
+ parameters: parameters12,
1454
+ async execute(params, _ctx) {
1455
+ switch (params.action) {
1456
+ case "create": {
1457
+ if (!params.subject) {
1458
+ return { content: "subject is required for create action.", isError: true };
1459
+ }
1460
+ const task = {
1461
+ id: nextId++,
1462
+ subject: params.subject,
1463
+ description: params.description,
1464
+ status: "pending",
1465
+ createdAt: Date.now(),
1466
+ updatedAt: Date.now()
1467
+ };
1468
+ tasks.push(task);
1469
+ console.log(chalk.gray(` ${formatTask(task)}`));
1470
+ return { content: `Task #${task.id} created: ${task.subject}` };
1471
+ }
1472
+ case "update": {
1473
+ if (params.task_id === void 0) {
1474
+ return { content: "task_id is required for update action.", isError: true };
1475
+ }
1476
+ const task = tasks.find((t) => t.id === params.task_id);
1477
+ if (!task) {
1478
+ return { content: `Task #${params.task_id} not found.`, isError: true };
1479
+ }
1480
+ if (params.status) task.status = params.status;
1481
+ if (params.subject) task.subject = params.subject;
1482
+ task.updatedAt = Date.now();
1483
+ console.log(chalk.gray(` ${formatTask(task)}`));
1484
+ return { content: `Task #${task.id} updated: ${task.subject} [${task.status}]` };
1485
+ }
1486
+ case "list": {
1487
+ const output = formatTaskList();
1488
+ return { content: output };
1489
+ }
1490
+ case "get": {
1491
+ if (params.task_id === void 0) {
1492
+ return { content: "task_id is required for get action.", isError: true };
1493
+ }
1494
+ const task = tasks.find((t) => t.id === params.task_id);
1495
+ if (!task) {
1496
+ return { content: `Task #${params.task_id} not found.`, isError: true };
1497
+ }
1498
+ const details = [
1499
+ formatTask(task),
1500
+ task.description ? ` ${task.description}` : "",
1501
+ ` Created: ${new Date(task.createdAt).toLocaleTimeString()}`,
1502
+ ` Updated: ${new Date(task.updatedAt).toLocaleTimeString()}`
1503
+ ].filter(Boolean).join("\n");
1504
+ return { content: details };
1505
+ }
1506
+ default:
1507
+ return { content: `Unknown action: ${params.action}`, isError: true };
1508
+ }
1509
+ }
1510
+ };
1511
+ function getAllTasks() {
1512
+ return [...tasks];
1513
+ }
1514
+ function formatTasksDisplay() {
1515
+ return formatTaskList();
1516
+ }
1517
+
1031
1518
  // src/mcp/client.ts
1519
+ import { z as z13 } from "zod";
1520
+
1521
+ // src/mcp/transport.ts
1522
+ function detectTransport(config) {
1523
+ if (config.transport) return config.transport;
1524
+ if (config.url) return "http";
1525
+ return "stdio";
1526
+ }
1527
+
1528
+ // src/mcp/stdio-transport.ts
1032
1529
  import { spawn } from "child_process";
1033
- import { z as z10 } from "zod";
1034
- var MCPClient = class {
1035
- constructor(config, serverName) {
1530
+ var StdioTransport = class {
1531
+ constructor(config, name) {
1036
1532
  this.config = config;
1037
- this.serverName = serverName;
1533
+ this.name = name;
1038
1534
  }
1039
1535
  process = null;
1040
1536
  requestId = 0;
1041
1537
  pendingRequests = /* @__PURE__ */ new Map();
1042
1538
  buffer = "";
1043
- serverName;
1044
- _tools = [];
1045
- /**
1046
- * Start the MCP server and initialize the connection.
1047
- */
1539
+ name;
1048
1540
  async connect() {
1541
+ if (!this.config.command) {
1542
+ throw new Error(`Stdio transport requires 'command' in config for server ${this.name}`);
1543
+ }
1049
1544
  this.process = spawn(this.config.command, this.config.args ?? [], {
1050
1545
  stdio: ["pipe", "pipe", "pipe"],
1051
1546
  env: { ...process.env, ...this.config.env },
@@ -1058,54 +1553,17 @@ var MCPClient = class {
1058
1553
  });
1059
1554
  this.process.on("error", (err) => {
1060
1555
  for (const [id, pending] of this.pendingRequests) {
1061
- pending.reject(new Error(`MCP server ${this.serverName} error: ${err.message}`));
1556
+ pending.reject(new Error(`MCP server ${this.name} error: ${err.message}`));
1062
1557
  this.pendingRequests.delete(id);
1063
1558
  }
1064
1559
  });
1065
1560
  this.process.on("exit", (code) => {
1066
1561
  for (const [id, pending] of this.pendingRequests) {
1067
- pending.reject(new Error(`MCP server ${this.serverName} exited with code ${code}`));
1562
+ pending.reject(new Error(`MCP server ${this.name} exited with code ${code}`));
1068
1563
  this.pendingRequests.delete(id);
1069
1564
  }
1070
1565
  });
1071
- await this.sendRequest("initialize", {
1072
- protocolVersion: "2024-11-05",
1073
- capabilities: {},
1074
- clientInfo: { name: "notch-cli", version: "0.3.0" }
1075
- });
1076
- this.sendNotification("notifications/initialized", {});
1077
- const result = await this.sendRequest("tools/list", {});
1078
- this._tools = result.tools ?? [];
1079
- }
1080
- /**
1081
- * Get discovered tools from this server.
1082
- */
1083
- get tools() {
1084
- return this._tools;
1085
- }
1086
- /**
1087
- * Check if the MCP server process is still alive.
1088
- */
1089
- get isAlive() {
1090
- return this.process !== null && this.process.exitCode === null && !this.process.killed;
1091
- }
1092
- /**
1093
- * Call a tool on the MCP server. Auto-reconnects if the server has crashed.
1094
- */
1095
- async callTool(name, args) {
1096
- if (!this.isAlive) {
1097
- try {
1098
- await this.connect();
1099
- } catch (err) {
1100
- throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
1101
- }
1102
- }
1103
- const result = await this.sendRequest("tools/call", { name, arguments: args });
1104
- return result;
1105
1566
  }
1106
- /**
1107
- * Disconnect from the MCP server.
1108
- */
1109
1567
  disconnect() {
1110
1568
  if (this.process) {
1111
1569
  this.process.stdin?.end();
@@ -1114,15 +1572,13 @@ var MCPClient = class {
1114
1572
  }
1115
1573
  this.pendingRequests.clear();
1116
1574
  }
1575
+ get isAlive() {
1576
+ return this.process !== null && this.process.exitCode === null && !this.process.killed;
1577
+ }
1117
1578
  sendRequest(method, params) {
1118
1579
  return new Promise((resolve2, reject) => {
1119
1580
  const id = ++this.requestId;
1120
- const msg = {
1121
- jsonrpc: "2.0",
1122
- id,
1123
- method,
1124
- params
1125
- };
1581
+ const msg = { jsonrpc: "2.0", id, method, params };
1126
1582
  this.pendingRequests.set(id, { resolve: resolve2, reject });
1127
1583
  const data = JSON.stringify(msg);
1128
1584
  const header = `Content-Length: ${Buffer.byteLength(data)}\r
@@ -1138,11 +1594,7 @@ var MCPClient = class {
1138
1594
  });
1139
1595
  }
1140
1596
  sendNotification(method, params) {
1141
- const msg = {
1142
- jsonrpc: "2.0",
1143
- method,
1144
- params
1145
- };
1597
+ const msg = { jsonrpc: "2.0", method, params };
1146
1598
  const data = JSON.stringify(msg);
1147
1599
  const header = `Content-Length: ${Buffer.byteLength(data)}\r
1148
1600
  \r
@@ -1187,40 +1639,499 @@ var MCPClient = class {
1187
1639
  }
1188
1640
  }
1189
1641
  };
1190
- function parseMCPConfig(config) {
1191
- const servers = config?.mcpServers;
1192
- if (!servers || typeof servers !== "object") return {};
1193
- const result = {};
1194
- for (const [name, cfg] of Object.entries(servers)) {
1195
- const c = cfg;
1196
- if (c?.command) {
1197
- result[name] = {
1198
- command: c.command,
1199
- args: c.args,
1200
- env: c.env,
1201
- cwd: c.cwd
1202
- };
1642
+
1643
+ // src/mcp/http-transport.ts
1644
+ var HttpTransport = class {
1645
+ requestId = 0;
1646
+ connected = false;
1647
+ name;
1648
+ baseUrl;
1649
+ headers;
1650
+ constructor(config, name) {
1651
+ this.name = name;
1652
+ if (!config.url) {
1653
+ throw new Error(`HTTP transport requires 'url' in config for server ${name}`);
1654
+ }
1655
+ this.baseUrl = config.url.replace(/\/$/, "");
1656
+ this.headers = {
1657
+ "Content-Type": "application/json",
1658
+ ...config.headers
1659
+ };
1660
+ }
1661
+ async connect() {
1662
+ try {
1663
+ const response = await fetch(`${this.baseUrl}`, {
1664
+ method: "POST",
1665
+ headers: this.headers,
1666
+ body: JSON.stringify({
1667
+ jsonrpc: "2.0",
1668
+ id: ++this.requestId,
1669
+ method: "initialize",
1670
+ params: {
1671
+ protocolVersion: "2024-11-05",
1672
+ capabilities: {},
1673
+ clientInfo: { name: "notch-cli", version: "0.4.8" }
1674
+ }
1675
+ }),
1676
+ signal: AbortSignal.timeout(15e3)
1677
+ });
1678
+ if (!response.ok) {
1679
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1680
+ }
1681
+ this.sendNotification("notifications/initialized", {});
1682
+ this.connected = true;
1683
+ } catch (err) {
1684
+ throw new Error(`MCP HTTP server ${this.name} unreachable: ${err.message}`);
1203
1685
  }
1204
1686
  }
1205
- return result;
1206
- }
1687
+ disconnect() {
1688
+ this.connected = false;
1689
+ }
1690
+ get isAlive() {
1691
+ return this.connected;
1692
+ }
1693
+ async sendRequest(method, params) {
1694
+ const id = ++this.requestId;
1695
+ const body = { jsonrpc: "2.0", id, method, params };
1696
+ const response = await fetch(this.baseUrl, {
1697
+ method: "POST",
1698
+ headers: this.headers,
1699
+ body: JSON.stringify(body),
1700
+ signal: AbortSignal.timeout(3e4)
1701
+ });
1702
+ if (!response.ok) {
1703
+ throw new Error(`MCP HTTP error (${this.name}): ${response.status} ${response.statusText}`);
1704
+ }
1705
+ const json = await response.json();
1706
+ if (json.error) {
1707
+ throw new Error(`MCP error (${this.name}): ${json.error.message}`);
1708
+ }
1709
+ return json.result;
1710
+ }
1711
+ sendNotification(method, params) {
1712
+ void fetch(this.baseUrl, {
1713
+ method: "POST",
1714
+ headers: this.headers,
1715
+ body: JSON.stringify({ jsonrpc: "2.0", method, params }),
1716
+ signal: AbortSignal.timeout(1e4)
1717
+ }).catch(() => {
1718
+ });
1719
+ }
1720
+ };
1207
1721
 
1208
- // src/tools/index.ts
1209
- var BUILTIN_TOOLS = [
1210
- readTool,
1211
- writeTool,
1212
- editTool,
1213
- shellTool,
1214
- gitTool,
1215
- githubTool,
1216
- grepTool,
1217
- globTool,
1218
- webFetchTool
1219
- ];
1220
- var mcpTools = [];
1221
- function getAllTools() {
1222
- return [...BUILTIN_TOOLS, ...mcpTools];
1223
- }
1722
+ // src/mcp/sse-transport.ts
1723
+ var SSETransport = class {
1724
+ requestId = 0;
1725
+ pendingRequests = /* @__PURE__ */ new Map();
1726
+ abortController = null;
1727
+ connected = false;
1728
+ name;
1729
+ baseUrl;
1730
+ messageEndpoint;
1731
+ sseEndpoint;
1732
+ headers;
1733
+ constructor(config, name) {
1734
+ this.name = name;
1735
+ if (!config.url) {
1736
+ throw new Error(`SSE transport requires 'url' in config for server ${name}`);
1737
+ }
1738
+ this.baseUrl = config.url.replace(/\/$/, "");
1739
+ this.sseEndpoint = `${this.baseUrl}/sse`;
1740
+ this.messageEndpoint = `${this.baseUrl}/message`;
1741
+ this.headers = {
1742
+ "Content-Type": "application/json",
1743
+ ...config.headers
1744
+ };
1745
+ }
1746
+ async connect() {
1747
+ this.abortController = new AbortController();
1748
+ const ssePromise = this.startSSEListener();
1749
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
1750
+ const initResult = await this.sendRequest("initialize", {
1751
+ protocolVersion: "2024-11-05",
1752
+ capabilities: {},
1753
+ clientInfo: { name: "notch-cli", version: "0.4.8" }
1754
+ });
1755
+ if (!initResult) {
1756
+ throw new Error(`MCP SSE server ${this.name} failed to initialize`);
1757
+ }
1758
+ this.sendNotification("notifications/initialized", {});
1759
+ this.connected = true;
1760
+ ssePromise.catch(() => {
1761
+ this.connected = false;
1762
+ });
1763
+ }
1764
+ disconnect() {
1765
+ this.abortController?.abort();
1766
+ this.abortController = null;
1767
+ this.connected = false;
1768
+ for (const [id, pending] of this.pendingRequests) {
1769
+ pending.reject(new Error(`MCP SSE transport disconnected`));
1770
+ this.pendingRequests.delete(id);
1771
+ }
1772
+ }
1773
+ get isAlive() {
1774
+ return this.connected;
1775
+ }
1776
+ async sendRequest(method, params) {
1777
+ const id = ++this.requestId;
1778
+ const body = { jsonrpc: "2.0", id, method, params };
1779
+ const resultPromise = new Promise((resolve2, reject) => {
1780
+ this.pendingRequests.set(id, { resolve: resolve2, reject });
1781
+ setTimeout(() => {
1782
+ if (this.pendingRequests.has(id)) {
1783
+ this.pendingRequests.delete(id);
1784
+ reject(new Error(`MCP SSE request ${method} timed out`));
1785
+ }
1786
+ }, 3e4);
1787
+ });
1788
+ const response = await fetch(this.messageEndpoint, {
1789
+ method: "POST",
1790
+ headers: this.headers,
1791
+ body: JSON.stringify(body),
1792
+ signal: AbortSignal.timeout(1e4)
1793
+ });
1794
+ if (!response.ok) {
1795
+ this.pendingRequests.delete(id);
1796
+ throw new Error(`MCP SSE POST error (${this.name}): ${response.status} ${response.statusText}`);
1797
+ }
1798
+ return resultPromise;
1799
+ }
1800
+ sendNotification(method, params) {
1801
+ const body = { jsonrpc: "2.0", method, params };
1802
+ void fetch(this.messageEndpoint, {
1803
+ method: "POST",
1804
+ headers: this.headers,
1805
+ body: JSON.stringify(body),
1806
+ signal: AbortSignal.timeout(1e4)
1807
+ }).catch(() => {
1808
+ });
1809
+ }
1810
+ /**
1811
+ * Start listening for Server-Sent Events.
1812
+ * Parses the SSE stream and resolves pending requests.
1813
+ */
1814
+ async startSSEListener() {
1815
+ const response = await fetch(this.sseEndpoint, {
1816
+ headers: {
1817
+ Accept: "text/event-stream",
1818
+ ...this.headers
1819
+ },
1820
+ signal: this.abortController?.signal
1821
+ });
1822
+ if (!response.ok || !response.body) {
1823
+ throw new Error(`SSE connection failed: ${response.status}`);
1824
+ }
1825
+ const reader = response.body.getReader();
1826
+ const decoder = new TextDecoder();
1827
+ let buffer = "";
1828
+ try {
1829
+ while (true) {
1830
+ const { done, value } = await reader.read();
1831
+ if (done) break;
1832
+ buffer += decoder.decode(value, { stream: true });
1833
+ const events = buffer.split("\n\n");
1834
+ buffer = events.pop() ?? "";
1835
+ for (const event of events) {
1836
+ const lines = event.split("\n");
1837
+ let data = "";
1838
+ for (const line of lines) {
1839
+ if (line.startsWith("data: ")) {
1840
+ data += line.slice(6);
1841
+ }
1842
+ }
1843
+ if (data) {
1844
+ this.handleSSEMessage(data);
1845
+ }
1846
+ }
1847
+ }
1848
+ } catch (err) {
1849
+ if (err.name !== "AbortError") {
1850
+ this.connected = false;
1851
+ }
1852
+ }
1853
+ }
1854
+ handleSSEMessage(raw) {
1855
+ try {
1856
+ const msg = JSON.parse(raw);
1857
+ if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
1858
+ const pending = this.pendingRequests.get(msg.id);
1859
+ this.pendingRequests.delete(msg.id);
1860
+ if (msg.error) {
1861
+ pending.reject(new Error(`MCP SSE error: ${msg.error.message}`));
1862
+ } else {
1863
+ pending.resolve(msg.result);
1864
+ }
1865
+ }
1866
+ } catch {
1867
+ }
1868
+ }
1869
+ };
1870
+
1871
+ // src/mcp/client.ts
1872
+ function createTransport(config, name) {
1873
+ const type = detectTransport(config);
1874
+ switch (type) {
1875
+ case "http":
1876
+ return new HttpTransport(config, name);
1877
+ case "sse":
1878
+ return new SSETransport(config, name);
1879
+ case "stdio":
1880
+ default:
1881
+ return new StdioTransport(config, name);
1882
+ }
1883
+ }
1884
+ var MCPClient = class {
1885
+ transport;
1886
+ serverName;
1887
+ _tools = [];
1888
+ constructor(config, serverName) {
1889
+ this.serverName = serverName;
1890
+ this.transport = createTransport(config, serverName);
1891
+ }
1892
+ /**
1893
+ * Start the MCP server and initialize the connection.
1894
+ */
1895
+ async connect() {
1896
+ await this.transport.connect();
1897
+ await this.transport.sendRequest("initialize", {
1898
+ protocolVersion: "2024-11-05",
1899
+ capabilities: {},
1900
+ clientInfo: { name: "notch-cli", version: "0.4.8" }
1901
+ });
1902
+ this.transport.sendNotification("notifications/initialized", {});
1903
+ const result = await this.transport.sendRequest("tools/list", {});
1904
+ this._tools = result.tools ?? [];
1905
+ }
1906
+ /**
1907
+ * Get discovered tools from this server.
1908
+ */
1909
+ get tools() {
1910
+ return this._tools;
1911
+ }
1912
+ /**
1913
+ * Check if the MCP server is still alive.
1914
+ */
1915
+ get isAlive() {
1916
+ return this.transport.isAlive;
1917
+ }
1918
+ /**
1919
+ * Call a tool on the MCP server. Auto-reconnects if the server has crashed.
1920
+ */
1921
+ async callTool(name, args) {
1922
+ if (!this.isAlive) {
1923
+ try {
1924
+ await this.transport.connect();
1925
+ } catch (err) {
1926
+ throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
1927
+ }
1928
+ }
1929
+ return this.transport.sendRequest("tools/call", { name, arguments: args });
1930
+ }
1931
+ /**
1932
+ * Disconnect from the MCP server.
1933
+ */
1934
+ disconnect() {
1935
+ this.transport.disconnect();
1936
+ }
1937
+ };
1938
+ function parseMCPConfig(config) {
1939
+ const servers = config?.mcpServers;
1940
+ if (!servers || typeof servers !== "object") return {};
1941
+ const result = {};
1942
+ for (const [name, cfg] of Object.entries(servers)) {
1943
+ const c = cfg;
1944
+ if (c?.command || c?.url) {
1945
+ result[name] = {
1946
+ command: c.command,
1947
+ args: c.args,
1948
+ env: c.env,
1949
+ cwd: c.cwd,
1950
+ transport: c.transport,
1951
+ url: c.url,
1952
+ headers: c.headers
1953
+ };
1954
+ }
1955
+ }
1956
+ return result;
1957
+ }
1958
+
1959
+ // src/plugins/discovery.ts
1960
+ import fs8 from "fs/promises";
1961
+ import path9 from "path";
1962
+ import os from "os";
1963
+ async function discoverPlugins(projectRoot) {
1964
+ const globalDir = path9.join(os.homedir(), ".notch", "plugins");
1965
+ const projectDir = path9.join(projectRoot, ".notch", "plugins");
1966
+ const plugins = /* @__PURE__ */ new Map();
1967
+ for (const plugin of await scanDirectory(globalDir)) {
1968
+ plugins.set(plugin.name, plugin);
1969
+ }
1970
+ for (const plugin of await scanDirectory(projectDir)) {
1971
+ plugins.set(plugin.name, plugin);
1972
+ }
1973
+ return [...plugins.values()];
1974
+ }
1975
+ async function scanDirectory(dir) {
1976
+ const results = [];
1977
+ try {
1978
+ const entries = await fs8.readdir(dir, { withFileTypes: true });
1979
+ for (const entry of entries) {
1980
+ if (!entry.isDirectory()) continue;
1981
+ const pluginDir = path9.join(dir, entry.name);
1982
+ const pkgPath = path9.join(pluginDir, "package.json");
1983
+ try {
1984
+ const raw = await fs8.readFile(pkgPath, "utf-8");
1985
+ const pkg = JSON.parse(raw);
1986
+ if (pkg["notch-plugin"] && pkg.name) {
1987
+ results.push({
1988
+ name: pkg.name,
1989
+ version: pkg.version ?? "0.0.0",
1990
+ description: pkg.description ?? "",
1991
+ path: pluginDir,
1992
+ manifest: pkg["notch-plugin"]
1993
+ });
1994
+ }
1995
+ } catch {
1996
+ }
1997
+ }
1998
+ } catch {
1999
+ }
2000
+ return results;
2001
+ }
2002
+
2003
+ // src/plugins/loader.ts
2004
+ import path10 from "path";
2005
+ import { pathToFileURL } from "url";
2006
+
2007
+ // src/commands/registry.ts
2008
+ var registry = /* @__PURE__ */ new Map();
2009
+ function registerCommand(name, handler) {
2010
+ registry.set(name, handler);
2011
+ }
2012
+ async function dispatchCommand(input, ctx) {
2013
+ for (const [name, handler] of registry) {
2014
+ if (input === name || input.startsWith(name + " ")) {
2015
+ const args = input.slice(name.length).trim();
2016
+ await handler(args, ctx);
2017
+ return true;
2018
+ }
2019
+ }
2020
+ return false;
2021
+ }
2022
+
2023
+ // src/plugins/loader.ts
2024
+ async function loadPlugin(discovered, projectRoot, log) {
2025
+ const entryPath = path10.resolve(discovered.path, discovered.manifest.main);
2026
+ const tools = [];
2027
+ const hooks = [];
2028
+ const ctx = {
2029
+ cwd: projectRoot,
2030
+ log,
2031
+ registerTool(tool2) {
2032
+ tools.push(tool2);
2033
+ },
2034
+ registerCommand(name, handler) {
2035
+ registerCommand(name, handler);
2036
+ },
2037
+ registerHook(hook) {
2038
+ hooks.push(hook);
2039
+ }
2040
+ };
2041
+ try {
2042
+ const moduleUrl = pathToFileURL(entryPath).href;
2043
+ const mod = await import(moduleUrl);
2044
+ const plugin = mod.default ?? mod;
2045
+ if (typeof plugin.activate !== "function") {
2046
+ log(`Plugin ${discovered.name}: no activate() function \u2014 skipping`);
2047
+ return null;
2048
+ }
2049
+ await plugin.activate(ctx);
2050
+ return {
2051
+ name: discovered.name,
2052
+ version: discovered.version,
2053
+ description: discovered.description,
2054
+ path: discovered.path,
2055
+ instance: plugin,
2056
+ tools,
2057
+ hooks
2058
+ };
2059
+ } catch (err) {
2060
+ log(`Plugin ${discovered.name} failed to load: ${err.message}`);
2061
+ return null;
2062
+ }
2063
+ }
2064
+
2065
+ // src/plugins/index.ts
2066
+ var PluginManager = class {
2067
+ plugins = [];
2068
+ initialized = false;
2069
+ /**
2070
+ * Discover and load all plugins. Call once at startup.
2071
+ */
2072
+ async init(projectRoot, log) {
2073
+ if (this.initialized) return this.plugins.length;
2074
+ const discovered = await discoverPlugins(projectRoot);
2075
+ if (discovered.length === 0) {
2076
+ this.initialized = true;
2077
+ return 0;
2078
+ }
2079
+ for (const d of discovered) {
2080
+ const loaded = await loadPlugin(d, projectRoot, log);
2081
+ if (loaded) {
2082
+ this.plugins.push(loaded);
2083
+ }
2084
+ }
2085
+ this.initialized = true;
2086
+ return this.plugins.length;
2087
+ }
2088
+ /** All tools registered by plugins */
2089
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2090
+ getTools() {
2091
+ return this.plugins.flatMap((p) => p.tools);
2092
+ }
2093
+ /** All hooks registered by plugins */
2094
+ getHooks() {
2095
+ return this.plugins.flatMap((p) => p.hooks);
2096
+ }
2097
+ /** List all loaded plugins */
2098
+ list() {
2099
+ return [...this.plugins];
2100
+ }
2101
+ /** Shut down all plugins gracefully */
2102
+ async shutdown() {
2103
+ for (const p of this.plugins) {
2104
+ try {
2105
+ await p.instance.deactivate?.();
2106
+ } catch (err) {
2107
+ console.warn(` Plugin ${p.name} deactivation failed: ${err.message}`);
2108
+ }
2109
+ }
2110
+ this.plugins = [];
2111
+ this.initialized = false;
2112
+ }
2113
+ };
2114
+ var pluginManager = new PluginManager();
2115
+
2116
+ // src/tools/index.ts
2117
+ var BUILTIN_TOOLS = [
2118
+ readTool,
2119
+ writeTool,
2120
+ editTool,
2121
+ shellTool,
2122
+ gitTool,
2123
+ githubTool,
2124
+ grepTool,
2125
+ globTool,
2126
+ webFetchTool,
2127
+ lspTool,
2128
+ notebookTool,
2129
+ taskTool
2130
+ ];
2131
+ var mcpTools = [];
2132
+ function getAllTools() {
2133
+ return [...BUILTIN_TOOLS, ...mcpTools, ...pluginManager.getTools()];
2134
+ }
1224
2135
  function buildToolMap(ctx) {
1225
2136
  const map = {};
1226
2137
  for (const t of getAllTools()) {
@@ -1229,8 +2140,10 @@ function buildToolMap(ctx) {
1229
2140
  parameters: t.parameters,
1230
2141
  execute: async (params) => {
1231
2142
  if (ctx.checkPermission) {
2143
+ await ctx.runHook?.("permission-request", { tool: t.name, args: params });
1232
2144
  const level = ctx.checkPermission(t.name, params);
1233
2145
  if (level === "deny") {
2146
+ await ctx.runHook?.("permission-denied", { tool: t.name, args: params, reason: "denied-by-config" });
1234
2147
  return {
1235
2148
  content: `Permission denied: ${t.name} is not allowed by your permission config.`,
1236
2149
  isError: true
@@ -1242,6 +2155,7 @@ function buildToolMap(ctx) {
1242
2155
  `Tool ${t.name}(${paramSummary}) requires approval. Proceed?`
1243
2156
  );
1244
2157
  if (!confirmed) {
2158
+ await ctx.runHook?.("permission-denied", { tool: t.name, args: params, reason: "cancelled-by-user" });
1245
2159
  return { content: "Cancelled by user.", isError: true };
1246
2160
  }
1247
2161
  }
@@ -1274,15 +2188,15 @@ function describeTools() {
1274
2188
  }
1275
2189
 
1276
2190
  // src/context/project-instructions.ts
1277
- import fs6 from "fs/promises";
1278
- import path7 from "path";
1279
- import os from "os";
2191
+ import fs9 from "fs/promises";
2192
+ import path11 from "path";
2193
+ import os2 from "os";
1280
2194
  var INSTRUCTION_FILES = [".notch.md", "NOTCH.md", ".notch/instructions.md"];
1281
2195
  async function loadProjectInstructions(projectRoot) {
1282
2196
  const sources = [];
1283
- const homeDir = os.homedir();
2197
+ const homeDir = os2.homedir();
1284
2198
  for (const file of INSTRUCTION_FILES) {
1285
- const globalPath = path7.join(homeDir, file);
2199
+ const globalPath = path11.join(homeDir, file);
1286
2200
  const content = await safeRead(globalPath);
1287
2201
  if (content) {
1288
2202
  sources.push({ path: globalPath, content, scope: "global" });
@@ -1290,7 +2204,7 @@ async function loadProjectInstructions(projectRoot) {
1290
2204
  }
1291
2205
  }
1292
2206
  for (const file of INSTRUCTION_FILES) {
1293
- const projectPath = path7.join(projectRoot, file);
2207
+ const projectPath = path11.join(projectRoot, file);
1294
2208
  const content = await safeRead(projectPath);
1295
2209
  if (content) {
1296
2210
  sources.push({ path: projectPath, content, scope: "project" });
@@ -1312,7 +2226,7 @@ ${sections.join("\n\n")}`;
1312
2226
  }
1313
2227
  async function safeRead(filePath) {
1314
2228
  try {
1315
- const content = await fs6.readFile(filePath, "utf-8");
2229
+ const content = await fs9.readFile(filePath, "utf-8");
1316
2230
  return content.trim() || null;
1317
2231
  } catch {
1318
2232
  return null;
@@ -1320,19 +2234,19 @@ async function safeRead(filePath) {
1320
2234
  }
1321
2235
 
1322
2236
  // src/memory/store.ts
1323
- import fs7 from "fs/promises";
1324
- import path8 from "path";
1325
- import os2 from "os";
1326
- var MEMORY_DIR = path8.join(os2.homedir(), ".notch", "memory");
1327
- var INDEX_FILE = path8.join(MEMORY_DIR, "MEMORY.md");
2237
+ import fs10 from "fs/promises";
2238
+ import path12 from "path";
2239
+ import os3 from "os";
2240
+ var MEMORY_DIR = path12.join(os3.homedir(), ".notch", "memory");
2241
+ var INDEX_FILE = path12.join(MEMORY_DIR, "MEMORY.md");
1328
2242
  async function ensureDir() {
1329
- await fs7.mkdir(MEMORY_DIR, { recursive: true });
2243
+ await fs10.mkdir(MEMORY_DIR, { recursive: true });
1330
2244
  }
1331
2245
  async function saveMemory(memory) {
1332
2246
  await ensureDir();
1333
2247
  const slug = memory.name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
1334
2248
  const filename = `${memory.type}_${slug}.md`;
1335
- const filePath = path8.join(MEMORY_DIR, filename);
2249
+ const filePath = path12.join(MEMORY_DIR, filename);
1336
2250
  const fileContent = [
1337
2251
  "---",
1338
2252
  `name: ${memory.name}`,
@@ -1343,18 +2257,18 @@ async function saveMemory(memory) {
1343
2257
  "",
1344
2258
  memory.content
1345
2259
  ].join("\n");
1346
- await fs7.writeFile(filePath, fileContent, "utf-8");
2260
+ await fs10.writeFile(filePath, fileContent, "utf-8");
1347
2261
  await updateIndex();
1348
2262
  return filename;
1349
2263
  }
1350
2264
  async function loadMemories() {
1351
2265
  await ensureDir();
1352
- const files = await fs7.readdir(MEMORY_DIR);
2266
+ const files = await fs10.readdir(MEMORY_DIR);
1353
2267
  const memories = [];
1354
2268
  for (const file of files) {
1355
2269
  if (!file.endsWith(".md") || file === "MEMORY.md") continue;
1356
2270
  try {
1357
- const content = await fs7.readFile(path8.join(MEMORY_DIR, file), "utf-8");
2271
+ const content = await fs10.readFile(path12.join(MEMORY_DIR, file), "utf-8");
1358
2272
  const memory = parseMemoryFile(content, file);
1359
2273
  if (memory) memories.push(memory);
1360
2274
  } catch {
@@ -1364,7 +2278,7 @@ async function loadMemories() {
1364
2278
  }
1365
2279
  async function deleteMemory(filename) {
1366
2280
  try {
1367
- await fs7.unlink(path8.join(MEMORY_DIR, filename));
2281
+ await fs10.unlink(path12.join(MEMORY_DIR, filename));
1368
2282
  await updateIndex();
1369
2283
  return true;
1370
2284
  } catch {
@@ -1440,7 +2354,7 @@ async function updateIndex() {
1440
2354
  }
1441
2355
  lines.push("");
1442
2356
  }
1443
- await fs7.writeFile(INDEX_FILE, lines.join("\n"), "utf-8");
2357
+ await fs10.writeFile(INDEX_FILE, lines.join("\n"), "utf-8");
1444
2358
  }
1445
2359
 
1446
2360
  // src/agent/loop.ts
@@ -1459,10 +2373,14 @@ async function runAgentLoop(messages, config) {
1459
2373
  let totalCompletionTokens = 0;
1460
2374
  let wasCompressed = false;
1461
2375
  let history = [...messages];
2376
+ await config.toolContext.runHook?.("pre-compact", { messageCount: history.length });
1462
2377
  history = await autoCompress(history, config.model, contextWindow, () => {
1463
2378
  wasCompressed = true;
1464
2379
  config.onCompress?.();
1465
2380
  });
2381
+ if (wasCompressed) {
2382
+ await config.toolContext.runHook?.("post-compact", { messageCount: history.length });
2383
+ }
1466
2384
  while (iterations < maxIter) {
1467
2385
  iterations++;
1468
2386
  const result = streamText({
@@ -1534,10 +2452,15 @@ async function runAgentLoop(messages, config) {
1534
2452
  }))
1535
2453
  });
1536
2454
  if (iterations % 5 === 0) {
2455
+ const prevLen = history.length;
2456
+ await config.toolContext.runHook?.("pre-compact", { messageCount: prevLen });
1537
2457
  history = await autoCompress(history, config.model, contextWindow, () => {
1538
2458
  wasCompressed = true;
1539
2459
  config.onCompress?.();
1540
2460
  });
2461
+ if (history.length < prevLen) {
2462
+ await config.toolContext.runHook?.("post-compact", { messageCount: history.length });
2463
+ }
1541
2464
  }
1542
2465
  continue;
1543
2466
  }
@@ -1620,8 +2543,8 @@ async function buildSystemPrompt(projectRoot, modelId) {
1620
2543
  }
1621
2544
 
1622
2545
  // src/agent/checkpoints.ts
1623
- import fs8 from "fs/promises";
1624
- import path9 from "path";
2546
+ import fs11 from "fs/promises";
2547
+ import path13 from "path";
1625
2548
  var CheckpointManager = class {
1626
2549
  checkpoints = [];
1627
2550
  nextId = 1;
@@ -1630,7 +2553,7 @@ var CheckpointManager = class {
1630
2553
  async recordBefore(filePath) {
1631
2554
  if (this.pendingFiles.has(filePath)) return;
1632
2555
  try {
1633
- const content = await fs8.readFile(filePath, "utf-8");
2556
+ const content = await fs11.readFile(filePath, "utf-8");
1634
2557
  this.pendingFiles.set(filePath, content);
1635
2558
  } catch {
1636
2559
  this.pendingFiles.set(filePath, null);
@@ -1642,7 +2565,7 @@ var CheckpointManager = class {
1642
2565
  for (const [filePath, before] of this.pendingFiles) {
1643
2566
  let after = null;
1644
2567
  try {
1645
- after = await fs8.readFile(filePath, "utf-8");
2568
+ after = await fs11.readFile(filePath, "utf-8");
1646
2569
  } catch {
1647
2570
  }
1648
2571
  files.push({ path: filePath, before, after });
@@ -1664,12 +2587,12 @@ var CheckpointManager = class {
1664
2587
  for (const snap of checkpoint.files) {
1665
2588
  if (snap.before === null) {
1666
2589
  try {
1667
- await fs8.unlink(snap.path);
2590
+ await fs11.unlink(snap.path);
1668
2591
  } catch {
1669
2592
  }
1670
2593
  } else {
1671
- await fs8.mkdir(path9.dirname(snap.path), { recursive: true });
1672
- await fs8.writeFile(snap.path, snap.before, "utf-8");
2594
+ await fs11.mkdir(path13.dirname(snap.path), { recursive: true });
2595
+ await fs11.writeFile(snap.path, snap.before, "utf-8");
1673
2596
  }
1674
2597
  }
1675
2598
  return checkpoint;
@@ -1701,8 +2624,8 @@ var CheckpointManager = class {
1701
2624
  }
1702
2625
  }
1703
2626
  }
1704
- return Array.from(fileMap.entries()).map(([path19, { before, after }]) => ({
1705
- path: path19,
2627
+ return Array.from(fileMap.entries()).map(([path25, { before, after }]) => ({
2628
+ path: path25,
1706
2629
  before,
1707
2630
  after
1708
2631
  }));
@@ -1710,7 +2633,7 @@ var CheckpointManager = class {
1710
2633
  };
1711
2634
 
1712
2635
  // src/agent/usage.ts
1713
- import chalk from "chalk";
2636
+ import chalk2 from "chalk";
1714
2637
  var UsageTracker = class {
1715
2638
  turns = [];
1716
2639
  record(usage) {
@@ -1735,18 +2658,18 @@ var UsageTracker = class {
1735
2658
  const last = this.turns[this.turns.length - 1];
1736
2659
  if (!last) return "";
1737
2660
  const t = last.totalTokens;
1738
- const label = t > 1e4 ? chalk.yellow(`${(t / 1e3).toFixed(1)}K`) : chalk.gray(`${t}`);
1739
- return chalk.gray(` [${label} tokens, ${last.toolCalls} tool calls, ${last.iterations} rounds]`);
2661
+ const label = t > 1e4 ? chalk2.yellow(`${(t / 1e3).toFixed(1)}K`) : chalk2.gray(`${t}`);
2662
+ return chalk2.gray(` [${label} tokens, ${last.toolCalls} tool calls, ${last.iterations} rounds]`);
1740
2663
  }
1741
2664
  formatSession() {
1742
2665
  const total = this.sessionTotal;
1743
2666
  return [
1744
- chalk.gray(`
2667
+ chalk2.gray(`
1745
2668
  Session usage (${this.turnCount} turns):`),
1746
- chalk.gray(` Prompt: ${total.promptTokens.toLocaleString()} tokens`),
1747
- chalk.gray(` Completion: ${total.completionTokens.toLocaleString()} tokens`),
1748
- chalk.gray(` Total: ${total.totalTokens.toLocaleString()} tokens`),
1749
- chalk.gray(` Tool calls: ${total.toolCalls}`)
2669
+ chalk2.gray(` Prompt: ${total.promptTokens.toLocaleString()} tokens`),
2670
+ chalk2.gray(` Completion: ${total.completionTokens.toLocaleString()} tokens`),
2671
+ chalk2.gray(` Total: ${total.totalTokens.toLocaleString()} tokens`),
2672
+ chalk2.gray(` Tool calls: ${total.toolCalls}`)
1750
2673
  ].join("\n");
1751
2674
  }
1752
2675
  };
@@ -1942,7 +2865,7 @@ function nextSubagentId(type) {
1942
2865
 
1943
2866
  // src/agent/planner.ts
1944
2867
  import { generateText as generateText2 } from "ai";
1945
- import chalk2 from "chalk";
2868
+ import chalk3 from "chalk";
1946
2869
  var PLAN_SYSTEM_PROMPT = `You are a planning assistant. Given a task, produce a structured implementation plan.
1947
2870
 
1948
2871
  Respond in EXACTLY this format (no other text):
@@ -2034,25 +2957,25 @@ function parsePlan(text) {
2034
2957
  function formatPlan(plan) {
2035
2958
  const lines = [];
2036
2959
  lines.push("");
2037
- lines.push(chalk2.bold.white(` Plan: ${plan.summary}`));
2960
+ lines.push(chalk3.bold.white(` Plan: ${plan.summary}`));
2038
2961
  lines.push("");
2039
2962
  for (const step of plan.steps) {
2040
- const icon = step.status === "done" ? chalk2.green("\u2713") : step.status === "in_progress" ? chalk2.yellow("\u25B6") : step.status === "failed" ? chalk2.red("\u2717") : step.status === "skipped" ? chalk2.gray("\u2013") : chalk2.gray("\u25CB");
2041
- const num = chalk2.gray(`${step.index + 1}.`);
2042
- const action = step.status === "done" ? chalk2.gray(step.action) : chalk2.white(step.action);
2043
- const files = step.files.length > 0 ? chalk2.cyan(` [${step.files.join(", ")}]`) : "";
2963
+ const icon = step.status === "done" ? chalk3.green("\u2713") : step.status === "in_progress" ? chalk3.yellow("\u25B6") : step.status === "failed" ? chalk3.red("\u2717") : step.status === "skipped" ? chalk3.gray("\u2013") : chalk3.gray("\u25CB");
2964
+ const num = chalk3.gray(`${step.index + 1}.`);
2965
+ const action = step.status === "done" ? chalk3.gray(step.action) : chalk3.white(step.action);
2966
+ const files = step.files.length > 0 ? chalk3.cyan(` [${step.files.join(", ")}]`) : "";
2044
2967
  lines.push(` ${icon} ${num} ${action}${files}`);
2045
2968
  }
2046
2969
  if (plan.risks.length > 0) {
2047
2970
  lines.push("");
2048
- lines.push(chalk2.yellow(" Risks:"));
2971
+ lines.push(chalk3.yellow(" Risks:"));
2049
2972
  for (const risk of plan.risks) {
2050
- lines.push(chalk2.yellow(` \u26A0 ${risk}`));
2973
+ lines.push(chalk3.yellow(` \u26A0 ${risk}`));
2051
2974
  }
2052
2975
  }
2053
2976
  lines.push("");
2054
2977
  if (!plan.approved) {
2055
- lines.push(chalk2.gray(" Approve: /plan approve | Modify: /plan edit | Cancel: /plan cancel"));
2978
+ lines.push(chalk3.gray(" Approve: /plan approve | Modify: /plan edit | Cancel: /plan cancel"));
2056
2979
  }
2057
2980
  lines.push("");
2058
2981
  return lines.join("\n");
@@ -2083,7 +3006,7 @@ function isPlanComplete(plan) {
2083
3006
  }
2084
3007
 
2085
3008
  // src/agent/cost.ts
2086
- import chalk3 from "chalk";
3009
+ import chalk4 from "chalk";
2087
3010
  var MODEL_COSTS = {
2088
3011
  "notch-cinder": { input: 0.05, output: 0.15 },
2089
3012
  // L4 — cheapest
@@ -2126,19 +3049,19 @@ var CostTracker = class {
2126
3049
  formatLastCost() {
2127
3050
  const last = this.entries[this.entries.length - 1];
2128
3051
  if (!last) return "";
2129
- return chalk3.gray(`$${last.cost.toFixed(4)}`);
3052
+ return chalk4.gray(`$${last.cost.toFixed(4)}`);
2130
3053
  }
2131
3054
  formatSession() {
2132
- if (this.entries.length === 0) return chalk3.gray(" No cost data.");
3055
+ if (this.entries.length === 0) return chalk4.gray(" No cost data.");
2133
3056
  const total = this.totalCost;
2134
3057
  const tokens = this.totalTokens;
2135
3058
  return [
2136
- chalk3.gray(`
3059
+ chalk4.gray(`
2137
3060
  Session cost estimate:`),
2138
- chalk3.gray(` Input: ${tokens.prompt.toLocaleString()} tokens`),
2139
- chalk3.gray(` Output: ${tokens.completion.toLocaleString()} tokens`),
2140
- chalk3.gray(` Est. cost: `) + chalk3.white(`$${total.toFixed(4)}`),
2141
- chalk3.gray(` (${this.entries.length} turns)`)
3061
+ chalk4.gray(` Input: ${tokens.prompt.toLocaleString()} tokens`),
3062
+ chalk4.gray(` Output: ${tokens.completion.toLocaleString()} tokens`),
3063
+ chalk4.gray(` Est. cost: `) + chalk4.white(`$${total.toFixed(4)}`),
3064
+ chalk4.gray(` (${this.entries.length} turns)`)
2142
3065
  ].join("\n");
2143
3066
  }
2144
3067
  /** Alias for formatSession */
@@ -2157,10 +3080,10 @@ var CostTracker = class {
2157
3080
  existing.turns += 1;
2158
3081
  byModel.set(e.model, existing);
2159
3082
  }
2160
- const lines = [chalk3.gray("\n Cost by model:")];
3083
+ const lines = [chalk4.gray("\n Cost by model:")];
2161
3084
  for (const [modelId, data] of byModel) {
2162
3085
  lines.push(
2163
- chalk3.gray(` ${modelId.padEnd(14)} ${data.turns} turns `) + chalk3.white(`$${data.cost.toFixed(4)}`) + chalk3.gray(` (${(data.prompt + data.completion).toLocaleString()} tokens)`)
3086
+ chalk4.gray(` ${modelId.padEnd(14)} ${data.turns} turns `) + chalk4.white(`$${data.cost.toFixed(4)}`) + chalk4.gray(` (${(data.prompt + data.completion).toLocaleString()} tokens)`)
2164
3087
  );
2165
3088
  }
2166
3089
  return lines.join("\n");
@@ -2168,14 +3091,14 @@ var CostTracker = class {
2168
3091
  };
2169
3092
 
2170
3093
  // src/agent/ralph.ts
2171
- import fs10 from "fs/promises";
2172
- import path11 from "path";
2173
- import chalk4 from "chalk";
3094
+ import fs13 from "fs/promises";
3095
+ import path15 from "path";
3096
+ import chalk5 from "chalk";
2174
3097
  import { generateText as generateText3, streamText as streamText3 } from "ai";
2175
3098
 
2176
3099
  // src/context/repo-map.ts
2177
- import fs9 from "fs/promises";
2178
- import path10 from "path";
3100
+ import fs12 from "fs/promises";
3101
+ import path14 from "path";
2179
3102
  import { glob } from "glob";
2180
3103
  var PATTERNS = {
2181
3104
  ts: [
@@ -2206,7 +3129,7 @@ var PATTERNS = {
2206
3129
  };
2207
3130
  var IMPORT_PATTERN = /(?:import|from)\s+['"]([^'"]+)['"]/g;
2208
3131
  function getPatterns(filePath) {
2209
- const ext = path10.extname(filePath).slice(1);
3132
+ const ext = path14.extname(filePath).slice(1);
2210
3133
  if (["ts", "tsx", "mts", "cts"].includes(ext)) return PATTERNS.ts;
2211
3134
  if (["js", "jsx", "mjs", "cjs"].includes(ext)) return PATTERNS.js;
2212
3135
  if (ext === "py") return PATTERNS.py;
@@ -2257,9 +3180,9 @@ async function buildRepoMap(root) {
2257
3180
  });
2258
3181
  const entries = [];
2259
3182
  for (const file of files.slice(0, 500)) {
2260
- const fullPath = path10.resolve(root, file);
3183
+ const fullPath = path14.resolve(root, file);
2261
3184
  try {
2262
- const content = await fs9.readFile(fullPath, "utf-8");
3185
+ const content = await fs12.readFile(fullPath, "utf-8");
2263
3186
  const lines = content.split("\n").length;
2264
3187
  const patterns = getPatterns(file);
2265
3188
  const symbols = extractSymbols(content, patterns);
@@ -2361,11 +3284,11 @@ ${repoContext || "(empty project)"}`;
2361
3284
  async function savePlan(plan, cwd) {
2362
3285
  plan.updated = (/* @__PURE__ */ new Date()).toISOString();
2363
3286
  plan.completedCount = plan.tasks.filter((t) => t.status === "done").length;
2364
- await fs10.writeFile(path11.join(cwd, PLAN_FILE), JSON.stringify(plan, null, 2), "utf-8");
3287
+ await fs13.writeFile(path15.join(cwd, PLAN_FILE), JSON.stringify(plan, null, 2), "utf-8");
2365
3288
  }
2366
3289
  async function loadPlan(cwd) {
2367
3290
  try {
2368
- const raw = await fs10.readFile(path11.join(cwd, PLAN_FILE), "utf-8");
3291
+ const raw = await fs13.readFile(path15.join(cwd, PLAN_FILE), "utf-8");
2369
3292
  return JSON.parse(raw);
2370
3293
  } catch {
2371
3294
  return null;
@@ -2373,7 +3296,7 @@ async function loadPlan(cwd) {
2373
3296
  }
2374
3297
  async function deletePlan(cwd) {
2375
3298
  try {
2376
- await fs10.unlink(path11.join(cwd, PLAN_FILE));
3299
+ await fs13.unlink(path15.join(cwd, PLAN_FILE));
2377
3300
  } catch {
2378
3301
  }
2379
3302
  }
@@ -2537,27 +3460,27 @@ function formatRalphStatus(plan) {
2537
3460
  const progress = plan.totalCount > 0 ? Math.round(done / plan.totalCount * 100) : 0;
2538
3461
  const barWidth = 25;
2539
3462
  const filled = Math.round(done / plan.totalCount * barWidth);
2540
- const bar = chalk4.green("\u2588".repeat(filled)) + chalk4.gray("\u2591".repeat(barWidth - filled));
3463
+ const bar = chalk5.green("\u2588".repeat(filled)) + chalk5.gray("\u2591".repeat(barWidth - filled));
2541
3464
  lines.push("");
2542
- lines.push(chalk4.bold.white(` Ralph Mode: ${plan.goal}`));
3465
+ lines.push(chalk5.bold.white(` Ralph Mode: ${plan.goal}`));
2543
3466
  lines.push(` [${bar}] ${progress}% (${done}/${plan.totalCount} tasks)`);
2544
- if (failed > 0) lines.push(chalk4.red(` ${failed} failed`));
3467
+ if (failed > 0) lines.push(chalk5.red(` ${failed} failed`));
2545
3468
  lines.push("");
2546
3469
  for (const task of plan.tasks) {
2547
- const icon = task.status === "done" ? chalk4.green("\u2713") : task.status === "failed" ? chalk4.red("\u2717") : task.status === "in_progress" ? chalk4.yellow("\u25B6") : task.status === "skipped" ? chalk4.gray("\u2013") : chalk4.gray("\u25CB");
2548
- const title = task.status === "done" ? chalk4.gray(task.title) : task.status === "failed" ? chalk4.red(task.title) : chalk4.white(task.title);
2549
- const files = task.files.length > 0 ? chalk4.cyan(` [${task.files.join(", ")}]`) : "";
2550
- const err = task.error ? chalk4.red(` \u2014 ${task.error.slice(0, 60)}`) : "";
2551
- const attempts = task.attempts > 1 ? chalk4.yellow(` (${task.attempts} attempts)`) : "";
2552
- lines.push(` ${icon} ${chalk4.gray(`${task.id}.`)} ${title}${files}${attempts}${err}`);
3470
+ const icon = task.status === "done" ? chalk5.green("\u2713") : task.status === "failed" ? chalk5.red("\u2717") : task.status === "in_progress" ? chalk5.yellow("\u25B6") : task.status === "skipped" ? chalk5.gray("\u2013") : chalk5.gray("\u25CB");
3471
+ const title = task.status === "done" ? chalk5.gray(task.title) : task.status === "failed" ? chalk5.red(task.title) : chalk5.white(task.title);
3472
+ const files = task.files.length > 0 ? chalk5.cyan(` [${task.files.join(", ")}]`) : "";
3473
+ const err = task.error ? chalk5.red(` \u2014 ${task.error.slice(0, 60)}`) : "";
3474
+ const attempts = task.attempts > 1 ? chalk5.yellow(` (${task.attempts} attempts)`) : "";
3475
+ lines.push(` ${icon} ${chalk5.gray(`${task.id}.`)} ${title}${files}${attempts}${err}`);
2553
3476
  }
2554
3477
  lines.push("");
2555
3478
  return lines.join("\n");
2556
3479
  }
2557
3480
 
2558
3481
  // src/context/references.ts
2559
- import fs11 from "fs/promises";
2560
- import path12 from "path";
3482
+ import fs14 from "fs/promises";
3483
+ import path16 from "path";
2561
3484
  import { glob as glob2 } from "glob";
2562
3485
  async function resolveReferences(input, cwd) {
2563
3486
  const references = [];
@@ -2600,9 +3523,9 @@ ${truncated}
2600
3523
  return sections.join("\n\n") + "\n\n";
2601
3524
  }
2602
3525
  async function resolveFile(ref, cwd) {
2603
- const filePath = path12.isAbsolute(ref) ? ref : path12.resolve(cwd, ref);
3526
+ const filePath = path16.isAbsolute(ref) ? ref : path16.resolve(cwd, ref);
2604
3527
  try {
2605
- const content = await fs11.readFile(filePath, "utf-8");
3528
+ const content = await fs14.readFile(filePath, "utf-8");
2606
3529
  const lines = content.split("\n");
2607
3530
  const numbered = lines.map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
2608
3531
  return {
@@ -2698,448 +3621,448 @@ async function resolveGlob(pattern, cwd) {
2698
3621
  }
2699
3622
 
2700
3623
  // src/ui/banner.ts
2701
- import chalk6 from "chalk";
3624
+ import chalk7 from "chalk";
2702
3625
 
2703
3626
  // src/ui/themes.ts
2704
- import chalk5 from "chalk";
3627
+ import chalk6 from "chalk";
2705
3628
  var defaultTheme = {
2706
3629
  name: "Default",
2707
3630
  description: "FreeSyntax \u2014 silver, white, monochrome",
2708
- brand: chalk5.hex("#D4D4D4"),
3631
+ brand: chalk6.hex("#D4D4D4"),
2709
3632
  // silver (banner uses gradient override)
2710
- mascot: chalk5.hex("#AAAAAA"),
3633
+ mascot: chalk6.hex("#AAAAAA"),
2711
3634
  // medium gray mantis
2712
- mascotAccent: chalk5.hex("#FFFFFF"),
3635
+ mascotAccent: chalk6.hex("#FFFFFF"),
2713
3636
  // white eyes
2714
- tagline: chalk5.hex("#777777"),
3637
+ tagline: chalk6.hex("#777777"),
2715
3638
  // muted gray
2716
- prompt: chalk5.hex("#CCCCCC"),
3639
+ prompt: chalk6.hex("#CCCCCC"),
2717
3640
  // silver prompt
2718
- border: chalk5.hex("#444444"),
3641
+ border: chalk6.hex("#444444"),
2719
3642
  // dark border
2720
- dim: chalk5.hex("#666666"),
3643
+ dim: chalk6.hex("#666666"),
2721
3644
  // muted text
2722
- text: chalk5.hex("#D4D4D4"),
3645
+ text: chalk6.hex("#D4D4D4"),
2723
3646
  // silver body text
2724
- bold: chalk5.hex("#FFFFFF").bold,
3647
+ bold: chalk6.hex("#FFFFFF").bold,
2725
3648
  // pure white emphasis
2726
- success: chalk5.green,
2727
- warning: chalk5.yellow,
2728
- error: chalk5.red,
2729
- info: chalk5.hex("#BBBBBB"),
3649
+ success: chalk6.green,
3650
+ warning: chalk6.yellow,
3651
+ error: chalk6.red,
3652
+ info: chalk6.hex("#BBBBBB"),
2730
3653
  // light gray info
2731
- toolName: chalk5.hex("#AAAAAA"),
2732
- toolArgs: chalk5.hex("#777777"),
2733
- toolResult: chalk5.hex("#555555"),
2734
- diffAdd: chalk5.green,
2735
- diffRemove: chalk5.red,
2736
- diffHeader: chalk5.hex("#CCCCCC"),
2737
- mdH1: chalk5.hex("#FFFFFF").bold,
2738
- mdH2: chalk5.hex("#D4D4D4"),
2739
- mdH3: chalk5.hex("#AAAAAA"),
2740
- mdCode: chalk5.hex("#BBBBBB"),
2741
- mdInlineCode: chalk5.hex("#CCCCCC"),
2742
- mdLink: chalk5.hex("#D4D4D4").underline,
2743
- meterLow: chalk5.green,
2744
- meterMid: chalk5.yellow,
2745
- meterHigh: chalk5.red
3654
+ toolName: chalk6.hex("#AAAAAA"),
3655
+ toolArgs: chalk6.hex("#777777"),
3656
+ toolResult: chalk6.hex("#555555"),
3657
+ diffAdd: chalk6.green,
3658
+ diffRemove: chalk6.red,
3659
+ diffHeader: chalk6.hex("#CCCCCC"),
3660
+ mdH1: chalk6.hex("#FFFFFF").bold,
3661
+ mdH2: chalk6.hex("#D4D4D4"),
3662
+ mdH3: chalk6.hex("#AAAAAA"),
3663
+ mdCode: chalk6.hex("#BBBBBB"),
3664
+ mdInlineCode: chalk6.hex("#CCCCCC"),
3665
+ mdLink: chalk6.hex("#D4D4D4").underline,
3666
+ meterLow: chalk6.green,
3667
+ meterMid: chalk6.yellow,
3668
+ meterHigh: chalk6.red
2746
3669
  };
2747
3670
  var interstellarTheme = {
2748
3671
  name: "Interstellar",
2749
3672
  description: "Deep space amber \u2014 Endurance control panels",
2750
- brand: chalk5.hex("#FFB347"),
3673
+ brand: chalk6.hex("#FFB347"),
2751
3674
  // warm amber (instrument readout)
2752
- mascot: chalk5.hex("#4A90D9"),
3675
+ mascot: chalk6.hex("#4A90D9"),
2753
3676
  // pale blue (frozen clouds)
2754
- mascotAccent: chalk5.hex("#FFD700"),
3677
+ mascotAccent: chalk6.hex("#FFD700"),
2755
3678
  // gold (Gargantua light)
2756
- tagline: chalk5.hex("#708090"),
3679
+ tagline: chalk6.hex("#708090"),
2757
3680
  // slate gray (ship hull)
2758
- prompt: chalk5.hex("#FFB347"),
2759
- border: chalk5.hex("#3A3A3A"),
3681
+ prompt: chalk6.hex("#FFB347"),
3682
+ border: chalk6.hex("#3A3A3A"),
2760
3683
  // dark hull metal
2761
- dim: chalk5.hex("#5C5C5C"),
2762
- text: chalk5.hex("#D4D4D4"),
3684
+ dim: chalk6.hex("#5C5C5C"),
3685
+ text: chalk6.hex("#D4D4D4"),
2763
3686
  // cool white (display text)
2764
- bold: chalk5.hex("#FFFFFF").bold,
2765
- success: chalk5.hex("#7CFC00"),
3687
+ bold: chalk6.hex("#FFFFFF").bold,
3688
+ success: chalk6.hex("#7CFC00"),
2766
3689
  // "CASE: All systems nominal"
2767
- warning: chalk5.hex("#FFB347"),
3690
+ warning: chalk6.hex("#FFB347"),
2768
3691
  // amber warning
2769
- error: chalk5.hex("#FF4444"),
3692
+ error: chalk6.hex("#FF4444"),
2770
3693
  // critical alert
2771
- info: chalk5.hex("#4A90D9"),
3694
+ info: chalk6.hex("#4A90D9"),
2772
3695
  // blue data readout
2773
- toolName: chalk5.hex("#FFB347"),
2774
- toolArgs: chalk5.hex("#708090"),
2775
- toolResult: chalk5.hex("#5C5C5C"),
2776
- diffAdd: chalk5.hex("#7CFC00"),
2777
- diffRemove: chalk5.hex("#FF4444"),
2778
- diffHeader: chalk5.hex("#FFB347"),
2779
- mdH1: chalk5.hex("#FFB347").bold,
2780
- mdH2: chalk5.hex("#4A90D9"),
2781
- mdH3: chalk5.hex("#708090"),
2782
- mdCode: chalk5.hex("#FFD700"),
2783
- mdInlineCode: chalk5.hex("#FFB347"),
2784
- mdLink: chalk5.hex("#4A90D9").underline,
2785
- meterLow: chalk5.hex("#4A90D9"),
2786
- meterMid: chalk5.hex("#FFB347"),
2787
- meterHigh: chalk5.hex("#FF4444")
3696
+ toolName: chalk6.hex("#FFB347"),
3697
+ toolArgs: chalk6.hex("#708090"),
3698
+ toolResult: chalk6.hex("#5C5C5C"),
3699
+ diffAdd: chalk6.hex("#7CFC00"),
3700
+ diffRemove: chalk6.hex("#FF4444"),
3701
+ diffHeader: chalk6.hex("#FFB347"),
3702
+ mdH1: chalk6.hex("#FFB347").bold,
3703
+ mdH2: chalk6.hex("#4A90D9"),
3704
+ mdH3: chalk6.hex("#708090"),
3705
+ mdCode: chalk6.hex("#FFD700"),
3706
+ mdInlineCode: chalk6.hex("#FFB347"),
3707
+ mdLink: chalk6.hex("#4A90D9").underline,
3708
+ meterLow: chalk6.hex("#4A90D9"),
3709
+ meterMid: chalk6.hex("#FFB347"),
3710
+ meterHigh: chalk6.hex("#FF4444")
2788
3711
  };
2789
3712
  var spaceGrayTheme = {
2790
3713
  name: "Space Gray",
2791
3714
  description: "Apple minimalism \u2014 silver, cool gray, ice blue",
2792
- brand: chalk5.hex("#A2AAAD"),
3715
+ brand: chalk6.hex("#A2AAAD"),
2793
3716
  // apple silver
2794
- mascot: chalk5.hex("#86868B"),
3717
+ mascot: chalk6.hex("#86868B"),
2795
3718
  // space gray
2796
- mascotAccent: chalk5.hex("#0071E3"),
3719
+ mascotAccent: chalk6.hex("#0071E3"),
2797
3720
  // apple blue
2798
- tagline: chalk5.hex("#6E6E73"),
3721
+ tagline: chalk6.hex("#6E6E73"),
2799
3722
  // secondary gray
2800
- prompt: chalk5.hex("#A2AAAD"),
2801
- border: chalk5.hex("#3A3A3C"),
3723
+ prompt: chalk6.hex("#A2AAAD"),
3724
+ border: chalk6.hex("#3A3A3C"),
2802
3725
  // dark separator
2803
- dim: chalk5.hex("#6E6E73"),
2804
- text: chalk5.hex("#E5E5EA"),
3726
+ dim: chalk6.hex("#6E6E73"),
3727
+ text: chalk6.hex("#E5E5EA"),
2805
3728
  // system gray 6
2806
- bold: chalk5.hex("#F5F5F7").bold,
3729
+ bold: chalk6.hex("#F5F5F7").bold,
2807
3730
  // near white
2808
- success: chalk5.hex("#30D158"),
3731
+ success: chalk6.hex("#30D158"),
2809
3732
  // apple green
2810
- warning: chalk5.hex("#FFD60A"),
3733
+ warning: chalk6.hex("#FFD60A"),
2811
3734
  // apple yellow
2812
- error: chalk5.hex("#FF453A"),
3735
+ error: chalk6.hex("#FF453A"),
2813
3736
  // apple red
2814
- info: chalk5.hex("#0A84FF"),
3737
+ info: chalk6.hex("#0A84FF"),
2815
3738
  // apple blue
2816
- toolName: chalk5.hex("#A2AAAD"),
2817
- toolArgs: chalk5.hex("#6E6E73"),
2818
- toolResult: chalk5.hex("#48484A"),
2819
- diffAdd: chalk5.hex("#30D158"),
2820
- diffRemove: chalk5.hex("#FF453A"),
2821
- diffHeader: chalk5.hex("#0A84FF"),
2822
- mdH1: chalk5.hex("#F5F5F7").bold,
2823
- mdH2: chalk5.hex("#0A84FF"),
2824
- mdH3: chalk5.hex("#A2AAAD"),
2825
- mdCode: chalk5.hex("#BF5AF2"),
3739
+ toolName: chalk6.hex("#A2AAAD"),
3740
+ toolArgs: chalk6.hex("#6E6E73"),
3741
+ toolResult: chalk6.hex("#48484A"),
3742
+ diffAdd: chalk6.hex("#30D158"),
3743
+ diffRemove: chalk6.hex("#FF453A"),
3744
+ diffHeader: chalk6.hex("#0A84FF"),
3745
+ mdH1: chalk6.hex("#F5F5F7").bold,
3746
+ mdH2: chalk6.hex("#0A84FF"),
3747
+ mdH3: chalk6.hex("#A2AAAD"),
3748
+ mdCode: chalk6.hex("#BF5AF2"),
2826
3749
  // apple purple for code
2827
- mdInlineCode: chalk5.hex("#BF5AF2"),
2828
- mdLink: chalk5.hex("#0A84FF").underline,
2829
- meterLow: chalk5.hex("#30D158"),
2830
- meterMid: chalk5.hex("#FFD60A"),
2831
- meterHigh: chalk5.hex("#FF453A")
3750
+ mdInlineCode: chalk6.hex("#BF5AF2"),
3751
+ mdLink: chalk6.hex("#0A84FF").underline,
3752
+ meterLow: chalk6.hex("#30D158"),
3753
+ meterMid: chalk6.hex("#FFD60A"),
3754
+ meterHigh: chalk6.hex("#FF453A")
2832
3755
  };
2833
3756
  var cyberpunkTheme = {
2834
3757
  name: "Cyberpunk",
2835
3758
  description: "Neon pink & cyan \u2014 Night City terminal",
2836
- brand: chalk5.hex("#FF2079"),
3759
+ brand: chalk6.hex("#FF2079"),
2837
3760
  // hot pink
2838
- mascot: chalk5.hex("#00FFFF"),
3761
+ mascot: chalk6.hex("#00FFFF"),
2839
3762
  // electric cyan
2840
- mascotAccent: chalk5.hex("#FF2079"),
3763
+ mascotAccent: chalk6.hex("#FF2079"),
2841
3764
  // hot pink eyes
2842
- tagline: chalk5.hex("#9D00FF"),
3765
+ tagline: chalk6.hex("#9D00FF"),
2843
3766
  // deep violet
2844
- prompt: chalk5.hex("#FF2079"),
2845
- border: chalk5.hex("#1A1A2E"),
2846
- dim: chalk5.hex("#4A4A6A"),
2847
- text: chalk5.hex("#E0E0FF"),
2848
- bold: chalk5.hex("#FFFFFF").bold,
2849
- success: chalk5.hex("#00FF41"),
3767
+ prompt: chalk6.hex("#FF2079"),
3768
+ border: chalk6.hex("#1A1A2E"),
3769
+ dim: chalk6.hex("#4A4A6A"),
3770
+ text: chalk6.hex("#E0E0FF"),
3771
+ bold: chalk6.hex("#FFFFFF").bold,
3772
+ success: chalk6.hex("#00FF41"),
2850
3773
  // matrix green
2851
- warning: chalk5.hex("#FFFF00"),
3774
+ warning: chalk6.hex("#FFFF00"),
2852
3775
  // neon yellow
2853
- error: chalk5.hex("#FF0000"),
2854
- info: chalk5.hex("#00FFFF"),
2855
- toolName: chalk5.hex("#FF2079"),
2856
- toolArgs: chalk5.hex("#9D00FF"),
2857
- toolResult: chalk5.hex("#4A4A6A"),
2858
- diffAdd: chalk5.hex("#00FF41"),
2859
- diffRemove: chalk5.hex("#FF0000"),
2860
- diffHeader: chalk5.hex("#FF2079"),
2861
- mdH1: chalk5.hex("#FF2079").bold,
2862
- mdH2: chalk5.hex("#00FFFF"),
2863
- mdH3: chalk5.hex("#9D00FF"),
2864
- mdCode: chalk5.hex("#00FF41"),
2865
- mdInlineCode: chalk5.hex("#00FFFF"),
2866
- mdLink: chalk5.hex("#9D00FF").underline,
2867
- meterLow: chalk5.hex("#00FF41"),
2868
- meterMid: chalk5.hex("#FFFF00"),
2869
- meterHigh: chalk5.hex("#FF0000")
3776
+ error: chalk6.hex("#FF0000"),
3777
+ info: chalk6.hex("#00FFFF"),
3778
+ toolName: chalk6.hex("#FF2079"),
3779
+ toolArgs: chalk6.hex("#9D00FF"),
3780
+ toolResult: chalk6.hex("#4A4A6A"),
3781
+ diffAdd: chalk6.hex("#00FF41"),
3782
+ diffRemove: chalk6.hex("#FF0000"),
3783
+ diffHeader: chalk6.hex("#FF2079"),
3784
+ mdH1: chalk6.hex("#FF2079").bold,
3785
+ mdH2: chalk6.hex("#00FFFF"),
3786
+ mdH3: chalk6.hex("#9D00FF"),
3787
+ mdCode: chalk6.hex("#00FF41"),
3788
+ mdInlineCode: chalk6.hex("#00FFFF"),
3789
+ mdLink: chalk6.hex("#9D00FF").underline,
3790
+ meterLow: chalk6.hex("#00FF41"),
3791
+ meterMid: chalk6.hex("#FFFF00"),
3792
+ meterHigh: chalk6.hex("#FF0000")
2870
3793
  };
2871
3794
  var auroraTheme = {
2872
3795
  name: "Aurora",
2873
3796
  description: "Northern lights \u2014 teal, violet, polar green",
2874
- brand: chalk5.hex("#7FFFD4"),
3797
+ brand: chalk6.hex("#7FFFD4"),
2875
3798
  // aquamarine
2876
- mascot: chalk5.hex("#00CED1"),
3799
+ mascot: chalk6.hex("#00CED1"),
2877
3800
  // dark turquoise
2878
- mascotAccent: chalk5.hex("#DA70D6"),
3801
+ mascotAccent: chalk6.hex("#DA70D6"),
2879
3802
  // orchid
2880
- tagline: chalk5.hex("#9370DB"),
3803
+ tagline: chalk6.hex("#9370DB"),
2881
3804
  // medium purple
2882
- prompt: chalk5.hex("#7FFFD4"),
2883
- border: chalk5.hex("#2F4F4F"),
3805
+ prompt: chalk6.hex("#7FFFD4"),
3806
+ border: chalk6.hex("#2F4F4F"),
2884
3807
  // dark slate
2885
- dim: chalk5.hex("#5F9EA0"),
3808
+ dim: chalk6.hex("#5F9EA0"),
2886
3809
  // cadet blue
2887
- text: chalk5.hex("#E0FFFF"),
3810
+ text: chalk6.hex("#E0FFFF"),
2888
3811
  // light cyan
2889
- bold: chalk5.hex("#F0FFFF").bold,
2890
- success: chalk5.hex("#00FA9A"),
3812
+ bold: chalk6.hex("#F0FFFF").bold,
3813
+ success: chalk6.hex("#00FA9A"),
2891
3814
  // medium spring green
2892
- warning: chalk5.hex("#DDA0DD"),
3815
+ warning: chalk6.hex("#DDA0DD"),
2893
3816
  // plum
2894
- error: chalk5.hex("#FF6B6B"),
2895
- info: chalk5.hex("#87CEEB"),
3817
+ error: chalk6.hex("#FF6B6B"),
3818
+ info: chalk6.hex("#87CEEB"),
2896
3819
  // sky blue
2897
- toolName: chalk5.hex("#7FFFD4"),
2898
- toolArgs: chalk5.hex("#5F9EA0"),
2899
- toolResult: chalk5.hex("#2F4F4F"),
2900
- diffAdd: chalk5.hex("#00FA9A"),
2901
- diffRemove: chalk5.hex("#FF6B6B"),
2902
- diffHeader: chalk5.hex("#DA70D6"),
2903
- mdH1: chalk5.hex("#7FFFD4").bold,
2904
- mdH2: chalk5.hex("#DA70D6"),
2905
- mdH3: chalk5.hex("#87CEEB"),
2906
- mdCode: chalk5.hex("#00CED1"),
2907
- mdInlineCode: chalk5.hex("#7FFFD4"),
2908
- mdLink: chalk5.hex("#DA70D6").underline,
2909
- meterLow: chalk5.hex("#00FA9A"),
2910
- meterMid: chalk5.hex("#DDA0DD"),
2911
- meterHigh: chalk5.hex("#FF6B6B")
3820
+ toolName: chalk6.hex("#7FFFD4"),
3821
+ toolArgs: chalk6.hex("#5F9EA0"),
3822
+ toolResult: chalk6.hex("#2F4F4F"),
3823
+ diffAdd: chalk6.hex("#00FA9A"),
3824
+ diffRemove: chalk6.hex("#FF6B6B"),
3825
+ diffHeader: chalk6.hex("#DA70D6"),
3826
+ mdH1: chalk6.hex("#7FFFD4").bold,
3827
+ mdH2: chalk6.hex("#DA70D6"),
3828
+ mdH3: chalk6.hex("#87CEEB"),
3829
+ mdCode: chalk6.hex("#00CED1"),
3830
+ mdInlineCode: chalk6.hex("#7FFFD4"),
3831
+ mdLink: chalk6.hex("#DA70D6").underline,
3832
+ meterLow: chalk6.hex("#00FA9A"),
3833
+ meterMid: chalk6.hex("#DDA0DD"),
3834
+ meterHigh: chalk6.hex("#FF6B6B")
2912
3835
  };
2913
3836
  var solarizedTheme = {
2914
3837
  name: "Solarized",
2915
3838
  description: "Solarized Dark \u2014 warm, precise, classic dev palette",
2916
- brand: chalk5.hex("#268BD2"),
3839
+ brand: chalk6.hex("#268BD2"),
2917
3840
  // blue
2918
- mascot: chalk5.hex("#859900"),
3841
+ mascot: chalk6.hex("#859900"),
2919
3842
  // green
2920
- mascotAccent: chalk5.hex("#B58900"),
3843
+ mascotAccent: chalk6.hex("#B58900"),
2921
3844
  // yellow
2922
- tagline: chalk5.hex("#2AA198"),
3845
+ tagline: chalk6.hex("#2AA198"),
2923
3846
  // cyan
2924
- prompt: chalk5.hex("#268BD2"),
2925
- border: chalk5.hex("#073642"),
3847
+ prompt: chalk6.hex("#268BD2"),
3848
+ border: chalk6.hex("#073642"),
2926
3849
  // base02
2927
- dim: chalk5.hex("#586E75"),
3850
+ dim: chalk6.hex("#586E75"),
2928
3851
  // base01
2929
- text: chalk5.hex("#839496"),
3852
+ text: chalk6.hex("#839496"),
2930
3853
  // base0
2931
- bold: chalk5.hex("#93A1A1").bold,
3854
+ bold: chalk6.hex("#93A1A1").bold,
2932
3855
  // base1
2933
- success: chalk5.hex("#859900"),
2934
- warning: chalk5.hex("#B58900"),
2935
- error: chalk5.hex("#DC322F"),
2936
- info: chalk5.hex("#2AA198"),
2937
- toolName: chalk5.hex("#268BD2"),
2938
- toolArgs: chalk5.hex("#586E75"),
2939
- toolResult: chalk5.hex("#073642"),
2940
- diffAdd: chalk5.hex("#859900"),
2941
- diffRemove: chalk5.hex("#DC322F"),
2942
- diffHeader: chalk5.hex("#268BD2"),
2943
- mdH1: chalk5.hex("#93A1A1").bold,
2944
- mdH2: chalk5.hex("#268BD2"),
2945
- mdH3: chalk5.hex("#2AA198"),
2946
- mdCode: chalk5.hex("#859900"),
2947
- mdInlineCode: chalk5.hex("#2AA198"),
2948
- mdLink: chalk5.hex("#268BD2").underline,
2949
- meterLow: chalk5.hex("#859900"),
2950
- meterMid: chalk5.hex("#B58900"),
2951
- meterHigh: chalk5.hex("#DC322F")
3856
+ success: chalk6.hex("#859900"),
3857
+ warning: chalk6.hex("#B58900"),
3858
+ error: chalk6.hex("#DC322F"),
3859
+ info: chalk6.hex("#2AA198"),
3860
+ toolName: chalk6.hex("#268BD2"),
3861
+ toolArgs: chalk6.hex("#586E75"),
3862
+ toolResult: chalk6.hex("#073642"),
3863
+ diffAdd: chalk6.hex("#859900"),
3864
+ diffRemove: chalk6.hex("#DC322F"),
3865
+ diffHeader: chalk6.hex("#268BD2"),
3866
+ mdH1: chalk6.hex("#93A1A1").bold,
3867
+ mdH2: chalk6.hex("#268BD2"),
3868
+ mdH3: chalk6.hex("#2AA198"),
3869
+ mdCode: chalk6.hex("#859900"),
3870
+ mdInlineCode: chalk6.hex("#2AA198"),
3871
+ mdLink: chalk6.hex("#268BD2").underline,
3872
+ meterLow: chalk6.hex("#859900"),
3873
+ meterMid: chalk6.hex("#B58900"),
3874
+ meterHigh: chalk6.hex("#DC322F")
2952
3875
  };
2953
3876
  var draculaTheme = {
2954
3877
  name: "Dracula",
2955
3878
  description: "Dracula \u2014 purple, pink, green on charcoal",
2956
- brand: chalk5.hex("#BD93F9"),
3879
+ brand: chalk6.hex("#BD93F9"),
2957
3880
  // purple
2958
- mascot: chalk5.hex("#50FA7B"),
3881
+ mascot: chalk6.hex("#50FA7B"),
2959
3882
  // green
2960
- mascotAccent: chalk5.hex("#FF79C6"),
3883
+ mascotAccent: chalk6.hex("#FF79C6"),
2961
3884
  // pink
2962
- tagline: chalk5.hex("#6272A4"),
3885
+ tagline: chalk6.hex("#6272A4"),
2963
3886
  // comment gray
2964
- prompt: chalk5.hex("#BD93F9"),
2965
- border: chalk5.hex("#44475A"),
3887
+ prompt: chalk6.hex("#BD93F9"),
3888
+ border: chalk6.hex("#44475A"),
2966
3889
  // current line
2967
- dim: chalk5.hex("#6272A4"),
3890
+ dim: chalk6.hex("#6272A4"),
2968
3891
  // comment
2969
- text: chalk5.hex("#F8F8F2"),
3892
+ text: chalk6.hex("#F8F8F2"),
2970
3893
  // foreground
2971
- bold: chalk5.hex("#F8F8F2").bold,
2972
- success: chalk5.hex("#50FA7B"),
2973
- warning: chalk5.hex("#F1FA8C"),
3894
+ bold: chalk6.hex("#F8F8F2").bold,
3895
+ success: chalk6.hex("#50FA7B"),
3896
+ warning: chalk6.hex("#F1FA8C"),
2974
3897
  // yellow
2975
- error: chalk5.hex("#FF5555"),
2976
- info: chalk5.hex("#8BE9FD"),
3898
+ error: chalk6.hex("#FF5555"),
3899
+ info: chalk6.hex("#8BE9FD"),
2977
3900
  // cyan
2978
- toolName: chalk5.hex("#BD93F9"),
2979
- toolArgs: chalk5.hex("#6272A4"),
2980
- toolResult: chalk5.hex("#44475A"),
2981
- diffAdd: chalk5.hex("#50FA7B"),
2982
- diffRemove: chalk5.hex("#FF5555"),
2983
- diffHeader: chalk5.hex("#FF79C6"),
2984
- mdH1: chalk5.hex("#FF79C6").bold,
2985
- mdH2: chalk5.hex("#BD93F9"),
2986
- mdH3: chalk5.hex("#8BE9FD"),
2987
- mdCode: chalk5.hex("#50FA7B"),
2988
- mdInlineCode: chalk5.hex("#F1FA8C"),
2989
- mdLink: chalk5.hex("#8BE9FD").underline,
2990
- meterLow: chalk5.hex("#50FA7B"),
2991
- meterMid: chalk5.hex("#F1FA8C"),
2992
- meterHigh: chalk5.hex("#FF5555")
3901
+ toolName: chalk6.hex("#BD93F9"),
3902
+ toolArgs: chalk6.hex("#6272A4"),
3903
+ toolResult: chalk6.hex("#44475A"),
3904
+ diffAdd: chalk6.hex("#50FA7B"),
3905
+ diffRemove: chalk6.hex("#FF5555"),
3906
+ diffHeader: chalk6.hex("#FF79C6"),
3907
+ mdH1: chalk6.hex("#FF79C6").bold,
3908
+ mdH2: chalk6.hex("#BD93F9"),
3909
+ mdH3: chalk6.hex("#8BE9FD"),
3910
+ mdCode: chalk6.hex("#50FA7B"),
3911
+ mdInlineCode: chalk6.hex("#F1FA8C"),
3912
+ mdLink: chalk6.hex("#8BE9FD").underline,
3913
+ meterLow: chalk6.hex("#50FA7B"),
3914
+ meterMid: chalk6.hex("#F1FA8C"),
3915
+ meterHigh: chalk6.hex("#FF5555")
2993
3916
  };
2994
3917
  var monokaiTheme = {
2995
3918
  name: "Monokai",
2996
3919
  description: "Monokai Pro \u2014 vivid on dark, a classic",
2997
- brand: chalk5.hex("#A6E22E"),
3920
+ brand: chalk6.hex("#A6E22E"),
2998
3921
  // green
2999
- mascot: chalk5.hex("#66D9EF"),
3922
+ mascot: chalk6.hex("#66D9EF"),
3000
3923
  // blue
3001
- mascotAccent: chalk5.hex("#F92672"),
3924
+ mascotAccent: chalk6.hex("#F92672"),
3002
3925
  // pink
3003
- tagline: chalk5.hex("#75715E"),
3926
+ tagline: chalk6.hex("#75715E"),
3004
3927
  // comment
3005
- prompt: chalk5.hex("#A6E22E"),
3006
- border: chalk5.hex("#3E3D32"),
3007
- dim: chalk5.hex("#75715E"),
3008
- text: chalk5.hex("#F8F8F2"),
3009
- bold: chalk5.hex("#F8F8F2").bold,
3010
- success: chalk5.hex("#A6E22E"),
3011
- warning: chalk5.hex("#E6DB74"),
3928
+ prompt: chalk6.hex("#A6E22E"),
3929
+ border: chalk6.hex("#3E3D32"),
3930
+ dim: chalk6.hex("#75715E"),
3931
+ text: chalk6.hex("#F8F8F2"),
3932
+ bold: chalk6.hex("#F8F8F2").bold,
3933
+ success: chalk6.hex("#A6E22E"),
3934
+ warning: chalk6.hex("#E6DB74"),
3012
3935
  // yellow
3013
- error: chalk5.hex("#F92672"),
3014
- info: chalk5.hex("#66D9EF"),
3015
- toolName: chalk5.hex("#A6E22E"),
3016
- toolArgs: chalk5.hex("#75715E"),
3017
- toolResult: chalk5.hex("#3E3D32"),
3018
- diffAdd: chalk5.hex("#A6E22E"),
3019
- diffRemove: chalk5.hex("#F92672"),
3020
- diffHeader: chalk5.hex("#66D9EF"),
3021
- mdH1: chalk5.hex("#F92672").bold,
3022
- mdH2: chalk5.hex("#A6E22E"),
3023
- mdH3: chalk5.hex("#66D9EF"),
3024
- mdCode: chalk5.hex("#E6DB74"),
3025
- mdInlineCode: chalk5.hex("#A6E22E"),
3026
- mdLink: chalk5.hex("#66D9EF").underline,
3027
- meterLow: chalk5.hex("#A6E22E"),
3028
- meterMid: chalk5.hex("#E6DB74"),
3029
- meterHigh: chalk5.hex("#F92672")
3936
+ error: chalk6.hex("#F92672"),
3937
+ info: chalk6.hex("#66D9EF"),
3938
+ toolName: chalk6.hex("#A6E22E"),
3939
+ toolArgs: chalk6.hex("#75715E"),
3940
+ toolResult: chalk6.hex("#3E3D32"),
3941
+ diffAdd: chalk6.hex("#A6E22E"),
3942
+ diffRemove: chalk6.hex("#F92672"),
3943
+ diffHeader: chalk6.hex("#66D9EF"),
3944
+ mdH1: chalk6.hex("#F92672").bold,
3945
+ mdH2: chalk6.hex("#A6E22E"),
3946
+ mdH3: chalk6.hex("#66D9EF"),
3947
+ mdCode: chalk6.hex("#E6DB74"),
3948
+ mdInlineCode: chalk6.hex("#A6E22E"),
3949
+ mdLink: chalk6.hex("#66D9EF").underline,
3950
+ meterLow: chalk6.hex("#A6E22E"),
3951
+ meterMid: chalk6.hex("#E6DB74"),
3952
+ meterHigh: chalk6.hex("#F92672")
3030
3953
  };
3031
3954
  var oceanTheme = {
3032
3955
  name: "Ocean",
3033
3956
  description: "Deep sea \u2014 midnight blue, bioluminescent glow",
3034
- brand: chalk5.hex("#00B4D8"),
3957
+ brand: chalk6.hex("#00B4D8"),
3035
3958
  // cerulean
3036
- mascot: chalk5.hex("#0077B6"),
3959
+ mascot: chalk6.hex("#0077B6"),
3037
3960
  // deep blue
3038
- mascotAccent: chalk5.hex("#90E0EF"),
3961
+ mascotAccent: chalk6.hex("#90E0EF"),
3039
3962
  // light blue (bioluminescent)
3040
- tagline: chalk5.hex("#023E8A"),
3963
+ tagline: chalk6.hex("#023E8A"),
3041
3964
  // navy
3042
- prompt: chalk5.hex("#00B4D8"),
3043
- border: chalk5.hex("#03045E"),
3965
+ prompt: chalk6.hex("#00B4D8"),
3966
+ border: chalk6.hex("#03045E"),
3044
3967
  // deep navy
3045
- dim: chalk5.hex("#0077B6"),
3046
- text: chalk5.hex("#CAF0F8"),
3968
+ dim: chalk6.hex("#0077B6"),
3969
+ text: chalk6.hex("#CAF0F8"),
3047
3970
  // lightest blue
3048
- bold: chalk5.hex("#CAF0F8").bold,
3049
- success: chalk5.hex("#2DC653"),
3971
+ bold: chalk6.hex("#CAF0F8").bold,
3972
+ success: chalk6.hex("#2DC653"),
3050
3973
  // kelp green
3051
- warning: chalk5.hex("#F4A261"),
3974
+ warning: chalk6.hex("#F4A261"),
3052
3975
  // sandy
3053
- error: chalk5.hex("#E76F51"),
3976
+ error: chalk6.hex("#E76F51"),
3054
3977
  // coral
3055
- info: chalk5.hex("#90E0EF"),
3056
- toolName: chalk5.hex("#00B4D8"),
3057
- toolArgs: chalk5.hex("#0077B6"),
3058
- toolResult: chalk5.hex("#03045E"),
3059
- diffAdd: chalk5.hex("#2DC653"),
3060
- diffRemove: chalk5.hex("#E76F51"),
3061
- diffHeader: chalk5.hex("#00B4D8"),
3062
- mdH1: chalk5.hex("#90E0EF").bold,
3063
- mdH2: chalk5.hex("#00B4D8"),
3064
- mdH3: chalk5.hex("#0077B6"),
3065
- mdCode: chalk5.hex("#90E0EF"),
3066
- mdInlineCode: chalk5.hex("#00B4D8"),
3067
- mdLink: chalk5.hex("#90E0EF").underline,
3068
- meterLow: chalk5.hex("#00B4D8"),
3069
- meterMid: chalk5.hex("#F4A261"),
3070
- meterHigh: chalk5.hex("#E76F51")
3978
+ info: chalk6.hex("#90E0EF"),
3979
+ toolName: chalk6.hex("#00B4D8"),
3980
+ toolArgs: chalk6.hex("#0077B6"),
3981
+ toolResult: chalk6.hex("#03045E"),
3982
+ diffAdd: chalk6.hex("#2DC653"),
3983
+ diffRemove: chalk6.hex("#E76F51"),
3984
+ diffHeader: chalk6.hex("#00B4D8"),
3985
+ mdH1: chalk6.hex("#90E0EF").bold,
3986
+ mdH2: chalk6.hex("#00B4D8"),
3987
+ mdH3: chalk6.hex("#0077B6"),
3988
+ mdCode: chalk6.hex("#90E0EF"),
3989
+ mdInlineCode: chalk6.hex("#00B4D8"),
3990
+ mdLink: chalk6.hex("#90E0EF").underline,
3991
+ meterLow: chalk6.hex("#00B4D8"),
3992
+ meterMid: chalk6.hex("#F4A261"),
3993
+ meterHigh: chalk6.hex("#E76F51")
3071
3994
  };
3072
3995
  var emberTheme = {
3073
3996
  name: "Ember",
3074
3997
  description: "Fire forged \u2014 charcoal, ember orange, flame red",
3075
- brand: chalk5.hex("#FF6B35"),
3998
+ brand: chalk6.hex("#FF6B35"),
3076
3999
  // flame orange
3077
- mascot: chalk5.hex("#D62828"),
4000
+ mascot: chalk6.hex("#D62828"),
3078
4001
  // deep red
3079
- mascotAccent: chalk5.hex("#FFD166"),
4002
+ mascotAccent: chalk6.hex("#FFD166"),
3080
4003
  // bright flame
3081
- tagline: chalk5.hex("#8B4513"),
4004
+ tagline: chalk6.hex("#8B4513"),
3082
4005
  // saddle brown
3083
- prompt: chalk5.hex("#FF6B35"),
3084
- border: chalk5.hex("#2B2B2B"),
4006
+ prompt: chalk6.hex("#FF6B35"),
4007
+ border: chalk6.hex("#2B2B2B"),
3085
4008
  // charcoal
3086
- dim: chalk5.hex("#6B3A2A"),
3087
- text: chalk5.hex("#F4E4C1"),
4009
+ dim: chalk6.hex("#6B3A2A"),
4010
+ text: chalk6.hex("#F4E4C1"),
3088
4011
  // warm parchment
3089
- bold: chalk5.hex("#FFF5E1").bold,
3090
- success: chalk5.hex("#FFD166"),
4012
+ bold: chalk6.hex("#FFF5E1").bold,
4013
+ success: chalk6.hex("#FFD166"),
3091
4014
  // bright flame = success
3092
- warning: chalk5.hex("#FF6B35"),
3093
- error: chalk5.hex("#D62828"),
3094
- info: chalk5.hex("#F4845F"),
4015
+ warning: chalk6.hex("#FF6B35"),
4016
+ error: chalk6.hex("#D62828"),
4017
+ info: chalk6.hex("#F4845F"),
3095
4018
  // soft coral
3096
- toolName: chalk5.hex("#FF6B35"),
3097
- toolArgs: chalk5.hex("#6B3A2A"),
3098
- toolResult: chalk5.hex("#2B2B2B"),
3099
- diffAdd: chalk5.hex("#FFD166"),
3100
- diffRemove: chalk5.hex("#D62828"),
3101
- diffHeader: chalk5.hex("#FF6B35"),
3102
- mdH1: chalk5.hex("#FF6B35").bold,
3103
- mdH2: chalk5.hex("#F4845F"),
3104
- mdH3: chalk5.hex("#8B4513"),
3105
- mdCode: chalk5.hex("#FFD166"),
3106
- mdInlineCode: chalk5.hex("#FF6B35"),
3107
- mdLink: chalk5.hex("#F4845F").underline,
3108
- meterLow: chalk5.hex("#FFD166"),
3109
- meterMid: chalk5.hex("#FF6B35"),
3110
- meterHigh: chalk5.hex("#D62828")
4019
+ toolName: chalk6.hex("#FF6B35"),
4020
+ toolArgs: chalk6.hex("#6B3A2A"),
4021
+ toolResult: chalk6.hex("#2B2B2B"),
4022
+ diffAdd: chalk6.hex("#FFD166"),
4023
+ diffRemove: chalk6.hex("#D62828"),
4024
+ diffHeader: chalk6.hex("#FF6B35"),
4025
+ mdH1: chalk6.hex("#FF6B35").bold,
4026
+ mdH2: chalk6.hex("#F4845F"),
4027
+ mdH3: chalk6.hex("#8B4513"),
4028
+ mdCode: chalk6.hex("#FFD166"),
4029
+ mdInlineCode: chalk6.hex("#FF6B35"),
4030
+ mdLink: chalk6.hex("#F4845F").underline,
4031
+ meterLow: chalk6.hex("#FFD166"),
4032
+ meterMid: chalk6.hex("#FF6B35"),
4033
+ meterHigh: chalk6.hex("#D62828")
3111
4034
  };
3112
4035
  var ghostTheme = {
3113
4036
  name: "Ghost",
3114
4037
  description: "Monochrome \u2014 pure grayscale minimalism",
3115
- brand: chalk5.hex("#E0E0E0"),
3116
- mascot: chalk5.hex("#AAAAAA"),
3117
- mascotAccent: chalk5.hex("#FFFFFF"),
3118
- tagline: chalk5.hex("#666666"),
3119
- prompt: chalk5.hex("#CCCCCC"),
3120
- border: chalk5.hex("#333333"),
3121
- dim: chalk5.hex("#555555"),
3122
- text: chalk5.hex("#CCCCCC"),
3123
- bold: chalk5.hex("#FFFFFF").bold,
3124
- success: chalk5.hex("#AAAAAA"),
3125
- warning: chalk5.hex("#CCCCCC"),
3126
- error: chalk5.hex("#FFFFFF"),
3127
- info: chalk5.hex("#999999"),
3128
- toolName: chalk5.hex("#AAAAAA"),
3129
- toolArgs: chalk5.hex("#666666"),
3130
- toolResult: chalk5.hex("#444444"),
3131
- diffAdd: chalk5.hex("#CCCCCC"),
3132
- diffRemove: chalk5.hex("#777777").strikethrough,
3133
- diffHeader: chalk5.hex("#E0E0E0"),
3134
- mdH1: chalk5.hex("#FFFFFF").bold,
3135
- mdH2: chalk5.hex("#CCCCCC"),
3136
- mdH3: chalk5.hex("#AAAAAA"),
3137
- mdCode: chalk5.hex("#BBBBBB"),
3138
- mdInlineCode: chalk5.hex("#CCCCCC"),
3139
- mdLink: chalk5.hex("#E0E0E0").underline,
3140
- meterLow: chalk5.hex("#AAAAAA"),
3141
- meterMid: chalk5.hex("#CCCCCC"),
3142
- meterHigh: chalk5.hex("#FFFFFF")
4038
+ brand: chalk6.hex("#E0E0E0"),
4039
+ mascot: chalk6.hex("#AAAAAA"),
4040
+ mascotAccent: chalk6.hex("#FFFFFF"),
4041
+ tagline: chalk6.hex("#666666"),
4042
+ prompt: chalk6.hex("#CCCCCC"),
4043
+ border: chalk6.hex("#333333"),
4044
+ dim: chalk6.hex("#555555"),
4045
+ text: chalk6.hex("#CCCCCC"),
4046
+ bold: chalk6.hex("#FFFFFF").bold,
4047
+ success: chalk6.hex("#AAAAAA"),
4048
+ warning: chalk6.hex("#CCCCCC"),
4049
+ error: chalk6.hex("#FFFFFF"),
4050
+ info: chalk6.hex("#999999"),
4051
+ toolName: chalk6.hex("#AAAAAA"),
4052
+ toolArgs: chalk6.hex("#666666"),
4053
+ toolResult: chalk6.hex("#444444"),
4054
+ diffAdd: chalk6.hex("#CCCCCC"),
4055
+ diffRemove: chalk6.hex("#777777").strikethrough,
4056
+ diffHeader: chalk6.hex("#E0E0E0"),
4057
+ mdH1: chalk6.hex("#FFFFFF").bold,
4058
+ mdH2: chalk6.hex("#CCCCCC"),
4059
+ mdH3: chalk6.hex("#AAAAAA"),
4060
+ mdCode: chalk6.hex("#BBBBBB"),
4061
+ mdInlineCode: chalk6.hex("#CCCCCC"),
4062
+ mdLink: chalk6.hex("#E0E0E0").underline,
4063
+ meterLow: chalk6.hex("#AAAAAA"),
4064
+ meterMid: chalk6.hex("#CCCCCC"),
4065
+ meterHigh: chalk6.hex("#FFFFFF")
3143
4066
  };
3144
4067
  var THEME_CATALOG = {
3145
4068
  "default": defaultTheme,
@@ -3178,11 +4101,11 @@ function formatThemeList(activeId) {
3178
4101
  for (const id of THEME_IDS) {
3179
4102
  const t = THEME_CATALOG[id];
3180
4103
  const active = id === activeId ? t.success(" \u25CF") : " ";
3181
- const name = id === activeId ? t.bold(t.name) : chalk5.gray(t.name);
3182
- const desc = chalk5.gray(t.description);
3183
- lines.push(` ${active} ${chalk5.hex("#888")(id.padEnd(14))} ${name} ${desc}`);
4104
+ const name = id === activeId ? t.bold(t.name) : chalk6.gray(t.name);
4105
+ const desc = chalk6.gray(t.description);
4106
+ lines.push(` ${active} ${chalk6.hex("#888")(id.padEnd(14))} ${name} ${desc}`);
3184
4107
  }
3185
- lines.push(chalk5.gray(`
4108
+ lines.push(chalk6.gray(`
3186
4109
  Switch with: /theme <name>
3187
4110
  `));
3188
4111
  return lines.join("\n");
@@ -3220,7 +4143,7 @@ function colorBannerLine(line, rowIndex) {
3220
4143
  return t.brand(line);
3221
4144
  }
3222
4145
  const color = ROW_COLORS[rowIndex] ?? "#CCCCCC";
3223
- return chalk6.hex(color)(line);
4146
+ return chalk7.hex(color)(line);
3224
4147
  }
3225
4148
  var MANTIS = [
3226
4149
  " \u2571\u25C9\u25C9\u2572",
@@ -3326,12 +4249,13 @@ function formatTokens(n) {
3326
4249
  }
3327
4250
 
3328
4251
  // src/ui/update-checker.ts
3329
- import fs12 from "fs/promises";
3330
- import path13 from "path";
3331
- import os3 from "os";
3332
- import chalk7 from "chalk";
3333
- var CACHE_FILE = path13.join(os3.homedir(), ".notch", "update-check.json");
3334
- var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
4252
+ import fs15 from "fs/promises";
4253
+ import path17 from "path";
4254
+ import os4 from "os";
4255
+ import { execSync as execSync4 } from "child_process";
4256
+ import chalk8 from "chalk";
4257
+ var CACHE_FILE = path17.join(os4.homedir(), ".notch", "update-check.json");
4258
+ var CHECK_INTERVAL = 60 * 60 * 1e3;
3335
4259
  var PACKAGE_NAME = "@freesyntax/notch-cli";
3336
4260
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
3337
4261
  async function checkForUpdates(currentVersion) {
@@ -3339,13 +4263,12 @@ async function checkForUpdates(currentVersion) {
3339
4263
  const cache = await loadCache();
3340
4264
  if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL) {
3341
4265
  if (cache.latestVersion && isNewer(cache.latestVersion, currentVersion)) {
3342
- return formatUpdateMessage(currentVersion, cache.latestVersion);
4266
+ return autoUpdate(currentVersion, cache.latestVersion);
3343
4267
  }
3344
4268
  return null;
3345
4269
  }
3346
4270
  const response = await fetch(REGISTRY_URL, {
3347
- signal: AbortSignal.timeout(3e3),
3348
- // Fast timeout — don't slow down startup
4271
+ signal: AbortSignal.timeout(5e3),
3349
4272
  headers: { "Accept": "application/json" }
3350
4273
  });
3351
4274
  if (!response.ok) {
@@ -3356,13 +4279,34 @@ async function checkForUpdates(currentVersion) {
3356
4279
  const latest = data.version;
3357
4280
  await saveCache({ lastCheck: Date.now(), latestVersion: latest });
3358
4281
  if (isNewer(latest, currentVersion)) {
3359
- return formatUpdateMessage(currentVersion, latest);
4282
+ return autoUpdate(currentVersion, latest);
3360
4283
  }
3361
4284
  return null;
3362
4285
  } catch {
3363
4286
  return null;
3364
4287
  }
3365
4288
  }
4289
+ function autoUpdate(current, latest) {
4290
+ console.log(chalk8.cyan(`
4291
+ \u2B06 Updating Notch CLI: ${current} \u2192 ${latest}...
4292
+ `));
4293
+ try {
4294
+ execSync4(`npm install -g ${PACKAGE_NAME}@${latest}`, {
4295
+ stdio: "inherit",
4296
+ timeout: 6e4
4297
+ });
4298
+ console.log(chalk8.green(`
4299
+ \u2713 Updated to v${latest}. Restarting...
4300
+ `));
4301
+ const args = process.argv.slice(2);
4302
+ const { spawnSync } = __require("child_process");
4303
+ const result = spawnSync("notch", args, { stdio: "inherit" });
4304
+ process.exit(result.status ?? 0);
4305
+ } catch {
4306
+ return chalk8.yellow(` Update available: ${current} \u2192 ${latest}. Run: npm install -g ${PACKAGE_NAME}@latest
4307
+ `);
4308
+ }
4309
+ }
3366
4310
  function isNewer(latest, current) {
3367
4311
  const l = latest.split(".").map(Number);
3368
4312
  const c = current.split(".").map(Number);
@@ -3372,13 +4316,9 @@ function isNewer(latest, current) {
3372
4316
  }
3373
4317
  return false;
3374
4318
  }
3375
- function formatUpdateMessage(current, latest) {
3376
- return chalk7.yellow(` Update available: ${current} \u2192 ${latest}. Run: npm install -g ${PACKAGE_NAME}@latest
3377
- `);
3378
- }
3379
4319
  async function loadCache() {
3380
4320
  try {
3381
- const raw = await fs12.readFile(CACHE_FILE, "utf-8");
4321
+ const raw = await fs15.readFile(CACHE_FILE, "utf-8");
3382
4322
  return JSON.parse(raw);
3383
4323
  } catch {
3384
4324
  return null;
@@ -3386,16 +4326,16 @@ async function loadCache() {
3386
4326
  }
3387
4327
  async function saveCache(cache) {
3388
4328
  try {
3389
- await fs12.mkdir(path13.dirname(CACHE_FILE), { recursive: true });
3390
- await fs12.writeFile(CACHE_FILE, JSON.stringify(cache), "utf-8");
4329
+ await fs15.mkdir(path17.dirname(CACHE_FILE), { recursive: true });
4330
+ await fs15.writeFile(CACHE_FILE, JSON.stringify(cache), "utf-8");
3391
4331
  } catch {
3392
4332
  }
3393
4333
  }
3394
4334
 
3395
4335
  // src/permissions/index.ts
3396
- import fs13 from "fs/promises";
3397
- import path14 from "path";
3398
- import os4 from "os";
4336
+ import fs16 from "fs/promises";
4337
+ import path18 from "path";
4338
+ import os5 from "os";
3399
4339
  var DEFAULT_PERMISSIONS = {
3400
4340
  default: "prompt",
3401
4341
  rules: [
@@ -3412,11 +4352,11 @@ var DEFAULT_PERMISSIONS = {
3412
4352
  ]
3413
4353
  };
3414
4354
  async function loadPermissions(projectRoot) {
3415
- const projectPath = path14.join(projectRoot, ".notch.json");
3416
- const globalPath = path14.join(os4.homedir(), ".notch", "permissions.json");
4355
+ const projectPath = path18.join(projectRoot, ".notch.json");
4356
+ const globalPath = path18.join(os5.homedir(), ".notch", "permissions.json");
3417
4357
  let config = { ...DEFAULT_PERMISSIONS };
3418
4358
  try {
3419
- const raw = await fs13.readFile(globalPath, "utf-8");
4359
+ const raw = await fs16.readFile(globalPath, "utf-8");
3420
4360
  const parsed = JSON.parse(raw);
3421
4361
  if (parsed.permissions) {
3422
4362
  config = mergePermissions(config, parsed.permissions);
@@ -3424,7 +4364,7 @@ async function loadPermissions(projectRoot) {
3424
4364
  } catch {
3425
4365
  }
3426
4366
  try {
3427
- const raw = await fs13.readFile(projectPath, "utf-8");
4367
+ const raw = await fs16.readFile(projectPath, "utf-8");
3428
4368
  const parsed = JSON.parse(raw);
3429
4369
  if (parsed.permissions) {
3430
4370
  config = mergePermissions(config, parsed.permissions);
@@ -3470,17 +4410,18 @@ function mergePermissions(base, override) {
3470
4410
  }
3471
4411
 
3472
4412
  // src/hooks/index.ts
3473
- import { execSync as execSync3 } from "child_process";
3474
- import fs14 from "fs/promises";
3475
- import path15 from "path";
3476
- import os5 from "os";
4413
+ import { execSync as execSync5 } from "child_process";
4414
+ import fs17 from "fs/promises";
4415
+ import { watch } from "fs";
4416
+ import path19 from "path";
4417
+ import os6 from "os";
3477
4418
  import crypto from "crypto";
3478
- var TRUST_STORE_PATH = path15.join(os5.homedir(), ".notch", "trusted-projects.json");
4419
+ var TRUST_STORE_PATH = path19.join(os6.homedir(), ".notch", "trusted-projects.json");
3479
4420
  async function isTrustedProject(projectRoot, raw) {
3480
4421
  const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
3481
- const key = path15.resolve(projectRoot);
4422
+ const key = path19.resolve(projectRoot);
3482
4423
  try {
3483
- const store = JSON.parse(await fs14.readFile(TRUST_STORE_PATH, "utf-8"));
4424
+ const store = JSON.parse(await fs17.readFile(TRUST_STORE_PATH, "utf-8"));
3484
4425
  return store[key] === fingerprint;
3485
4426
  } catch {
3486
4427
  return false;
@@ -3488,30 +4429,30 @@ async function isTrustedProject(projectRoot, raw) {
3488
4429
  }
3489
4430
  async function trustProject(projectRoot, raw) {
3490
4431
  const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
3491
- const key = path15.resolve(projectRoot);
4432
+ const key = path19.resolve(projectRoot);
3492
4433
  let store = {};
3493
4434
  try {
3494
- store = JSON.parse(await fs14.readFile(TRUST_STORE_PATH, "utf-8"));
4435
+ store = JSON.parse(await fs17.readFile(TRUST_STORE_PATH, "utf-8"));
3495
4436
  } catch {
3496
4437
  }
3497
4438
  store[key] = fingerprint;
3498
- await fs14.mkdir(path15.dirname(TRUST_STORE_PATH), { recursive: true });
3499
- await fs14.writeFile(TRUST_STORE_PATH, JSON.stringify(store, null, 2));
4439
+ await fs17.mkdir(path19.dirname(TRUST_STORE_PATH), { recursive: true });
4440
+ await fs17.writeFile(TRUST_STORE_PATH, JSON.stringify(store, null, 2));
3500
4441
  }
3501
4442
  async function loadHooks(projectRoot, promptTrust) {
3502
4443
  const hooks = [];
3503
- const globalPath = path15.join(os5.homedir(), ".notch", "hooks.json");
4444
+ const globalPath = path19.join(os6.homedir(), ".notch", "hooks.json");
3504
4445
  try {
3505
- const raw = await fs14.readFile(globalPath, "utf-8");
4446
+ const raw = await fs17.readFile(globalPath, "utf-8");
3506
4447
  const parsed = JSON.parse(raw);
3507
4448
  if (Array.isArray(parsed.hooks)) {
3508
4449
  hooks.push(...parsed.hooks);
3509
4450
  }
3510
4451
  } catch {
3511
4452
  }
3512
- const projectPath = path15.join(projectRoot, ".notch.json");
4453
+ const projectPath = path19.join(projectRoot, ".notch.json");
3513
4454
  try {
3514
- const raw = await fs14.readFile(projectPath, "utf-8");
4455
+ const raw = await fs17.readFile(projectPath, "utf-8");
3515
4456
  const parsed = JSON.parse(raw);
3516
4457
  if (Array.isArray(parsed.hooks) && parsed.hooks.length > 0) {
3517
4458
  const alreadyTrusted = await isTrustedProject(projectRoot, raw);
@@ -3533,7 +4474,8 @@ async function loadHooks(projectRoot, promptTrust) {
3533
4474
  return { hooks };
3534
4475
  }
3535
4476
  async function runHooks(config, event, context) {
3536
- const matching = config.hooks.filter((h) => {
4477
+ const allHooks = [...config.hooks, ...pluginManager.getHooks()];
4478
+ const matching = allHooks.filter((h) => {
3537
4479
  if (h.event !== event) return false;
3538
4480
  if (h.tool && h.tool !== context.tool) return false;
3539
4481
  if (h.pattern && context.file && !context.file.includes(h.pattern)) return false;
@@ -3576,7 +4518,7 @@ async function executeHook(hook, context) {
3576
4518
  NOTCH_CWD: context.cwd
3577
4519
  };
3578
4520
  try {
3579
- const output = execSync3(hook.command, {
4521
+ const output = execSync5(hook.command, {
3580
4522
  cwd: context.cwd,
3581
4523
  encoding: "utf-8",
3582
4524
  timeout: hook.timeout ?? 1e4,
@@ -3593,18 +4535,48 @@ async function executeHook(hook, context) {
3593
4535
  };
3594
4536
  }
3595
4537
  }
4538
+ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
4539
+ const debounceMs = 150;
4540
+ const pending = /* @__PURE__ */ new Map();
4541
+ let watcher = null;
4542
+ try {
4543
+ watcher = watch(projectRoot, { recursive: true }, (_eventType, filename) => {
4544
+ if (!filename) return;
4545
+ if (filename.startsWith(".git/") || filename.startsWith(".git\\") || filename.includes("node_modules") || filename.startsWith("dist/") || filename.startsWith("dist\\")) return;
4546
+ const existing = pending.get(filename);
4547
+ if (existing) clearTimeout(existing);
4548
+ pending.set(filename, setTimeout(async () => {
4549
+ pending.delete(filename);
4550
+ const filePath = path19.join(projectRoot, filename);
4551
+ const context = { cwd: projectRoot, file: filePath };
4552
+ const { results } = await runHooks(hookConfig, "file-changed", context);
4553
+ onHookResult?.("file-changed", results);
4554
+ if (filename === ".notch.json") {
4555
+ const { results: cfgResults } = await runHooks(hookConfig, "config-change", context);
4556
+ onHookResult?.("config-change", cfgResults);
4557
+ }
4558
+ }, debounceMs));
4559
+ });
4560
+ } catch {
4561
+ }
4562
+ return () => {
4563
+ watcher?.close();
4564
+ for (const timer of pending.values()) clearTimeout(timer);
4565
+ pending.clear();
4566
+ };
4567
+ }
3596
4568
 
3597
4569
  // src/session/index.ts
3598
- import fs15 from "fs/promises";
3599
- import path16 from "path";
3600
- import os6 from "os";
3601
- var SESSION_DIR = path16.join(os6.homedir(), ".notch", "sessions");
4570
+ import fs18 from "fs/promises";
4571
+ import path20 from "path";
4572
+ import os7 from "os";
4573
+ var SESSION_DIR = path20.join(os7.homedir(), ".notch", "sessions");
3602
4574
  var MAX_SESSIONS = 20;
3603
4575
  async function ensureDir2() {
3604
- await fs15.mkdir(SESSION_DIR, { recursive: true });
4576
+ await fs18.mkdir(SESSION_DIR, { recursive: true });
3605
4577
  }
3606
4578
  function sessionPath(id) {
3607
- return path16.join(SESSION_DIR, `${id}.json`);
4579
+ return path20.join(SESSION_DIR, `${id}.json`);
3608
4580
  }
3609
4581
  function generateId() {
3610
4582
  const now = /* @__PURE__ */ new Date();
@@ -3631,13 +4603,13 @@ async function saveSession(messages, project, model, existingId) {
3631
4603
  },
3632
4604
  messages
3633
4605
  };
3634
- await fs15.writeFile(sessionPath(id), JSON.stringify(session, null, 2), "utf-8");
4606
+ await fs18.writeFile(sessionPath(id), JSON.stringify(session, null, 2), "utf-8");
3635
4607
  await pruneOldSessions();
3636
4608
  return id;
3637
4609
  }
3638
4610
  async function loadSession(id) {
3639
4611
  try {
3640
- const raw = await fs15.readFile(sessionPath(id), "utf-8");
4612
+ const raw = await fs18.readFile(sessionPath(id), "utf-8");
3641
4613
  return JSON.parse(raw);
3642
4614
  } catch {
3643
4615
  return null;
@@ -3645,12 +4617,12 @@ async function loadSession(id) {
3645
4617
  }
3646
4618
  async function listSessions() {
3647
4619
  await ensureDir2();
3648
- const files = await fs15.readdir(SESSION_DIR);
4620
+ const files = await fs18.readdir(SESSION_DIR);
3649
4621
  const sessions = [];
3650
4622
  for (const file of files) {
3651
4623
  if (!file.endsWith(".json")) continue;
3652
4624
  try {
3653
- const raw = await fs15.readFile(path16.join(SESSION_DIR, file), "utf-8");
4625
+ const raw = await fs18.readFile(path20.join(SESSION_DIR, file), "utf-8");
3654
4626
  const session = JSON.parse(raw);
3655
4627
  sessions.push(session.meta);
3656
4628
  } catch {
@@ -3666,7 +4638,7 @@ async function loadLastSession(project) {
3666
4638
  }
3667
4639
  async function deleteSession(id) {
3668
4640
  try {
3669
- await fs15.unlink(sessionPath(id));
4641
+ await fs18.unlink(sessionPath(id));
3670
4642
  return true;
3671
4643
  } catch {
3672
4644
  return false;
@@ -3720,14 +4692,14 @@ async function exportSession(messages, outputPath, meta) {
3720
4692
  lines.push("");
3721
4693
  }
3722
4694
  }
3723
- await fs15.writeFile(outputPath, lines.join("\n"), "utf-8");
4695
+ await fs18.writeFile(outputPath, lines.join("\n"), "utf-8");
3724
4696
  return outputPath;
3725
4697
  }
3726
4698
 
3727
4699
  // src/init.ts
3728
- import fs16 from "fs/promises";
3729
- import path17 from "path";
3730
- import chalk8 from "chalk";
4700
+ import fs19 from "fs/promises";
4701
+ import path21 from "path";
4702
+ import chalk9 from "chalk";
3731
4703
  var DEFAULT_CONFIG = {
3732
4704
  model: "notch-forge",
3733
4705
  temperature: 0.3,
@@ -3758,47 +4730,47 @@ var DEFAULT_INSTRUCTIONS = `# Project Instructions for Notch
3758
4730
  <!-- Files or areas Notch should NOT modify -->
3759
4731
  `;
3760
4732
  async function initProject(projectRoot) {
3761
- const configPath = path17.join(projectRoot, ".notch.json");
3762
- const instructionsPath = path17.join(projectRoot, ".notch.md");
4733
+ const configPath = path21.join(projectRoot, ".notch.json");
4734
+ const instructionsPath = path21.join(projectRoot, ".notch.md");
3763
4735
  let configExists = false;
3764
4736
  let instructionsExist = false;
3765
4737
  try {
3766
- await fs16.access(configPath);
4738
+ await fs19.access(configPath);
3767
4739
  configExists = true;
3768
4740
  } catch {
3769
4741
  }
3770
4742
  try {
3771
- await fs16.access(instructionsPath);
4743
+ await fs19.access(instructionsPath);
3772
4744
  instructionsExist = true;
3773
4745
  } catch {
3774
4746
  }
3775
4747
  if (!configExists) {
3776
- await fs16.writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
3777
- console.log(chalk8.green(` Created ${configPath}`));
4748
+ await fs19.writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
4749
+ console.log(chalk9.green(` Created ${configPath}`));
3778
4750
  } else {
3779
- console.log(chalk8.gray(` Skipped ${configPath} (already exists)`));
4751
+ console.log(chalk9.gray(` Skipped ${configPath} (already exists)`));
3780
4752
  }
3781
4753
  if (!instructionsExist) {
3782
- await fs16.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS, "utf-8");
3783
- console.log(chalk8.green(` Created ${instructionsPath}`));
4754
+ await fs19.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS, "utf-8");
4755
+ console.log(chalk9.green(` Created ${instructionsPath}`));
3784
4756
  } else {
3785
- console.log(chalk8.gray(` Skipped ${instructionsPath} (already exists)`));
4757
+ console.log(chalk9.gray(` Skipped ${instructionsPath} (already exists)`));
3786
4758
  }
3787
- const gitignorePath = path17.join(projectRoot, ".gitignore");
4759
+ const gitignorePath = path21.join(projectRoot, ".gitignore");
3788
4760
  try {
3789
- const gitignore = await fs16.readFile(gitignorePath, "utf-8");
4761
+ const gitignore = await fs19.readFile(gitignorePath, "utf-8");
3790
4762
  const additions = [];
3791
4763
  if (!gitignore.includes(".notch.json")) additions.push(".notch.json");
3792
4764
  if (additions.length > 0) {
3793
4765
  const append = "\n# Notch CLI\n" + additions.join("\n") + "\n";
3794
- await fs16.appendFile(gitignorePath, append, "utf-8");
3795
- console.log(chalk8.green(` Updated .gitignore`));
4766
+ await fs19.appendFile(gitignorePath, append, "utf-8");
4767
+ console.log(chalk9.green(` Updated .gitignore`));
3796
4768
  }
3797
4769
  } catch {
3798
4770
  }
3799
4771
  console.log("");
3800
- console.log(chalk8.cyan(" Notch initialized! Edit .notch.md to customize behavior."));
3801
- console.log(chalk8.gray(' Run "notch" to start.\n'));
4772
+ console.log(chalk9.cyan(" Notch initialized! Edit .notch.md to customize behavior."));
4773
+ console.log(chalk9.gray(' Run "notch" to start.\n'));
3802
4774
  }
3803
4775
 
3804
4776
  // src/tools/diff-preview.ts
@@ -3917,9 +4889,1198 @@ function findSync(oldLines, newLines, oi, ni, lookAhead) {
3917
4889
  return null;
3918
4890
  }
3919
4891
 
4892
+ // src/commands/doctor.ts
4893
+ import { execSync as execSync6 } from "child_process";
4894
+ import fs20 from "fs/promises";
4895
+ import path22 from "path";
4896
+ import os8 from "os";
4897
+ import chalk10 from "chalk";
4898
+ async function runDiagnostics(cwd) {
4899
+ const results = [];
4900
+ const nodeVersion = process.versions.node;
4901
+ const major = parseInt(nodeVersion.split(".")[0], 10);
4902
+ if (major >= 20) {
4903
+ results.push({ name: "Node.js", status: "ok", message: `v${nodeVersion}` });
4904
+ } else if (major >= 18) {
4905
+ results.push({ name: "Node.js", status: "warn", message: `v${nodeVersion} (recommend >= 20)` });
4906
+ } else {
4907
+ results.push({ name: "Node.js", status: "fail", message: `v${nodeVersion} (requires >= 18)` });
4908
+ }
4909
+ try {
4910
+ const gitVersion = execSync6("git --version", { encoding: "utf-8", timeout: 5e3 }).trim();
4911
+ results.push({ name: "Git", status: "ok", message: gitVersion });
4912
+ } catch {
4913
+ results.push({ name: "Git", status: "fail", message: "Not found. Install git to use git tools." });
4914
+ }
4915
+ try {
4916
+ const config = await loadConfig({ projectRoot: cwd });
4917
+ const apiKey = config.models.chat.apiKey;
4918
+ if (apiKey) {
4919
+ results.push({ name: "API Key", status: "ok", message: `Set (${apiKey.slice(0, 8)}...)` });
4920
+ } else {
4921
+ results.push({ name: "API Key", status: "fail", message: "Not set. Run: notch login" });
4922
+ }
4923
+ try {
4924
+ const check = await validateConfig(config.models.chat);
4925
+ if (check.ok) {
4926
+ results.push({ name: "API Endpoint", status: "ok", message: `${config.models.chat.model} reachable` });
4927
+ } else {
4928
+ results.push({ name: "API Endpoint", status: "fail", message: check.error ?? "Unreachable" });
4929
+ }
4930
+ } catch (err) {
4931
+ results.push({ name: "API Endpoint", status: "fail", message: err.message });
4932
+ }
4933
+ } catch (err) {
4934
+ results.push({ name: "Config", status: "fail", message: `Could not load: ${err.message}` });
4935
+ }
4936
+ try {
4937
+ await fs20.access(path22.join(cwd, ".notch.json"));
4938
+ results.push({ name: ".notch.json", status: "ok", message: "Found" });
4939
+ } catch {
4940
+ results.push({ name: ".notch.json", status: "warn", message: "Not found. Run: notch init" });
4941
+ }
4942
+ const notchDir = path22.join(os8.homedir(), ".notch");
4943
+ try {
4944
+ await fs20.access(notchDir);
4945
+ results.push({ name: "~/.notch/", status: "ok", message: "Exists" });
4946
+ } catch {
4947
+ results.push({ name: "~/.notch/", status: "warn", message: "Not found (will be created on first use)" });
4948
+ }
4949
+ try {
4950
+ await fs20.access(path22.join(cwd, ".notch.md"));
4951
+ results.push({ name: ".notch.md", status: "ok", message: "Found" });
4952
+ } catch {
4953
+ results.push({ name: ".notch.md", status: "warn", message: "Not found. Run: notch init" });
4954
+ }
4955
+ try {
4956
+ const configRaw = await fs20.readFile(path22.join(cwd, ".notch.json"), "utf-8").catch(() => "{}");
4957
+ const mcpConfigs = parseMCPConfig(JSON.parse(configRaw));
4958
+ const serverNames = Object.keys(mcpConfigs);
4959
+ if (serverNames.length === 0) {
4960
+ results.push({ name: "MCP Servers", status: "ok", message: "None configured" });
4961
+ } else {
4962
+ for (const name of serverNames) {
4963
+ try {
4964
+ const client = new MCPClient(mcpConfigs[name], name);
4965
+ await client.connect();
4966
+ results.push({ name: `MCP: ${name}`, status: "ok", message: `${client.tools.length} tools` });
4967
+ client.disconnect();
4968
+ } catch (err) {
4969
+ results.push({ name: `MCP: ${name}`, status: "fail", message: err.message });
4970
+ }
4971
+ }
4972
+ }
4973
+ } catch {
4974
+ results.push({ name: "MCP Servers", status: "ok", message: "No config to check" });
4975
+ }
4976
+ try {
4977
+ const sessionsDir = path22.join(notchDir, "sessions");
4978
+ const entries = await fs20.readdir(sessionsDir).catch(() => []);
4979
+ results.push({ name: "Sessions", status: "ok", message: `${entries.length} saved` });
4980
+ } catch {
4981
+ results.push({ name: "Sessions", status: "ok", message: "0 saved" });
4982
+ }
4983
+ return results;
4984
+ }
4985
+ function formatDiagnostics(results) {
4986
+ const icons = { ok: chalk10.green("\u2713"), warn: chalk10.yellow("!"), fail: chalk10.red("\u2717") };
4987
+ const lines = results.map((r) => {
4988
+ const icon = icons[r.status];
4989
+ const name = r.name.padEnd(16);
4990
+ return ` ${icon} ${name} ${chalk10.gray(r.message)}`;
4991
+ });
4992
+ return "\n" + lines.join("\n") + "\n";
4993
+ }
4994
+ registerCommand("/doctor", async (_args, ctx) => {
4995
+ ctx.log("Running diagnostics...");
4996
+ const results = await runDiagnostics(ctx.cwd);
4997
+ console.log(formatDiagnostics(results));
4998
+ const failCount = results.filter((r) => r.status === "fail").length;
4999
+ const warnCount = results.filter((r) => r.status === "warn").length;
5000
+ if (failCount > 0) {
5001
+ console.log(chalk10.red(` ${failCount} issue(s) need attention.
5002
+ `));
5003
+ } else if (warnCount > 0) {
5004
+ console.log(chalk10.yellow(` All clear with ${warnCount} suggestion(s).
5005
+ `));
5006
+ } else {
5007
+ console.log(chalk10.green(" Everything looks good!\n"));
5008
+ }
5009
+ });
5010
+
5011
+ // src/commands/copy.ts
5012
+ import { execSync as execSync7 } from "child_process";
5013
+ import chalk11 from "chalk";
5014
+ function copyToClipboard(text) {
5015
+ try {
5016
+ const platform = process.platform;
5017
+ if (platform === "win32") {
5018
+ execSync7("clip.exe", { input: text, timeout: 5e3 });
5019
+ } else if (platform === "darwin") {
5020
+ execSync7("pbcopy", { input: text, timeout: 5e3 });
5021
+ } else {
5022
+ try {
5023
+ execSync7("xclip -selection clipboard", { input: text, timeout: 5e3 });
5024
+ } catch {
5025
+ try {
5026
+ execSync7("xsel --clipboard --input", { input: text, timeout: 5e3 });
5027
+ } catch {
5028
+ execSync7("wl-copy", { input: text, timeout: 5e3 });
5029
+ }
5030
+ }
5031
+ }
5032
+ return true;
5033
+ } catch {
5034
+ return false;
5035
+ }
5036
+ }
5037
+ registerCommand("/copy", async (_args, ctx) => {
5038
+ if (!ctx.lastResponse) {
5039
+ console.log(chalk11.gray(" Nothing to copy yet.\n"));
5040
+ return;
5041
+ }
5042
+ if (copyToClipboard(ctx.lastResponse)) {
5043
+ const preview = ctx.lastResponse.slice(0, 60).replace(/\n/g, " ");
5044
+ console.log(chalk11.green(` \u2713 Copied to clipboard`) + chalk11.gray(` (${ctx.lastResponse.length} chars)`));
5045
+ console.log(chalk11.gray(` "${preview}${ctx.lastResponse.length > 60 ? "..." : ""}"
5046
+ `));
5047
+ } else {
5048
+ console.log(chalk11.red(" Clipboard not available."));
5049
+ console.log(chalk11.gray(" Install xclip (Linux) or ensure clip.exe/pbcopy is available.\n"));
5050
+ }
5051
+ });
5052
+
5053
+ // src/commands/btw.ts
5054
+ import chalk12 from "chalk";
5055
+ registerCommand("/btw", async (args, ctx) => {
5056
+ if (!args) {
5057
+ console.log(chalk12.gray(" Usage: /btw <question>\n"));
5058
+ return;
5059
+ }
5060
+ console.log(chalk12.gray(" (Side question \u2014 won't affect main conversation)\n"));
5061
+ const tempMessages = [
5062
+ { role: "user", content: args }
5063
+ ];
5064
+ try {
5065
+ await ctx.runPrompt(args, tempMessages);
5066
+ console.log(chalk12.gray("\n (Side question complete \u2014 context unchanged)\n"));
5067
+ } catch (err) {
5068
+ console.log(chalk12.red(` Side question failed: ${err.message}
5069
+ `));
5070
+ }
5071
+ });
5072
+
5073
+ // src/commands/security-review.ts
5074
+ import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
5075
+ import chalk13 from "chalk";
5076
+ function isValidGitRange(range) {
5077
+ return /^[a-zA-Z0-9._~^\/\-]+(\.\.[a-zA-Z0-9._~^\/\-]+)?$/.test(range);
5078
+ }
5079
+ registerCommand("/security-review", async (args, ctx) => {
5080
+ const range = args || "HEAD~5..HEAD";
5081
+ if (!isValidGitRange(range)) {
5082
+ console.log(chalk13.red(" Invalid git range. Use formats like: HEAD~5..HEAD, main..feature, abc123\n"));
5083
+ return;
5084
+ }
5085
+ let diff;
5086
+ let stat;
5087
+ try {
5088
+ stat = execFileSync2("git", ["diff", range, "--stat"], {
5089
+ cwd: ctx.cwd,
5090
+ encoding: "utf-8",
5091
+ timeout: 1e4
5092
+ }).trim();
5093
+ diff = execFileSync2("git", ["diff", range], {
5094
+ cwd: ctx.cwd,
5095
+ encoding: "utf-8",
5096
+ timeout: 1e4,
5097
+ maxBuffer: 1024 * 1024 * 5
5098
+ // 5MB
5099
+ }).trim();
5100
+ } catch {
5101
+ try {
5102
+ stat = execSync8("git diff --stat", {
5103
+ cwd: ctx.cwd,
5104
+ encoding: "utf-8",
5105
+ timeout: 1e4
5106
+ }).trim();
5107
+ diff = execSync8("git diff", {
5108
+ cwd: ctx.cwd,
5109
+ encoding: "utf-8",
5110
+ timeout: 1e4,
5111
+ maxBuffer: 1024 * 1024 * 5
5112
+ }).trim();
5113
+ } catch (err) {
5114
+ console.log(chalk13.red(` Could not get diff: ${err.message}
5115
+ `));
5116
+ return;
5117
+ }
5118
+ }
5119
+ if (!diff) {
5120
+ console.log(chalk13.gray(" No changes to review.\n"));
5121
+ return;
5122
+ }
5123
+ console.log(chalk13.gray(` Reviewing changes...
5124
+ `));
5125
+ console.log(chalk13.gray(` ${stat}
5126
+ `));
5127
+ const maxDiffLen = 5e4;
5128
+ const truncatedDiff = diff.length > maxDiffLen ? diff.slice(0, maxDiffLen) + "\n... (truncated)" : diff;
5129
+ const prompt = `Perform a thorough security review of these recent code changes. Use your tools to read full files if you need more context.
5130
+
5131
+ ## Changed Files
5132
+ ${stat}
5133
+
5134
+ ## Diff
5135
+ \`\`\`diff
5136
+ ${truncatedDiff}
5137
+ \`\`\`
5138
+
5139
+ ## Review Checklist
5140
+ Analyze for these vulnerability categories:
5141
+ 1. **Secrets & Credentials** \u2014 Hardcoded API keys, tokens, passwords, or connection strings
5142
+ 2. **Injection** \u2014 SQL injection, command injection, XSS, template injection
5143
+ 3. **Authentication & Authorization** \u2014 Broken auth, missing access controls, session issues
5144
+ 4. **Input Validation** \u2014 Missing or insufficient input validation at system boundaries
5145
+ 5. **Path Traversal** \u2014 Unsanitized file paths that could escape the project root
5146
+ 6. **Dependency Issues** \u2014 New dependencies with known vulnerabilities
5147
+ 7. **Cryptography** \u2014 Weak algorithms, hardcoded IVs/salts, insecure random
5148
+ 8. **Information Disclosure** \u2014 Verbose errors, stack traces in production, debug endpoints
5149
+ 9. **Race Conditions** \u2014 TOCTOU, unsafe concurrent access to shared state
5150
+ 10. **Configuration** \u2014 Insecure defaults, overly permissive CORS/CSP/permissions
5151
+
5152
+ For each finding, provide:
5153
+ - **Severity**: Critical / High / Medium / Low
5154
+ - **File and line**: Where the issue is
5155
+ - **Description**: What the vulnerability is
5156
+ - **Fix**: How to remediate it
5157
+
5158
+ If no issues are found, say so explicitly. Do not fabricate issues.`;
5159
+ ctx.messages.push({ role: "user", content: prompt });
5160
+ try {
5161
+ await ctx.runPrompt(prompt, ctx.messages);
5162
+ } catch (err) {
5163
+ console.log(chalk13.red(` Security review failed: ${err.message}
5164
+ `));
5165
+ }
5166
+ });
5167
+
5168
+ // src/commands/loop.ts
5169
+ import chalk14 from "chalk";
5170
+ var activeLoop = null;
5171
+ function stopActiveLoop() {
5172
+ if (activeLoop) {
5173
+ clearInterval(activeLoop.timer);
5174
+ activeLoop = null;
5175
+ }
5176
+ }
5177
+ function parseDuration(s) {
5178
+ const match = s.match(/^(\d+)(s|m|h)$/);
5179
+ if (!match) return null;
5180
+ const n = parseInt(match[1], 10);
5181
+ switch (match[2]) {
5182
+ case "s":
5183
+ return n * 1e3;
5184
+ case "m":
5185
+ return n * 6e4;
5186
+ case "h":
5187
+ return n * 36e5;
5188
+ default:
5189
+ return null;
5190
+ }
5191
+ }
5192
+ function formatDuration(ms) {
5193
+ if (ms >= 36e5) return `${ms / 36e5}h`;
5194
+ if (ms >= 6e4) return `${ms / 6e4}m`;
5195
+ return `${ms / 1e3}s`;
5196
+ }
5197
+ registerCommand("/loop", async (args, ctx) => {
5198
+ if (args === "stop" || args === "cancel") {
5199
+ if (activeLoop) {
5200
+ clearInterval(activeLoop.timer);
5201
+ console.log(chalk14.gray(` Stopped loop: ${activeLoop.command} (was every ${formatDuration(activeLoop.intervalMs)})
5202
+ `));
5203
+ activeLoop = null;
5204
+ } else {
5205
+ console.log(chalk14.gray(" No active loop.\n"));
5206
+ }
5207
+ return;
5208
+ }
5209
+ if (args === "status" || !args) {
5210
+ if (activeLoop) {
5211
+ console.log(chalk14.gray(` Active loop: ${chalk14.white(activeLoop.command)} every ${formatDuration(activeLoop.intervalMs)}
5212
+ `));
5213
+ console.log(chalk14.gray(" Stop with: /loop stop\n"));
5214
+ } else {
5215
+ console.log(chalk14.gray(" No active loop.\n"));
5216
+ console.log(chalk14.gray(" Usage: /loop <interval> <command>"));
5217
+ console.log(chalk14.gray(" Example: /loop 5m /status\n"));
5218
+ }
5219
+ return;
5220
+ }
5221
+ const parts = args.split(/\s+/);
5222
+ if (parts.length < 2) {
5223
+ console.log(chalk14.gray(" Usage: /loop <interval> <command>"));
5224
+ console.log(chalk14.gray(" Example: /loop 5m /status"));
5225
+ console.log(chalk14.gray(" Intervals: 30s, 5m, 1h\n"));
5226
+ return;
5227
+ }
5228
+ const intervalMs = parseDuration(parts[0]);
5229
+ if (!intervalMs) {
5230
+ console.log(chalk14.red(` Invalid interval: ${parts[0]}`));
5231
+ console.log(chalk14.gray(" Use formats like: 30s, 5m, 1h\n"));
5232
+ return;
5233
+ }
5234
+ if (intervalMs < 1e4) {
5235
+ console.log(chalk14.red(" Minimum interval is 10s.\n"));
5236
+ return;
5237
+ }
5238
+ const command = parts.slice(1).join(" ");
5239
+ if (!command.startsWith("/")) {
5240
+ console.log(chalk14.red(" Command must start with /\n"));
5241
+ return;
5242
+ }
5243
+ if (activeLoop) {
5244
+ clearInterval(activeLoop.timer);
5245
+ }
5246
+ const timer = setInterval(async () => {
5247
+ console.log(chalk14.gray(`
5248
+ [loop] Running: ${command}`));
5249
+ try {
5250
+ const handled = await dispatchCommand(command, ctx);
5251
+ if (!handled) {
5252
+ console.log(chalk14.gray(` [loop] Unknown command: ${command}`));
5253
+ }
5254
+ } catch (err) {
5255
+ console.log(chalk14.red(` [loop] Error: ${err.message}`));
5256
+ }
5257
+ }, intervalMs);
5258
+ activeLoop = { timer, command, intervalMs };
5259
+ console.log(chalk14.green(` \u2713 Loop started: ${chalk14.white(command)} every ${formatDuration(intervalMs)}`));
5260
+ console.log(chalk14.gray(" Stop with: /loop stop\n"));
5261
+ });
5262
+
5263
+ // src/commands/batch.ts
5264
+ import chalk15 from "chalk";
5265
+ import ora from "ora";
5266
+ var MAX_CONCURRENT = 3;
5267
+ async function pLimit(tasks2, limit) {
5268
+ const results = [];
5269
+ let idx = 0;
5270
+ async function runNext() {
5271
+ while (idx < tasks2.length) {
5272
+ const i = idx++;
5273
+ try {
5274
+ const value = await tasks2[i]();
5275
+ results[i] = { status: "fulfilled", value };
5276
+ } catch (reason) {
5277
+ results[i] = { status: "rejected", reason };
5278
+ }
5279
+ }
5280
+ }
5281
+ const workers = Array.from({ length: Math.min(limit, tasks2.length) }, () => runNext());
5282
+ await Promise.all(workers);
5283
+ return results;
5284
+ }
5285
+ registerCommand("/batch", async (args, ctx) => {
5286
+ if (!args) {
5287
+ console.log(chalk15.gray(" Usage: /batch <task description>"));
5288
+ console.log(chalk15.gray(" Example: /batch add JSDoc to all exported functions"));
5289
+ console.log(chalk15.gray(" Example: /batch convert var to const in src/**/*.js\n"));
5290
+ return;
5291
+ }
5292
+ const spinner = ora("Identifying target files...").start();
5293
+ const discoveryPrompt = `I need to apply this change across multiple files: "${args}"
5294
+
5295
+ List ONLY the file paths that need this change, one per line. Use the glob and grep tools to find them. Output the final list as a simple newline-separated list of file paths with no other text, wrapped in <files> tags like:
5296
+
5297
+ <files>
5298
+ src/foo.ts
5299
+ src/bar.ts
5300
+ </files>`;
5301
+ const tempMessages = [
5302
+ { role: "user", content: discoveryPrompt }
5303
+ ];
5304
+ let fileList = [];
5305
+ try {
5306
+ const responseText = await ctx.runPrompt(discoveryPrompt, tempMessages);
5307
+ spinner.stop();
5308
+ const match = responseText.match(/<files>\s*([\s\S]*?)\s*<\/files>/);
5309
+ if (match) {
5310
+ fileList = match[1].split("\n").map((f) => f.trim()).filter(Boolean);
5311
+ }
5312
+ if (fileList.length === 0) {
5313
+ console.log(chalk15.yellow(" No target files identified.\n"));
5314
+ return;
5315
+ }
5316
+ } catch (err) {
5317
+ spinner.fail(`Failed to identify files: ${err.message}`);
5318
+ return;
5319
+ }
5320
+ console.log(chalk15.gray(`
5321
+ Found ${fileList.length} file(s) to process:
5322
+ `));
5323
+ for (const f of fileList) {
5324
+ console.log(chalk15.gray(` \u2022 ${f}`));
5325
+ }
5326
+ console.log("");
5327
+ const batchSpinner = ora(`Processing 0/${fileList.length} files...`).start();
5328
+ let completed = 0;
5329
+ const tasks2 = fileList.map((file) => async () => {
5330
+ const fileMessages = [
5331
+ {
5332
+ role: "user",
5333
+ content: `Apply this change to the file "${file}": ${args}
5334
+
5335
+ Read the file first, then make the change. Only modify this one file.`
5336
+ }
5337
+ ];
5338
+ try {
5339
+ await ctx.runPrompt(fileMessages[0].content, fileMessages);
5340
+ completed++;
5341
+ batchSpinner.text = `Processing ${completed}/${fileList.length} files...`;
5342
+ return { file, ok: true };
5343
+ } catch (err) {
5344
+ completed++;
5345
+ batchSpinner.text = `Processing ${completed}/${fileList.length} files...`;
5346
+ return { file, ok: false, error: err.message };
5347
+ }
5348
+ });
5349
+ const results = await pLimit(tasks2, MAX_CONCURRENT);
5350
+ batchSpinner.stop();
5351
+ console.log(chalk15.gray("\n Batch Results:\n"));
5352
+ let successes = 0;
5353
+ let failures = 0;
5354
+ for (const r of results) {
5355
+ if (r.status === "fulfilled") {
5356
+ if (r.value.ok) {
5357
+ console.log(chalk15.green(` \u2713 ${r.value.file}`));
5358
+ successes++;
5359
+ } else {
5360
+ console.log(chalk15.red(` \u2717 ${r.value.file}: ${r.value.error}`));
5361
+ failures++;
5362
+ }
5363
+ } else {
5364
+ console.log(chalk15.red(` \u2717 Unknown: ${r.reason?.message ?? "failed"}`));
5365
+ failures++;
5366
+ }
5367
+ }
5368
+ console.log("");
5369
+ if (failures === 0) {
5370
+ console.log(chalk15.green(` \u2713 All ${successes} file(s) processed successfully.
5371
+ `));
5372
+ } else {
5373
+ console.log(chalk15.yellow(` ${successes} succeeded, ${failures} failed.
5374
+ `));
5375
+ }
5376
+ });
5377
+
5378
+ // src/commands/plugin.ts
5379
+ import { execSync as execSync9, execFileSync as execFileSync3 } from "child_process";
5380
+ import fs21 from "fs/promises";
5381
+ import path23 from "path";
5382
+ import os9 from "os";
5383
+ import chalk16 from "chalk";
5384
+ var GLOBAL_PLUGINS_DIR = path23.join(os9.homedir(), ".notch", "plugins");
5385
+ registerCommand("/plugin", async (args, ctx) => {
5386
+ const parts = args.split(/\s+/);
5387
+ const subcommand = parts[0] || "list";
5388
+ const target = parts.slice(1).join(" ");
5389
+ switch (subcommand) {
5390
+ case "list":
5391
+ case "ls": {
5392
+ const plugins = pluginManager.list();
5393
+ if (plugins.length === 0) {
5394
+ console.log(chalk16.gray(" No plugins installed.\n"));
5395
+ console.log(chalk16.gray(" Install one: /plugin install <npm-package-or-git-url>\n"));
5396
+ return;
5397
+ }
5398
+ console.log(chalk16.gray("\n Installed plugins:\n"));
5399
+ for (const p of plugins) {
5400
+ const toolCount = p.tools.length;
5401
+ const hookCount = p.hooks.length;
5402
+ const meta = [
5403
+ toolCount > 0 ? `${toolCount} tool${toolCount > 1 ? "s" : ""}` : "",
5404
+ hookCount > 0 ? `${hookCount} hook${hookCount > 1 ? "s" : ""}` : ""
5405
+ ].filter(Boolean).join(", ");
5406
+ console.log(` ${chalk16.green("\u25CF")} ${chalk16.white(p.name)} ${chalk16.gray(`v${p.version}`)}${meta ? chalk16.gray(` (${meta})`) : ""}`);
5407
+ if (p.description) {
5408
+ console.log(chalk16.gray(` ${p.description}`));
5409
+ }
5410
+ }
5411
+ console.log("");
5412
+ return;
5413
+ }
5414
+ case "install":
5415
+ case "add": {
5416
+ if (!target) {
5417
+ console.log(chalk16.gray(" Usage: /plugin install <npm-package-or-git-url>\n"));
5418
+ return;
5419
+ }
5420
+ await fs21.mkdir(GLOBAL_PLUGINS_DIR, { recursive: true });
5421
+ const pluginDir = path23.join(GLOBAL_PLUGINS_DIR, path23.basename(target).replace(/\.git$/, ""));
5422
+ console.log(chalk16.gray(` Installing ${target}...`));
5423
+ try {
5424
+ if (target.includes("/") && !target.startsWith("@")) {
5425
+ execFileSync3("git", ["clone", "--depth", "1", target, pluginDir], {
5426
+ encoding: "utf-8",
5427
+ timeout: 6e4,
5428
+ stdio: "pipe"
5429
+ });
5430
+ } else {
5431
+ await fs21.mkdir(pluginDir, { recursive: true });
5432
+ execFileSync3("npm", ["init", "-y"], {
5433
+ cwd: pluginDir,
5434
+ encoding: "utf-8",
5435
+ timeout: 3e4,
5436
+ stdio: "pipe"
5437
+ });
5438
+ execFileSync3("npm", ["install", target], {
5439
+ cwd: pluginDir,
5440
+ encoding: "utf-8",
5441
+ timeout: 12e4,
5442
+ stdio: "pipe"
5443
+ });
5444
+ }
5445
+ try {
5446
+ const pkgExists = await fs21.access(path23.join(pluginDir, "package.json")).then(() => true).catch(() => false);
5447
+ if (pkgExists) {
5448
+ execSync9("npm install --production", {
5449
+ cwd: pluginDir,
5450
+ encoding: "utf-8",
5451
+ timeout: 12e4,
5452
+ stdio: "pipe"
5453
+ });
5454
+ }
5455
+ } catch {
5456
+ }
5457
+ console.log(chalk16.green(` \u2713 Installed to ${pluginDir}`));
5458
+ console.log(chalk16.gray(" Restart notch to load the plugin.\n"));
5459
+ } catch (err) {
5460
+ console.log(chalk16.red(` Install failed: ${err.message}
5461
+ `));
5462
+ }
5463
+ return;
5464
+ }
5465
+ case "remove":
5466
+ case "rm":
5467
+ case "uninstall": {
5468
+ if (!target) {
5469
+ console.log(chalk16.gray(" Usage: /plugin remove <plugin-name>\n"));
5470
+ return;
5471
+ }
5472
+ const pluginDir = path23.join(GLOBAL_PLUGINS_DIR, target);
5473
+ try {
5474
+ await fs21.access(pluginDir);
5475
+ await fs21.rm(pluginDir, { recursive: true, force: true });
5476
+ console.log(chalk16.green(` \u2713 Removed ${target}`));
5477
+ console.log(chalk16.gray(" Restart notch to apply.\n"));
5478
+ } catch {
5479
+ console.log(chalk16.red(` Plugin not found: ${target}
5480
+ `));
5481
+ }
5482
+ return;
5483
+ }
5484
+ default:
5485
+ console.log(chalk16.gray(" Usage: /plugin <list|install|remove> [name]\n"));
5486
+ }
5487
+ });
5488
+
5489
+ // src/commands/commit.ts
5490
+ import { execFileSync as execFileSync4 } from "child_process";
5491
+ import chalk17 from "chalk";
5492
+ import ora2 from "ora";
5493
+ function gitCommit(message, cwd) {
5494
+ execFileSync4("git", ["commit", "-m", message], {
5495
+ cwd,
5496
+ encoding: "utf-8",
5497
+ timeout: 3e4
5498
+ });
5499
+ }
5500
+ registerCommand("/commit", async (args, ctx) => {
5501
+ let diff;
5502
+ let stat;
5503
+ try {
5504
+ const staged = execFileSync4("git", ["diff", "--cached", "--stat"], { cwd: ctx.cwd, encoding: "utf-8" }).trim();
5505
+ if (!staged) {
5506
+ execFileSync4("git", ["add", "-A"], { cwd: ctx.cwd, encoding: "utf-8" });
5507
+ }
5508
+ stat = execFileSync4("git", ["diff", "--cached", "--stat"], { cwd: ctx.cwd, encoding: "utf-8" }).trim();
5509
+ diff = execFileSync4("git", ["diff", "--cached"], { cwd: ctx.cwd, encoding: "utf-8", maxBuffer: 5 * 1024 * 1024 }).trim();
5510
+ } catch (err) {
5511
+ console.log(chalk17.red(` Git error: ${err.message}
5512
+ `));
5513
+ return;
5514
+ }
5515
+ if (!diff) {
5516
+ console.log(chalk17.gray(" No changes to commit.\n"));
5517
+ return;
5518
+ }
5519
+ console.log(chalk17.gray(`
5520
+ Staged changes:
5521
+ ${stat}
5522
+ `));
5523
+ if (args) {
5524
+ try {
5525
+ gitCommit(args, ctx.cwd);
5526
+ console.log(chalk17.green(` \u2713 Committed: ${args}
5527
+ `));
5528
+ } catch (err) {
5529
+ console.log(chalk17.red(` Commit failed: ${err.message}
5530
+ `));
5531
+ }
5532
+ return;
5533
+ }
5534
+ const spinner = ora2("Generating commit message...").start();
5535
+ const truncatedDiff = diff.length > 3e4 ? diff.slice(0, 3e4) + "\n... (truncated)" : diff;
5536
+ const prompt = `Generate a concise git commit message for these changes. Follow conventional commit format (feat:, fix:, refactor:, docs:, chore:, etc.). Keep the first line under 72 characters. Add a blank line then a brief body if needed.
5537
+
5538
+ Changes:
5539
+ ${stat}
5540
+
5541
+ Diff:
5542
+ \`\`\`diff
5543
+ ${truncatedDiff}
5544
+ \`\`\`
5545
+
5546
+ Reply with ONLY the commit message, nothing else. No markdown, no explanation.`;
5547
+ const tempMessages = [
5548
+ { role: "user", content: prompt }
5549
+ ];
5550
+ try {
5551
+ const message = await ctx.runPrompt(prompt, tempMessages);
5552
+ spinner.stop();
5553
+ const cleanMessage = message.replace(/^```\n?/gm, "").replace(/\n?```$/gm, "").replace(/^["'`]/g, "").replace(/["'`]$/g, "").trim();
5554
+ console.log(chalk17.white(`
5555
+ ${cleanMessage.split("\n")[0]}
5556
+ `));
5557
+ if (cleanMessage.includes("\n")) {
5558
+ const body = cleanMessage.split("\n").slice(1).join("\n").trim();
5559
+ if (body) console.log(chalk17.gray(` ${body}
5560
+ `));
5561
+ }
5562
+ const rl = await import("readline");
5563
+ const confirm = rl.createInterface({ input: process.stdin, output: process.stdout });
5564
+ const answer = await new Promise((resolve2) => {
5565
+ confirm.question(chalk17.yellow(" Commit with this message? [Y/n/e(dit)] "), resolve2);
5566
+ });
5567
+ confirm.close();
5568
+ const choice = answer.trim().toLowerCase();
5569
+ if (choice === "n" || choice === "no") {
5570
+ console.log(chalk17.gray(" Commit cancelled.\n"));
5571
+ return;
5572
+ }
5573
+ let finalMessage = cleanMessage;
5574
+ if (choice === "e" || choice === "edit") {
5575
+ const editRl = rl.createInterface({ input: process.stdin, output: process.stdout });
5576
+ finalMessage = await new Promise((resolve2) => {
5577
+ editRl.question(chalk17.gray(" Enter commit message: "), resolve2);
5578
+ });
5579
+ editRl.close();
5580
+ if (!finalMessage.trim()) {
5581
+ console.log(chalk17.gray(" Commit cancelled.\n"));
5582
+ return;
5583
+ }
5584
+ }
5585
+ try {
5586
+ gitCommit(finalMessage, ctx.cwd);
5587
+ console.log(chalk17.green(` \u2713 Committed: ${finalMessage.split("\n")[0]}
5588
+ `));
5589
+ } catch (err) {
5590
+ console.log(chalk17.red(` Commit failed: ${err.message}
5591
+ `));
5592
+ }
5593
+ } catch (err) {
5594
+ spinner.fail(`Failed: ${err.message}`);
5595
+ }
5596
+ });
5597
+
5598
+ // src/commands/pr.ts
5599
+ import { execSync as execSync11, execFileSync as execFileSync5 } from "child_process";
5600
+ import chalk18 from "chalk";
5601
+ import ora3 from "ora";
5602
+ function tryExec(cmd, cwd) {
5603
+ try {
5604
+ return execSync11(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
5605
+ } catch {
5606
+ return null;
5607
+ }
5608
+ }
5609
+ registerCommand("/pr", async (args, ctx) => {
5610
+ const branch = tryExec("git branch --show-current", ctx.cwd);
5611
+ if (!branch) {
5612
+ console.log(chalk18.red(" Not a git repository or not on a branch.\n"));
5613
+ return;
5614
+ }
5615
+ const baseBranch = tryExec("git rev-parse --abbrev-ref origin/HEAD", ctx.cwd)?.replace("origin/", "") || tryExec("git symbolic-ref refs/remotes/origin/HEAD", ctx.cwd)?.replace("refs/remotes/origin/", "") || "main";
5616
+ if (branch === baseBranch) {
5617
+ console.log(chalk18.red(` You're on ${baseBranch}. Switch to a feature branch first.
5618
+ `));
5619
+ return;
5620
+ }
5621
+ if (!tryExec("gh --version", ctx.cwd)) {
5622
+ console.log(chalk18.red(" GitHub CLI (gh) not found. Install it: https://cli.github.com\n"));
5623
+ return;
5624
+ }
5625
+ const stat = tryExec(`git diff ${baseBranch}...HEAD --stat`, ctx.cwd) || "";
5626
+ const log = tryExec(`git log ${baseBranch}..HEAD --oneline`, ctx.cwd) || "";
5627
+ const diff = tryExec(`git diff ${baseBranch}...HEAD`, ctx.cwd) || "";
5628
+ if (!log) {
5629
+ console.log(chalk18.gray(" No commits ahead of base branch.\n"));
5630
+ return;
5631
+ }
5632
+ console.log(chalk18.gray(`
5633
+ Branch: ${chalk18.white(branch)} \u2192 ${baseBranch}`));
5634
+ console.log(chalk18.gray(` Commits:
5635
+ ${log.split("\n").map((l) => ` ${l}`).join("\n")}
5636
+ `));
5637
+ console.log(chalk18.gray(` Files:
5638
+ ${stat}
5639
+ `));
5640
+ const hasRemote = tryExec(`git rev-parse --abbrev-ref ${branch}@{upstream}`, ctx.cwd);
5641
+ if (!hasRemote) {
5642
+ console.log(chalk18.gray(" Pushing branch to origin..."));
5643
+ try {
5644
+ execFileSync5("git", ["push", "-u", "origin", branch], { cwd: ctx.cwd, encoding: "utf-8", timeout: 3e4 });
5645
+ console.log(chalk18.green(" \u2713 Pushed\n"));
5646
+ } catch (err) {
5647
+ console.log(chalk18.red(` Push failed: ${err.message}
5648
+ `));
5649
+ return;
5650
+ }
5651
+ }
5652
+ if (args) {
5653
+ try {
5654
+ const result = execFileSync5("gh", ["pr", "create", "--title", args, "--body", "Auto-generated by Notch CLI", "--base", baseBranch], {
5655
+ cwd: ctx.cwd,
5656
+ encoding: "utf-8",
5657
+ timeout: 3e4
5658
+ });
5659
+ console.log(chalk18.green(` \u2713 ${result.trim()}
5660
+ `));
5661
+ } catch (err) {
5662
+ console.log(chalk18.red(` PR creation failed: ${err.message}
5663
+ `));
5664
+ }
5665
+ return;
5666
+ }
5667
+ const spinner = ora3("Generating PR description...").start();
5668
+ const truncatedDiff = diff.length > 3e4 ? diff.slice(0, 3e4) + "\n... (truncated)" : diff;
5669
+ const prompt = `Generate a GitHub pull request title and body for these changes.
5670
+
5671
+ Branch: ${branch} \u2192 ${baseBranch}
5672
+
5673
+ Commits:
5674
+ ${log}
5675
+
5676
+ Files changed:
5677
+ ${stat}
5678
+
5679
+ Diff:
5680
+ \`\`\`diff
5681
+ ${truncatedDiff}
5682
+ \`\`\`
5683
+
5684
+ Reply in this exact format (no other text):
5685
+ TITLE: <short PR title, under 72 chars>
5686
+ BODY:
5687
+ ## Summary
5688
+ <2-3 bullet points describing the changes>
5689
+
5690
+ ## Changes
5691
+ <brief list of what was changed and why>`;
5692
+ const tempMessages = [
5693
+ { role: "user", content: prompt }
5694
+ ];
5695
+ try {
5696
+ const response = await ctx.runPrompt(prompt, tempMessages);
5697
+ spinner.stop();
5698
+ const titleMatch = response.match(/TITLE:\s*(.+)/);
5699
+ const bodyMatch = response.match(/BODY:\s*([\s\S]+)/);
5700
+ const title = titleMatch?.[1]?.trim() || `${branch}: changes from Notch`;
5701
+ const body = bodyMatch?.[1]?.trim() || response;
5702
+ console.log(chalk18.white(`
5703
+ Title: ${title}
5704
+ `));
5705
+ console.log(chalk18.gray(` ${body.slice(0, 200)}${body.length > 200 ? "..." : ""}
5706
+ `));
5707
+ const rl = await import("readline");
5708
+ const confirm = rl.createInterface({ input: process.stdin, output: process.stdout });
5709
+ const answer = await new Promise((resolve2) => {
5710
+ confirm.question(chalk18.yellow(" Create PR? [Y/n] "), resolve2);
5711
+ });
5712
+ confirm.close();
5713
+ if (answer.trim().toLowerCase() === "n") {
5714
+ console.log(chalk18.gray(" PR cancelled.\n"));
5715
+ return;
5716
+ }
5717
+ try {
5718
+ const result = execFileSync5("gh", ["pr", "create", "--title", title, "--body", body, "--base", baseBranch], {
5719
+ cwd: ctx.cwd,
5720
+ encoding: "utf-8",
5721
+ timeout: 3e4
5722
+ });
5723
+ console.log(chalk18.green(` \u2713 ${result.trim()}
5724
+ `));
5725
+ } catch (err) {
5726
+ console.log(chalk18.red(` PR creation failed: ${err.message}
5727
+ `));
5728
+ }
5729
+ } catch (err) {
5730
+ spinner.fail(`Failed: ${err.message}`);
5731
+ }
5732
+ });
5733
+
5734
+ // src/commands/worktree.ts
5735
+ import { execSync as execSync12, execFileSync as execFileSync6 } from "child_process";
5736
+ import chalk19 from "chalk";
5737
+ function tryExec2(cmd, cwd) {
5738
+ try {
5739
+ return execSync12(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
5740
+ } catch {
5741
+ return null;
5742
+ }
5743
+ }
5744
+ registerCommand("/worktree", async (args, ctx) => {
5745
+ const parts = args.split(/\s+/);
5746
+ const subcommand = parts[0] || "list";
5747
+ switch (subcommand) {
5748
+ case "list":
5749
+ case "ls": {
5750
+ const result = tryExec2("git worktree list", ctx.cwd);
5751
+ if (!result) {
5752
+ console.log(chalk19.red(" Not a git repository.\n"));
5753
+ return;
5754
+ }
5755
+ console.log(chalk19.gray("\n Git Worktrees:\n"));
5756
+ for (const line of result.split("\n")) {
5757
+ const isCurrent = line.includes(ctx.cwd);
5758
+ console.log(isCurrent ? chalk19.green(` \u25CF ${line}`) : chalk19.gray(` ${line}`));
5759
+ }
5760
+ console.log("");
5761
+ return;
5762
+ }
5763
+ case "create":
5764
+ case "new": {
5765
+ const branchName = parts[1];
5766
+ if (!branchName) {
5767
+ console.log(chalk19.gray(" Usage: /worktree create <branch-name> [base-branch]\n"));
5768
+ return;
5769
+ }
5770
+ const baseBranch = parts[2] || "HEAD";
5771
+ const worktreePath = `../${branchName}`;
5772
+ try {
5773
+ execFileSync6("git", ["worktree", "add", worktreePath, "-b", branchName, baseBranch], {
5774
+ cwd: ctx.cwd,
5775
+ encoding: "utf-8",
5776
+ timeout: 15e3
5777
+ });
5778
+ const absPath = tryExec2(`cd "${worktreePath}" && pwd`, ctx.cwd);
5779
+ console.log(chalk19.green(` \u2713 Created worktree: ${branchName}`));
5780
+ console.log(chalk19.gray(` Path: ${absPath || worktreePath}`));
5781
+ console.log(chalk19.gray(` Base: ${baseBranch}`));
5782
+ console.log(chalk19.gray(`
5783
+ Run: /worktree switch ${branchName}
5784
+ `));
5785
+ } catch (err) {
5786
+ console.log(chalk19.red(` Failed: ${err.message}
5787
+ `));
5788
+ }
5789
+ return;
5790
+ }
5791
+ case "remove":
5792
+ case "rm": {
5793
+ const target = parts[1];
5794
+ if (!target) {
5795
+ console.log(chalk19.gray(" Usage: /worktree remove <branch-or-path>\n"));
5796
+ return;
5797
+ }
5798
+ try {
5799
+ execFileSync6("git", ["worktree", "remove", target, "--force"], {
5800
+ cwd: ctx.cwd,
5801
+ encoding: "utf-8",
5802
+ timeout: 15e3
5803
+ });
5804
+ try {
5805
+ execFileSync6("git", ["branch", "-D", target], { cwd: ctx.cwd, encoding: "utf-8", timeout: 5e3 });
5806
+ } catch {
5807
+ }
5808
+ console.log(chalk19.green(` \u2713 Removed worktree: ${target}
5809
+ `));
5810
+ } catch (err) {
5811
+ console.log(chalk19.red(` Failed: ${err.message}
5812
+ `));
5813
+ }
5814
+ return;
5815
+ }
5816
+ case "switch": {
5817
+ const target = parts[1];
5818
+ if (!target) {
5819
+ console.log(chalk19.gray(" Usage: /worktree switch <branch-name>\n"));
5820
+ return;
5821
+ }
5822
+ const list = tryExec2("git worktree list --porcelain", ctx.cwd);
5823
+ if (!list) {
5824
+ console.log(chalk19.red(" Not a git repository.\n"));
5825
+ return;
5826
+ }
5827
+ const worktrees = list.split("\n\n").filter((block) => block.trim().length > 0).map((block) => {
5828
+ const lines = block.split("\n");
5829
+ const wtPath = lines.find((l) => l.startsWith("worktree "))?.slice(9)?.trim();
5830
+ const branch = lines.find((l) => l.startsWith("branch "))?.slice(7)?.replace("refs/heads/", "")?.trim();
5831
+ return { path: wtPath, branch };
5832
+ }).filter((wt) => wt.path);
5833
+ const match = worktrees.find(
5834
+ (wt) => wt.branch === target || wt.path?.includes(target)
5835
+ );
5836
+ if (!match?.path) {
5837
+ console.log(chalk19.red(` Worktree not found: ${target}
5838
+ `));
5839
+ return;
5840
+ }
5841
+ console.log(chalk19.green(` \u2713 Switched context to: ${match.branch || target}`));
5842
+ console.log(chalk19.gray(` Path: ${match.path}`));
5843
+ console.log(chalk19.gray(`
5844
+ Note: This changes the working directory for Notch tools.
5845
+ `));
5846
+ return;
5847
+ }
5848
+ case "prune": {
5849
+ try {
5850
+ execSync12("git worktree prune", { cwd: ctx.cwd, encoding: "utf-8" });
5851
+ console.log(chalk19.green(" \u2713 Pruned stale worktrees.\n"));
5852
+ } catch (err) {
5853
+ console.log(chalk19.red(` Failed: ${err.message}
5854
+ `));
5855
+ }
5856
+ return;
5857
+ }
5858
+ default:
5859
+ console.log(chalk19.gray(" Usage: /worktree <list|create|remove|switch|prune>\n"));
5860
+ console.log(chalk19.gray(" Commands:"));
5861
+ console.log(chalk19.gray(" list \u2014 List all worktrees"));
5862
+ console.log(chalk19.gray(" create <branch> [base] \u2014 Create new worktree"));
5863
+ console.log(chalk19.gray(" remove <branch> \u2014 Remove a worktree"));
5864
+ console.log(chalk19.gray(" switch <branch> \u2014 Switch Notch context"));
5865
+ console.log(chalk19.gray(" prune \u2014 Clean up stale worktrees\n"));
5866
+ }
5867
+ });
5868
+
5869
+ // src/commands/tasks.ts
5870
+ import chalk20 from "chalk";
5871
+ registerCommand("/tasks", async (_args, _ctx) => {
5872
+ const tasks2 = getAllTasks();
5873
+ if (tasks2.length === 0) {
5874
+ console.log(chalk20.gray(" No tasks. The agent will create tasks automatically during complex work.\n"));
5875
+ return;
5876
+ }
5877
+ console.log(chalk20.gray("\n Task List:\n"));
5878
+ console.log(" " + formatTasksDisplay().split("\n").join("\n "));
5879
+ const completed = tasks2.filter((t) => t.status === "completed").length;
5880
+ const total = tasks2.length;
5881
+ const pct = total > 0 ? Math.round(completed / total * 100) : 0;
5882
+ console.log(chalk20.gray(`
5883
+ Progress: ${completed}/${total} (${pct}%)
5884
+ `));
5885
+ });
5886
+
5887
+ // src/commands/debug.ts
5888
+ import chalk21 from "chalk";
5889
+ var debugEnabled = false;
5890
+ registerCommand("/debug", async (args, ctx) => {
5891
+ if (args === "off") {
5892
+ debugEnabled = false;
5893
+ console.log(chalk21.gray(" Debug logging disabled.\n"));
5894
+ return;
5895
+ }
5896
+ if (args === "on" || !args) {
5897
+ debugEnabled = !debugEnabled;
5898
+ console.log(debugEnabled ? chalk21.yellow(" Debug logging enabled. Verbose output will be shown.\n") : chalk21.gray(" Debug logging disabled.\n"));
5899
+ return;
5900
+ }
5901
+ if (args === "status") {
5902
+ console.log(chalk21.gray("\n Debug Status:"));
5903
+ console.log(chalk21.gray(` Model: ${ctx.modelId}`));
5904
+ console.log(chalk21.gray(` Messages: ${ctx.messages.length}`));
5905
+ console.log(chalk21.gray(` CWD: ${ctx.cwd}`));
5906
+ console.log(chalk21.gray(` Debug: ${debugEnabled ? "on" : "off"}`));
5907
+ console.log(chalk21.gray(` Last response: ${ctx.lastResponse ? ctx.lastResponse.length + " chars" : "none"}`));
5908
+ const charCount = ctx.messages.reduce((sum, m) => {
5909
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
5910
+ return sum + content.length;
5911
+ }, 0);
5912
+ console.log(chalk21.gray(` Estimated tokens: ~${Math.round(charCount / 4)}`));
5913
+ console.log("");
5914
+ return;
5915
+ }
5916
+ console.log(chalk21.gray(" Usage: /debug [on|off|status]\n"));
5917
+ });
5918
+
5919
+ // src/commands/rewind.ts
5920
+ import chalk22 from "chalk";
5921
+ registerCommand("/rewind", async (args, ctx) => {
5922
+ if (ctx.messages.length === 0) {
5923
+ console.log(chalk22.gray(" No conversation to rewind.\n"));
5924
+ return;
5925
+ }
5926
+ const userTurns = [];
5927
+ for (let i = 0; i < ctx.messages.length; i++) {
5928
+ const msg = ctx.messages[i];
5929
+ if (msg.role === "user") {
5930
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
5931
+ const preview = content.slice(0, 60).replace(/\n/g, " ");
5932
+ userTurns.push({ index: i, preview });
5933
+ }
5934
+ }
5935
+ if (userTurns.length === 0) {
5936
+ console.log(chalk22.gray(" No turns to rewind to.\n"));
5937
+ return;
5938
+ }
5939
+ if (args) {
5940
+ const turnNum = parseInt(args, 10);
5941
+ if (isNaN(turnNum) || turnNum < 1 || turnNum > userTurns.length) {
5942
+ console.log(chalk22.red(` Invalid turn number: ${args}`));
5943
+ console.log(chalk22.gray(` Valid range: 1-${userTurns.length}
5944
+ `));
5945
+ return;
5946
+ }
5947
+ const target = userTurns[turnNum - 1];
5948
+ const kept = ctx.messages.slice(0, target.index);
5949
+ ctx.messages.length = 0;
5950
+ ctx.messages.push(...kept);
5951
+ console.log(chalk22.green(` \u2713 Rewound to before turn ${turnNum}`));
5952
+ console.log(chalk22.gray(` "${target.preview}"`));
5953
+ console.log(chalk22.gray(` ${ctx.messages.length} messages remaining.
5954
+ `));
5955
+ return;
5956
+ }
5957
+ console.log(chalk22.gray("\n Conversation turns:\n"));
5958
+ for (let i = 0; i < userTurns.length; i++) {
5959
+ console.log(chalk22.gray(` ${i + 1}. ${userTurns[i].preview}${userTurns[i].preview.length >= 60 ? "..." : ""}`));
5960
+ }
5961
+ console.log(chalk22.gray(`
5962
+ Rewind to a turn: /rewind <turn-number>
5963
+ `));
5964
+ });
5965
+
5966
+ // src/commands/insights.ts
5967
+ import chalk23 from "chalk";
5968
+ registerCommand("/insights", async (_args, ctx) => {
5969
+ if (ctx.messages.length === 0) {
5970
+ console.log(chalk23.gray(" No conversation data to analyze.\n"));
5971
+ return;
5972
+ }
5973
+ let userMsgs = 0;
5974
+ let assistantMsgs = 0;
5975
+ let toolCalls = 0;
5976
+ const toolUsage = /* @__PURE__ */ new Map();
5977
+ const filesReferenced = /* @__PURE__ */ new Set();
5978
+ let totalChars = 0;
5979
+ for (const msg of ctx.messages) {
5980
+ if (msg.role === "user") {
5981
+ userMsgs++;
5982
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
5983
+ totalChars += content.length;
5984
+ } else if (msg.role === "assistant") {
5985
+ assistantMsgs++;
5986
+ if (Array.isArray(msg.content)) {
5987
+ for (const part of msg.content) {
5988
+ if (typeof part === "object" && "type" in part && part.type === "tool-call") {
5989
+ toolCalls++;
5990
+ const name = part.toolName || "unknown";
5991
+ toolUsage.set(name, (toolUsage.get(name) || 0) + 1);
5992
+ const args = part.args;
5993
+ if (args?.path) filesReferenced.add(String(args.path));
5994
+ if (args?.file) filesReferenced.add(String(args.file));
5995
+ }
5996
+ }
5997
+ }
5998
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
5999
+ totalChars += content.length;
6000
+ }
6001
+ }
6002
+ const estimatedTokens = Math.round(totalChars / 4);
6003
+ console.log(chalk23.gray("\n \u2550\u2550\u2550 Session Insights \u2550\u2550\u2550\n"));
6004
+ console.log(chalk23.gray(" Conversation:"));
6005
+ console.log(chalk23.gray(` ${userMsgs} user turns, ${assistantMsgs} assistant responses`));
6006
+ console.log(chalk23.gray(` ${ctx.messages.length} total messages`));
6007
+ console.log(chalk23.gray(` ~${estimatedTokens.toLocaleString()} tokens estimated`));
6008
+ console.log("");
6009
+ if (toolCalls > 0) {
6010
+ console.log(chalk23.gray(" Tool Usage:"));
6011
+ const sorted = [...toolUsage.entries()].sort((a, b) => b[1] - a[1]);
6012
+ for (const [name, count] of sorted) {
6013
+ const bar = "\u2588".repeat(Math.min(count, 20));
6014
+ console.log(chalk23.gray(` ${name.padEnd(14)} ${chalk23.cyan(bar)} ${count}`));
6015
+ }
6016
+ console.log(chalk23.gray(` ${"Total".padEnd(14)} ${toolCalls} calls`));
6017
+ console.log("");
6018
+ }
6019
+ if (filesReferenced.size > 0) {
6020
+ console.log(chalk23.gray(" Files Referenced:"));
6021
+ const files = [...filesReferenced].slice(0, 15);
6022
+ for (const f of files) {
6023
+ console.log(chalk23.gray(` \u2022 ${f}`));
6024
+ }
6025
+ if (filesReferenced.size > 15) {
6026
+ console.log(chalk23.gray(` ... and ${filesReferenced.size - 15} more`));
6027
+ }
6028
+ console.log("");
6029
+ }
6030
+ const avgToolsPerTurn = userMsgs > 0 ? (toolCalls / userMsgs).toFixed(1) : "0";
6031
+ console.log(chalk23.gray(" Efficiency:"));
6032
+ console.log(chalk23.gray(` Avg tools per turn: ${avgToolsPerTurn}`));
6033
+ console.log(chalk23.gray(` Files per session: ${filesReferenced.size}`));
6034
+ console.log(chalk23.gray(` Tool diversity: ${toolUsage.size} unique tools used`));
6035
+ console.log("");
6036
+ });
6037
+
6038
+ // src/commands/rename.ts
6039
+ import chalk24 from "chalk";
6040
+ var sessionName = "";
6041
+ function getSessionName() {
6042
+ return sessionName;
6043
+ }
6044
+ registerCommand("/rename", async (args, _ctx) => {
6045
+ if (!args) {
6046
+ if (sessionName) {
6047
+ console.log(chalk24.gray(` Current session name: ${chalk24.white(sessionName)}
6048
+ `));
6049
+ } else {
6050
+ console.log(chalk24.gray(" Usage: /rename <name>\n"));
6051
+ }
6052
+ return;
6053
+ }
6054
+ sessionName = args.trim();
6055
+ console.log(chalk24.green(` \u2713 Session named: "${sessionName}"
6056
+ `));
6057
+ });
6058
+
6059
+ // src/commands/sandbox.ts
6060
+ import chalk25 from "chalk";
6061
+ var sandboxEnabled = false;
6062
+ function isSandboxEnabled() {
6063
+ return sandboxEnabled;
6064
+ }
6065
+ registerCommand("/sandbox", async (args, _ctx) => {
6066
+ if (args === "on") {
6067
+ sandboxEnabled = true;
6068
+ } else if (args === "off") {
6069
+ sandboxEnabled = false;
6070
+ } else {
6071
+ sandboxEnabled = !sandboxEnabled;
6072
+ }
6073
+ if (sandboxEnabled) {
6074
+ console.log(chalk25.yellow(" \u26A0 Sandbox mode ON \u2014 write/edit/shell tools will run in dry-run mode."));
6075
+ console.log(chalk25.gray(" Only read, grep, glob, and web-fetch will execute normally.\n"));
6076
+ } else {
6077
+ console.log(chalk25.green(" \u2713 Sandbox mode OFF \u2014 all tools enabled.\n"));
6078
+ }
6079
+ });
6080
+
3920
6081
  // src/ui/completions.ts
3921
- import fs17 from "fs";
3922
- import path18 from "path";
6082
+ import fs22 from "fs";
6083
+ import path24 from "path";
3923
6084
  var COMMANDS = [
3924
6085
  "/help",
3925
6086
  "/quit",
@@ -3949,7 +6110,39 @@ var COMMANDS = [
3949
6110
  "/mascot",
3950
6111
  "/cost",
3951
6112
  "/branch",
3952
- "/branches"
6113
+ "/branches",
6114
+ // Utility commands
6115
+ "/doctor",
6116
+ "/copy",
6117
+ "/btw",
6118
+ "/security-review",
6119
+ "/loop",
6120
+ "/loop stop",
6121
+ "/batch",
6122
+ "/plugin",
6123
+ "/plugin list",
6124
+ "/plugin install",
6125
+ "/plugin remove",
6126
+ // Git workflow
6127
+ "/commit",
6128
+ "/pr",
6129
+ // Worktree
6130
+ "/worktree",
6131
+ "/worktree list",
6132
+ "/worktree create",
6133
+ "/worktree remove",
6134
+ "/worktree switch",
6135
+ // Task management
6136
+ "/tasks",
6137
+ // UX commands
6138
+ "/debug",
6139
+ "/debug on",
6140
+ "/debug off",
6141
+ "/debug status",
6142
+ "/rewind",
6143
+ "/insights",
6144
+ "/rename",
6145
+ "/sandbox"
3953
6146
  ];
3954
6147
  function buildCompleter(cwd) {
3955
6148
  return function completer(line) {
@@ -3980,15 +6173,15 @@ function buildCompleter(cwd) {
3980
6173
  }
3981
6174
  function completeFilePath(partial, cwd) {
3982
6175
  try {
3983
- const dir = partial.includes("/") ? path18.resolve(cwd, path18.dirname(partial)) : cwd;
3984
- const prefix = partial.includes("/") ? path18.basename(partial) : partial;
3985
- const entries = fs17.readdirSync(dir, { withFileTypes: true });
6176
+ const dir = partial.includes("/") ? path24.resolve(cwd, path24.dirname(partial)) : cwd;
6177
+ const prefix = partial.includes("/") ? path24.basename(partial) : partial;
6178
+ const entries = fs22.readdirSync(dir, { withFileTypes: true });
3986
6179
  const matches = [];
3987
6180
  for (const entry of entries) {
3988
6181
  if (entry.name.startsWith(".")) continue;
3989
6182
  if (entry.name === "node_modules" || entry.name === ".git") continue;
3990
6183
  if (entry.name.startsWith(prefix)) {
3991
- const relative = partial.includes("/") ? path18.dirname(partial) + "/" + entry.name : entry.name;
6184
+ const relative = partial.includes("/") ? path24.dirname(partial) + "/" + entry.name : entry.name;
3992
6185
  if (entry.isDirectory()) {
3993
6186
  matches.push(relative + "/");
3994
6187
  } else {
@@ -4003,7 +6196,7 @@ function completeFilePath(partial, cwd) {
4003
6196
  }
4004
6197
 
4005
6198
  // src/index.ts
4006
- import fs18 from "fs/promises";
6199
+ import fs23 from "fs/promises";
4007
6200
  import { createRequire } from "module";
4008
6201
  var _require = createRequire(import.meta.url);
4009
6202
  var VERSION = _require("../package.json").version;
@@ -4026,7 +6219,7 @@ function printModelTable(activeModel) {
4026
6219
  `));
4027
6220
  }
4028
6221
  function printHelp() {
4029
- console.log(chalk9.gray(`
6222
+ console.log(chalk26.gray(`
4030
6223
  Commands:
4031
6224
  /model \u2014 Show available models
4032
6225
  /model <name> \u2014 Switch model (e.g., /model pyre)
@@ -4060,6 +6253,30 @@ function printHelp() {
4060
6253
  /ralph status \u2014 Show plan progress
4061
6254
  /ralph clear \u2014 Discard plan
4062
6255
 
6256
+ Git Workflow:
6257
+ /commit [msg] \u2014 Smart commit (AI-generated message)
6258
+ /pr [title] \u2014 Create PR with AI-generated summary
6259
+ /worktree <cmd> \u2014 Manage git worktrees (create/switch/remove)
6260
+
6261
+ Utilities:
6262
+ /doctor \u2014 Diagnose installation health
6263
+ /copy \u2014 Copy last response to clipboard
6264
+ /btw <question> \u2014 Side question (won't affect context)
6265
+ /security-review \u2014 Review recent changes for vulnerabilities
6266
+ /loop <int> <cmd> \u2014 Run a command on interval (e.g. /loop 5m /status)
6267
+ /batch <task> \u2014 Apply a task across multiple files in parallel
6268
+ /tasks \u2014 Show agent task list
6269
+ /insights \u2014 Session analytics and tool usage
6270
+ /rewind [turn] \u2014 Rewind conversation to a previous turn
6271
+ /rename <name> \u2014 Name this session
6272
+ /debug [on|off] \u2014 Toggle debug logging
6273
+ /sandbox \u2014 Toggle sandbox mode (read-only)
6274
+
6275
+ Plugins:
6276
+ /plugin list \u2014 List installed plugins
6277
+ /plugin install <n> \u2014 Install a plugin (npm package or git URL)
6278
+ /plugin remove <n> \u2014 Remove a plugin
6279
+
4063
6280
  Other:
4064
6281
  /branch \u2014 Fork conversation at this point
4065
6282
  /branches \u2014 List conversation branches
@@ -4095,17 +6312,17 @@ async function readStdin() {
4095
6312
  }
4096
6313
  async function main() {
4097
6314
  if (promptArgs[0] === "login") {
4098
- const spinner = ora("Opening browser...").start();
6315
+ const spinner = ora4("Opening browser...").start();
4099
6316
  try {
4100
6317
  spinner.stop();
4101
6318
  const creds = await login();
4102
- console.log(chalk9.green(`
6319
+ console.log(chalk26.green(`
4103
6320
  \u2713 Signed in as ${creds.email}`));
4104
- console.log(chalk9.gray(` API key stored in ${(await import("./auth-JQX6MHJG.js")).getCredentialsPath()}
6321
+ console.log(chalk26.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
4105
6322
  `));
4106
6323
  } catch (err) {
4107
6324
  spinner.stop();
4108
- console.error(chalk9.red(`
6325
+ console.error(chalk26.red(`
4109
6326
  Login failed: ${err.message}
4110
6327
  `));
4111
6328
  process.exit(1);
@@ -4115,10 +6332,10 @@ async function main() {
4115
6332
  if (promptArgs[0] === "logout") {
4116
6333
  const creds = await loadCredentials();
4117
6334
  if (!creds) {
4118
- console.log(chalk9.gray("\n Not signed in.\n"));
6335
+ console.log(chalk26.gray("\n Not signed in.\n"));
4119
6336
  } else {
4120
6337
  await clearCredentials();
4121
- console.log(chalk9.green(`
6338
+ console.log(chalk26.green(`
4122
6339
  \u2713 Signed out (${creds.email})
4123
6340
  `));
4124
6341
  }
@@ -4127,13 +6344,13 @@ async function main() {
4127
6344
  if (promptArgs[0] === "whoami") {
4128
6345
  const creds = await loadCredentials();
4129
6346
  if (!creds) {
4130
- console.log(chalk9.gray("\n Not signed in. Run: notch login\n"));
6347
+ console.log(chalk26.gray("\n Not signed in. Run: notch login\n"));
4131
6348
  } else {
4132
6349
  const keyPreview = `${creds.token.slice(0, 12)}...`;
4133
- console.log(chalk9.gray(`
4134
- Signed in as ${chalk9.white(creds.email)}`));
4135
- console.log(chalk9.gray(` Key: ${keyPreview}`));
4136
- console.log(chalk9.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
6350
+ console.log(chalk26.gray(`
6351
+ Signed in as ${chalk26.white(creds.email)}`));
6352
+ console.log(chalk26.gray(` Key: ${keyPreview}`));
6353
+ console.log(chalk26.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
4137
6354
  `));
4138
6355
  }
4139
6356
  return;
@@ -4157,8 +6374,8 @@ async function main() {
4157
6374
  const config = await loadConfig(configOverrides);
4158
6375
  if (opts.model) {
4159
6376
  if (!isValidModel(opts.model)) {
4160
- console.error(chalk9.red(` Unknown model: ${opts.model}`));
4161
- console.error(chalk9.gray(` Available: ${modelChoices}`));
6377
+ console.error(chalk26.red(` Unknown model: ${opts.model}`));
6378
+ console.error(chalk26.gray(` Available: ${modelChoices}`));
4162
6379
  process.exit(1);
4163
6380
  }
4164
6381
  config.models.chat.model = opts.model;
@@ -4194,15 +6411,14 @@ async function main() {
4194
6411
  }
4195
6412
  const info = MODEL_CATALOG[activeModelId];
4196
6413
  printBanner(VERSION, info.label, info.id, info.size, config.projectRoot);
4197
- checkForUpdates(VERSION).then((msg) => {
4198
- if (msg) console.log(msg);
4199
- });
6414
+ const updateMsg = await checkForUpdates(VERSION);
6415
+ if (updateMsg) console.log(updateMsg);
4200
6416
  const hookTrustPrompt = async (commands) => {
4201
- console.warn(chalk9.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
4202
- commands.forEach((cmd) => console.warn(chalk9.gray(` \u2022 ${cmd}`)));
6417
+ console.warn(chalk26.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
6418
+ commands.forEach((cmd) => console.warn(chalk26.gray(` \u2022 ${cmd}`)));
4203
6419
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
4204
6420
  return new Promise((resolve2) => {
4205
- rl2.question(chalk9.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
6421
+ rl2.question(chalk26.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
4206
6422
  rl2.close();
4207
6423
  resolve2(answer.trim().toLowerCase() === "y");
4208
6424
  });
@@ -4214,7 +6430,7 @@ async function main() {
4214
6430
  ]);
4215
6431
  let repoMapStr = "";
4216
6432
  if (config.useRepoMap) {
4217
- const spinner = ora("Mapping repository...").start();
6433
+ const spinner = ora4("Mapping repository...").start();
4218
6434
  try {
4219
6435
  const repoMap = await buildRepoMap(config.projectRoot);
4220
6436
  repoMapStr = condensedRepoMap(repoMap);
@@ -4241,20 +6457,30 @@ ${repoMapStr}` : ""
4241
6457
  const costTracker = new CostTracker();
4242
6458
  const mcpClients = [];
4243
6459
  try {
4244
- const configRaw = await fs18.readFile(nodePath.resolve(config.projectRoot, ".notch.json"), "utf-8").catch(() => "{}");
6460
+ const configRaw = await fs23.readFile(nodePath.resolve(config.projectRoot, ".notch.json"), "utf-8").catch(() => "{}");
4245
6461
  const mcpConfigs = parseMCPConfig(JSON.parse(configRaw));
4246
6462
  for (const [name, mcpConfig] of Object.entries(mcpConfigs)) {
4247
6463
  try {
4248
6464
  const client = new MCPClient(mcpConfig, name);
4249
6465
  await client.connect();
4250
6466
  mcpClients.push(client);
4251
- console.log(chalk9.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
6467
+ console.log(chalk26.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
4252
6468
  } catch (err) {
4253
- console.log(chalk9.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
6469
+ console.log(chalk26.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
4254
6470
  }
4255
6471
  }
4256
6472
  } catch {
4257
6473
  }
6474
+ try {
6475
+ const pluginCount = await pluginManager.init(config.projectRoot, (msg) => {
6476
+ console.log(chalk26.gray(` ${msg}`));
6477
+ });
6478
+ if (pluginCount > 0) {
6479
+ console.log(chalk26.green(` Plugins: ${pluginCount} loaded`));
6480
+ }
6481
+ } catch (err) {
6482
+ console.log(chalk26.yellow(` Plugin init failed: ${err.message}`));
6483
+ }
4258
6484
  const toolCtx = {
4259
6485
  cwd: config.projectRoot,
4260
6486
  requireConfirm: config.permissionMode !== "trust" && !config.autoConfirm,
@@ -4267,7 +6493,7 @@ ${repoMapStr}` : ""
4267
6493
  });
4268
6494
  });
4269
6495
  },
4270
- log: (msg) => console.log(chalk9.gray(` ${msg}`)),
6496
+ log: (msg) => console.log(chalk26.gray(` ${msg}`)),
4271
6497
  checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
4272
6498
  runHook: async (event, ctx) => {
4273
6499
  if (!config.enableHooks || hookConfig.hooks.length === 0) return;
@@ -4277,22 +6503,30 @@ ${repoMapStr}` : ""
4277
6503
  });
4278
6504
  for (const r of results) {
4279
6505
  if (!r.ok) {
4280
- console.log(chalk9.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
6506
+ console.log(chalk26.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
4281
6507
  }
4282
6508
  }
4283
6509
  }
4284
6510
  };
4285
6511
  await toolCtx.runHook?.("session-start", {});
6512
+ const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
6513
+ for (const r of results) {
6514
+ if (!r.ok) {
6515
+ console.log(chalk26.yellow(` Hook failed (${event}): ${r.error}`));
6516
+ }
6517
+ }
6518
+ });
4286
6519
  const messages = [];
6520
+ let lastAssistantResponse = "";
4287
6521
  if (opts.resume || opts.session) {
4288
6522
  const session = opts.session ? await loadSession(opts.session) : await loadLastSession(config.projectRoot);
4289
6523
  if (session) {
4290
6524
  messages.push(...session.messages);
4291
6525
  sessionId = session.meta.id;
4292
- console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6526
+ console.log(chalk26.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4293
6527
  `));
4294
6528
  } else {
4295
- console.log(chalk9.gray(" No session to resume.\n"));
6529
+ console.log(chalk26.gray(" No session to resume.\n"));
4296
6530
  }
4297
6531
  }
4298
6532
  const pipedInput = await readStdin();
@@ -4313,13 +6547,13 @@ Analyze the above input.`;
4313
6547
  const refContext = formatReferences(references);
4314
6548
  const finalPrompt = refContext + cleanInput;
4315
6549
  messages.push({ role: "user", content: finalPrompt });
4316
- console.log(chalk9.cyan(`> ${oneShot || "(piped input)"}
6550
+ console.log(chalk26.cyan(`> ${oneShot || "(piped input)"}
4317
6551
  `));
4318
6552
  if (references.length > 0) {
4319
- console.log(chalk9.gray(` Injected ${references.length} reference(s)
6553
+ console.log(chalk26.gray(` Injected ${references.length} reference(s)
4320
6554
  `));
4321
6555
  }
4322
- const spinner = ora("Thinking...").start();
6556
+ const spinner = ora4("Thinking...").start();
4323
6557
  try {
4324
6558
  const response = await withRetry(
4325
6559
  () => runAgentLoop(messages, {
@@ -4335,13 +6569,13 @@ Analyze the above input.`;
4335
6569
  onToolCall: (name, args) => {
4336
6570
  if (spinner.isSpinning) spinner.stop();
4337
6571
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
4338
- console.log(chalk9.gray(`
6572
+ console.log(chalk26.gray(`
4339
6573
  \u2192 ${name}(${argSummary})`));
4340
6574
  },
4341
6575
  onToolResult: (_name, result, isError) => {
4342
6576
  const preview = result.slice(0, 100).replace(/\n/g, " ");
4343
- const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
4344
- console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
6577
+ const icon = isError ? chalk26.red("\u2717") : chalk26.green("\u2713");
6578
+ console.log(chalk26.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4345
6579
  }
4346
6580
  })
4347
6581
  );
@@ -4366,7 +6600,7 @@ Analyze the above input.`;
4366
6600
  const savedPlan = await loadPlan(config.projectRoot);
4367
6601
  if (savedPlan) {
4368
6602
  ralphPlan = savedPlan;
4369
- console.log(chalk9.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
6603
+ console.log(chalk26.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
4370
6604
  `));
4371
6605
  }
4372
6606
  } catch {
@@ -4378,7 +6612,7 @@ Analyze the above input.`;
4378
6612
  prompt: theme().prompt("notch> "),
4379
6613
  completer: (line) => completer(line)
4380
6614
  });
4381
- console.log(chalk9.gray(" Type your request, or /help for commands.\n"));
6615
+ console.log(chalk26.gray(" Type your request, or /help for commands.\n"));
4382
6616
  rl.prompt();
4383
6617
  rl.on("line", async (line) => {
4384
6618
  const input = line.trim();
@@ -4393,8 +6627,9 @@ Analyze the above input.`;
4393
6627
  }
4394
6628
  if (messages.length > 0) {
4395
6629
  try {
6630
+ const name = getSessionName();
4396
6631
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
4397
- console.log(chalk9.gray(` Session saved: ${id}`));
6632
+ console.log(chalk26.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
4398
6633
  } catch {
4399
6634
  }
4400
6635
  }
@@ -4404,13 +6639,16 @@ Analyze the above input.`;
4404
6639
  } catch {
4405
6640
  }
4406
6641
  }
6642
+ stopActiveLoop();
6643
+ stopFileWatcher();
6644
+ await pluginManager.shutdown();
4407
6645
  await toolCtx.runHook?.("session-end", {});
4408
- console.log(chalk9.gray("\n Goodbye!\n"));
6646
+ console.log(chalk26.gray("\n Goodbye!\n"));
4409
6647
  process.exit(0);
4410
6648
  }
4411
6649
  if (input === "/clear") {
4412
6650
  messages.length = 0;
4413
- console.log(chalk9.gray(" Conversation cleared.\n"));
6651
+ console.log(chalk26.gray(" Conversation cleared.\n"));
4414
6652
  rl.prompt();
4415
6653
  return;
4416
6654
  }
@@ -4430,8 +6668,8 @@ Analyze the above input.`;
4430
6668
  newModel = `notch-${newModel}`;
4431
6669
  }
4432
6670
  if (!isValidModel(newModel)) {
4433
- console.log(chalk9.red(` Unknown model: ${newModel}`));
4434
- console.log(chalk9.gray(` Available: ${modelChoices}`));
6671
+ console.log(chalk26.red(` Unknown model: ${newModel}`));
6672
+ console.log(chalk26.gray(` Available: ${modelChoices}`));
4435
6673
  rl.prompt();
4436
6674
  return;
4437
6675
  }
@@ -4439,35 +6677,35 @@ Analyze the above input.`;
4439
6677
  config.models.chat.model = activeModelId;
4440
6678
  model = resolveModel(config.models.chat);
4441
6679
  const switchedInfo = MODEL_CATALOG[activeModelId];
4442
- console.log(chalk9.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
6680
+ console.log(chalk26.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
4443
6681
  `));
4444
6682
  rl.prompt();
4445
6683
  return;
4446
6684
  }
4447
6685
  if (input === "/status") {
4448
- const statusSpinner = ora("Checking Notch API...").start();
6686
+ const statusSpinner = ora4("Checking Notch API...").start();
4449
6687
  const check = await validateConfig(config.models.chat);
4450
6688
  if (check.ok) {
4451
6689
  const statusInfo = MODEL_CATALOG[activeModelId];
4452
6690
  statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
4453
6691
  } else {
4454
6692
  statusSpinner.fail(check.error ?? "API unreachable");
4455
- console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
6693
+ console.log(chalk26.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
4456
6694
  }
4457
6695
  rl.prompt();
4458
6696
  return;
4459
6697
  }
4460
6698
  if (input === "/undo") {
4461
6699
  if (checkpoints.undoCount === 0) {
4462
- console.log(chalk9.gray(" Nothing to undo.\n"));
6700
+ console.log(chalk26.gray(" Nothing to undo.\n"));
4463
6701
  rl.prompt();
4464
6702
  return;
4465
6703
  }
4466
6704
  const undone = await checkpoints.undo();
4467
6705
  if (undone) {
4468
6706
  const fileCount = undone.files.length;
4469
- console.log(chalk9.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
4470
- console.log(chalk9.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
6707
+ console.log(chalk26.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
6708
+ console.log(chalk26.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
4471
6709
  `));
4472
6710
  }
4473
6711
  rl.prompt();
@@ -4475,7 +6713,7 @@ Analyze the above input.`;
4475
6713
  }
4476
6714
  if (input === "/usage") {
4477
6715
  if (usage.turnCount === 0) {
4478
- console.log(chalk9.gray(" No usage yet.\n"));
6716
+ console.log(chalk26.gray(" No usage yet.\n"));
4479
6717
  } else {
4480
6718
  console.log(usage.formatSession());
4481
6719
  const currentTokens = estimateTokens(messages);
@@ -4488,7 +6726,7 @@ Analyze the above input.`;
4488
6726
  }
4489
6727
  if (input === "/cost") {
4490
6728
  if (costTracker.totalCost === 0) {
4491
- console.log(chalk9.gray(" No cost data yet.\n"));
6729
+ console.log(chalk26.gray(" No cost data yet.\n"));
4492
6730
  } else {
4493
6731
  console.log(costTracker.formatTotal());
4494
6732
  console.log(costTracker.formatByModel());
@@ -4498,12 +6736,12 @@ Analyze the above input.`;
4498
6736
  return;
4499
6737
  }
4500
6738
  if (input === "/compact") {
4501
- const { autoCompress: autoCompress2 } = await import("./compression-AGHTZF7D.js");
6739
+ const { autoCompress: autoCompress2 } = await import("./compression-CXJN2ZYN.js");
4502
6740
  const before = messages.length;
4503
6741
  const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
4504
6742
  messages.length = 0;
4505
6743
  messages.push(...compressed);
4506
- console.log(chalk9.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
6744
+ console.log(chalk26.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
4507
6745
  console.log("");
4508
6746
  rl.prompt();
4509
6747
  return;
@@ -4511,10 +6749,10 @@ Analyze the above input.`;
4511
6749
  if (input === "/diff") {
4512
6750
  const diffs = checkpoints.allDiffs();
4513
6751
  if (diffs.length === 0) {
4514
- console.log(chalk9.gray(" No file changes this session.\n"));
6752
+ console.log(chalk26.gray(" No file changes this session.\n"));
4515
6753
  } else {
4516
6754
  for (const df of diffs) {
4517
- console.log(chalk9.cyan(` ${df.path}:`));
6755
+ console.log(chalk26.cyan(` ${df.path}:`));
4518
6756
  console.log(unifiedDiff(df.before, df.after, df.path));
4519
6757
  console.log("");
4520
6758
  }
@@ -4530,10 +6768,10 @@ Analyze the above input.`;
4530
6768
  projectRoot: config.projectRoot,
4531
6769
  outputPath: exportPath
4532
6770
  });
4533
- console.log(chalk9.green(` Exported to ${ePath}
6771
+ console.log(chalk26.green(` Exported to ${ePath}
4534
6772
  `));
4535
6773
  } catch (err) {
4536
- console.log(chalk9.red(` Export failed: ${err.message}
6774
+ console.log(chalk26.red(` Export failed: ${err.message}
4537
6775
  `));
4538
6776
  }
4539
6777
  rl.prompt();
@@ -4543,10 +6781,10 @@ Analyze the above input.`;
4543
6781
  try {
4544
6782
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
4545
6783
  sessionId = id;
4546
- console.log(chalk9.green(` Session saved: ${id}
6784
+ console.log(chalk26.green(` Session saved: ${id}
4547
6785
  `));
4548
6786
  } catch (err) {
4549
- console.log(chalk9.red(` Save failed: ${err.message}
6787
+ console.log(chalk26.red(` Save failed: ${err.message}
4550
6788
  `));
4551
6789
  }
4552
6790
  rl.prompt();
@@ -4556,16 +6794,16 @@ Analyze the above input.`;
4556
6794
  try {
4557
6795
  const sessions = await listSessions(config.projectRoot);
4558
6796
  if (sessions.length === 0) {
4559
- console.log(chalk9.gray(" No saved sessions.\n"));
6797
+ console.log(chalk26.gray(" No saved sessions.\n"));
4560
6798
  } else {
4561
- console.log(chalk9.gray("\n Saved sessions:\n"));
6799
+ console.log(chalk26.gray("\n Saved sessions:\n"));
4562
6800
  for (const s of sessions.slice(0, 10)) {
4563
- console.log(chalk9.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
6801
+ console.log(chalk26.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
4564
6802
  }
4565
6803
  console.log("");
4566
6804
  }
4567
6805
  } catch (err) {
4568
- console.log(chalk9.red(` Error listing sessions: ${err.message}
6806
+ console.log(chalk26.red(` Error listing sessions: ${err.message}
4569
6807
  `));
4570
6808
  }
4571
6809
  rl.prompt();
@@ -4583,18 +6821,18 @@ Analyze the above input.`;
4583
6821
  const savedPlan = await loadPlan(config.projectRoot);
4584
6822
  if (savedPlan) {
4585
6823
  ralphPlan = savedPlan;
4586
- console.log(chalk9.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
6824
+ console.log(chalk26.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
4587
6825
  `));
4588
6826
  }
4589
6827
  } catch {
4590
6828
  }
4591
- console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6829
+ console.log(chalk26.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4592
6830
  `));
4593
6831
  } else {
4594
- console.log(chalk9.gray(" No session found.\n"));
6832
+ console.log(chalk26.gray(" No session found.\n"));
4595
6833
  }
4596
6834
  } catch (err) {
4597
- console.log(chalk9.red(` Resume failed: ${err.message}
6835
+ console.log(chalk26.red(` Resume failed: ${err.message}
4598
6836
  `));
4599
6837
  }
4600
6838
  rl.prompt();
@@ -4603,7 +6841,7 @@ Analyze the above input.`;
4603
6841
  if (input.startsWith("/search ")) {
4604
6842
  const query = input.replace("/search ", "").trim().toLowerCase();
4605
6843
  if (!query) {
4606
- console.log(chalk9.gray(" Usage: /search <query>\n"));
6844
+ console.log(chalk26.gray(" Usage: /search <query>\n"));
4607
6845
  rl.prompt();
4608
6846
  return;
4609
6847
  }
@@ -4620,14 +6858,14 @@ Analyze the above input.`;
4620
6858
  }
4621
6859
  }
4622
6860
  if (matches.length === 0) {
4623
- console.log(chalk9.gray(` No matches for "${query}"
6861
+ console.log(chalk26.gray(` No matches for "${query}"
4624
6862
  `));
4625
6863
  } else {
4626
- console.log(chalk9.gray(`
6864
+ console.log(chalk26.gray(`
4627
6865
  ${matches.length} match(es) for "${query}":
4628
6866
  `));
4629
6867
  for (const m of matches.slice(0, 10)) {
4630
- console.log(chalk9.gray(` [${m.index}] ${m.role}: ${m.preview}`));
6868
+ console.log(chalk26.gray(` [${m.index}] ${m.role}: ${m.preview}`));
4631
6869
  }
4632
6870
  console.log("");
4633
6871
  }
@@ -4636,12 +6874,12 @@ Analyze the above input.`;
4636
6874
  }
4637
6875
  if (input.startsWith("/plan ") && !input.startsWith("/plan approve") && !input.startsWith("/plan cancel") && !input.startsWith("/plan edit")) {
4638
6876
  const task = input.replace("/plan ", "").trim();
4639
- const planSpinner = ora("Generating plan...").start();
6877
+ const planSpinner = ora4("Generating plan...").start();
4640
6878
  try {
4641
6879
  activePlan = await generatePlan(task, model, systemPrompt);
4642
6880
  planSpinner.succeed("Plan generated");
4643
6881
  console.log(formatPlan(activePlan));
4644
- console.log(chalk9.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
6882
+ console.log(chalk26.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
4645
6883
  } catch (err) {
4646
6884
  planSpinner.fail(`Plan failed: ${err.message}`);
4647
6885
  }
@@ -4650,15 +6888,15 @@ Analyze the above input.`;
4650
6888
  }
4651
6889
  if (input === "/plan approve") {
4652
6890
  if (!activePlan) {
4653
- console.log(chalk9.gray(" No active plan. Use /plan <task> to create one.\n"));
6891
+ console.log(chalk26.gray(" No active plan. Use /plan <task> to create one.\n"));
4654
6892
  rl.prompt();
4655
6893
  return;
4656
6894
  }
4657
- console.log(chalk9.green(" Executing plan...\n"));
6895
+ console.log(chalk26.green(" Executing plan...\n"));
4658
6896
  while (!isPlanComplete(activePlan)) {
4659
6897
  const stepPrompt = currentStepPrompt(activePlan);
4660
6898
  messages.push({ role: "user", content: stepPrompt });
4661
- const planStepSpinner = ora(`Step ${activePlan.currentStep + 1}/${activePlan.steps.length}...`).start();
6899
+ const planStepSpinner = ora4(`Step ${activePlan.currentStep + 1}/${activePlan.steps.length}...`).start();
4662
6900
  try {
4663
6901
  const response = await runAgentLoop(messages, {
4664
6902
  model,
@@ -4672,10 +6910,10 @@ Analyze the above input.`;
4672
6910
  },
4673
6911
  onToolCall: (name) => {
4674
6912
  if (planStepSpinner.isSpinning) planStepSpinner.stop();
4675
- console.log(chalk9.gray(` \u2192 ${name}`));
6913
+ console.log(chalk26.gray(` \u2192 ${name}`));
4676
6914
  },
4677
6915
  onToolResult: (_name, _result, isError) => {
4678
- console.log(isError ? chalk9.red(" \u2717") : chalk9.green(" \u2713"));
6916
+ console.log(isError ? chalk26.red(" \u2717") : chalk26.green(" \u2713"));
4679
6917
  }
4680
6918
  });
4681
6919
  console.log("\n");
@@ -4688,7 +6926,7 @@ Analyze the above input.`;
4688
6926
  }
4689
6927
  }
4690
6928
  if (activePlan && isPlanComplete(activePlan)) {
4691
- console.log(chalk9.green(" Plan completed!\n"));
6929
+ console.log(chalk26.green(" Plan completed!\n"));
4692
6930
  }
4693
6931
  activePlan = null;
4694
6932
  rl.prompt();
@@ -4696,26 +6934,26 @@ Analyze the above input.`;
4696
6934
  }
4697
6935
  if (input === "/plan edit") {
4698
6936
  if (!activePlan) {
4699
- console.log(chalk9.gray(" No active plan to edit.\n"));
6937
+ console.log(chalk26.gray(" No active plan to edit.\n"));
4700
6938
  rl.prompt();
4701
6939
  return;
4702
6940
  }
4703
6941
  console.log(formatPlan(activePlan));
4704
- console.log(chalk9.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
4705
- console.log(chalk9.gray(" or /plan approve to proceed with the current plan.\n"));
6942
+ console.log(chalk26.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
6943
+ console.log(chalk26.gray(" or /plan approve to proceed with the current plan.\n"));
4706
6944
  rl.prompt();
4707
6945
  return;
4708
6946
  }
4709
6947
  if (input === "/plan cancel") {
4710
6948
  activePlan = null;
4711
- console.log(chalk9.gray(" Plan discarded.\n"));
6949
+ console.log(chalk26.gray(" Plan discarded.\n"));
4712
6950
  rl.prompt();
4713
6951
  return;
4714
6952
  }
4715
6953
  if (input.startsWith("/agent ")) {
4716
6954
  const task = input.replace("/agent ", "").trim();
4717
6955
  const agentId = nextSubagentId();
4718
- console.log(chalk9.cyan(` Spawning subagent #${agentId}: ${task}
6956
+ console.log(chalk26.cyan(` Spawning subagent #${agentId}: ${task}
4719
6957
  `));
4720
6958
  spawnSubagent({
4721
6959
  id: agentId,
@@ -4725,14 +6963,14 @@ Analyze the above input.`;
4725
6963
  toolContext: toolCtx,
4726
6964
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4727
6965
  onComplete: (result) => {
4728
- console.log(chalk9.green(`
6966
+ console.log(chalk26.green(`
4729
6967
  Subagent #${agentId} finished:`));
4730
- console.log(chalk9.gray(` ${result.slice(0, 200)}
6968
+ console.log(chalk26.gray(` ${result.slice(0, 200)}
4731
6969
  `));
4732
6970
  rl.prompt();
4733
6971
  },
4734
6972
  onError: (err) => {
4735
- console.log(chalk9.red(`
6973
+ console.log(chalk26.red(`
4736
6974
  Subagent #${agentId} failed: ${err}
4737
6975
  `));
4738
6976
  rl.prompt();
@@ -4745,18 +6983,18 @@ Analyze the above input.`;
4745
6983
  try {
4746
6984
  const memories = await loadMemories(config.projectRoot);
4747
6985
  if (memories.length === 0) {
4748
- console.log(chalk9.gray(" No saved memories.\n"));
6986
+ console.log(chalk26.gray(" No saved memories.\n"));
4749
6987
  } else {
4750
- console.log(chalk9.gray(`
6988
+ console.log(chalk26.gray(`
4751
6989
  ${memories.length} saved memories:
4752
6990
  `));
4753
6991
  for (const m of memories) {
4754
- console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
6992
+ console.log(chalk26.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
4755
6993
  }
4756
6994
  console.log("");
4757
6995
  }
4758
6996
  } catch (err) {
4759
- console.log(chalk9.red(` Error: ${err.message}
6997
+ console.log(chalk26.red(` Error: ${err.message}
4760
6998
  `));
4761
6999
  }
4762
7000
  rl.prompt();
@@ -4767,16 +7005,16 @@ Analyze the above input.`;
4767
7005
  try {
4768
7006
  const results = await searchMemories(config.projectRoot, query);
4769
7007
  if (results.length === 0) {
4770
- console.log(chalk9.gray(` No memories matching "${query}"
7008
+ console.log(chalk26.gray(` No memories matching "${query}"
4771
7009
  `));
4772
7010
  } else {
4773
7011
  for (const m of results) {
4774
- console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
7012
+ console.log(chalk26.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
4775
7013
  }
4776
7014
  console.log("");
4777
7015
  }
4778
7016
  } catch (err) {
4779
- console.log(chalk9.red(` Error: ${err.message}
7017
+ console.log(chalk26.red(` Error: ${err.message}
4780
7018
  `));
4781
7019
  }
4782
7020
  rl.prompt();
@@ -4788,10 +7026,10 @@ Analyze the above input.`;
4788
7026
  for (const m of memories) {
4789
7027
  await deleteMemory(config.projectRoot, m.id);
4790
7028
  }
4791
- console.log(chalk9.yellow(` Cleared ${memories.length} memories.
7029
+ console.log(chalk26.yellow(` Cleared ${memories.length} memories.
4792
7030
  `));
4793
7031
  } catch (err) {
4794
- console.log(chalk9.red(` Error: ${err.message}
7032
+ console.log(chalk26.red(` Error: ${err.message}
4795
7033
  `));
4796
7034
  }
4797
7035
  rl.prompt();
@@ -4805,7 +7043,7 @@ Analyze the above input.`;
4805
7043
  }
4806
7044
  if (input.startsWith("/ralph plan ")) {
4807
7045
  const goal = input.replace("/ralph plan ", "").trim();
4808
- const planSpinner = ora("Ralph is planning...").start();
7046
+ const planSpinner = ora4("Ralph is planning...").start();
4809
7047
  try {
4810
7048
  ralphPlan = await generateRalphPlan(goal, model, systemPrompt);
4811
7049
  await savePlan(config.projectRoot, ralphPlan);
@@ -4819,27 +7057,27 @@ Analyze the above input.`;
4819
7057
  }
4820
7058
  if (input === "/ralph run") {
4821
7059
  if (!ralphPlan) {
4822
- console.log(chalk9.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
7060
+ console.log(chalk26.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
4823
7061
  rl.prompt();
4824
7062
  return;
4825
7063
  }
4826
- console.log(chalk9.green(" Ralph is running...\n"));
7064
+ console.log(chalk26.green(" Ralph is running...\n"));
4827
7065
  try {
4828
7066
  ralphPlan = await runRalphLoop(ralphPlan, {
4829
7067
  model,
4830
7068
  systemPrompt,
4831
7069
  toolContext: toolCtx,
4832
7070
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4833
- onTaskStart: (task) => console.log(chalk9.cyan(` \u25B6 Task: ${task.description}`)),
4834
- onTaskComplete: (task) => console.log(chalk9.green(` \u2713 Done: ${task.description}
7071
+ onTaskStart: (task) => console.log(chalk26.cyan(` \u25B6 Task: ${task.description}`)),
7072
+ onTaskComplete: (task) => console.log(chalk26.green(` \u2713 Done: ${task.description}
4835
7073
  `)),
4836
- onTaskFail: (task, err) => console.log(chalk9.red(` \u2717 Failed: ${task.description} (${err})
7074
+ onTaskFail: (task, err) => console.log(chalk26.red(` \u2717 Failed: ${task.description} (${err})
4837
7075
  `))
4838
7076
  });
4839
7077
  await savePlan(config.projectRoot, ralphPlan);
4840
7078
  console.log(formatRalphStatus(ralphPlan));
4841
7079
  } catch (err) {
4842
- console.log(chalk9.red(` Ralph error: ${err.message}
7080
+ console.log(chalk26.red(` Ralph error: ${err.message}
4843
7081
  `));
4844
7082
  }
4845
7083
  rl.prompt();
@@ -4847,7 +7085,7 @@ Analyze the above input.`;
4847
7085
  }
4848
7086
  if (input === "/ralph status") {
4849
7087
  if (!ralphPlan) {
4850
- console.log(chalk9.gray(" No Ralph plan active.\n"));
7088
+ console.log(chalk26.gray(" No Ralph plan active.\n"));
4851
7089
  } else {
4852
7090
  console.log(formatRalphStatus(ralphPlan));
4853
7091
  }
@@ -4858,26 +7096,26 @@ Analyze the above input.`;
4858
7096
  ralphPlan = null;
4859
7097
  await deletePlan(config.projectRoot).catch(() => {
4860
7098
  });
4861
- console.log(chalk9.gray(" Ralph plan cleared.\n"));
7099
+ console.log(chalk26.gray(" Ralph plan cleared.\n"));
4862
7100
  rl.prompt();
4863
7101
  return;
4864
7102
  }
4865
7103
  if (input === "/branch") {
4866
7104
  const branchId = `branch-${branches.size + 1}`;
4867
7105
  branches.set(branchId, [...messages]);
4868
- console.log(chalk9.green(` Forked conversation as "${branchId}" (${messages.length} messages)
7106
+ console.log(chalk26.green(` Forked conversation as "${branchId}" (${messages.length} messages)
4869
7107
  `));
4870
7108
  rl.prompt();
4871
7109
  return;
4872
7110
  }
4873
7111
  if (input === "/branches") {
4874
7112
  if (branches.size === 0) {
4875
- console.log(chalk9.gray(" No conversation branches. Use /branch to fork.\n"));
7113
+ console.log(chalk26.gray(" No conversation branches. Use /branch to fork.\n"));
4876
7114
  } else {
4877
- console.log(chalk9.gray("\n Branches:\n"));
7115
+ console.log(chalk26.gray("\n Branches:\n"));
4878
7116
  for (const [name, msgs] of branches) {
4879
- const marker = name === currentBranch ? chalk9.green(" \u25CF") : " ";
4880
- console.log(chalk9.gray(` ${marker} ${name} (${msgs.length} messages)`));
7117
+ const marker = name === currentBranch ? chalk26.green(" \u25CF") : " ";
7118
+ console.log(chalk26.gray(` ${marker} ${name} (${msgs.length} messages)`));
4881
7119
  }
4882
7120
  console.log("");
4883
7121
  }
@@ -4914,8 +7152,50 @@ Analyze the above input.`;
4914
7152
  return;
4915
7153
  }
4916
7154
  if (input.startsWith("/")) {
4917
- console.log(chalk9.red(` Unknown command: ${input}`));
4918
- console.log(chalk9.gray(" Type /help for available commands.\n"));
7155
+ const cmdCtx = {
7156
+ cwd: config.projectRoot,
7157
+ modelId: activeModelId,
7158
+ messages,
7159
+ lastResponse: lastAssistantResponse,
7160
+ log: (msg) => console.log(chalk26.gray(` ${msg}`)),
7161
+ runPrompt: async (prompt, msgs) => {
7162
+ const spinner2 = ora4("Thinking...").start();
7163
+ const response = await withRetry(
7164
+ () => runAgentLoop(msgs, {
7165
+ model,
7166
+ systemPrompt,
7167
+ toolContext: { ...toolCtx, dryRun: isSandboxEnabled() || toolCtx.dryRun },
7168
+ maxIterations: config.maxIterations,
7169
+ contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
7170
+ onTextChunk: (chunk) => {
7171
+ if (spinner2.isSpinning) spinner2.stop();
7172
+ process.stdout.write(chunk);
7173
+ },
7174
+ onToolCall: (name, args) => {
7175
+ if (spinner2.isSpinning) spinner2.stop();
7176
+ const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
7177
+ console.log(chalk26.gray(`
7178
+ \u2192 ${name}(${argSummary})`));
7179
+ },
7180
+ onToolResult: (_name, result, isError) => {
7181
+ const preview = result.slice(0, 100).replace(/\n/g, " ");
7182
+ const icon = isError ? chalk26.red("\u2717") : chalk26.green("\u2713");
7183
+ console.log(chalk26.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7184
+ }
7185
+ })
7186
+ );
7187
+ if (spinner2.isSpinning) spinner2.stop();
7188
+ lastAssistantResponse = response.text;
7189
+ return response.text;
7190
+ }
7191
+ };
7192
+ const handled = await dispatchCommand(input, cmdCtx);
7193
+ if (handled) {
7194
+ rl.prompt();
7195
+ return;
7196
+ }
7197
+ console.log(chalk26.red(` Unknown command: ${input}`));
7198
+ console.log(chalk26.gray(" Type /help for available commands.\n"));
4919
7199
  rl.prompt();
4920
7200
  return;
4921
7201
  }
@@ -4923,16 +7203,16 @@ Analyze the above input.`;
4923
7203
  const refContext = formatReferences(references);
4924
7204
  const finalPrompt = refContext + cleanInput;
4925
7205
  if (references.length > 0) {
4926
- console.log(chalk9.gray(` Injected ${references.length} reference(s)`));
7206
+ console.log(chalk26.gray(` Injected ${references.length} reference(s)`));
4927
7207
  }
4928
7208
  messages.push({ role: "user", content: finalPrompt });
4929
- const spinner = ora("Thinking...").start();
7209
+ const spinner = ora4("Thinking...").start();
4930
7210
  try {
4931
7211
  const response = await withRetry(
4932
7212
  () => runAgentLoop(messages, {
4933
7213
  model,
4934
7214
  systemPrompt,
4935
- toolContext: toolCtx,
7215
+ toolContext: { ...toolCtx, dryRun: isSandboxEnabled() || toolCtx.dryRun },
4936
7216
  maxIterations: config.maxIterations,
4937
7217
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4938
7218
  onTextChunk: (chunk) => {
@@ -4949,20 +7229,21 @@ Analyze the above input.`;
4949
7229
  const val = String(v);
4950
7230
  return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
4951
7231
  }).join(", ");
4952
- console.log(chalk9.gray(`
7232
+ console.log(chalk26.gray(`
4953
7233
  \u2192 ${name}(${argSummary})`));
4954
7234
  },
4955
7235
  onToolResult: (_name, result, isError) => {
4956
7236
  const preview = result.slice(0, 100).replace(/\n/g, " ");
4957
- const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
4958
- console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7237
+ const icon = isError ? chalk26.red("\u2717") : chalk26.green("\u2713");
7238
+ console.log(chalk26.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4959
7239
  },
4960
7240
  onCompress: () => {
4961
- console.log(chalk9.yellow("\n [Context compressed to fit window]\n"));
7241
+ console.log(chalk26.yellow("\n [Context compressed to fit window]\n"));
4962
7242
  }
4963
7243
  })
4964
7244
  );
4965
7245
  console.log("\n");
7246
+ lastAssistantResponse = response.text;
4966
7247
  checkpoints.commit(`Turn ${usage.turnCount + 1}`);
4967
7248
  if (response.usage) {
4968
7249
  usage.record({
@@ -4991,7 +7272,7 @@ Analyze the above input.`;
4991
7272
  type: "auto",
4992
7273
  content: memMatch[1]
4993
7274
  });
4994
- console.log(chalk9.gray(" (Saved to memory)\n"));
7275
+ console.log(chalk26.gray(" (Saved to memory)\n"));
4995
7276
  } catch {
4996
7277
  }
4997
7278
  }
@@ -5001,13 +7282,13 @@ Analyze the above input.`;
5001
7282
  checkpoints.discard();
5002
7283
  const msg = err.message?.toLowerCase() ?? "";
5003
7284
  if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
5004
- console.log(chalk9.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
7285
+ console.log(chalk26.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
5005
7286
  } else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
5006
- console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
7287
+ console.log(chalk26.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
5007
7288
  } else if (msg.includes("429") || msg.includes("rate limit")) {
5008
- console.log(chalk9.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
7289
+ console.log(chalk26.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
5009
7290
  } else {
5010
- console.log(chalk9.gray(" (The conversation history is preserved. Try again.)\n"));
7291
+ console.log(chalk26.gray(" (The conversation history is preserved. Try again.)\n"));
5011
7292
  }
5012
7293
  }
5013
7294
  rl.prompt();
@@ -5025,16 +7306,16 @@ async function handleRalphSubcommand(args, cliOpts) {
5025
7306
  cwd: config.projectRoot,
5026
7307
  requireConfirm: false,
5027
7308
  confirm: async () => true,
5028
- log: (msg) => console.log(chalk9.gray(` ${msg}`))
7309
+ log: (msg) => console.log(chalk26.gray(` ${msg}`))
5029
7310
  };
5030
7311
  const subcommand = args[0];
5031
7312
  if (subcommand === "plan") {
5032
7313
  const goal = args.slice(1).join(" ");
5033
7314
  if (!goal) {
5034
- console.error(chalk9.red(" Usage: notch ralph plan <goal>"));
7315
+ console.error(chalk26.red(" Usage: notch ralph plan <goal>"));
5035
7316
  process.exit(1);
5036
7317
  }
5037
- const spinner = ora("Ralph is planning...").start();
7318
+ const spinner = ora4("Ralph is planning...").start();
5038
7319
  const plan = await generateRalphPlan(goal, model, systemPrompt);
5039
7320
  await savePlan(config.projectRoot, plan);
5040
7321
  spinner.succeed(`Planned ${plan.tasks.length} tasks`);
@@ -5042,7 +7323,7 @@ async function handleRalphSubcommand(args, cliOpts) {
5042
7323
  } else if (subcommand === "run") {
5043
7324
  let plan = await loadPlan(config.projectRoot);
5044
7325
  if (!plan) {
5045
- console.error(chalk9.red(" No plan found. Run: notch ralph plan <goal>"));
7326
+ console.error(chalk26.red(" No plan found. Run: notch ralph plan <goal>"));
5046
7327
  process.exit(1);
5047
7328
  }
5048
7329
  plan = await runRalphLoop(plan, {
@@ -5050,27 +7331,27 @@ async function handleRalphSubcommand(args, cliOpts) {
5050
7331
  systemPrompt,
5051
7332
  toolContext: toolCtx,
5052
7333
  contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
5053
- onTaskStart: (t) => console.log(chalk9.cyan(` \u25B6 ${t.description}`)),
5054
- onTaskComplete: (t) => console.log(chalk9.green(` \u2713 ${t.description}`)),
5055
- onTaskFail: (t, e) => console.log(chalk9.red(` \u2717 ${t.description}: ${e}`))
7334
+ onTaskStart: (t) => console.log(chalk26.cyan(` \u25B6 ${t.description}`)),
7335
+ onTaskComplete: (t) => console.log(chalk26.green(` \u2713 ${t.description}`)),
7336
+ onTaskFail: (t, e) => console.log(chalk26.red(` \u2717 ${t.description}: ${e}`))
5056
7337
  });
5057
7338
  await savePlan(config.projectRoot, plan);
5058
7339
  console.log(formatRalphStatus(plan));
5059
7340
  } else if (subcommand === "status") {
5060
7341
  const plan = await loadPlan(config.projectRoot);
5061
7342
  if (!plan) {
5062
- console.log(chalk9.gray(" No Ralph plan found."));
7343
+ console.log(chalk26.gray(" No Ralph plan found."));
5063
7344
  } else {
5064
7345
  console.log(formatRalphStatus(plan));
5065
7346
  }
5066
7347
  } else {
5067
- console.error(chalk9.red(` Unknown: notch ralph ${subcommand}`));
5068
- console.error(chalk9.gray(" Usage: notch ralph <plan|run|status>"));
7348
+ console.error(chalk26.red(` Unknown: notch ralph ${subcommand}`));
7349
+ console.error(chalk26.gray(" Usage: notch ralph <plan|run|status>"));
5069
7350
  process.exit(1);
5070
7351
  }
5071
7352
  }
5072
7353
  main().catch((err) => {
5073
- console.error(chalk9.red(`
7354
+ console.error(chalk26.red(`
5074
7355
  Fatal: ${err.message}
5075
7356
  `));
5076
7357
  process.exit(1);