@freesyntax/notch-cli 0.5.0 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +587 -272
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
 
15
15
  // src/index.ts
16
16
  import { Command } from "commander";
17
- import chalk26 from "chalk";
17
+ import chalk27 from "chalk";
18
18
  import ora4 from "ora";
19
19
  import * as readline from "readline";
20
20
  import * as nodePath from "path";
@@ -418,6 +418,7 @@ var editTool = {
418
418
 
419
419
  // src/tools/shell.ts
420
420
  import { execSync } from "child_process";
421
+ import path5 from "path";
421
422
  import { z as z4 } from "zod";
422
423
  var BLOCKED_PATTERNS = [
423
424
  /rm\s+-rf\s+\/(?!\S)/,
@@ -446,14 +447,17 @@ var parameters4 = z4.object({
446
447
  timeout: z4.number().optional().describe("Timeout in ms (default 30s, max configurable up to 10m)")
447
448
  });
448
449
  function validateCommand(command, cwd) {
449
- const absolutePathRegex = /(?:^|\s)(?:>|>>|cat|cp|mv|ln)\s+(\/(?!tmp|dev\/null)[^\s]+)/g;
450
+ const fileOpRegex = /(?:^|\s)(?:>|>>|cat|cp|mv|ln|tee|tar|zip|scp|rsync|chmod|chown|rm)\s+(\/(?!tmp\b|dev\/null\b)[^\s]+)/g;
450
451
  let match;
451
- while ((match = absolutePathRegex.exec(command)) !== null) {
452
- const targetPath = match[1];
452
+ while ((match = fileOpRegex.exec(command)) !== null) {
453
+ const targetPath = path5.normalize(match[1]);
453
454
  if (!targetPath.startsWith(cwd) && !targetPath.startsWith("/tmp")) {
454
455
  return `Blocked: command targets path outside project root: ${targetPath}`;
455
456
  }
456
457
  }
458
+ if (/(?:^|\s)(?:\.\.\/){3,}/.test(command)) {
459
+ return "Blocked: deep path traversal detected";
460
+ }
457
461
  return null;
458
462
  }
459
463
  var shellTool = {
@@ -664,7 +668,7 @@ Proceed?`
664
668
  // src/tools/grep.ts
665
669
  import { execSync as execSync2 } from "child_process";
666
670
  import fs5 from "fs/promises";
667
- import path5 from "path";
671
+ import path6 from "path";
668
672
  import { z as z6 } from "zod";
669
673
  var parameters6 = z6.object({
670
674
  pattern: z6.string().describe("Regex pattern to search for"),
@@ -678,7 +682,7 @@ var grepTool = {
678
682
  description: "Search file contents by regex pattern. Returns matching lines with file paths and line numbers. Supports glob filtering and context lines.",
679
683
  parameters: parameters6,
680
684
  async execute(params, ctx) {
681
- const searchPath = params.path ? path5.isAbsolute(params.path) ? params.path : path5.resolve(ctx.cwd, params.path) : ctx.cwd;
685
+ const searchPath = params.path ? path6.isAbsolute(params.path) ? params.path : path6.resolve(ctx.cwd, params.path) : ctx.cwd;
682
686
  try {
683
687
  const rgArgs = [
684
688
  "rg",
@@ -710,7 +714,7 @@ var grepTool = {
710
714
  for (const entry of entries) {
711
715
  if (results.length >= (params.max_results ?? 50)) return;
712
716
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
713
- const full = path5.join(dir, entry.name);
717
+ const full = path6.join(dir, entry.name);
714
718
  if (entry.isDirectory()) {
715
719
  await searchDir(full);
716
720
  } else if (entry.isFile()) {
@@ -723,7 +727,7 @@ var grepTool = {
723
727
  const lines = content.split("\n");
724
728
  for (let i = 0; i < lines.length; i++) {
725
729
  if (regex.test(lines[i])) {
726
- const rel = path5.relative(ctx.cwd, full);
730
+ const rel = path6.relative(ctx.cwd, full);
727
731
  results.push(`${rel}:${i + 1}:${lines[i]}`);
728
732
  regex.lastIndex = 0;
729
733
  if (results.length >= (params.max_results ?? 50)) return;
@@ -738,7 +742,7 @@ var grepTool = {
738
742
  if (stat.isFile()) {
739
743
  const content = await fs5.readFile(searchPath, "utf-8");
740
744
  const lines = content.split("\n");
741
- const rel = path5.relative(ctx.cwd, searchPath);
745
+ const rel = path6.relative(ctx.cwd, searchPath);
742
746
  for (let i = 0; i < lines.length; i++) {
743
747
  if (regex.test(lines[i])) {
744
748
  results.push(`${rel}:${i + 1}:${lines[i]}`);
@@ -758,7 +762,7 @@ var grepTool = {
758
762
 
759
763
  // src/tools/glob.ts
760
764
  import { glob as globFn } from "glob";
761
- import path6 from "path";
765
+ import path7 from "path";
762
766
  import { z as z7 } from "zod";
763
767
  var parameters7 = z7.object({
764
768
  pattern: z7.string().describe('Glob pattern, e.g. "**/*.ts" or "src/**/*.tsx"'),
@@ -769,7 +773,7 @@ var globTool = {
769
773
  description: "Find files matching a glob pattern. Returns relative paths sorted by modification time. Ignores node_modules and .git by default.",
770
774
  parameters: parameters7,
771
775
  async execute(params, ctx) {
772
- const searchDir = params.path ? path6.isAbsolute(params.path) ? params.path : path6.resolve(ctx.cwd, params.path) : ctx.cwd;
776
+ const searchDir = params.path ? path7.isAbsolute(params.path) ? params.path : path7.resolve(ctx.cwd, params.path) : ctx.cwd;
773
777
  try {
774
778
  const matches = await globFn(params.pattern, {
775
779
  cwd: searchDir,
@@ -778,9 +782,9 @@ var globTool = {
778
782
  dot: false
779
783
  });
780
784
  if (matches.length === 0) {
781
- return { content: `No files matching "${params.pattern}" in ${path6.relative(ctx.cwd, searchDir) || "."}` };
785
+ return { content: `No files matching "${params.pattern}" in ${path7.relative(ctx.cwd, searchDir) || "."}` };
782
786
  }
783
- const relative = matches.map((m) => path6.relative(ctx.cwd, path6.resolve(searchDir, m)));
787
+ const relative = matches.map((m) => path7.relative(ctx.cwd, path7.resolve(searchDir, m)));
784
788
  return {
785
789
  content: `Found ${relative.length} file(s):
786
790
  ${relative.join("\n")}`
@@ -866,7 +870,7 @@ var GITHUB_API = "https://api.github.com";
866
870
  function getToken() {
867
871
  return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
868
872
  }
869
- async function ghFetch(path25, opts2 = {}) {
873
+ async function ghFetch(path26, opts2 = {}) {
870
874
  const token = getToken();
871
875
  const headers = {
872
876
  "Accept": "application/vnd.github+json",
@@ -874,7 +878,7 @@ async function ghFetch(path25, opts2 = {}) {
874
878
  ...opts2.headers ?? {}
875
879
  };
876
880
  if (token) headers["Authorization"] = `Bearer ${token}`;
877
- return fetch(`${GITHUB_API}${path25}`, { ...opts2, headers });
881
+ return fetch(`${GITHUB_API}${path26}`, { ...opts2, headers });
878
882
  }
879
883
  var parameters9 = z9.object({
880
884
  action: z9.enum([
@@ -1032,9 +1036,9 @@ var githubTool = {
1032
1036
  };
1033
1037
 
1034
1038
  // src/tools/lsp.ts
1035
- import { execSync as execSync3, execFileSync } from "child_process";
1039
+ import { execFileSync } from "child_process";
1036
1040
  import fs6 from "fs/promises";
1037
- import path7 from "path";
1041
+ import path8 from "path";
1038
1042
  import { z as z10 } from "zod";
1039
1043
  var parameters10 = z10.object({
1040
1044
  action: z10.enum(["definition", "references", "diagnostics", "hover"]).describe("LSP action: definition (go-to-def), references (find usages), diagnostics (type errors), hover (type info)"),
@@ -1044,7 +1048,7 @@ var parameters10 = z10.object({
1044
1048
  symbol: z10.string().optional().describe("Symbol name to search for (alternative to line/character)")
1045
1049
  });
1046
1050
  function detectLanguage(filePath) {
1047
- const ext = path7.extname(filePath).toLowerCase();
1051
+ const ext = path8.extname(filePath).toLowerCase();
1048
1052
  switch (ext) {
1049
1053
  case ".ts":
1050
1054
  case ".tsx":
@@ -1074,10 +1078,11 @@ function isAvailable(cmd) {
1074
1078
  }
1075
1079
  async function tsDiagnostics(filePath, cwd) {
1076
1080
  try {
1077
- const result = execSync3(
1078
- `npx tsc --noEmit --pretty false "${filePath}" 2>&1`,
1079
- { cwd, encoding: "utf-8", timeout: 3e4 }
1080
- );
1081
+ const result = execFileSync("npx", ["tsc", "--noEmit", "--pretty", "false", filePath], {
1082
+ cwd,
1083
+ encoding: "utf-8",
1084
+ timeout: 3e4
1085
+ });
1081
1086
  return result.trim() || "No type errors found.";
1082
1087
  } catch (err) {
1083
1088
  const output = err.stdout || err.stderr || "";
@@ -1086,11 +1091,13 @@ async function tsDiagnostics(filePath, cwd) {
1086
1091
  return lines.slice(0, 20).join("\n") || "No type errors found.";
1087
1092
  }
1088
1093
  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
+ const result = execFileSync("npx", ["tsc", "--noEmit", "--pretty", "false"], {
1095
+ cwd,
1096
+ encoding: "utf-8",
1097
+ timeout: 3e4
1098
+ });
1099
+ const allLines = result.trim().split("\n").slice(0, 30).join("\n");
1100
+ return allLines || "No type errors found.";
1094
1101
  } catch (err2) {
1095
1102
  const out = err2.stdout || err2.stderr || "";
1096
1103
  const errLines = out.split("\n").filter((l) => l.includes("error TS"));
@@ -1099,6 +1106,9 @@ async function tsDiagnostics(filePath, cwd) {
1099
1106
  }
1100
1107
  }
1101
1108
  async function findDefinition(symbol, cwd) {
1109
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(symbol)) {
1110
+ return `Invalid symbol name: "${symbol}"`;
1111
+ }
1102
1112
  const patterns = [
1103
1113
  `(function|const|let|var|class|interface|type|enum)\\s+${symbol}\\b`,
1104
1114
  `export\\s+(default\\s+)?(function|const|let|var|class|interface|type|enum)\\s+${symbol}\\b`,
@@ -1111,40 +1121,76 @@ async function findDefinition(symbol, cwd) {
1111
1121
  ];
1112
1122
  const combinedPattern = patterns.join("|");
1113
1123
  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
- );
1124
+ const result = execFileSync("rg", [
1125
+ "--no-heading",
1126
+ "--line-number",
1127
+ "-e",
1128
+ combinedPattern,
1129
+ "--type-add",
1130
+ "code:*.{ts,tsx,js,jsx,py,go,rs}",
1131
+ "--type",
1132
+ "code",
1133
+ "-m",
1134
+ "10"
1135
+ ], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
1118
1136
  return result.trim() || `No definition found for "${symbol}"`;
1119
1137
  } catch {
1120
1138
  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}"`;
1139
+ const result = execFileSync("grep", [
1140
+ "-rn",
1141
+ "-E",
1142
+ combinedPattern,
1143
+ "--include=*.ts",
1144
+ "--include=*.tsx",
1145
+ "--include=*.js",
1146
+ "--include=*.py",
1147
+ "--include=*.go",
1148
+ "--include=*.rs",
1149
+ "."
1150
+ ], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
1151
+ const lines = result.trim().split("\n").slice(0, 10).join("\n");
1152
+ return lines || `No definition found for "${symbol}"`;
1126
1153
  } catch {
1127
1154
  return `No definition found for "${symbol}"`;
1128
1155
  }
1129
1156
  }
1130
1157
  }
1131
1158
  async function findReferences(symbol, cwd) {
1159
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(symbol)) {
1160
+ return `Invalid symbol name: "${symbol}"`;
1161
+ }
1132
1162
  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
- );
1163
+ const result = execFileSync("rg", [
1164
+ "--no-heading",
1165
+ "--line-number",
1166
+ "-w",
1167
+ symbol,
1168
+ "--type-add",
1169
+ "code:*.{ts,tsx,js,jsx,py,go,rs}",
1170
+ "--type",
1171
+ "code",
1172
+ "-m",
1173
+ "30"
1174
+ ], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
1137
1175
  const lines = result.trim().split("\n");
1138
1176
  return `Found ${lines.length} reference(s) for "${symbol}":
1139
1177
 
1140
1178
  ${lines.join("\n")}`;
1141
1179
  } catch {
1142
1180
  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");
1181
+ const result = execFileSync("grep", [
1182
+ "-rn",
1183
+ "-w",
1184
+ symbol,
1185
+ "--include=*.ts",
1186
+ "--include=*.tsx",
1187
+ "--include=*.js",
1188
+ "--include=*.py",
1189
+ "--include=*.go",
1190
+ "--include=*.rs",
1191
+ "."
1192
+ ], { cwd, encoding: "utf-8", timeout: 1e4, maxBuffer: 1024 * 1024 });
1193
+ const lines = result.trim().split("\n").slice(0, 30);
1148
1194
  return `Found ${lines.length} reference(s) for "${symbol}":
1149
1195
 
1150
1196
  ${lines.join("\n")}`;
@@ -1155,7 +1201,7 @@ ${lines.join("\n")}`;
1155
1201
  }
1156
1202
  async function getHoverInfo(symbol, filePath, line, cwd) {
1157
1203
  try {
1158
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.resolve(cwd, filePath);
1204
+ const absPath = path8.isAbsolute(filePath) ? filePath : path8.resolve(cwd, filePath);
1159
1205
  const content = await fs6.readFile(absPath, "utf-8");
1160
1206
  const lines = content.split("\n");
1161
1207
  const startLine = Math.max(0, line - 3);
@@ -1174,7 +1220,7 @@ ${def}`;
1174
1220
  }
1175
1221
  }
1176
1222
  async function getDiagnostics(filePath, cwd) {
1177
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.resolve(cwd, filePath);
1223
+ const absPath = path8.isAbsolute(filePath) ? filePath : path8.resolve(cwd, filePath);
1178
1224
  const { lang } = detectLanguage(absPath);
1179
1225
  switch (lang) {
1180
1226
  case "typescript":
@@ -1217,13 +1263,13 @@ async function getDiagnostics(filePath, cwd) {
1217
1263
  }
1218
1264
  case "rust": {
1219
1265
  try {
1220
- const result = execSync3(`cargo check --message-format short 2>&1 | head -30`, {
1266
+ const result = execFileSync("cargo", ["check", "--message-format", "short"], {
1221
1267
  cwd,
1222
1268
  encoding: "utf-8",
1223
- timeout: 6e4,
1224
- shell: true
1269
+ timeout: 6e4
1225
1270
  });
1226
- return result.trim() || "No issues found.";
1271
+ const lines = result.trim().split("\n").slice(0, 30).join("\n");
1272
+ return lines || "No issues found.";
1227
1273
  } catch (err) {
1228
1274
  return err.stderr || err.stdout || "Cargo check failed.";
1229
1275
  }
@@ -1238,7 +1284,7 @@ var lspTool = {
1238
1284
  parameters: parameters10,
1239
1285
  async execute(params, ctx) {
1240
1286
  const filePath = params.file;
1241
- const absPath = path7.isAbsolute(filePath) ? filePath : path7.resolve(ctx.cwd, filePath);
1287
+ const absPath = path8.isAbsolute(filePath) ? filePath : path8.resolve(ctx.cwd, filePath);
1242
1288
  try {
1243
1289
  switch (params.action) {
1244
1290
  case "definition": {
@@ -1303,7 +1349,7 @@ async function extractSymbol(filePath, line, character) {
1303
1349
 
1304
1350
  // src/tools/notebook.ts
1305
1351
  import fs7 from "fs/promises";
1306
- import path8 from "path";
1352
+ import path9 from "path";
1307
1353
  import { z as z11 } from "zod";
1308
1354
  var parameters11 = z11.object({
1309
1355
  action: z11.enum(["read", "edit", "add", "remove"]).describe("Action: read (view cells), edit (modify cell source), add (insert new cell), remove (delete cell)"),
@@ -1315,7 +1361,7 @@ var parameters11 = z11.object({
1315
1361
  });
1316
1362
  function formatNotebook(nb, filePath) {
1317
1363
  const parts = [
1318
- `# ${path8.basename(filePath)}`,
1364
+ `# ${path9.basename(filePath)}`,
1319
1365
  `# ${nb.cells.length} cells | nbformat ${nb.nbformat}.${nb.nbformat_minor}`,
1320
1366
  ""
1321
1367
  ];
@@ -1346,7 +1392,7 @@ var notebookTool = {
1346
1392
  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
1393
  parameters: parameters11,
1348
1394
  async execute(params, ctx) {
1349
- const filePath = path8.isAbsolute(params.path) ? params.path : path8.resolve(ctx.cwd, params.path);
1395
+ const filePath = path9.isAbsolute(params.path) ? params.path : path9.resolve(ctx.cwd, params.path);
1350
1396
  try {
1351
1397
  switch (params.action) {
1352
1398
  case "read": {
@@ -1371,7 +1417,7 @@ var notebookTool = {
1371
1417
  );
1372
1418
  nb.cells[params.cell_index].source = lines;
1373
1419
  await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
1374
- return { content: `Cell [${params.cell_index}] updated in ${path8.basename(filePath)}.` };
1420
+ return { content: `Cell [${params.cell_index}] updated in ${path9.basename(filePath)}.` };
1375
1421
  }
1376
1422
  case "add": {
1377
1423
  if (params.content === void 0) {
@@ -1392,7 +1438,7 @@ var notebookTool = {
1392
1438
  const insertIdx = params.insert_after !== void 0 ? params.insert_after + 1 : nb.cells.length;
1393
1439
  nb.cells.splice(insertIdx, 0, newCell);
1394
1440
  await fs7.writeFile(filePath, JSON.stringify(nb, null, 1) + "\n");
1395
- return { content: `Added ${cellType} cell at index [${insertIdx}] in ${path8.basename(filePath)}.` };
1441
+ return { content: `Added ${cellType} cell at index [${insertIdx}] in ${path9.basename(filePath)}.` };
1396
1442
  }
1397
1443
  case "remove": {
1398
1444
  if (params.cell_index === void 0) {
@@ -1405,7 +1451,7 @@ var notebookTool = {
1405
1451
  }
1406
1452
  const removed = nb.cells.splice(params.cell_index, 1)[0];
1407
1453
  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.` };
1454
+ return { content: `Removed ${removed.cell_type} cell [${params.cell_index}] from ${path9.basename(filePath)}. ${nb.cells.length} cells remaining.` };
1409
1455
  }
1410
1456
  default:
1411
1457
  return { content: `Unknown action: ${params.action}`, isError: true };
@@ -1958,11 +2004,11 @@ function parseMCPConfig(config) {
1958
2004
 
1959
2005
  // src/plugins/discovery.ts
1960
2006
  import fs8 from "fs/promises";
1961
- import path9 from "path";
2007
+ import path10 from "path";
1962
2008
  import os from "os";
1963
2009
  async function discoverPlugins(projectRoot) {
1964
- const globalDir = path9.join(os.homedir(), ".notch", "plugins");
1965
- const projectDir = path9.join(projectRoot, ".notch", "plugins");
2010
+ const globalDir = path10.join(os.homedir(), ".notch", "plugins");
2011
+ const projectDir = path10.join(projectRoot, ".notch", "plugins");
1966
2012
  const plugins = /* @__PURE__ */ new Map();
1967
2013
  for (const plugin of await scanDirectory(globalDir)) {
1968
2014
  plugins.set(plugin.name, plugin);
@@ -1978,8 +2024,8 @@ async function scanDirectory(dir) {
1978
2024
  const entries = await fs8.readdir(dir, { withFileTypes: true });
1979
2025
  for (const entry of entries) {
1980
2026
  if (!entry.isDirectory()) continue;
1981
- const pluginDir = path9.join(dir, entry.name);
1982
- const pkgPath = path9.join(pluginDir, "package.json");
2027
+ const pluginDir = path10.join(dir, entry.name);
2028
+ const pkgPath = path10.join(pluginDir, "package.json");
1983
2029
  try {
1984
2030
  const raw = await fs8.readFile(pkgPath, "utf-8");
1985
2031
  const pkg = JSON.parse(raw);
@@ -2001,7 +2047,7 @@ async function scanDirectory(dir) {
2001
2047
  }
2002
2048
 
2003
2049
  // src/plugins/loader.ts
2004
- import path10 from "path";
2050
+ import path11 from "path";
2005
2051
  import { pathToFileURL } from "url";
2006
2052
 
2007
2053
  // src/commands/registry.ts
@@ -2022,7 +2068,7 @@ async function dispatchCommand(input, ctx) {
2022
2068
 
2023
2069
  // src/plugins/loader.ts
2024
2070
  async function loadPlugin(discovered, projectRoot, log) {
2025
- const entryPath = path10.resolve(discovered.path, discovered.manifest.main);
2071
+ const entryPath = path11.resolve(discovered.path, discovered.manifest.main);
2026
2072
  const tools = [];
2027
2073
  const hooks = [];
2028
2074
  const ctx = {
@@ -2189,14 +2235,14 @@ function describeTools() {
2189
2235
 
2190
2236
  // src/context/project-instructions.ts
2191
2237
  import fs9 from "fs/promises";
2192
- import path11 from "path";
2238
+ import path12 from "path";
2193
2239
  import os2 from "os";
2194
2240
  var INSTRUCTION_FILES = [".notch.md", "NOTCH.md", ".notch/instructions.md"];
2195
2241
  async function loadProjectInstructions(projectRoot) {
2196
2242
  const sources = [];
2197
2243
  const homeDir = os2.homedir();
2198
2244
  for (const file of INSTRUCTION_FILES) {
2199
- const globalPath = path11.join(homeDir, file);
2245
+ const globalPath = path12.join(homeDir, file);
2200
2246
  const content = await safeRead(globalPath);
2201
2247
  if (content) {
2202
2248
  sources.push({ path: globalPath, content, scope: "global" });
@@ -2204,7 +2250,7 @@ async function loadProjectInstructions(projectRoot) {
2204
2250
  }
2205
2251
  }
2206
2252
  for (const file of INSTRUCTION_FILES) {
2207
- const projectPath = path11.join(projectRoot, file);
2253
+ const projectPath = path12.join(projectRoot, file);
2208
2254
  const content = await safeRead(projectPath);
2209
2255
  if (content) {
2210
2256
  sources.push({ path: projectPath, content, scope: "project" });
@@ -2235,10 +2281,10 @@ async function safeRead(filePath) {
2235
2281
 
2236
2282
  // src/memory/store.ts
2237
2283
  import fs10 from "fs/promises";
2238
- import path12 from "path";
2284
+ import path13 from "path";
2239
2285
  import os3 from "os";
2240
- var MEMORY_DIR = path12.join(os3.homedir(), ".notch", "memory");
2241
- var INDEX_FILE = path12.join(MEMORY_DIR, "MEMORY.md");
2286
+ var MEMORY_DIR = path13.join(os3.homedir(), ".notch", "memory");
2287
+ var INDEX_FILE = path13.join(MEMORY_DIR, "MEMORY.md");
2242
2288
  async function ensureDir() {
2243
2289
  await fs10.mkdir(MEMORY_DIR, { recursive: true });
2244
2290
  }
@@ -2246,7 +2292,7 @@ async function saveMemory(memory) {
2246
2292
  await ensureDir();
2247
2293
  const slug = memory.name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
2248
2294
  const filename = `${memory.type}_${slug}.md`;
2249
- const filePath = path12.join(MEMORY_DIR, filename);
2295
+ const filePath = path13.join(MEMORY_DIR, filename);
2250
2296
  const fileContent = [
2251
2297
  "---",
2252
2298
  `name: ${memory.name}`,
@@ -2268,7 +2314,7 @@ async function loadMemories() {
2268
2314
  for (const file of files) {
2269
2315
  if (!file.endsWith(".md") || file === "MEMORY.md") continue;
2270
2316
  try {
2271
- const content = await fs10.readFile(path12.join(MEMORY_DIR, file), "utf-8");
2317
+ const content = await fs10.readFile(path13.join(MEMORY_DIR, file), "utf-8");
2272
2318
  const memory = parseMemoryFile(content, file);
2273
2319
  if (memory) memories.push(memory);
2274
2320
  } catch {
@@ -2278,7 +2324,7 @@ async function loadMemories() {
2278
2324
  }
2279
2325
  async function deleteMemory(filename) {
2280
2326
  try {
2281
- await fs10.unlink(path12.join(MEMORY_DIR, filename));
2327
+ await fs10.unlink(path13.join(MEMORY_DIR, filename));
2282
2328
  await updateIndex();
2283
2329
  return true;
2284
2330
  } catch {
@@ -2544,7 +2590,7 @@ async function buildSystemPrompt(projectRoot, modelId) {
2544
2590
 
2545
2591
  // src/agent/checkpoints.ts
2546
2592
  import fs11 from "fs/promises";
2547
- import path13 from "path";
2593
+ import path14 from "path";
2548
2594
  var CheckpointManager = class {
2549
2595
  checkpoints = [];
2550
2596
  nextId = 1;
@@ -2591,7 +2637,7 @@ var CheckpointManager = class {
2591
2637
  } catch {
2592
2638
  }
2593
2639
  } else {
2594
- await fs11.mkdir(path13.dirname(snap.path), { recursive: true });
2640
+ await fs11.mkdir(path14.dirname(snap.path), { recursive: true });
2595
2641
  await fs11.writeFile(snap.path, snap.before, "utf-8");
2596
2642
  }
2597
2643
  }
@@ -2624,8 +2670,8 @@ var CheckpointManager = class {
2624
2670
  }
2625
2671
  }
2626
2672
  }
2627
- return Array.from(fileMap.entries()).map(([path25, { before, after }]) => ({
2628
- path: path25,
2673
+ return Array.from(fileMap.entries()).map(([path26, { before, after }]) => ({
2674
+ path: path26,
2629
2675
  before,
2630
2676
  after
2631
2677
  }));
@@ -3092,13 +3138,13 @@ var CostTracker = class {
3092
3138
 
3093
3139
  // src/agent/ralph.ts
3094
3140
  import fs13 from "fs/promises";
3095
- import path15 from "path";
3141
+ import path16 from "path";
3096
3142
  import chalk5 from "chalk";
3097
3143
  import { generateText as generateText3, streamText as streamText3 } from "ai";
3098
3144
 
3099
3145
  // src/context/repo-map.ts
3100
3146
  import fs12 from "fs/promises";
3101
- import path14 from "path";
3147
+ import path15 from "path";
3102
3148
  import { glob } from "glob";
3103
3149
  var PATTERNS = {
3104
3150
  ts: [
@@ -3129,7 +3175,7 @@ var PATTERNS = {
3129
3175
  };
3130
3176
  var IMPORT_PATTERN = /(?:import|from)\s+['"]([^'"]+)['"]/g;
3131
3177
  function getPatterns(filePath) {
3132
- const ext = path14.extname(filePath).slice(1);
3178
+ const ext = path15.extname(filePath).slice(1);
3133
3179
  if (["ts", "tsx", "mts", "cts"].includes(ext)) return PATTERNS.ts;
3134
3180
  if (["js", "jsx", "mjs", "cjs"].includes(ext)) return PATTERNS.js;
3135
3181
  if (ext === "py") return PATTERNS.py;
@@ -3180,7 +3226,7 @@ async function buildRepoMap(root) {
3180
3226
  });
3181
3227
  const entries = [];
3182
3228
  for (const file of files.slice(0, 500)) {
3183
- const fullPath = path14.resolve(root, file);
3229
+ const fullPath = path15.resolve(root, file);
3184
3230
  try {
3185
3231
  const content = await fs12.readFile(fullPath, "utf-8");
3186
3232
  const lines = content.split("\n").length;
@@ -3284,11 +3330,11 @@ ${repoContext || "(empty project)"}`;
3284
3330
  async function savePlan(plan, cwd) {
3285
3331
  plan.updated = (/* @__PURE__ */ new Date()).toISOString();
3286
3332
  plan.completedCount = plan.tasks.filter((t) => t.status === "done").length;
3287
- await fs13.writeFile(path15.join(cwd, PLAN_FILE), JSON.stringify(plan, null, 2), "utf-8");
3333
+ await fs13.writeFile(path16.join(cwd, PLAN_FILE), JSON.stringify(plan, null, 2), "utf-8");
3288
3334
  }
3289
3335
  async function loadPlan(cwd) {
3290
3336
  try {
3291
- const raw = await fs13.readFile(path15.join(cwd, PLAN_FILE), "utf-8");
3337
+ const raw = await fs13.readFile(path16.join(cwd, PLAN_FILE), "utf-8");
3292
3338
  return JSON.parse(raw);
3293
3339
  } catch {
3294
3340
  return null;
@@ -3296,7 +3342,7 @@ async function loadPlan(cwd) {
3296
3342
  }
3297
3343
  async function deletePlan(cwd) {
3298
3344
  try {
3299
- await fs13.unlink(path15.join(cwd, PLAN_FILE));
3345
+ await fs13.unlink(path16.join(cwd, PLAN_FILE));
3300
3346
  } catch {
3301
3347
  }
3302
3348
  }
@@ -3480,7 +3526,7 @@ function formatRalphStatus(plan) {
3480
3526
 
3481
3527
  // src/context/references.ts
3482
3528
  import fs14 from "fs/promises";
3483
- import path16 from "path";
3529
+ import path17 from "path";
3484
3530
  import { glob as glob2 } from "glob";
3485
3531
  async function resolveReferences(input, cwd) {
3486
3532
  const references = [];
@@ -3523,7 +3569,7 @@ ${truncated}
3523
3569
  return sections.join("\n\n") + "\n\n";
3524
3570
  }
3525
3571
  async function resolveFile(ref, cwd) {
3526
- const filePath = path16.isAbsolute(ref) ? ref : path16.resolve(cwd, ref);
3572
+ const filePath = path17.isAbsolute(ref) ? ref : path17.resolve(cwd, ref);
3527
3573
  try {
3528
3574
  const content = await fs14.readFile(filePath, "utf-8");
3529
3575
  const lines = content.split("\n");
@@ -4250,11 +4296,11 @@ function formatTokens(n) {
4250
4296
 
4251
4297
  // src/ui/update-checker.ts
4252
4298
  import fs15 from "fs/promises";
4253
- import path17 from "path";
4299
+ import path18 from "path";
4254
4300
  import os4 from "os";
4255
- import { execSync as execSync4 } from "child_process";
4301
+ import { execSync as execSync3 } from "child_process";
4256
4302
  import chalk8 from "chalk";
4257
- var CACHE_FILE = path17.join(os4.homedir(), ".notch", "update-check.json");
4303
+ var CACHE_FILE = path18.join(os4.homedir(), ".notch", "update-check.json");
4258
4304
  var CHECK_INTERVAL = 60 * 60 * 1e3;
4259
4305
  var PACKAGE_NAME = "@freesyntax/notch-cli";
4260
4306
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
@@ -4291,7 +4337,7 @@ function autoUpdate(current, latest) {
4291
4337
  \u2B06 Updating Notch CLI: ${current} \u2192 ${latest}...
4292
4338
  `));
4293
4339
  try {
4294
- execSync4(`npm install -g ${PACKAGE_NAME}@${latest}`, {
4340
+ execSync3(`npm install -g ${PACKAGE_NAME}@${latest}`, {
4295
4341
  stdio: "inherit",
4296
4342
  timeout: 6e4
4297
4343
  });
@@ -4326,7 +4372,7 @@ async function loadCache() {
4326
4372
  }
4327
4373
  async function saveCache(cache) {
4328
4374
  try {
4329
- await fs15.mkdir(path17.dirname(CACHE_FILE), { recursive: true });
4375
+ await fs15.mkdir(path18.dirname(CACHE_FILE), { recursive: true });
4330
4376
  await fs15.writeFile(CACHE_FILE, JSON.stringify(cache), "utf-8");
4331
4377
  } catch {
4332
4378
  }
@@ -4334,7 +4380,7 @@ async function saveCache(cache) {
4334
4380
 
4335
4381
  // src/permissions/index.ts
4336
4382
  import fs16 from "fs/promises";
4337
- import path18 from "path";
4383
+ import path19 from "path";
4338
4384
  import os5 from "os";
4339
4385
  var DEFAULT_PERMISSIONS = {
4340
4386
  default: "prompt",
@@ -4352,8 +4398,8 @@ var DEFAULT_PERMISSIONS = {
4352
4398
  ]
4353
4399
  };
4354
4400
  async function loadPermissions(projectRoot) {
4355
- const projectPath = path18.join(projectRoot, ".notch.json");
4356
- const globalPath = path18.join(os5.homedir(), ".notch", "permissions.json");
4401
+ const projectPath = path19.join(projectRoot, ".notch.json");
4402
+ const globalPath = path19.join(os5.homedir(), ".notch", "permissions.json");
4357
4403
  let config = { ...DEFAULT_PERMISSIONS };
4358
4404
  try {
4359
4405
  const raw = await fs16.readFile(globalPath, "utf-8");
@@ -4410,16 +4456,16 @@ function mergePermissions(base, override) {
4410
4456
  }
4411
4457
 
4412
4458
  // src/hooks/index.ts
4413
- import { execSync as execSync5 } from "child_process";
4459
+ import { execSync as execSync4 } from "child_process";
4414
4460
  import fs17 from "fs/promises";
4415
4461
  import { watch } from "fs";
4416
- import path19 from "path";
4462
+ import path20 from "path";
4417
4463
  import os6 from "os";
4418
4464
  import crypto from "crypto";
4419
- var TRUST_STORE_PATH = path19.join(os6.homedir(), ".notch", "trusted-projects.json");
4465
+ var TRUST_STORE_PATH = path20.join(os6.homedir(), ".notch", "trusted-projects.json");
4420
4466
  async function isTrustedProject(projectRoot, raw) {
4421
4467
  const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
4422
- const key = path19.resolve(projectRoot);
4468
+ const key = path20.resolve(projectRoot);
4423
4469
  try {
4424
4470
  const store = JSON.parse(await fs17.readFile(TRUST_STORE_PATH, "utf-8"));
4425
4471
  return store[key] === fingerprint;
@@ -4429,19 +4475,19 @@ async function isTrustedProject(projectRoot, raw) {
4429
4475
  }
4430
4476
  async function trustProject(projectRoot, raw) {
4431
4477
  const fingerprint = crypto.createHash("sha256").update(raw).digest("hex");
4432
- const key = path19.resolve(projectRoot);
4478
+ const key = path20.resolve(projectRoot);
4433
4479
  let store = {};
4434
4480
  try {
4435
4481
  store = JSON.parse(await fs17.readFile(TRUST_STORE_PATH, "utf-8"));
4436
4482
  } catch {
4437
4483
  }
4438
4484
  store[key] = fingerprint;
4439
- await fs17.mkdir(path19.dirname(TRUST_STORE_PATH), { recursive: true });
4485
+ await fs17.mkdir(path20.dirname(TRUST_STORE_PATH), { recursive: true });
4440
4486
  await fs17.writeFile(TRUST_STORE_PATH, JSON.stringify(store, null, 2));
4441
4487
  }
4442
4488
  async function loadHooks(projectRoot, promptTrust) {
4443
4489
  const hooks = [];
4444
- const globalPath = path19.join(os6.homedir(), ".notch", "hooks.json");
4490
+ const globalPath = path20.join(os6.homedir(), ".notch", "hooks.json");
4445
4491
  try {
4446
4492
  const raw = await fs17.readFile(globalPath, "utf-8");
4447
4493
  const parsed = JSON.parse(raw);
@@ -4450,7 +4496,7 @@ async function loadHooks(projectRoot, promptTrust) {
4450
4496
  }
4451
4497
  } catch {
4452
4498
  }
4453
- const projectPath = path19.join(projectRoot, ".notch.json");
4499
+ const projectPath = path20.join(projectRoot, ".notch.json");
4454
4500
  try {
4455
4501
  const raw = await fs17.readFile(projectPath, "utf-8");
4456
4502
  const parsed = JSON.parse(raw);
@@ -4518,7 +4564,7 @@ async function executeHook(hook, context) {
4518
4564
  NOTCH_CWD: context.cwd
4519
4565
  };
4520
4566
  try {
4521
- const output = execSync5(hook.command, {
4567
+ const output = execSync4(hook.command, {
4522
4568
  cwd: context.cwd,
4523
4569
  encoding: "utf-8",
4524
4570
  timeout: hook.timeout ?? 1e4,
@@ -4547,7 +4593,7 @@ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
4547
4593
  if (existing) clearTimeout(existing);
4548
4594
  pending.set(filename, setTimeout(async () => {
4549
4595
  pending.delete(filename);
4550
- const filePath = path19.join(projectRoot, filename);
4596
+ const filePath = path20.join(projectRoot, filename);
4551
4597
  const context = { cwd: projectRoot, file: filePath };
4552
4598
  const { results } = await runHooks(hookConfig, "file-changed", context);
4553
4599
  onHookResult?.("file-changed", results);
@@ -4568,15 +4614,15 @@ function startFileWatcher(projectRoot, hookConfig, onHookResult) {
4568
4614
 
4569
4615
  // src/session/index.ts
4570
4616
  import fs18 from "fs/promises";
4571
- import path20 from "path";
4617
+ import path21 from "path";
4572
4618
  import os7 from "os";
4573
- var SESSION_DIR = path20.join(os7.homedir(), ".notch", "sessions");
4619
+ var SESSION_DIR = path21.join(os7.homedir(), ".notch", "sessions");
4574
4620
  var MAX_SESSIONS = 20;
4575
4621
  async function ensureDir2() {
4576
4622
  await fs18.mkdir(SESSION_DIR, { recursive: true });
4577
4623
  }
4578
4624
  function sessionPath(id) {
4579
- return path20.join(SESSION_DIR, `${id}.json`);
4625
+ return path21.join(SESSION_DIR, `${id}.json`);
4580
4626
  }
4581
4627
  function generateId() {
4582
4628
  const now = /* @__PURE__ */ new Date();
@@ -4622,7 +4668,7 @@ async function listSessions() {
4622
4668
  for (const file of files) {
4623
4669
  if (!file.endsWith(".json")) continue;
4624
4670
  try {
4625
- const raw = await fs18.readFile(path20.join(SESSION_DIR, file), "utf-8");
4671
+ const raw = await fs18.readFile(path21.join(SESSION_DIR, file), "utf-8");
4626
4672
  const session = JSON.parse(raw);
4627
4673
  sessions.push(session.meta);
4628
4674
  } catch {
@@ -4698,7 +4744,7 @@ async function exportSession(messages, outputPath, meta) {
4698
4744
 
4699
4745
  // src/init.ts
4700
4746
  import fs19 from "fs/promises";
4701
- import path21 from "path";
4747
+ import path22 from "path";
4702
4748
  import chalk9 from "chalk";
4703
4749
  var DEFAULT_CONFIG = {
4704
4750
  model: "notch-forge",
@@ -4730,8 +4776,8 @@ var DEFAULT_INSTRUCTIONS = `# Project Instructions for Notch
4730
4776
  <!-- Files or areas Notch should NOT modify -->
4731
4777
  `;
4732
4778
  async function initProject(projectRoot) {
4733
- const configPath = path21.join(projectRoot, ".notch.json");
4734
- const instructionsPath = path21.join(projectRoot, ".notch.md");
4779
+ const configPath = path22.join(projectRoot, ".notch.json");
4780
+ const instructionsPath = path22.join(projectRoot, ".notch.md");
4735
4781
  let configExists = false;
4736
4782
  let instructionsExist = false;
4737
4783
  try {
@@ -4756,7 +4802,7 @@ async function initProject(projectRoot) {
4756
4802
  } else {
4757
4803
  console.log(chalk9.gray(` Skipped ${instructionsPath} (already exists)`));
4758
4804
  }
4759
- const gitignorePath = path21.join(projectRoot, ".gitignore");
4805
+ const gitignorePath = path22.join(projectRoot, ".gitignore");
4760
4806
  try {
4761
4807
  const gitignore = await fs19.readFile(gitignorePath, "utf-8");
4762
4808
  const additions = [];
@@ -4890,9 +4936,9 @@ function findSync(oldLines, newLines, oi, ni, lookAhead) {
4890
4936
  }
4891
4937
 
4892
4938
  // src/commands/doctor.ts
4893
- import { execSync as execSync6 } from "child_process";
4939
+ import { execSync as execSync5 } from "child_process";
4894
4940
  import fs20 from "fs/promises";
4895
- import path22 from "path";
4941
+ import path23 from "path";
4896
4942
  import os8 from "os";
4897
4943
  import chalk10 from "chalk";
4898
4944
  async function runDiagnostics(cwd) {
@@ -4907,7 +4953,7 @@ async function runDiagnostics(cwd) {
4907
4953
  results.push({ name: "Node.js", status: "fail", message: `v${nodeVersion} (requires >= 18)` });
4908
4954
  }
4909
4955
  try {
4910
- const gitVersion = execSync6("git --version", { encoding: "utf-8", timeout: 5e3 }).trim();
4956
+ const gitVersion = execSync5("git --version", { encoding: "utf-8", timeout: 5e3 }).trim();
4911
4957
  results.push({ name: "Git", status: "ok", message: gitVersion });
4912
4958
  } catch {
4913
4959
  results.push({ name: "Git", status: "fail", message: "Not found. Install git to use git tools." });
@@ -4934,12 +4980,12 @@ async function runDiagnostics(cwd) {
4934
4980
  results.push({ name: "Config", status: "fail", message: `Could not load: ${err.message}` });
4935
4981
  }
4936
4982
  try {
4937
- await fs20.access(path22.join(cwd, ".notch.json"));
4983
+ await fs20.access(path23.join(cwd, ".notch.json"));
4938
4984
  results.push({ name: ".notch.json", status: "ok", message: "Found" });
4939
4985
  } catch {
4940
4986
  results.push({ name: ".notch.json", status: "warn", message: "Not found. Run: notch init" });
4941
4987
  }
4942
- const notchDir = path22.join(os8.homedir(), ".notch");
4988
+ const notchDir = path23.join(os8.homedir(), ".notch");
4943
4989
  try {
4944
4990
  await fs20.access(notchDir);
4945
4991
  results.push({ name: "~/.notch/", status: "ok", message: "Exists" });
@@ -4947,13 +4993,13 @@ async function runDiagnostics(cwd) {
4947
4993
  results.push({ name: "~/.notch/", status: "warn", message: "Not found (will be created on first use)" });
4948
4994
  }
4949
4995
  try {
4950
- await fs20.access(path22.join(cwd, ".notch.md"));
4996
+ await fs20.access(path23.join(cwd, ".notch.md"));
4951
4997
  results.push({ name: ".notch.md", status: "ok", message: "Found" });
4952
4998
  } catch {
4953
4999
  results.push({ name: ".notch.md", status: "warn", message: "Not found. Run: notch init" });
4954
5000
  }
4955
5001
  try {
4956
- const configRaw = await fs20.readFile(path22.join(cwd, ".notch.json"), "utf-8").catch(() => "{}");
5002
+ const configRaw = await fs20.readFile(path23.join(cwd, ".notch.json"), "utf-8").catch(() => "{}");
4957
5003
  const mcpConfigs = parseMCPConfig(JSON.parse(configRaw));
4958
5004
  const serverNames = Object.keys(mcpConfigs);
4959
5005
  if (serverNames.length === 0) {
@@ -4974,7 +5020,7 @@ async function runDiagnostics(cwd) {
4974
5020
  results.push({ name: "MCP Servers", status: "ok", message: "No config to check" });
4975
5021
  }
4976
5022
  try {
4977
- const sessionsDir = path22.join(notchDir, "sessions");
5023
+ const sessionsDir = path23.join(notchDir, "sessions");
4978
5024
  const entries = await fs20.readdir(sessionsDir).catch(() => []);
4979
5025
  results.push({ name: "Sessions", status: "ok", message: `${entries.length} saved` });
4980
5026
  } catch {
@@ -5009,23 +5055,23 @@ registerCommand("/doctor", async (_args, ctx) => {
5009
5055
  });
5010
5056
 
5011
5057
  // src/commands/copy.ts
5012
- import { execSync as execSync7 } from "child_process";
5058
+ import { execSync as execSync6 } from "child_process";
5013
5059
  import chalk11 from "chalk";
5014
5060
  function copyToClipboard(text) {
5015
5061
  try {
5016
5062
  const platform = process.platform;
5017
5063
  if (platform === "win32") {
5018
- execSync7("clip.exe", { input: text, timeout: 5e3 });
5064
+ execSync6("clip.exe", { input: text, timeout: 5e3 });
5019
5065
  } else if (platform === "darwin") {
5020
- execSync7("pbcopy", { input: text, timeout: 5e3 });
5066
+ execSync6("pbcopy", { input: text, timeout: 5e3 });
5021
5067
  } else {
5022
5068
  try {
5023
- execSync7("xclip -selection clipboard", { input: text, timeout: 5e3 });
5069
+ execSync6("xclip -selection clipboard", { input: text, timeout: 5e3 });
5024
5070
  } catch {
5025
5071
  try {
5026
- execSync7("xsel --clipboard --input", { input: text, timeout: 5e3 });
5072
+ execSync6("xsel --clipboard --input", { input: text, timeout: 5e3 });
5027
5073
  } catch {
5028
- execSync7("wl-copy", { input: text, timeout: 5e3 });
5074
+ execSync6("wl-copy", { input: text, timeout: 5e3 });
5029
5075
  }
5030
5076
  }
5031
5077
  }
@@ -5071,7 +5117,7 @@ registerCommand("/btw", async (args, ctx) => {
5071
5117
  });
5072
5118
 
5073
5119
  // src/commands/security-review.ts
5074
- import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
5120
+ import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5075
5121
  import chalk13 from "chalk";
5076
5122
  function isValidGitRange(range) {
5077
5123
  return /^[a-zA-Z0-9._~^\/\-]+(\.\.[a-zA-Z0-9._~^\/\-]+)?$/.test(range);
@@ -5099,12 +5145,12 @@ registerCommand("/security-review", async (args, ctx) => {
5099
5145
  }).trim();
5100
5146
  } catch {
5101
5147
  try {
5102
- stat = execSync8("git diff --stat", {
5148
+ stat = execSync7("git diff --stat", {
5103
5149
  cwd: ctx.cwd,
5104
5150
  encoding: "utf-8",
5105
5151
  timeout: 1e4
5106
5152
  }).trim();
5107
- diff = execSync8("git diff", {
5153
+ diff = execSync7("git diff", {
5108
5154
  cwd: ctx.cwd,
5109
5155
  encoding: "utf-8",
5110
5156
  timeout: 1e4,
@@ -5376,12 +5422,12 @@ Read the file first, then make the change. Only modify this one file.`
5376
5422
  });
5377
5423
 
5378
5424
  // src/commands/plugin.ts
5379
- import { execSync as execSync9, execFileSync as execFileSync3 } from "child_process";
5425
+ import { execSync as execSync8, execFileSync as execFileSync3 } from "child_process";
5380
5426
  import fs21 from "fs/promises";
5381
- import path23 from "path";
5427
+ import path24 from "path";
5382
5428
  import os9 from "os";
5383
5429
  import chalk16 from "chalk";
5384
- var GLOBAL_PLUGINS_DIR = path23.join(os9.homedir(), ".notch", "plugins");
5430
+ var GLOBAL_PLUGINS_DIR = path24.join(os9.homedir(), ".notch", "plugins");
5385
5431
  registerCommand("/plugin", async (args, ctx) => {
5386
5432
  const parts = args.split(/\s+/);
5387
5433
  const subcommand = parts[0] || "list";
@@ -5418,7 +5464,7 @@ registerCommand("/plugin", async (args, ctx) => {
5418
5464
  return;
5419
5465
  }
5420
5466
  await fs21.mkdir(GLOBAL_PLUGINS_DIR, { recursive: true });
5421
- const pluginDir = path23.join(GLOBAL_PLUGINS_DIR, path23.basename(target).replace(/\.git$/, ""));
5467
+ const pluginDir = path24.join(GLOBAL_PLUGINS_DIR, path24.basename(target).replace(/\.git$/, ""));
5422
5468
  console.log(chalk16.gray(` Installing ${target}...`));
5423
5469
  try {
5424
5470
  if (target.includes("/") && !target.startsWith("@")) {
@@ -5443,9 +5489,9 @@ registerCommand("/plugin", async (args, ctx) => {
5443
5489
  });
5444
5490
  }
5445
5491
  try {
5446
- const pkgExists = await fs21.access(path23.join(pluginDir, "package.json")).then(() => true).catch(() => false);
5492
+ const pkgExists = await fs21.access(path24.join(pluginDir, "package.json")).then(() => true).catch(() => false);
5447
5493
  if (pkgExists) {
5448
- execSync9("npm install --production", {
5494
+ execSync8("npm install --production", {
5449
5495
  cwd: pluginDir,
5450
5496
  encoding: "utf-8",
5451
5497
  timeout: 12e4,
@@ -5469,7 +5515,7 @@ registerCommand("/plugin", async (args, ctx) => {
5469
5515
  console.log(chalk16.gray(" Usage: /plugin remove <plugin-name>\n"));
5470
5516
  return;
5471
5517
  }
5472
- const pluginDir = path23.join(GLOBAL_PLUGINS_DIR, target);
5518
+ const pluginDir = path24.join(GLOBAL_PLUGINS_DIR, target);
5473
5519
  try {
5474
5520
  await fs21.access(pluginDir);
5475
5521
  await fs21.rm(pluginDir, { recursive: true, force: true });
@@ -5596,12 +5642,12 @@ Reply with ONLY the commit message, nothing else. No markdown, no explanation.`;
5596
5642
  });
5597
5643
 
5598
5644
  // src/commands/pr.ts
5599
- import { execSync as execSync11, execFileSync as execFileSync5 } from "child_process";
5645
+ import { execSync as execSync10, execFileSync as execFileSync5 } from "child_process";
5600
5646
  import chalk18 from "chalk";
5601
5647
  import ora3 from "ora";
5602
5648
  function tryExec(cmd, cwd) {
5603
5649
  try {
5604
- return execSync11(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
5650
+ return execSync10(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
5605
5651
  } catch {
5606
5652
  return null;
5607
5653
  }
@@ -5732,11 +5778,11 @@ BODY:
5732
5778
  });
5733
5779
 
5734
5780
  // src/commands/worktree.ts
5735
- import { execSync as execSync12, execFileSync as execFileSync6 } from "child_process";
5781
+ import { execSync as execSync11, execFileSync as execFileSync6 } from "child_process";
5736
5782
  import chalk19 from "chalk";
5737
5783
  function tryExec2(cmd, cwd) {
5738
5784
  try {
5739
- return execSync12(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
5785
+ return execSync11(cmd, { cwd, encoding: "utf-8", timeout: 15e3 }).trim();
5740
5786
  } catch {
5741
5787
  return null;
5742
5788
  }
@@ -5847,7 +5893,7 @@ registerCommand("/worktree", async (args, ctx) => {
5847
5893
  }
5848
5894
  case "prune": {
5849
5895
  try {
5850
- execSync12("git worktree prune", { cwd: ctx.cwd, encoding: "utf-8" });
5896
+ execSync11("git worktree prune", { cwd: ctx.cwd, encoding: "utf-8" });
5851
5897
  console.log(chalk19.green(" \u2713 Pruned stale worktrees.\n"));
5852
5898
  } catch (err) {
5853
5899
  console.log(chalk19.red(` Failed: ${err.message}
@@ -6080,7 +6126,7 @@ registerCommand("/sandbox", async (args, _ctx) => {
6080
6126
 
6081
6127
  // src/ui/completions.ts
6082
6128
  import fs22 from "fs";
6083
- import path24 from "path";
6129
+ import path25 from "path";
6084
6130
  var COMMANDS = [
6085
6131
  "/help",
6086
6132
  "/quit",
@@ -6173,15 +6219,15 @@ function buildCompleter(cwd) {
6173
6219
  }
6174
6220
  function completeFilePath(partial, cwd) {
6175
6221
  try {
6176
- const dir = partial.includes("/") ? path24.resolve(cwd, path24.dirname(partial)) : cwd;
6177
- const prefix = partial.includes("/") ? path24.basename(partial) : partial;
6222
+ const dir = partial.includes("/") ? path25.resolve(cwd, path25.dirname(partial)) : cwd;
6223
+ const prefix = partial.includes("/") ? path25.basename(partial) : partial;
6178
6224
  const entries = fs22.readdirSync(dir, { withFileTypes: true });
6179
6225
  const matches = [];
6180
6226
  for (const entry of entries) {
6181
6227
  if (entry.name.startsWith(".")) continue;
6182
6228
  if (entry.name === "node_modules" || entry.name === ".git") continue;
6183
6229
  if (entry.name.startsWith(prefix)) {
6184
- const relative = partial.includes("/") ? path24.dirname(partial) + "/" + entry.name : entry.name;
6230
+ const relative = partial.includes("/") ? path25.dirname(partial) + "/" + entry.name : entry.name;
6185
6231
  if (entry.isDirectory()) {
6186
6232
  matches.push(relative + "/");
6187
6233
  } else {
@@ -6195,6 +6241,272 @@ function completeFilePath(partial, cwd) {
6195
6241
  }
6196
6242
  }
6197
6243
 
6244
+ // src/ui/slash-menu.ts
6245
+ import chalk26 from "chalk";
6246
+ var SLASH_COMMANDS = [
6247
+ // Core
6248
+ { name: "/help", description: "Show available commands", category: "Core" },
6249
+ { name: "/clear", description: "Clear conversation history", category: "Core" },
6250
+ { name: "/quit", description: "Exit Notch", category: "Core" },
6251
+ // Model & Status
6252
+ { name: "/model", description: "Switch or list models", category: "Model" },
6253
+ { name: "/status", description: "Check API endpoint health", category: "Model" },
6254
+ // Session
6255
+ { name: "/save", description: "Save current session", category: "Session" },
6256
+ { name: "/sessions", description: "List saved sessions", category: "Session" },
6257
+ { name: "/resume", description: "Resume a saved session", category: "Session" },
6258
+ { name: "/rename", description: "Name this session", category: "Session" },
6259
+ { name: "/export", description: "Export conversation to markdown", category: "Session" },
6260
+ { name: "/branch", description: "Fork conversation at this point", category: "Session" },
6261
+ // Context
6262
+ { name: "/usage", description: "Show token usage + context meter", category: "Context" },
6263
+ { name: "/cost", description: "Show estimated session cost", category: "Context" },
6264
+ { name: "/compact", description: "Compress conversation history", category: "Context" },
6265
+ { name: "/rewind", description: "Rewind to a previous turn", category: "Context" },
6266
+ { name: "/insights", description: "Session analytics & tool usage", category: "Context" },
6267
+ // Tools
6268
+ { name: "/undo", description: "Undo last file changes", category: "Tools" },
6269
+ { name: "/diff", description: "Show all file changes this session", category: "Tools" },
6270
+ { name: "/search", description: "Search conversation history", category: "Tools" },
6271
+ { name: "/tasks", description: "Show agent task list", category: "Tools" },
6272
+ // Git
6273
+ { name: "/commit", description: "Smart commit with AI message", category: "Git" },
6274
+ { name: "/pr", description: "Create PR with AI summary", category: "Git" },
6275
+ { name: "/worktree", description: "Manage git worktrees", category: "Git" },
6276
+ // Planning & Agents
6277
+ { name: "/plan", description: "Generate a step-by-step plan", category: "Planning" },
6278
+ { name: "/agent", description: "Spawn a background subagent", category: "Planning" },
6279
+ { name: "/ralph plan", description: "Generate autonomous task plan", category: "Planning" },
6280
+ { name: "/ralph run", description: "Execute autonomous tasks", category: "Planning" },
6281
+ // Utilities
6282
+ { name: "/doctor", description: "Diagnose installation health", category: "Utility" },
6283
+ { name: "/copy", description: "Copy last response to clipboard", category: "Utility" },
6284
+ { name: "/btw", description: "Side question (isolated context)", category: "Utility" },
6285
+ { name: "/security-review", description: "Review changes for vulnerabilities", category: "Utility" },
6286
+ { name: "/loop", description: "Run command on a recurring interval", category: "Utility" },
6287
+ { name: "/batch", description: "Apply task across files in parallel", category: "Utility" },
6288
+ { name: "/sandbox", description: "Toggle read-only sandbox mode", category: "Utility" },
6289
+ { name: "/debug", description: "Toggle debug logging", category: "Utility" },
6290
+ // Config
6291
+ { name: "/memory", description: "List saved memories", category: "Config" },
6292
+ { name: "/permissions", description: "Show permission config", category: "Config" },
6293
+ { name: "/theme", description: "Switch color theme", category: "Config" },
6294
+ { name: "/plugin", description: "Manage plugins", category: "Config" }
6295
+ ];
6296
+ var MAX_VISIBLE = 10;
6297
+ function createMenuState() {
6298
+ return {
6299
+ visible: false,
6300
+ filter: "",
6301
+ selected: 0,
6302
+ filtered: [],
6303
+ renderedLines: 0
6304
+ };
6305
+ }
6306
+ function updateMenu(state, input) {
6307
+ if (!input.startsWith("/") || input.includes(" ") || input.length === 0) {
6308
+ state.visible = false;
6309
+ state.filtered = [];
6310
+ state.selected = 0;
6311
+ return false;
6312
+ }
6313
+ state.filter = input.toLowerCase();
6314
+ state.filtered = SLASH_COMMANDS.filter(
6315
+ (cmd) => cmd.name.toLowerCase().startsWith(state.filter) || cmd.name.toLowerCase().includes(state.filter.slice(1)) || cmd.description.toLowerCase().includes(state.filter.slice(1))
6316
+ );
6317
+ if (state.selected >= state.filtered.length) {
6318
+ state.selected = Math.max(0, state.filtered.length - 1);
6319
+ }
6320
+ state.visible = state.filtered.length > 0;
6321
+ return state.visible;
6322
+ }
6323
+ function menuUp(state) {
6324
+ if (state.selected > 0) state.selected--;
6325
+ }
6326
+ function menuDown(state) {
6327
+ if (state.selected < state.filtered.length - 1) state.selected++;
6328
+ }
6329
+ function menuSelect(state) {
6330
+ if (!state.visible || state.filtered.length === 0) return null;
6331
+ return state.filtered[state.selected]?.name ?? null;
6332
+ }
6333
+ function menuDismiss(state) {
6334
+ state.visible = false;
6335
+ state.filtered = [];
6336
+ state.selected = 0;
6337
+ }
6338
+ function renderMenu(state) {
6339
+ if (!state.visible || state.filtered.length === 0) return "";
6340
+ const total = state.filtered.length;
6341
+ let scrollTop = 0;
6342
+ if (state.selected >= MAX_VISIBLE) {
6343
+ scrollTop = state.selected - MAX_VISIBLE + 1;
6344
+ }
6345
+ if (scrollTop + MAX_VISIBLE > total) {
6346
+ scrollTop = Math.max(0, total - MAX_VISIBLE);
6347
+ }
6348
+ const items = state.filtered.slice(scrollTop, scrollTop + MAX_VISIBLE);
6349
+ const hasMore = scrollTop + MAX_VISIBLE < total;
6350
+ const hasAbove = scrollTop > 0;
6351
+ let currentCategory = "";
6352
+ const lines = [];
6353
+ lines.push(chalk26.gray(" \u250C\u2500 Commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
6354
+ if (hasAbove) {
6355
+ const above = scrollTop;
6356
+ lines.push(chalk26.gray(` \u2502 ${chalk26.dim(`\u2191 ${above} more\u2026`)}${"".padEnd(40 - String(above).length)}\u2502`));
6357
+ }
6358
+ for (let i = 0; i < items.length; i++) {
6359
+ const cmd = items[i];
6360
+ const absoluteIndex = scrollTop + i;
6361
+ const isSelected = absoluteIndex === state.selected;
6362
+ if (cmd.category !== currentCategory) {
6363
+ currentCategory = cmd.category;
6364
+ if (i > 0) {
6365
+ lines.push(chalk26.gray(" \u2502 \u2502"));
6366
+ }
6367
+ lines.push(chalk26.gray(` \u2502 `) + chalk26.gray.dim(cmd.category.toUpperCase().padEnd(47)) + chalk26.gray("\u2502"));
6368
+ }
6369
+ const name = cmd.name.padEnd(22);
6370
+ const desc = cmd.description.slice(0, 25).padEnd(25);
6371
+ if (isSelected) {
6372
+ lines.push(
6373
+ chalk26.gray(" \u2502 ") + chalk26.bgWhite.black(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
6374
+ );
6375
+ } else {
6376
+ lines.push(
6377
+ chalk26.gray(" \u2502 ") + chalk26.cyan(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
6378
+ );
6379
+ }
6380
+ }
6381
+ if (hasMore) {
6382
+ const remaining = state.filtered.length - MAX_VISIBLE;
6383
+ lines.push(chalk26.gray(` \u2502 ${chalk26.dim(`+${remaining} more\u2026`)}${"".padEnd(41 - String(remaining).length)}\u2502`));
6384
+ }
6385
+ lines.push(chalk26.gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2191\u2193 navigate \xB7 enter select \u2500\u2500\u2518"));
6386
+ state.renderedLines = lines.length;
6387
+ return lines.join("\n");
6388
+ }
6389
+ function clearMenu(state) {
6390
+ if (state.renderedLines === 0) return "";
6391
+ const lines = state.renderedLines;
6392
+ state.renderedLines = 0;
6393
+ let ansi = "";
6394
+ for (let i = 0; i < lines; i++) {
6395
+ ansi += "\x1B[1A\x1B[2K";
6396
+ }
6397
+ return ansi;
6398
+ }
6399
+
6400
+ // src/ui/interactive-input.ts
6401
+ function attachSlashMenu(rl) {
6402
+ const state = createMenuState();
6403
+ let lastInput = "";
6404
+ const keypressHandler = (_ch, key) => {
6405
+ const currentLine = rl.line ?? "";
6406
+ if (state.visible && key) {
6407
+ if (key.name === "up") {
6408
+ process.stdout.write(clearMenu(state));
6409
+ menuUp(state);
6410
+ process.stdout.write(renderMenu(state) + "\n");
6411
+ rewritePromptLine(rl);
6412
+ return;
6413
+ }
6414
+ if (key.name === "down") {
6415
+ process.stdout.write(clearMenu(state));
6416
+ menuDown(state);
6417
+ process.stdout.write(renderMenu(state) + "\n");
6418
+ rewritePromptLine(rl);
6419
+ return;
6420
+ }
6421
+ if (key.name === "escape") {
6422
+ process.stdout.write(clearMenu(state));
6423
+ menuDismiss(state);
6424
+ return;
6425
+ }
6426
+ if (key.name === "return") {
6427
+ const selected = menuSelect(state);
6428
+ if (selected) {
6429
+ process.stdout.write(clearMenu(state));
6430
+ menuDismiss(state);
6431
+ rl.line = "";
6432
+ rl.cursor = 0;
6433
+ rl.write(null, { ctrl: true, name: "u" });
6434
+ rl.write(selected + " ");
6435
+ return;
6436
+ }
6437
+ }
6438
+ if (key.name === "tab") {
6439
+ const selected = menuSelect(state);
6440
+ if (selected) {
6441
+ process.stdout.write(clearMenu(state));
6442
+ menuDismiss(state);
6443
+ rl.line = "";
6444
+ rl.cursor = 0;
6445
+ rl.write(null, { ctrl: true, name: "u" });
6446
+ rl.write(selected + " ");
6447
+ return;
6448
+ }
6449
+ }
6450
+ }
6451
+ setImmediate(() => {
6452
+ const line = rl.line ?? "";
6453
+ if (line !== lastInput) {
6454
+ lastInput = line;
6455
+ const wasVisible = state.visible;
6456
+ const shouldShow = updateMenu(state, line);
6457
+ if (wasVisible && !shouldShow) {
6458
+ process.stdout.write(clearMenu(state));
6459
+ } else if (shouldShow) {
6460
+ if (wasVisible) {
6461
+ process.stdout.write(clearMenu(state));
6462
+ }
6463
+ const menuStr = renderMenu(state);
6464
+ if (menuStr) {
6465
+ process.stdout.write("\x1B[s");
6466
+ process.stdout.write(menuStr + "\n");
6467
+ rewritePromptLine(rl);
6468
+ process.stdout.write("\x1B[u");
6469
+ }
6470
+ }
6471
+ }
6472
+ });
6473
+ };
6474
+ process.stdin.on("keypress", keypressHandler);
6475
+ const closeHandler = () => {
6476
+ if (state.visible) {
6477
+ process.stdout.write(clearMenu(state));
6478
+ }
6479
+ process.stdin.removeListener("keypress", keypressHandler);
6480
+ };
6481
+ rl.on("close", closeHandler);
6482
+ const lineHandler = () => {
6483
+ if (state.visible) {
6484
+ process.stdout.write(clearMenu(state));
6485
+ menuDismiss(state);
6486
+ }
6487
+ lastInput = "";
6488
+ };
6489
+ rl.on("line", lineHandler);
6490
+ return {
6491
+ cleanup: () => {
6492
+ process.stdin.removeListener("keypress", keypressHandler);
6493
+ rl.removeListener("close", closeHandler);
6494
+ rl.removeListener("line", lineHandler);
6495
+ }
6496
+ };
6497
+ }
6498
+ function rewritePromptLine(rl) {
6499
+ const prompt = rl._prompt ?? "notch> ";
6500
+ const line = rl.line ?? "";
6501
+ const cursor = rl.cursor ?? line.length;
6502
+ process.stdout.write("\x1B[2K\r");
6503
+ process.stdout.write(prompt + line);
6504
+ const cursorOffset = line.length - cursor;
6505
+ if (cursorOffset > 0) {
6506
+ process.stdout.write(`\x1B[${cursorOffset}D`);
6507
+ }
6508
+ }
6509
+
6198
6510
  // src/index.ts
6199
6511
  import fs23 from "fs/promises";
6200
6512
  import { createRequire } from "module";
@@ -6219,7 +6531,7 @@ function printModelTable(activeModel) {
6219
6531
  `));
6220
6532
  }
6221
6533
  function printHelp() {
6222
- console.log(chalk26.gray(`
6534
+ console.log(chalk27.gray(`
6223
6535
  Commands:
6224
6536
  /model \u2014 Show available models
6225
6537
  /model <name> \u2014 Switch model (e.g., /model pyre)
@@ -6316,13 +6628,13 @@ async function main() {
6316
6628
  try {
6317
6629
  spinner.stop();
6318
6630
  const creds = await login();
6319
- console.log(chalk26.green(`
6631
+ console.log(chalk27.green(`
6320
6632
  \u2713 Signed in as ${creds.email}`));
6321
- console.log(chalk26.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
6633
+ console.log(chalk27.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
6322
6634
  `));
6323
6635
  } catch (err) {
6324
6636
  spinner.stop();
6325
- console.error(chalk26.red(`
6637
+ console.error(chalk27.red(`
6326
6638
  Login failed: ${err.message}
6327
6639
  `));
6328
6640
  process.exit(1);
@@ -6332,10 +6644,10 @@ async function main() {
6332
6644
  if (promptArgs[0] === "logout") {
6333
6645
  const creds = await loadCredentials();
6334
6646
  if (!creds) {
6335
- console.log(chalk26.gray("\n Not signed in.\n"));
6647
+ console.log(chalk27.gray("\n Not signed in.\n"));
6336
6648
  } else {
6337
6649
  await clearCredentials();
6338
- console.log(chalk26.green(`
6650
+ console.log(chalk27.green(`
6339
6651
  \u2713 Signed out (${creds.email})
6340
6652
  `));
6341
6653
  }
@@ -6344,13 +6656,13 @@ async function main() {
6344
6656
  if (promptArgs[0] === "whoami") {
6345
6657
  const creds = await loadCredentials();
6346
6658
  if (!creds) {
6347
- console.log(chalk26.gray("\n Not signed in. Run: notch login\n"));
6659
+ console.log(chalk27.gray("\n Not signed in. Run: notch login\n"));
6348
6660
  } else {
6349
6661
  const keyPreview = `${creds.token.slice(0, 12)}...`;
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()}
6662
+ console.log(chalk27.gray(`
6663
+ Signed in as ${chalk27.white(creds.email)}`));
6664
+ console.log(chalk27.gray(` Key: ${keyPreview}`));
6665
+ console.log(chalk27.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
6354
6666
  `));
6355
6667
  }
6356
6668
  return;
@@ -6374,8 +6686,8 @@ async function main() {
6374
6686
  const config = await loadConfig(configOverrides);
6375
6687
  if (opts.model) {
6376
6688
  if (!isValidModel(opts.model)) {
6377
- console.error(chalk26.red(` Unknown model: ${opts.model}`));
6378
- console.error(chalk26.gray(` Available: ${modelChoices}`));
6689
+ console.error(chalk27.red(` Unknown model: ${opts.model}`));
6690
+ console.error(chalk27.gray(` Available: ${modelChoices}`));
6379
6691
  process.exit(1);
6380
6692
  }
6381
6693
  config.models.chat.model = opts.model;
@@ -6414,11 +6726,11 @@ async function main() {
6414
6726
  const updateMsg = await checkForUpdates(VERSION);
6415
6727
  if (updateMsg) console.log(updateMsg);
6416
6728
  const hookTrustPrompt = async (commands) => {
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}`)));
6729
+ console.warn(chalk27.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
6730
+ commands.forEach((cmd) => console.warn(chalk27.gray(` \u2022 ${cmd}`)));
6419
6731
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
6420
6732
  return new Promise((resolve2) => {
6421
- rl2.question(chalk26.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
6733
+ rl2.question(chalk27.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
6422
6734
  rl2.close();
6423
6735
  resolve2(answer.trim().toLowerCase() === "y");
6424
6736
  });
@@ -6464,22 +6776,22 @@ ${repoMapStr}` : ""
6464
6776
  const client = new MCPClient(mcpConfig, name);
6465
6777
  await client.connect();
6466
6778
  mcpClients.push(client);
6467
- console.log(chalk26.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
6779
+ console.log(chalk27.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
6468
6780
  } catch (err) {
6469
- console.log(chalk26.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
6781
+ console.log(chalk27.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
6470
6782
  }
6471
6783
  }
6472
6784
  } catch {
6473
6785
  }
6474
6786
  try {
6475
6787
  const pluginCount = await pluginManager.init(config.projectRoot, (msg) => {
6476
- console.log(chalk26.gray(` ${msg}`));
6788
+ console.log(chalk27.gray(` ${msg}`));
6477
6789
  });
6478
6790
  if (pluginCount > 0) {
6479
- console.log(chalk26.green(` Plugins: ${pluginCount} loaded`));
6791
+ console.log(chalk27.green(` Plugins: ${pluginCount} loaded`));
6480
6792
  }
6481
6793
  } catch (err) {
6482
- console.log(chalk26.yellow(` Plugin init failed: ${err.message}`));
6794
+ console.log(chalk27.yellow(` Plugin init failed: ${err.message}`));
6483
6795
  }
6484
6796
  const toolCtx = {
6485
6797
  cwd: config.projectRoot,
@@ -6493,7 +6805,7 @@ ${repoMapStr}` : ""
6493
6805
  });
6494
6806
  });
6495
6807
  },
6496
- log: (msg) => console.log(chalk26.gray(` ${msg}`)),
6808
+ log: (msg) => console.log(chalk27.gray(` ${msg}`)),
6497
6809
  checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
6498
6810
  runHook: async (event, ctx) => {
6499
6811
  if (!config.enableHooks || hookConfig.hooks.length === 0) return;
@@ -6503,7 +6815,7 @@ ${repoMapStr}` : ""
6503
6815
  });
6504
6816
  for (const r of results) {
6505
6817
  if (!r.ok) {
6506
- console.log(chalk26.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
6818
+ console.log(chalk27.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
6507
6819
  }
6508
6820
  }
6509
6821
  }
@@ -6512,7 +6824,7 @@ ${repoMapStr}` : ""
6512
6824
  const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
6513
6825
  for (const r of results) {
6514
6826
  if (!r.ok) {
6515
- console.log(chalk26.yellow(` Hook failed (${event}): ${r.error}`));
6827
+ console.log(chalk27.yellow(` Hook failed (${event}): ${r.error}`));
6516
6828
  }
6517
6829
  }
6518
6830
  });
@@ -6523,10 +6835,10 @@ ${repoMapStr}` : ""
6523
6835
  if (session) {
6524
6836
  messages.push(...session.messages);
6525
6837
  sessionId = session.meta.id;
6526
- console.log(chalk26.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6838
+ console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6527
6839
  `));
6528
6840
  } else {
6529
- console.log(chalk26.gray(" No session to resume.\n"));
6841
+ console.log(chalk27.gray(" No session to resume.\n"));
6530
6842
  }
6531
6843
  }
6532
6844
  const pipedInput = await readStdin();
@@ -6547,10 +6859,10 @@ Analyze the above input.`;
6547
6859
  const refContext = formatReferences(references);
6548
6860
  const finalPrompt = refContext + cleanInput;
6549
6861
  messages.push({ role: "user", content: finalPrompt });
6550
- console.log(chalk26.cyan(`> ${oneShot || "(piped input)"}
6862
+ console.log(chalk27.cyan(`> ${oneShot || "(piped input)"}
6551
6863
  `));
6552
6864
  if (references.length > 0) {
6553
- console.log(chalk26.gray(` Injected ${references.length} reference(s)
6865
+ console.log(chalk27.gray(` Injected ${references.length} reference(s)
6554
6866
  `));
6555
6867
  }
6556
6868
  const spinner = ora4("Thinking...").start();
@@ -6569,13 +6881,13 @@ Analyze the above input.`;
6569
6881
  onToolCall: (name, args) => {
6570
6882
  if (spinner.isSpinning) spinner.stop();
6571
6883
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
6572
- console.log(chalk26.gray(`
6884
+ console.log(chalk27.gray(`
6573
6885
  \u2192 ${name}(${argSummary})`));
6574
6886
  },
6575
6887
  onToolResult: (_name, result, isError) => {
6576
6888
  const preview = result.slice(0, 100).replace(/\n/g, " ");
6577
- const icon = isError ? chalk26.red("\u2717") : chalk26.green("\u2713");
6578
- console.log(chalk26.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
6889
+ const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
6890
+ console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
6579
6891
  }
6580
6892
  })
6581
6893
  );
@@ -6600,19 +6912,21 @@ Analyze the above input.`;
6600
6912
  const savedPlan = await loadPlan(config.projectRoot);
6601
6913
  if (savedPlan) {
6602
6914
  ralphPlan = savedPlan;
6603
- console.log(chalk26.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
6915
+ console.log(chalk27.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
6604
6916
  `));
6605
6917
  }
6606
6918
  } catch {
6607
6919
  }
6608
6920
  const completer = buildCompleter(config.projectRoot);
6921
+ readline.emitKeypressEvents(process.stdin);
6609
6922
  const rl = readline.createInterface({
6610
6923
  input: process.stdin,
6611
6924
  output: process.stdout,
6612
6925
  prompt: theme().prompt("notch> "),
6613
6926
  completer: (line) => completer(line)
6614
6927
  });
6615
- console.log(chalk26.gray(" Type your request, or /help for commands.\n"));
6928
+ const slashMenu = attachSlashMenu(rl);
6929
+ console.log(chalk27.gray(" Type your request, or /help for commands.\n"));
6616
6930
  rl.prompt();
6617
6931
  rl.on("line", async (line) => {
6618
6932
  const input = line.trim();
@@ -6629,7 +6943,7 @@ Analyze the above input.`;
6629
6943
  try {
6630
6944
  const name = getSessionName();
6631
6945
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
6632
- console.log(chalk26.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
6946
+ console.log(chalk27.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
6633
6947
  } catch {
6634
6948
  }
6635
6949
  }
@@ -6639,16 +6953,17 @@ Analyze the above input.`;
6639
6953
  } catch {
6640
6954
  }
6641
6955
  }
6956
+ slashMenu.cleanup();
6642
6957
  stopActiveLoop();
6643
6958
  stopFileWatcher();
6644
6959
  await pluginManager.shutdown();
6645
6960
  await toolCtx.runHook?.("session-end", {});
6646
- console.log(chalk26.gray("\n Goodbye!\n"));
6961
+ console.log(chalk27.gray("\n Goodbye!\n"));
6647
6962
  process.exit(0);
6648
6963
  }
6649
6964
  if (input === "/clear") {
6650
6965
  messages.length = 0;
6651
- console.log(chalk26.gray(" Conversation cleared.\n"));
6966
+ console.log(chalk27.gray(" Conversation cleared.\n"));
6652
6967
  rl.prompt();
6653
6968
  return;
6654
6969
  }
@@ -6668,8 +6983,8 @@ Analyze the above input.`;
6668
6983
  newModel = `notch-${newModel}`;
6669
6984
  }
6670
6985
  if (!isValidModel(newModel)) {
6671
- console.log(chalk26.red(` Unknown model: ${newModel}`));
6672
- console.log(chalk26.gray(` Available: ${modelChoices}`));
6986
+ console.log(chalk27.red(` Unknown model: ${newModel}`));
6987
+ console.log(chalk27.gray(` Available: ${modelChoices}`));
6673
6988
  rl.prompt();
6674
6989
  return;
6675
6990
  }
@@ -6677,7 +6992,7 @@ Analyze the above input.`;
6677
6992
  config.models.chat.model = activeModelId;
6678
6993
  model = resolveModel(config.models.chat);
6679
6994
  const switchedInfo = MODEL_CATALOG[activeModelId];
6680
- console.log(chalk26.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
6995
+ console.log(chalk27.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
6681
6996
  `));
6682
6997
  rl.prompt();
6683
6998
  return;
@@ -6690,22 +7005,22 @@ Analyze the above input.`;
6690
7005
  statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
6691
7006
  } else {
6692
7007
  statusSpinner.fail(check.error ?? "API unreachable");
6693
- console.log(chalk26.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
7008
+ console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
6694
7009
  }
6695
7010
  rl.prompt();
6696
7011
  return;
6697
7012
  }
6698
7013
  if (input === "/undo") {
6699
7014
  if (checkpoints.undoCount === 0) {
6700
- console.log(chalk26.gray(" Nothing to undo.\n"));
7015
+ console.log(chalk27.gray(" Nothing to undo.\n"));
6701
7016
  rl.prompt();
6702
7017
  return;
6703
7018
  }
6704
7019
  const undone = await checkpoints.undo();
6705
7020
  if (undone) {
6706
7021
  const fileCount = undone.files.length;
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
7022
+ console.log(chalk27.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
7023
+ console.log(chalk27.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
6709
7024
  `));
6710
7025
  }
6711
7026
  rl.prompt();
@@ -6713,7 +7028,7 @@ Analyze the above input.`;
6713
7028
  }
6714
7029
  if (input === "/usage") {
6715
7030
  if (usage.turnCount === 0) {
6716
- console.log(chalk26.gray(" No usage yet.\n"));
7031
+ console.log(chalk27.gray(" No usage yet.\n"));
6717
7032
  } else {
6718
7033
  console.log(usage.formatSession());
6719
7034
  const currentTokens = estimateTokens(messages);
@@ -6726,7 +7041,7 @@ Analyze the above input.`;
6726
7041
  }
6727
7042
  if (input === "/cost") {
6728
7043
  if (costTracker.totalCost === 0) {
6729
- console.log(chalk26.gray(" No cost data yet.\n"));
7044
+ console.log(chalk27.gray(" No cost data yet.\n"));
6730
7045
  } else {
6731
7046
  console.log(costTracker.formatTotal());
6732
7047
  console.log(costTracker.formatByModel());
@@ -6741,7 +7056,7 @@ Analyze the above input.`;
6741
7056
  const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
6742
7057
  messages.length = 0;
6743
7058
  messages.push(...compressed);
6744
- console.log(chalk26.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
7059
+ console.log(chalk27.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
6745
7060
  console.log("");
6746
7061
  rl.prompt();
6747
7062
  return;
@@ -6749,10 +7064,10 @@ Analyze the above input.`;
6749
7064
  if (input === "/diff") {
6750
7065
  const diffs = checkpoints.allDiffs();
6751
7066
  if (diffs.length === 0) {
6752
- console.log(chalk26.gray(" No file changes this session.\n"));
7067
+ console.log(chalk27.gray(" No file changes this session.\n"));
6753
7068
  } else {
6754
7069
  for (const df of diffs) {
6755
- console.log(chalk26.cyan(` ${df.path}:`));
7070
+ console.log(chalk27.cyan(` ${df.path}:`));
6756
7071
  console.log(unifiedDiff(df.before, df.after, df.path));
6757
7072
  console.log("");
6758
7073
  }
@@ -6768,10 +7083,10 @@ Analyze the above input.`;
6768
7083
  projectRoot: config.projectRoot,
6769
7084
  outputPath: exportPath
6770
7085
  });
6771
- console.log(chalk26.green(` Exported to ${ePath}
7086
+ console.log(chalk27.green(` Exported to ${ePath}
6772
7087
  `));
6773
7088
  } catch (err) {
6774
- console.log(chalk26.red(` Export failed: ${err.message}
7089
+ console.log(chalk27.red(` Export failed: ${err.message}
6775
7090
  `));
6776
7091
  }
6777
7092
  rl.prompt();
@@ -6781,10 +7096,10 @@ Analyze the above input.`;
6781
7096
  try {
6782
7097
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
6783
7098
  sessionId = id;
6784
- console.log(chalk26.green(` Session saved: ${id}
7099
+ console.log(chalk27.green(` Session saved: ${id}
6785
7100
  `));
6786
7101
  } catch (err) {
6787
- console.log(chalk26.red(` Save failed: ${err.message}
7102
+ console.log(chalk27.red(` Save failed: ${err.message}
6788
7103
  `));
6789
7104
  }
6790
7105
  rl.prompt();
@@ -6794,16 +7109,16 @@ Analyze the above input.`;
6794
7109
  try {
6795
7110
  const sessions = await listSessions(config.projectRoot);
6796
7111
  if (sessions.length === 0) {
6797
- console.log(chalk26.gray(" No saved sessions.\n"));
7112
+ console.log(chalk27.gray(" No saved sessions.\n"));
6798
7113
  } else {
6799
- console.log(chalk26.gray("\n Saved sessions:\n"));
7114
+ console.log(chalk27.gray("\n Saved sessions:\n"));
6800
7115
  for (const s of sessions.slice(0, 10)) {
6801
- console.log(chalk26.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
7116
+ console.log(chalk27.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
6802
7117
  }
6803
7118
  console.log("");
6804
7119
  }
6805
7120
  } catch (err) {
6806
- console.log(chalk26.red(` Error listing sessions: ${err.message}
7121
+ console.log(chalk27.red(` Error listing sessions: ${err.message}
6807
7122
  `));
6808
7123
  }
6809
7124
  rl.prompt();
@@ -6821,18 +7136,18 @@ Analyze the above input.`;
6821
7136
  const savedPlan = await loadPlan(config.projectRoot);
6822
7137
  if (savedPlan) {
6823
7138
  ralphPlan = savedPlan;
6824
- console.log(chalk26.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
7139
+ console.log(chalk27.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
6825
7140
  `));
6826
7141
  }
6827
7142
  } catch {
6828
7143
  }
6829
- console.log(chalk26.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
7144
+ console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6830
7145
  `));
6831
7146
  } else {
6832
- console.log(chalk26.gray(" No session found.\n"));
7147
+ console.log(chalk27.gray(" No session found.\n"));
6833
7148
  }
6834
7149
  } catch (err) {
6835
- console.log(chalk26.red(` Resume failed: ${err.message}
7150
+ console.log(chalk27.red(` Resume failed: ${err.message}
6836
7151
  `));
6837
7152
  }
6838
7153
  rl.prompt();
@@ -6841,7 +7156,7 @@ Analyze the above input.`;
6841
7156
  if (input.startsWith("/search ")) {
6842
7157
  const query = input.replace("/search ", "").trim().toLowerCase();
6843
7158
  if (!query) {
6844
- console.log(chalk26.gray(" Usage: /search <query>\n"));
7159
+ console.log(chalk27.gray(" Usage: /search <query>\n"));
6845
7160
  rl.prompt();
6846
7161
  return;
6847
7162
  }
@@ -6858,14 +7173,14 @@ Analyze the above input.`;
6858
7173
  }
6859
7174
  }
6860
7175
  if (matches.length === 0) {
6861
- console.log(chalk26.gray(` No matches for "${query}"
7176
+ console.log(chalk27.gray(` No matches for "${query}"
6862
7177
  `));
6863
7178
  } else {
6864
- console.log(chalk26.gray(`
7179
+ console.log(chalk27.gray(`
6865
7180
  ${matches.length} match(es) for "${query}":
6866
7181
  `));
6867
7182
  for (const m of matches.slice(0, 10)) {
6868
- console.log(chalk26.gray(` [${m.index}] ${m.role}: ${m.preview}`));
7183
+ console.log(chalk27.gray(` [${m.index}] ${m.role}: ${m.preview}`));
6869
7184
  }
6870
7185
  console.log("");
6871
7186
  }
@@ -6879,7 +7194,7 @@ Analyze the above input.`;
6879
7194
  activePlan = await generatePlan(task, model, systemPrompt);
6880
7195
  planSpinner.succeed("Plan generated");
6881
7196
  console.log(formatPlan(activePlan));
6882
- console.log(chalk26.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
7197
+ console.log(chalk27.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
6883
7198
  } catch (err) {
6884
7199
  planSpinner.fail(`Plan failed: ${err.message}`);
6885
7200
  }
@@ -6888,11 +7203,11 @@ Analyze the above input.`;
6888
7203
  }
6889
7204
  if (input === "/plan approve") {
6890
7205
  if (!activePlan) {
6891
- console.log(chalk26.gray(" No active plan. Use /plan <task> to create one.\n"));
7206
+ console.log(chalk27.gray(" No active plan. Use /plan <task> to create one.\n"));
6892
7207
  rl.prompt();
6893
7208
  return;
6894
7209
  }
6895
- console.log(chalk26.green(" Executing plan...\n"));
7210
+ console.log(chalk27.green(" Executing plan...\n"));
6896
7211
  while (!isPlanComplete(activePlan)) {
6897
7212
  const stepPrompt = currentStepPrompt(activePlan);
6898
7213
  messages.push({ role: "user", content: stepPrompt });
@@ -6910,10 +7225,10 @@ Analyze the above input.`;
6910
7225
  },
6911
7226
  onToolCall: (name) => {
6912
7227
  if (planStepSpinner.isSpinning) planStepSpinner.stop();
6913
- console.log(chalk26.gray(` \u2192 ${name}`));
7228
+ console.log(chalk27.gray(` \u2192 ${name}`));
6914
7229
  },
6915
7230
  onToolResult: (_name, _result, isError) => {
6916
- console.log(isError ? chalk26.red(" \u2717") : chalk26.green(" \u2713"));
7231
+ console.log(isError ? chalk27.red(" \u2717") : chalk27.green(" \u2713"));
6917
7232
  }
6918
7233
  });
6919
7234
  console.log("\n");
@@ -6926,7 +7241,7 @@ Analyze the above input.`;
6926
7241
  }
6927
7242
  }
6928
7243
  if (activePlan && isPlanComplete(activePlan)) {
6929
- console.log(chalk26.green(" Plan completed!\n"));
7244
+ console.log(chalk27.green(" Plan completed!\n"));
6930
7245
  }
6931
7246
  activePlan = null;
6932
7247
  rl.prompt();
@@ -6934,26 +7249,26 @@ Analyze the above input.`;
6934
7249
  }
6935
7250
  if (input === "/plan edit") {
6936
7251
  if (!activePlan) {
6937
- console.log(chalk26.gray(" No active plan to edit.\n"));
7252
+ console.log(chalk27.gray(" No active plan to edit.\n"));
6938
7253
  rl.prompt();
6939
7254
  return;
6940
7255
  }
6941
7256
  console.log(formatPlan(activePlan));
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"));
7257
+ console.log(chalk27.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
7258
+ console.log(chalk27.gray(" or /plan approve to proceed with the current plan.\n"));
6944
7259
  rl.prompt();
6945
7260
  return;
6946
7261
  }
6947
7262
  if (input === "/plan cancel") {
6948
7263
  activePlan = null;
6949
- console.log(chalk26.gray(" Plan discarded.\n"));
7264
+ console.log(chalk27.gray(" Plan discarded.\n"));
6950
7265
  rl.prompt();
6951
7266
  return;
6952
7267
  }
6953
7268
  if (input.startsWith("/agent ")) {
6954
7269
  const task = input.replace("/agent ", "").trim();
6955
7270
  const agentId = nextSubagentId();
6956
- console.log(chalk26.cyan(` Spawning subagent #${agentId}: ${task}
7271
+ console.log(chalk27.cyan(` Spawning subagent #${agentId}: ${task}
6957
7272
  `));
6958
7273
  spawnSubagent({
6959
7274
  id: agentId,
@@ -6963,14 +7278,14 @@ Analyze the above input.`;
6963
7278
  toolContext: toolCtx,
6964
7279
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
6965
7280
  onComplete: (result) => {
6966
- console.log(chalk26.green(`
7281
+ console.log(chalk27.green(`
6967
7282
  Subagent #${agentId} finished:`));
6968
- console.log(chalk26.gray(` ${result.slice(0, 200)}
7283
+ console.log(chalk27.gray(` ${result.slice(0, 200)}
6969
7284
  `));
6970
7285
  rl.prompt();
6971
7286
  },
6972
7287
  onError: (err) => {
6973
- console.log(chalk26.red(`
7288
+ console.log(chalk27.red(`
6974
7289
  Subagent #${agentId} failed: ${err}
6975
7290
  `));
6976
7291
  rl.prompt();
@@ -6983,18 +7298,18 @@ Analyze the above input.`;
6983
7298
  try {
6984
7299
  const memories = await loadMemories(config.projectRoot);
6985
7300
  if (memories.length === 0) {
6986
- console.log(chalk26.gray(" No saved memories.\n"));
7301
+ console.log(chalk27.gray(" No saved memories.\n"));
6987
7302
  } else {
6988
- console.log(chalk26.gray(`
7303
+ console.log(chalk27.gray(`
6989
7304
  ${memories.length} saved memories:
6990
7305
  `));
6991
7306
  for (const m of memories) {
6992
- console.log(chalk26.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
7307
+ console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
6993
7308
  }
6994
7309
  console.log("");
6995
7310
  }
6996
7311
  } catch (err) {
6997
- console.log(chalk26.red(` Error: ${err.message}
7312
+ console.log(chalk27.red(` Error: ${err.message}
6998
7313
  `));
6999
7314
  }
7000
7315
  rl.prompt();
@@ -7005,16 +7320,16 @@ Analyze the above input.`;
7005
7320
  try {
7006
7321
  const results = await searchMemories(config.projectRoot, query);
7007
7322
  if (results.length === 0) {
7008
- console.log(chalk26.gray(` No memories matching "${query}"
7323
+ console.log(chalk27.gray(` No memories matching "${query}"
7009
7324
  `));
7010
7325
  } else {
7011
7326
  for (const m of results) {
7012
- console.log(chalk26.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
7327
+ console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
7013
7328
  }
7014
7329
  console.log("");
7015
7330
  }
7016
7331
  } catch (err) {
7017
- console.log(chalk26.red(` Error: ${err.message}
7332
+ console.log(chalk27.red(` Error: ${err.message}
7018
7333
  `));
7019
7334
  }
7020
7335
  rl.prompt();
@@ -7026,10 +7341,10 @@ Analyze the above input.`;
7026
7341
  for (const m of memories) {
7027
7342
  await deleteMemory(config.projectRoot, m.id);
7028
7343
  }
7029
- console.log(chalk26.yellow(` Cleared ${memories.length} memories.
7344
+ console.log(chalk27.yellow(` Cleared ${memories.length} memories.
7030
7345
  `));
7031
7346
  } catch (err) {
7032
- console.log(chalk26.red(` Error: ${err.message}
7347
+ console.log(chalk27.red(` Error: ${err.message}
7033
7348
  `));
7034
7349
  }
7035
7350
  rl.prompt();
@@ -7057,27 +7372,27 @@ Analyze the above input.`;
7057
7372
  }
7058
7373
  if (input === "/ralph run") {
7059
7374
  if (!ralphPlan) {
7060
- console.log(chalk26.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
7375
+ console.log(chalk27.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
7061
7376
  rl.prompt();
7062
7377
  return;
7063
7378
  }
7064
- console.log(chalk26.green(" Ralph is running...\n"));
7379
+ console.log(chalk27.green(" Ralph is running...\n"));
7065
7380
  try {
7066
7381
  ralphPlan = await runRalphLoop(ralphPlan, {
7067
7382
  model,
7068
7383
  systemPrompt,
7069
7384
  toolContext: toolCtx,
7070
7385
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
7071
- onTaskStart: (task) => console.log(chalk26.cyan(` \u25B6 Task: ${task.description}`)),
7072
- onTaskComplete: (task) => console.log(chalk26.green(` \u2713 Done: ${task.description}
7386
+ onTaskStart: (task) => console.log(chalk27.cyan(` \u25B6 Task: ${task.description}`)),
7387
+ onTaskComplete: (task) => console.log(chalk27.green(` \u2713 Done: ${task.description}
7073
7388
  `)),
7074
- onTaskFail: (task, err) => console.log(chalk26.red(` \u2717 Failed: ${task.description} (${err})
7389
+ onTaskFail: (task, err) => console.log(chalk27.red(` \u2717 Failed: ${task.description} (${err})
7075
7390
  `))
7076
7391
  });
7077
7392
  await savePlan(config.projectRoot, ralphPlan);
7078
7393
  console.log(formatRalphStatus(ralphPlan));
7079
7394
  } catch (err) {
7080
- console.log(chalk26.red(` Ralph error: ${err.message}
7395
+ console.log(chalk27.red(` Ralph error: ${err.message}
7081
7396
  `));
7082
7397
  }
7083
7398
  rl.prompt();
@@ -7085,7 +7400,7 @@ Analyze the above input.`;
7085
7400
  }
7086
7401
  if (input === "/ralph status") {
7087
7402
  if (!ralphPlan) {
7088
- console.log(chalk26.gray(" No Ralph plan active.\n"));
7403
+ console.log(chalk27.gray(" No Ralph plan active.\n"));
7089
7404
  } else {
7090
7405
  console.log(formatRalphStatus(ralphPlan));
7091
7406
  }
@@ -7096,26 +7411,26 @@ Analyze the above input.`;
7096
7411
  ralphPlan = null;
7097
7412
  await deletePlan(config.projectRoot).catch(() => {
7098
7413
  });
7099
- console.log(chalk26.gray(" Ralph plan cleared.\n"));
7414
+ console.log(chalk27.gray(" Ralph plan cleared.\n"));
7100
7415
  rl.prompt();
7101
7416
  return;
7102
7417
  }
7103
7418
  if (input === "/branch") {
7104
7419
  const branchId = `branch-${branches.size + 1}`;
7105
7420
  branches.set(branchId, [...messages]);
7106
- console.log(chalk26.green(` Forked conversation as "${branchId}" (${messages.length} messages)
7421
+ console.log(chalk27.green(` Forked conversation as "${branchId}" (${messages.length} messages)
7107
7422
  `));
7108
7423
  rl.prompt();
7109
7424
  return;
7110
7425
  }
7111
7426
  if (input === "/branches") {
7112
7427
  if (branches.size === 0) {
7113
- console.log(chalk26.gray(" No conversation branches. Use /branch to fork.\n"));
7428
+ console.log(chalk27.gray(" No conversation branches. Use /branch to fork.\n"));
7114
7429
  } else {
7115
- console.log(chalk26.gray("\n Branches:\n"));
7430
+ console.log(chalk27.gray("\n Branches:\n"));
7116
7431
  for (const [name, msgs] of branches) {
7117
- const marker = name === currentBranch ? chalk26.green(" \u25CF") : " ";
7118
- console.log(chalk26.gray(` ${marker} ${name} (${msgs.length} messages)`));
7432
+ const marker = name === currentBranch ? chalk27.green(" \u25CF") : " ";
7433
+ console.log(chalk27.gray(` ${marker} ${name} (${msgs.length} messages)`));
7119
7434
  }
7120
7435
  console.log("");
7121
7436
  }
@@ -7157,7 +7472,7 @@ Analyze the above input.`;
7157
7472
  modelId: activeModelId,
7158
7473
  messages,
7159
7474
  lastResponse: lastAssistantResponse,
7160
- log: (msg) => console.log(chalk26.gray(` ${msg}`)),
7475
+ log: (msg) => console.log(chalk27.gray(` ${msg}`)),
7161
7476
  runPrompt: async (prompt, msgs) => {
7162
7477
  const spinner2 = ora4("Thinking...").start();
7163
7478
  const response = await withRetry(
@@ -7174,13 +7489,13 @@ Analyze the above input.`;
7174
7489
  onToolCall: (name, args) => {
7175
7490
  if (spinner2.isSpinning) spinner2.stop();
7176
7491
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
7177
- console.log(chalk26.gray(`
7492
+ console.log(chalk27.gray(`
7178
7493
  \u2192 ${name}(${argSummary})`));
7179
7494
  },
7180
7495
  onToolResult: (_name, result, isError) => {
7181
7496
  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 ? "..." : ""}`));
7497
+ const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
7498
+ console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7184
7499
  }
7185
7500
  })
7186
7501
  );
@@ -7194,8 +7509,8 @@ Analyze the above input.`;
7194
7509
  rl.prompt();
7195
7510
  return;
7196
7511
  }
7197
- console.log(chalk26.red(` Unknown command: ${input}`));
7198
- console.log(chalk26.gray(" Type /help for available commands.\n"));
7512
+ console.log(chalk27.red(` Unknown command: ${input}`));
7513
+ console.log(chalk27.gray(" Type /help for available commands.\n"));
7199
7514
  rl.prompt();
7200
7515
  return;
7201
7516
  }
@@ -7203,7 +7518,7 @@ Analyze the above input.`;
7203
7518
  const refContext = formatReferences(references);
7204
7519
  const finalPrompt = refContext + cleanInput;
7205
7520
  if (references.length > 0) {
7206
- console.log(chalk26.gray(` Injected ${references.length} reference(s)`));
7521
+ console.log(chalk27.gray(` Injected ${references.length} reference(s)`));
7207
7522
  }
7208
7523
  messages.push({ role: "user", content: finalPrompt });
7209
7524
  const spinner = ora4("Thinking...").start();
@@ -7229,16 +7544,16 @@ Analyze the above input.`;
7229
7544
  const val = String(v);
7230
7545
  return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
7231
7546
  }).join(", ");
7232
- console.log(chalk26.gray(`
7547
+ console.log(chalk27.gray(`
7233
7548
  \u2192 ${name}(${argSummary})`));
7234
7549
  },
7235
7550
  onToolResult: (_name, result, isError) => {
7236
7551
  const preview = result.slice(0, 100).replace(/\n/g, " ");
7237
- const icon = isError ? chalk26.red("\u2717") : chalk26.green("\u2713");
7238
- console.log(chalk26.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7552
+ const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
7553
+ console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7239
7554
  },
7240
7555
  onCompress: () => {
7241
- console.log(chalk26.yellow("\n [Context compressed to fit window]\n"));
7556
+ console.log(chalk27.yellow("\n [Context compressed to fit window]\n"));
7242
7557
  }
7243
7558
  })
7244
7559
  );
@@ -7272,7 +7587,7 @@ Analyze the above input.`;
7272
7587
  type: "auto",
7273
7588
  content: memMatch[1]
7274
7589
  });
7275
- console.log(chalk26.gray(" (Saved to memory)\n"));
7590
+ console.log(chalk27.gray(" (Saved to memory)\n"));
7276
7591
  } catch {
7277
7592
  }
7278
7593
  }
@@ -7282,13 +7597,13 @@ Analyze the above input.`;
7282
7597
  checkpoints.discard();
7283
7598
  const msg = err.message?.toLowerCase() ?? "";
7284
7599
  if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
7285
- console.log(chalk26.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
7600
+ console.log(chalk27.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
7286
7601
  } else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
7287
- console.log(chalk26.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
7602
+ console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
7288
7603
  } else if (msg.includes("429") || msg.includes("rate limit")) {
7289
- console.log(chalk26.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
7604
+ console.log(chalk27.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
7290
7605
  } else {
7291
- console.log(chalk26.gray(" (The conversation history is preserved. Try again.)\n"));
7606
+ console.log(chalk27.gray(" (The conversation history is preserved. Try again.)\n"));
7292
7607
  }
7293
7608
  }
7294
7609
  rl.prompt();
@@ -7306,13 +7621,13 @@ async function handleRalphSubcommand(args, cliOpts) {
7306
7621
  cwd: config.projectRoot,
7307
7622
  requireConfirm: false,
7308
7623
  confirm: async () => true,
7309
- log: (msg) => console.log(chalk26.gray(` ${msg}`))
7624
+ log: (msg) => console.log(chalk27.gray(` ${msg}`))
7310
7625
  };
7311
7626
  const subcommand = args[0];
7312
7627
  if (subcommand === "plan") {
7313
7628
  const goal = args.slice(1).join(" ");
7314
7629
  if (!goal) {
7315
- console.error(chalk26.red(" Usage: notch ralph plan <goal>"));
7630
+ console.error(chalk27.red(" Usage: notch ralph plan <goal>"));
7316
7631
  process.exit(1);
7317
7632
  }
7318
7633
  const spinner = ora4("Ralph is planning...").start();
@@ -7323,7 +7638,7 @@ async function handleRalphSubcommand(args, cliOpts) {
7323
7638
  } else if (subcommand === "run") {
7324
7639
  let plan = await loadPlan(config.projectRoot);
7325
7640
  if (!plan) {
7326
- console.error(chalk26.red(" No plan found. Run: notch ralph plan <goal>"));
7641
+ console.error(chalk27.red(" No plan found. Run: notch ralph plan <goal>"));
7327
7642
  process.exit(1);
7328
7643
  }
7329
7644
  plan = await runRalphLoop(plan, {
@@ -7331,27 +7646,27 @@ async function handleRalphSubcommand(args, cliOpts) {
7331
7646
  systemPrompt,
7332
7647
  toolContext: toolCtx,
7333
7648
  contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
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}`))
7649
+ onTaskStart: (t) => console.log(chalk27.cyan(` \u25B6 ${t.description}`)),
7650
+ onTaskComplete: (t) => console.log(chalk27.green(` \u2713 ${t.description}`)),
7651
+ onTaskFail: (t, e) => console.log(chalk27.red(` \u2717 ${t.description}: ${e}`))
7337
7652
  });
7338
7653
  await savePlan(config.projectRoot, plan);
7339
7654
  console.log(formatRalphStatus(plan));
7340
7655
  } else if (subcommand === "status") {
7341
7656
  const plan = await loadPlan(config.projectRoot);
7342
7657
  if (!plan) {
7343
- console.log(chalk26.gray(" No Ralph plan found."));
7658
+ console.log(chalk27.gray(" No Ralph plan found."));
7344
7659
  } else {
7345
7660
  console.log(formatRalphStatus(plan));
7346
7661
  }
7347
7662
  } else {
7348
- console.error(chalk26.red(` Unknown: notch ralph ${subcommand}`));
7349
- console.error(chalk26.gray(" Usage: notch ralph <plan|run|status>"));
7663
+ console.error(chalk27.red(` Unknown: notch ralph ${subcommand}`));
7664
+ console.error(chalk27.gray(" Usage: notch ralph <plan|run|status>"));
7350
7665
  process.exit(1);
7351
7666
  }
7352
7667
  }
7353
7668
  main().catch((err) => {
7354
- console.error(chalk26.red(`
7669
+ console.error(chalk27.red(`
7355
7670
  Fatal: ${err.message}
7356
7671
  `));
7357
7672
  process.exit(1);