@freesyntax/notch-cli 0.5.0 → 0.5.1

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 +573 -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,258 @@ 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 items = state.filtered.slice(0, MAX_VISIBLE);
6341
+ const hasMore = state.filtered.length > MAX_VISIBLE;
6342
+ let currentCategory = "";
6343
+ const lines = [];
6344
+ 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"));
6345
+ for (let i = 0; i < items.length; i++) {
6346
+ const cmd = items[i];
6347
+ const isSelected = i === state.selected;
6348
+ if (cmd.category !== currentCategory) {
6349
+ currentCategory = cmd.category;
6350
+ if (i > 0) {
6351
+ lines.push(chalk26.gray(" \u2502 \u2502"));
6352
+ }
6353
+ lines.push(chalk26.gray(` \u2502 `) + chalk26.gray.dim(cmd.category.toUpperCase().padEnd(47)) + chalk26.gray("\u2502"));
6354
+ }
6355
+ const name = cmd.name.padEnd(22);
6356
+ const desc = cmd.description.slice(0, 25).padEnd(25);
6357
+ if (isSelected) {
6358
+ lines.push(
6359
+ chalk26.gray(" \u2502 ") + chalk26.bgWhite.black(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
6360
+ );
6361
+ } else {
6362
+ lines.push(
6363
+ chalk26.gray(" \u2502 ") + chalk26.cyan(` ${name}`) + chalk26.gray(` ${desc}`) + chalk26.gray(" \u2502")
6364
+ );
6365
+ }
6366
+ }
6367
+ if (hasMore) {
6368
+ const remaining = state.filtered.length - MAX_VISIBLE;
6369
+ lines.push(chalk26.gray(` \u2502 ${chalk26.dim(`+${remaining} more\u2026`)}${"".padEnd(41 - String(remaining).length)}\u2502`));
6370
+ }
6371
+ 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"));
6372
+ state.renderedLines = lines.length;
6373
+ return lines.join("\n");
6374
+ }
6375
+ function clearMenu(state) {
6376
+ if (state.renderedLines === 0) return "";
6377
+ const lines = state.renderedLines;
6378
+ state.renderedLines = 0;
6379
+ let ansi = "";
6380
+ for (let i = 0; i < lines; i++) {
6381
+ ansi += "\x1B[1A\x1B[2K";
6382
+ }
6383
+ return ansi;
6384
+ }
6385
+
6386
+ // src/ui/interactive-input.ts
6387
+ function attachSlashMenu(rl) {
6388
+ const state = createMenuState();
6389
+ let lastInput = "";
6390
+ const keypressHandler = (_ch, key) => {
6391
+ const currentLine = rl.line ?? "";
6392
+ if (state.visible && key) {
6393
+ if (key.name === "up") {
6394
+ process.stdout.write(clearMenu(state));
6395
+ menuUp(state);
6396
+ process.stdout.write(renderMenu(state) + "\n");
6397
+ rewritePromptLine(rl);
6398
+ return;
6399
+ }
6400
+ if (key.name === "down") {
6401
+ process.stdout.write(clearMenu(state));
6402
+ menuDown(state);
6403
+ process.stdout.write(renderMenu(state) + "\n");
6404
+ rewritePromptLine(rl);
6405
+ return;
6406
+ }
6407
+ if (key.name === "escape") {
6408
+ process.stdout.write(clearMenu(state));
6409
+ menuDismiss(state);
6410
+ return;
6411
+ }
6412
+ if (key.name === "return") {
6413
+ const selected = menuSelect(state);
6414
+ if (selected) {
6415
+ process.stdout.write(clearMenu(state));
6416
+ menuDismiss(state);
6417
+ rl.line = "";
6418
+ rl.cursor = 0;
6419
+ rl.write(null, { ctrl: true, name: "u" });
6420
+ rl.write(selected + " ");
6421
+ return;
6422
+ }
6423
+ }
6424
+ if (key.name === "tab") {
6425
+ const selected = menuSelect(state);
6426
+ if (selected) {
6427
+ process.stdout.write(clearMenu(state));
6428
+ menuDismiss(state);
6429
+ rl.line = "";
6430
+ rl.cursor = 0;
6431
+ rl.write(null, { ctrl: true, name: "u" });
6432
+ rl.write(selected + " ");
6433
+ return;
6434
+ }
6435
+ }
6436
+ }
6437
+ setImmediate(() => {
6438
+ const line = rl.line ?? "";
6439
+ if (line !== lastInput) {
6440
+ lastInput = line;
6441
+ const wasVisible = state.visible;
6442
+ const shouldShow = updateMenu(state, line);
6443
+ if (wasVisible && !shouldShow) {
6444
+ process.stdout.write(clearMenu(state));
6445
+ } else if (shouldShow) {
6446
+ if (wasVisible) {
6447
+ process.stdout.write(clearMenu(state));
6448
+ }
6449
+ const menuStr = renderMenu(state);
6450
+ if (menuStr) {
6451
+ process.stdout.write("\x1B[s");
6452
+ process.stdout.write(menuStr + "\n");
6453
+ rewritePromptLine(rl);
6454
+ process.stdout.write("\x1B[u");
6455
+ }
6456
+ }
6457
+ }
6458
+ });
6459
+ };
6460
+ process.stdin.on("keypress", keypressHandler);
6461
+ const closeHandler = () => {
6462
+ if (state.visible) {
6463
+ process.stdout.write(clearMenu(state));
6464
+ }
6465
+ process.stdin.removeListener("keypress", keypressHandler);
6466
+ };
6467
+ rl.on("close", closeHandler);
6468
+ const lineHandler = () => {
6469
+ if (state.visible) {
6470
+ process.stdout.write(clearMenu(state));
6471
+ menuDismiss(state);
6472
+ }
6473
+ lastInput = "";
6474
+ };
6475
+ rl.on("line", lineHandler);
6476
+ return {
6477
+ cleanup: () => {
6478
+ process.stdin.removeListener("keypress", keypressHandler);
6479
+ rl.removeListener("close", closeHandler);
6480
+ rl.removeListener("line", lineHandler);
6481
+ }
6482
+ };
6483
+ }
6484
+ function rewritePromptLine(rl) {
6485
+ const prompt = rl._prompt ?? "notch> ";
6486
+ const line = rl.line ?? "";
6487
+ const cursor = rl.cursor ?? line.length;
6488
+ process.stdout.write("\x1B[2K\r");
6489
+ process.stdout.write(prompt + line);
6490
+ const cursorOffset = line.length - cursor;
6491
+ if (cursorOffset > 0) {
6492
+ process.stdout.write(`\x1B[${cursorOffset}D`);
6493
+ }
6494
+ }
6495
+
6198
6496
  // src/index.ts
6199
6497
  import fs23 from "fs/promises";
6200
6498
  import { createRequire } from "module";
@@ -6219,7 +6517,7 @@ function printModelTable(activeModel) {
6219
6517
  `));
6220
6518
  }
6221
6519
  function printHelp() {
6222
- console.log(chalk26.gray(`
6520
+ console.log(chalk27.gray(`
6223
6521
  Commands:
6224
6522
  /model \u2014 Show available models
6225
6523
  /model <name> \u2014 Switch model (e.g., /model pyre)
@@ -6316,13 +6614,13 @@ async function main() {
6316
6614
  try {
6317
6615
  spinner.stop();
6318
6616
  const creds = await login();
6319
- console.log(chalk26.green(`
6617
+ console.log(chalk27.green(`
6320
6618
  \u2713 Signed in as ${creds.email}`));
6321
- console.log(chalk26.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
6619
+ console.log(chalk27.gray(` API key stored in ${(await import("./auth-S3FIB42I.js")).getCredentialsPath()}
6322
6620
  `));
6323
6621
  } catch (err) {
6324
6622
  spinner.stop();
6325
- console.error(chalk26.red(`
6623
+ console.error(chalk27.red(`
6326
6624
  Login failed: ${err.message}
6327
6625
  `));
6328
6626
  process.exit(1);
@@ -6332,10 +6630,10 @@ async function main() {
6332
6630
  if (promptArgs[0] === "logout") {
6333
6631
  const creds = await loadCredentials();
6334
6632
  if (!creds) {
6335
- console.log(chalk26.gray("\n Not signed in.\n"));
6633
+ console.log(chalk27.gray("\n Not signed in.\n"));
6336
6634
  } else {
6337
6635
  await clearCredentials();
6338
- console.log(chalk26.green(`
6636
+ console.log(chalk27.green(`
6339
6637
  \u2713 Signed out (${creds.email})
6340
6638
  `));
6341
6639
  }
@@ -6344,13 +6642,13 @@ async function main() {
6344
6642
  if (promptArgs[0] === "whoami") {
6345
6643
  const creds = await loadCredentials();
6346
6644
  if (!creds) {
6347
- console.log(chalk26.gray("\n Not signed in. Run: notch login\n"));
6645
+ console.log(chalk27.gray("\n Not signed in. Run: notch login\n"));
6348
6646
  } else {
6349
6647
  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()}
6648
+ console.log(chalk27.gray(`
6649
+ Signed in as ${chalk27.white(creds.email)}`));
6650
+ console.log(chalk27.gray(` Key: ${keyPreview}`));
6651
+ console.log(chalk27.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
6354
6652
  `));
6355
6653
  }
6356
6654
  return;
@@ -6374,8 +6672,8 @@ async function main() {
6374
6672
  const config = await loadConfig(configOverrides);
6375
6673
  if (opts.model) {
6376
6674
  if (!isValidModel(opts.model)) {
6377
- console.error(chalk26.red(` Unknown model: ${opts.model}`));
6378
- console.error(chalk26.gray(` Available: ${modelChoices}`));
6675
+ console.error(chalk27.red(` Unknown model: ${opts.model}`));
6676
+ console.error(chalk27.gray(` Available: ${modelChoices}`));
6379
6677
  process.exit(1);
6380
6678
  }
6381
6679
  config.models.chat.model = opts.model;
@@ -6414,11 +6712,11 @@ async function main() {
6414
6712
  const updateMsg = await checkForUpdates(VERSION);
6415
6713
  if (updateMsg) console.log(updateMsg);
6416
6714
  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}`)));
6715
+ console.warn(chalk27.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
6716
+ commands.forEach((cmd) => console.warn(chalk27.gray(` \u2022 ${cmd}`)));
6419
6717
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
6420
6718
  return new Promise((resolve2) => {
6421
- rl2.question(chalk26.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
6719
+ rl2.question(chalk27.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
6422
6720
  rl2.close();
6423
6721
  resolve2(answer.trim().toLowerCase() === "y");
6424
6722
  });
@@ -6464,22 +6762,22 @@ ${repoMapStr}` : ""
6464
6762
  const client = new MCPClient(mcpConfig, name);
6465
6763
  await client.connect();
6466
6764
  mcpClients.push(client);
6467
- console.log(chalk26.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
6765
+ console.log(chalk27.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
6468
6766
  } catch (err) {
6469
- console.log(chalk26.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
6767
+ console.log(chalk27.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
6470
6768
  }
6471
6769
  }
6472
6770
  } catch {
6473
6771
  }
6474
6772
  try {
6475
6773
  const pluginCount = await pluginManager.init(config.projectRoot, (msg) => {
6476
- console.log(chalk26.gray(` ${msg}`));
6774
+ console.log(chalk27.gray(` ${msg}`));
6477
6775
  });
6478
6776
  if (pluginCount > 0) {
6479
- console.log(chalk26.green(` Plugins: ${pluginCount} loaded`));
6777
+ console.log(chalk27.green(` Plugins: ${pluginCount} loaded`));
6480
6778
  }
6481
6779
  } catch (err) {
6482
- console.log(chalk26.yellow(` Plugin init failed: ${err.message}`));
6780
+ console.log(chalk27.yellow(` Plugin init failed: ${err.message}`));
6483
6781
  }
6484
6782
  const toolCtx = {
6485
6783
  cwd: config.projectRoot,
@@ -6493,7 +6791,7 @@ ${repoMapStr}` : ""
6493
6791
  });
6494
6792
  });
6495
6793
  },
6496
- log: (msg) => console.log(chalk26.gray(` ${msg}`)),
6794
+ log: (msg) => console.log(chalk27.gray(` ${msg}`)),
6497
6795
  checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
6498
6796
  runHook: async (event, ctx) => {
6499
6797
  if (!config.enableHooks || hookConfig.hooks.length === 0) return;
@@ -6503,7 +6801,7 @@ ${repoMapStr}` : ""
6503
6801
  });
6504
6802
  for (const r of results) {
6505
6803
  if (!r.ok) {
6506
- console.log(chalk26.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
6804
+ console.log(chalk27.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
6507
6805
  }
6508
6806
  }
6509
6807
  }
@@ -6512,7 +6810,7 @@ ${repoMapStr}` : ""
6512
6810
  const stopFileWatcher = startFileWatcher(config.projectRoot, hookConfig, (event, results) => {
6513
6811
  for (const r of results) {
6514
6812
  if (!r.ok) {
6515
- console.log(chalk26.yellow(` Hook failed (${event}): ${r.error}`));
6813
+ console.log(chalk27.yellow(` Hook failed (${event}): ${r.error}`));
6516
6814
  }
6517
6815
  }
6518
6816
  });
@@ -6523,10 +6821,10 @@ ${repoMapStr}` : ""
6523
6821
  if (session) {
6524
6822
  messages.push(...session.messages);
6525
6823
  sessionId = session.meta.id;
6526
- console.log(chalk26.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6824
+ console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6527
6825
  `));
6528
6826
  } else {
6529
- console.log(chalk26.gray(" No session to resume.\n"));
6827
+ console.log(chalk27.gray(" No session to resume.\n"));
6530
6828
  }
6531
6829
  }
6532
6830
  const pipedInput = await readStdin();
@@ -6547,10 +6845,10 @@ Analyze the above input.`;
6547
6845
  const refContext = formatReferences(references);
6548
6846
  const finalPrompt = refContext + cleanInput;
6549
6847
  messages.push({ role: "user", content: finalPrompt });
6550
- console.log(chalk26.cyan(`> ${oneShot || "(piped input)"}
6848
+ console.log(chalk27.cyan(`> ${oneShot || "(piped input)"}
6551
6849
  `));
6552
6850
  if (references.length > 0) {
6553
- console.log(chalk26.gray(` Injected ${references.length} reference(s)
6851
+ console.log(chalk27.gray(` Injected ${references.length} reference(s)
6554
6852
  `));
6555
6853
  }
6556
6854
  const spinner = ora4("Thinking...").start();
@@ -6569,13 +6867,13 @@ Analyze the above input.`;
6569
6867
  onToolCall: (name, args) => {
6570
6868
  if (spinner.isSpinning) spinner.stop();
6571
6869
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
6572
- console.log(chalk26.gray(`
6870
+ console.log(chalk27.gray(`
6573
6871
  \u2192 ${name}(${argSummary})`));
6574
6872
  },
6575
6873
  onToolResult: (_name, result, isError) => {
6576
6874
  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 ? "..." : ""}`));
6875
+ const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
6876
+ console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
6579
6877
  }
6580
6878
  })
6581
6879
  );
@@ -6600,19 +6898,21 @@ Analyze the above input.`;
6600
6898
  const savedPlan = await loadPlan(config.projectRoot);
6601
6899
  if (savedPlan) {
6602
6900
  ralphPlan = savedPlan;
6603
- console.log(chalk26.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
6901
+ console.log(chalk27.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
6604
6902
  `));
6605
6903
  }
6606
6904
  } catch {
6607
6905
  }
6608
6906
  const completer = buildCompleter(config.projectRoot);
6907
+ readline.emitKeypressEvents(process.stdin);
6609
6908
  const rl = readline.createInterface({
6610
6909
  input: process.stdin,
6611
6910
  output: process.stdout,
6612
6911
  prompt: theme().prompt("notch> "),
6613
6912
  completer: (line) => completer(line)
6614
6913
  });
6615
- console.log(chalk26.gray(" Type your request, or /help for commands.\n"));
6914
+ const slashMenu = attachSlashMenu(rl);
6915
+ console.log(chalk27.gray(" Type your request, or /help for commands.\n"));
6616
6916
  rl.prompt();
6617
6917
  rl.on("line", async (line) => {
6618
6918
  const input = line.trim();
@@ -6629,7 +6929,7 @@ Analyze the above input.`;
6629
6929
  try {
6630
6930
  const name = getSessionName();
6631
6931
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
6632
- console.log(chalk26.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
6932
+ console.log(chalk27.gray(` Session saved: ${id}${name ? ` (${name})` : ""}`));
6633
6933
  } catch {
6634
6934
  }
6635
6935
  }
@@ -6639,16 +6939,17 @@ Analyze the above input.`;
6639
6939
  } catch {
6640
6940
  }
6641
6941
  }
6942
+ slashMenu.cleanup();
6642
6943
  stopActiveLoop();
6643
6944
  stopFileWatcher();
6644
6945
  await pluginManager.shutdown();
6645
6946
  await toolCtx.runHook?.("session-end", {});
6646
- console.log(chalk26.gray("\n Goodbye!\n"));
6947
+ console.log(chalk27.gray("\n Goodbye!\n"));
6647
6948
  process.exit(0);
6648
6949
  }
6649
6950
  if (input === "/clear") {
6650
6951
  messages.length = 0;
6651
- console.log(chalk26.gray(" Conversation cleared.\n"));
6952
+ console.log(chalk27.gray(" Conversation cleared.\n"));
6652
6953
  rl.prompt();
6653
6954
  return;
6654
6955
  }
@@ -6668,8 +6969,8 @@ Analyze the above input.`;
6668
6969
  newModel = `notch-${newModel}`;
6669
6970
  }
6670
6971
  if (!isValidModel(newModel)) {
6671
- console.log(chalk26.red(` Unknown model: ${newModel}`));
6672
- console.log(chalk26.gray(` Available: ${modelChoices}`));
6972
+ console.log(chalk27.red(` Unknown model: ${newModel}`));
6973
+ console.log(chalk27.gray(` Available: ${modelChoices}`));
6673
6974
  rl.prompt();
6674
6975
  return;
6675
6976
  }
@@ -6677,7 +6978,7 @@ Analyze the above input.`;
6677
6978
  config.models.chat.model = activeModelId;
6678
6979
  model = resolveModel(config.models.chat);
6679
6980
  const switchedInfo = MODEL_CATALOG[activeModelId];
6680
- console.log(chalk26.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
6981
+ console.log(chalk27.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
6681
6982
  `));
6682
6983
  rl.prompt();
6683
6984
  return;
@@ -6690,22 +6991,22 @@ Analyze the above input.`;
6690
6991
  statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
6691
6992
  } else {
6692
6993
  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"));
6994
+ console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
6694
6995
  }
6695
6996
  rl.prompt();
6696
6997
  return;
6697
6998
  }
6698
6999
  if (input === "/undo") {
6699
7000
  if (checkpoints.undoCount === 0) {
6700
- console.log(chalk26.gray(" Nothing to undo.\n"));
7001
+ console.log(chalk27.gray(" Nothing to undo.\n"));
6701
7002
  rl.prompt();
6702
7003
  return;
6703
7004
  }
6704
7005
  const undone = await checkpoints.undo();
6705
7006
  if (undone) {
6706
7007
  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
7008
+ console.log(chalk27.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
7009
+ console.log(chalk27.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
6709
7010
  `));
6710
7011
  }
6711
7012
  rl.prompt();
@@ -6713,7 +7014,7 @@ Analyze the above input.`;
6713
7014
  }
6714
7015
  if (input === "/usage") {
6715
7016
  if (usage.turnCount === 0) {
6716
- console.log(chalk26.gray(" No usage yet.\n"));
7017
+ console.log(chalk27.gray(" No usage yet.\n"));
6717
7018
  } else {
6718
7019
  console.log(usage.formatSession());
6719
7020
  const currentTokens = estimateTokens(messages);
@@ -6726,7 +7027,7 @@ Analyze the above input.`;
6726
7027
  }
6727
7028
  if (input === "/cost") {
6728
7029
  if (costTracker.totalCost === 0) {
6729
- console.log(chalk26.gray(" No cost data yet.\n"));
7030
+ console.log(chalk27.gray(" No cost data yet.\n"));
6730
7031
  } else {
6731
7032
  console.log(costTracker.formatTotal());
6732
7033
  console.log(costTracker.formatByModel());
@@ -6741,7 +7042,7 @@ Analyze the above input.`;
6741
7042
  const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
6742
7043
  messages.length = 0;
6743
7044
  messages.push(...compressed);
6744
- console.log(chalk26.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
7045
+ console.log(chalk27.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
6745
7046
  console.log("");
6746
7047
  rl.prompt();
6747
7048
  return;
@@ -6749,10 +7050,10 @@ Analyze the above input.`;
6749
7050
  if (input === "/diff") {
6750
7051
  const diffs = checkpoints.allDiffs();
6751
7052
  if (diffs.length === 0) {
6752
- console.log(chalk26.gray(" No file changes this session.\n"));
7053
+ console.log(chalk27.gray(" No file changes this session.\n"));
6753
7054
  } else {
6754
7055
  for (const df of diffs) {
6755
- console.log(chalk26.cyan(` ${df.path}:`));
7056
+ console.log(chalk27.cyan(` ${df.path}:`));
6756
7057
  console.log(unifiedDiff(df.before, df.after, df.path));
6757
7058
  console.log("");
6758
7059
  }
@@ -6768,10 +7069,10 @@ Analyze the above input.`;
6768
7069
  projectRoot: config.projectRoot,
6769
7070
  outputPath: exportPath
6770
7071
  });
6771
- console.log(chalk26.green(` Exported to ${ePath}
7072
+ console.log(chalk27.green(` Exported to ${ePath}
6772
7073
  `));
6773
7074
  } catch (err) {
6774
- console.log(chalk26.red(` Export failed: ${err.message}
7075
+ console.log(chalk27.red(` Export failed: ${err.message}
6775
7076
  `));
6776
7077
  }
6777
7078
  rl.prompt();
@@ -6781,10 +7082,10 @@ Analyze the above input.`;
6781
7082
  try {
6782
7083
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
6783
7084
  sessionId = id;
6784
- console.log(chalk26.green(` Session saved: ${id}
7085
+ console.log(chalk27.green(` Session saved: ${id}
6785
7086
  `));
6786
7087
  } catch (err) {
6787
- console.log(chalk26.red(` Save failed: ${err.message}
7088
+ console.log(chalk27.red(` Save failed: ${err.message}
6788
7089
  `));
6789
7090
  }
6790
7091
  rl.prompt();
@@ -6794,16 +7095,16 @@ Analyze the above input.`;
6794
7095
  try {
6795
7096
  const sessions = await listSessions(config.projectRoot);
6796
7097
  if (sessions.length === 0) {
6797
- console.log(chalk26.gray(" No saved sessions.\n"));
7098
+ console.log(chalk27.gray(" No saved sessions.\n"));
6798
7099
  } else {
6799
- console.log(chalk26.gray("\n Saved sessions:\n"));
7100
+ console.log(chalk27.gray("\n Saved sessions:\n"));
6800
7101
  for (const s of sessions.slice(0, 10)) {
6801
- console.log(chalk26.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
7102
+ console.log(chalk27.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
6802
7103
  }
6803
7104
  console.log("");
6804
7105
  }
6805
7106
  } catch (err) {
6806
- console.log(chalk26.red(` Error listing sessions: ${err.message}
7107
+ console.log(chalk27.red(` Error listing sessions: ${err.message}
6807
7108
  `));
6808
7109
  }
6809
7110
  rl.prompt();
@@ -6821,18 +7122,18 @@ Analyze the above input.`;
6821
7122
  const savedPlan = await loadPlan(config.projectRoot);
6822
7123
  if (savedPlan) {
6823
7124
  ralphPlan = savedPlan;
6824
- console.log(chalk26.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
7125
+ console.log(chalk27.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
6825
7126
  `));
6826
7127
  }
6827
7128
  } catch {
6828
7129
  }
6829
- console.log(chalk26.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
7130
+ console.log(chalk27.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
6830
7131
  `));
6831
7132
  } else {
6832
- console.log(chalk26.gray(" No session found.\n"));
7133
+ console.log(chalk27.gray(" No session found.\n"));
6833
7134
  }
6834
7135
  } catch (err) {
6835
- console.log(chalk26.red(` Resume failed: ${err.message}
7136
+ console.log(chalk27.red(` Resume failed: ${err.message}
6836
7137
  `));
6837
7138
  }
6838
7139
  rl.prompt();
@@ -6841,7 +7142,7 @@ Analyze the above input.`;
6841
7142
  if (input.startsWith("/search ")) {
6842
7143
  const query = input.replace("/search ", "").trim().toLowerCase();
6843
7144
  if (!query) {
6844
- console.log(chalk26.gray(" Usage: /search <query>\n"));
7145
+ console.log(chalk27.gray(" Usage: /search <query>\n"));
6845
7146
  rl.prompt();
6846
7147
  return;
6847
7148
  }
@@ -6858,14 +7159,14 @@ Analyze the above input.`;
6858
7159
  }
6859
7160
  }
6860
7161
  if (matches.length === 0) {
6861
- console.log(chalk26.gray(` No matches for "${query}"
7162
+ console.log(chalk27.gray(` No matches for "${query}"
6862
7163
  `));
6863
7164
  } else {
6864
- console.log(chalk26.gray(`
7165
+ console.log(chalk27.gray(`
6865
7166
  ${matches.length} match(es) for "${query}":
6866
7167
  `));
6867
7168
  for (const m of matches.slice(0, 10)) {
6868
- console.log(chalk26.gray(` [${m.index}] ${m.role}: ${m.preview}`));
7169
+ console.log(chalk27.gray(` [${m.index}] ${m.role}: ${m.preview}`));
6869
7170
  }
6870
7171
  console.log("");
6871
7172
  }
@@ -6879,7 +7180,7 @@ Analyze the above input.`;
6879
7180
  activePlan = await generatePlan(task, model, systemPrompt);
6880
7181
  planSpinner.succeed("Plan generated");
6881
7182
  console.log(formatPlan(activePlan));
6882
- console.log(chalk26.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
7183
+ console.log(chalk27.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
6883
7184
  } catch (err) {
6884
7185
  planSpinner.fail(`Plan failed: ${err.message}`);
6885
7186
  }
@@ -6888,11 +7189,11 @@ Analyze the above input.`;
6888
7189
  }
6889
7190
  if (input === "/plan approve") {
6890
7191
  if (!activePlan) {
6891
- console.log(chalk26.gray(" No active plan. Use /plan <task> to create one.\n"));
7192
+ console.log(chalk27.gray(" No active plan. Use /plan <task> to create one.\n"));
6892
7193
  rl.prompt();
6893
7194
  return;
6894
7195
  }
6895
- console.log(chalk26.green(" Executing plan...\n"));
7196
+ console.log(chalk27.green(" Executing plan...\n"));
6896
7197
  while (!isPlanComplete(activePlan)) {
6897
7198
  const stepPrompt = currentStepPrompt(activePlan);
6898
7199
  messages.push({ role: "user", content: stepPrompt });
@@ -6910,10 +7211,10 @@ Analyze the above input.`;
6910
7211
  },
6911
7212
  onToolCall: (name) => {
6912
7213
  if (planStepSpinner.isSpinning) planStepSpinner.stop();
6913
- console.log(chalk26.gray(` \u2192 ${name}`));
7214
+ console.log(chalk27.gray(` \u2192 ${name}`));
6914
7215
  },
6915
7216
  onToolResult: (_name, _result, isError) => {
6916
- console.log(isError ? chalk26.red(" \u2717") : chalk26.green(" \u2713"));
7217
+ console.log(isError ? chalk27.red(" \u2717") : chalk27.green(" \u2713"));
6917
7218
  }
6918
7219
  });
6919
7220
  console.log("\n");
@@ -6926,7 +7227,7 @@ Analyze the above input.`;
6926
7227
  }
6927
7228
  }
6928
7229
  if (activePlan && isPlanComplete(activePlan)) {
6929
- console.log(chalk26.green(" Plan completed!\n"));
7230
+ console.log(chalk27.green(" Plan completed!\n"));
6930
7231
  }
6931
7232
  activePlan = null;
6932
7233
  rl.prompt();
@@ -6934,26 +7235,26 @@ Analyze the above input.`;
6934
7235
  }
6935
7236
  if (input === "/plan edit") {
6936
7237
  if (!activePlan) {
6937
- console.log(chalk26.gray(" No active plan to edit.\n"));
7238
+ console.log(chalk27.gray(" No active plan to edit.\n"));
6938
7239
  rl.prompt();
6939
7240
  return;
6940
7241
  }
6941
7242
  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"));
7243
+ console.log(chalk27.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
7244
+ console.log(chalk27.gray(" or /plan approve to proceed with the current plan.\n"));
6944
7245
  rl.prompt();
6945
7246
  return;
6946
7247
  }
6947
7248
  if (input === "/plan cancel") {
6948
7249
  activePlan = null;
6949
- console.log(chalk26.gray(" Plan discarded.\n"));
7250
+ console.log(chalk27.gray(" Plan discarded.\n"));
6950
7251
  rl.prompt();
6951
7252
  return;
6952
7253
  }
6953
7254
  if (input.startsWith("/agent ")) {
6954
7255
  const task = input.replace("/agent ", "").trim();
6955
7256
  const agentId = nextSubagentId();
6956
- console.log(chalk26.cyan(` Spawning subagent #${agentId}: ${task}
7257
+ console.log(chalk27.cyan(` Spawning subagent #${agentId}: ${task}
6957
7258
  `));
6958
7259
  spawnSubagent({
6959
7260
  id: agentId,
@@ -6963,14 +7264,14 @@ Analyze the above input.`;
6963
7264
  toolContext: toolCtx,
6964
7265
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
6965
7266
  onComplete: (result) => {
6966
- console.log(chalk26.green(`
7267
+ console.log(chalk27.green(`
6967
7268
  Subagent #${agentId} finished:`));
6968
- console.log(chalk26.gray(` ${result.slice(0, 200)}
7269
+ console.log(chalk27.gray(` ${result.slice(0, 200)}
6969
7270
  `));
6970
7271
  rl.prompt();
6971
7272
  },
6972
7273
  onError: (err) => {
6973
- console.log(chalk26.red(`
7274
+ console.log(chalk27.red(`
6974
7275
  Subagent #${agentId} failed: ${err}
6975
7276
  `));
6976
7277
  rl.prompt();
@@ -6983,18 +7284,18 @@ Analyze the above input.`;
6983
7284
  try {
6984
7285
  const memories = await loadMemories(config.projectRoot);
6985
7286
  if (memories.length === 0) {
6986
- console.log(chalk26.gray(" No saved memories.\n"));
7287
+ console.log(chalk27.gray(" No saved memories.\n"));
6987
7288
  } else {
6988
- console.log(chalk26.gray(`
7289
+ console.log(chalk27.gray(`
6989
7290
  ${memories.length} saved memories:
6990
7291
  `));
6991
7292
  for (const m of memories) {
6992
- console.log(chalk26.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
7293
+ console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
6993
7294
  }
6994
7295
  console.log("");
6995
7296
  }
6996
7297
  } catch (err) {
6997
- console.log(chalk26.red(` Error: ${err.message}
7298
+ console.log(chalk27.red(` Error: ${err.message}
6998
7299
  `));
6999
7300
  }
7000
7301
  rl.prompt();
@@ -7005,16 +7306,16 @@ Analyze the above input.`;
7005
7306
  try {
7006
7307
  const results = await searchMemories(config.projectRoot, query);
7007
7308
  if (results.length === 0) {
7008
- console.log(chalk26.gray(` No memories matching "${query}"
7309
+ console.log(chalk27.gray(` No memories matching "${query}"
7009
7310
  `));
7010
7311
  } else {
7011
7312
  for (const m of results) {
7012
- console.log(chalk26.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
7313
+ console.log(chalk27.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
7013
7314
  }
7014
7315
  console.log("");
7015
7316
  }
7016
7317
  } catch (err) {
7017
- console.log(chalk26.red(` Error: ${err.message}
7318
+ console.log(chalk27.red(` Error: ${err.message}
7018
7319
  `));
7019
7320
  }
7020
7321
  rl.prompt();
@@ -7026,10 +7327,10 @@ Analyze the above input.`;
7026
7327
  for (const m of memories) {
7027
7328
  await deleteMemory(config.projectRoot, m.id);
7028
7329
  }
7029
- console.log(chalk26.yellow(` Cleared ${memories.length} memories.
7330
+ console.log(chalk27.yellow(` Cleared ${memories.length} memories.
7030
7331
  `));
7031
7332
  } catch (err) {
7032
- console.log(chalk26.red(` Error: ${err.message}
7333
+ console.log(chalk27.red(` Error: ${err.message}
7033
7334
  `));
7034
7335
  }
7035
7336
  rl.prompt();
@@ -7057,27 +7358,27 @@ Analyze the above input.`;
7057
7358
  }
7058
7359
  if (input === "/ralph run") {
7059
7360
  if (!ralphPlan) {
7060
- console.log(chalk26.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
7361
+ console.log(chalk27.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
7061
7362
  rl.prompt();
7062
7363
  return;
7063
7364
  }
7064
- console.log(chalk26.green(" Ralph is running...\n"));
7365
+ console.log(chalk27.green(" Ralph is running...\n"));
7065
7366
  try {
7066
7367
  ralphPlan = await runRalphLoop(ralphPlan, {
7067
7368
  model,
7068
7369
  systemPrompt,
7069
7370
  toolContext: toolCtx,
7070
7371
  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}
7372
+ onTaskStart: (task) => console.log(chalk27.cyan(` \u25B6 Task: ${task.description}`)),
7373
+ onTaskComplete: (task) => console.log(chalk27.green(` \u2713 Done: ${task.description}
7073
7374
  `)),
7074
- onTaskFail: (task, err) => console.log(chalk26.red(` \u2717 Failed: ${task.description} (${err})
7375
+ onTaskFail: (task, err) => console.log(chalk27.red(` \u2717 Failed: ${task.description} (${err})
7075
7376
  `))
7076
7377
  });
7077
7378
  await savePlan(config.projectRoot, ralphPlan);
7078
7379
  console.log(formatRalphStatus(ralphPlan));
7079
7380
  } catch (err) {
7080
- console.log(chalk26.red(` Ralph error: ${err.message}
7381
+ console.log(chalk27.red(` Ralph error: ${err.message}
7081
7382
  `));
7082
7383
  }
7083
7384
  rl.prompt();
@@ -7085,7 +7386,7 @@ Analyze the above input.`;
7085
7386
  }
7086
7387
  if (input === "/ralph status") {
7087
7388
  if (!ralphPlan) {
7088
- console.log(chalk26.gray(" No Ralph plan active.\n"));
7389
+ console.log(chalk27.gray(" No Ralph plan active.\n"));
7089
7390
  } else {
7090
7391
  console.log(formatRalphStatus(ralphPlan));
7091
7392
  }
@@ -7096,26 +7397,26 @@ Analyze the above input.`;
7096
7397
  ralphPlan = null;
7097
7398
  await deletePlan(config.projectRoot).catch(() => {
7098
7399
  });
7099
- console.log(chalk26.gray(" Ralph plan cleared.\n"));
7400
+ console.log(chalk27.gray(" Ralph plan cleared.\n"));
7100
7401
  rl.prompt();
7101
7402
  return;
7102
7403
  }
7103
7404
  if (input === "/branch") {
7104
7405
  const branchId = `branch-${branches.size + 1}`;
7105
7406
  branches.set(branchId, [...messages]);
7106
- console.log(chalk26.green(` Forked conversation as "${branchId}" (${messages.length} messages)
7407
+ console.log(chalk27.green(` Forked conversation as "${branchId}" (${messages.length} messages)
7107
7408
  `));
7108
7409
  rl.prompt();
7109
7410
  return;
7110
7411
  }
7111
7412
  if (input === "/branches") {
7112
7413
  if (branches.size === 0) {
7113
- console.log(chalk26.gray(" No conversation branches. Use /branch to fork.\n"));
7414
+ console.log(chalk27.gray(" No conversation branches. Use /branch to fork.\n"));
7114
7415
  } else {
7115
- console.log(chalk26.gray("\n Branches:\n"));
7416
+ console.log(chalk27.gray("\n Branches:\n"));
7116
7417
  for (const [name, msgs] of branches) {
7117
- const marker = name === currentBranch ? chalk26.green(" \u25CF") : " ";
7118
- console.log(chalk26.gray(` ${marker} ${name} (${msgs.length} messages)`));
7418
+ const marker = name === currentBranch ? chalk27.green(" \u25CF") : " ";
7419
+ console.log(chalk27.gray(` ${marker} ${name} (${msgs.length} messages)`));
7119
7420
  }
7120
7421
  console.log("");
7121
7422
  }
@@ -7157,7 +7458,7 @@ Analyze the above input.`;
7157
7458
  modelId: activeModelId,
7158
7459
  messages,
7159
7460
  lastResponse: lastAssistantResponse,
7160
- log: (msg) => console.log(chalk26.gray(` ${msg}`)),
7461
+ log: (msg) => console.log(chalk27.gray(` ${msg}`)),
7161
7462
  runPrompt: async (prompt, msgs) => {
7162
7463
  const spinner2 = ora4("Thinking...").start();
7163
7464
  const response = await withRetry(
@@ -7174,13 +7475,13 @@ Analyze the above input.`;
7174
7475
  onToolCall: (name, args) => {
7175
7476
  if (spinner2.isSpinning) spinner2.stop();
7176
7477
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
7177
- console.log(chalk26.gray(`
7478
+ console.log(chalk27.gray(`
7178
7479
  \u2192 ${name}(${argSummary})`));
7179
7480
  },
7180
7481
  onToolResult: (_name, result, isError) => {
7181
7482
  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 ? "..." : ""}`));
7483
+ const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
7484
+ console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7184
7485
  }
7185
7486
  })
7186
7487
  );
@@ -7194,8 +7495,8 @@ Analyze the above input.`;
7194
7495
  rl.prompt();
7195
7496
  return;
7196
7497
  }
7197
- console.log(chalk26.red(` Unknown command: ${input}`));
7198
- console.log(chalk26.gray(" Type /help for available commands.\n"));
7498
+ console.log(chalk27.red(` Unknown command: ${input}`));
7499
+ console.log(chalk27.gray(" Type /help for available commands.\n"));
7199
7500
  rl.prompt();
7200
7501
  return;
7201
7502
  }
@@ -7203,7 +7504,7 @@ Analyze the above input.`;
7203
7504
  const refContext = formatReferences(references);
7204
7505
  const finalPrompt = refContext + cleanInput;
7205
7506
  if (references.length > 0) {
7206
- console.log(chalk26.gray(` Injected ${references.length} reference(s)`));
7507
+ console.log(chalk27.gray(` Injected ${references.length} reference(s)`));
7207
7508
  }
7208
7509
  messages.push({ role: "user", content: finalPrompt });
7209
7510
  const spinner = ora4("Thinking...").start();
@@ -7229,16 +7530,16 @@ Analyze the above input.`;
7229
7530
  const val = String(v);
7230
7531
  return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
7231
7532
  }).join(", ");
7232
- console.log(chalk26.gray(`
7533
+ console.log(chalk27.gray(`
7233
7534
  \u2192 ${name}(${argSummary})`));
7234
7535
  },
7235
7536
  onToolResult: (_name, result, isError) => {
7236
7537
  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 ? "..." : ""}`));
7538
+ const icon = isError ? chalk27.red("\u2717") : chalk27.green("\u2713");
7539
+ console.log(chalk27.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
7239
7540
  },
7240
7541
  onCompress: () => {
7241
- console.log(chalk26.yellow("\n [Context compressed to fit window]\n"));
7542
+ console.log(chalk27.yellow("\n [Context compressed to fit window]\n"));
7242
7543
  }
7243
7544
  })
7244
7545
  );
@@ -7272,7 +7573,7 @@ Analyze the above input.`;
7272
7573
  type: "auto",
7273
7574
  content: memMatch[1]
7274
7575
  });
7275
- console.log(chalk26.gray(" (Saved to memory)\n"));
7576
+ console.log(chalk27.gray(" (Saved to memory)\n"));
7276
7577
  } catch {
7277
7578
  }
7278
7579
  }
@@ -7282,13 +7583,13 @@ Analyze the above input.`;
7282
7583
  checkpoints.discard();
7283
7584
  const msg = err.message?.toLowerCase() ?? "";
7284
7585
  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"));
7586
+ console.log(chalk27.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
7286
7587
  } 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"));
7588
+ console.log(chalk27.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
7288
7589
  } else if (msg.includes("429") || msg.includes("rate limit")) {
7289
- console.log(chalk26.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
7590
+ console.log(chalk27.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
7290
7591
  } else {
7291
- console.log(chalk26.gray(" (The conversation history is preserved. Try again.)\n"));
7592
+ console.log(chalk27.gray(" (The conversation history is preserved. Try again.)\n"));
7292
7593
  }
7293
7594
  }
7294
7595
  rl.prompt();
@@ -7306,13 +7607,13 @@ async function handleRalphSubcommand(args, cliOpts) {
7306
7607
  cwd: config.projectRoot,
7307
7608
  requireConfirm: false,
7308
7609
  confirm: async () => true,
7309
- log: (msg) => console.log(chalk26.gray(` ${msg}`))
7610
+ log: (msg) => console.log(chalk27.gray(` ${msg}`))
7310
7611
  };
7311
7612
  const subcommand = args[0];
7312
7613
  if (subcommand === "plan") {
7313
7614
  const goal = args.slice(1).join(" ");
7314
7615
  if (!goal) {
7315
- console.error(chalk26.red(" Usage: notch ralph plan <goal>"));
7616
+ console.error(chalk27.red(" Usage: notch ralph plan <goal>"));
7316
7617
  process.exit(1);
7317
7618
  }
7318
7619
  const spinner = ora4("Ralph is planning...").start();
@@ -7323,7 +7624,7 @@ async function handleRalphSubcommand(args, cliOpts) {
7323
7624
  } else if (subcommand === "run") {
7324
7625
  let plan = await loadPlan(config.projectRoot);
7325
7626
  if (!plan) {
7326
- console.error(chalk26.red(" No plan found. Run: notch ralph plan <goal>"));
7627
+ console.error(chalk27.red(" No plan found. Run: notch ralph plan <goal>"));
7327
7628
  process.exit(1);
7328
7629
  }
7329
7630
  plan = await runRalphLoop(plan, {
@@ -7331,27 +7632,27 @@ async function handleRalphSubcommand(args, cliOpts) {
7331
7632
  systemPrompt,
7332
7633
  toolContext: toolCtx,
7333
7634
  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}`))
7635
+ onTaskStart: (t) => console.log(chalk27.cyan(` \u25B6 ${t.description}`)),
7636
+ onTaskComplete: (t) => console.log(chalk27.green(` \u2713 ${t.description}`)),
7637
+ onTaskFail: (t, e) => console.log(chalk27.red(` \u2717 ${t.description}: ${e}`))
7337
7638
  });
7338
7639
  await savePlan(config.projectRoot, plan);
7339
7640
  console.log(formatRalphStatus(plan));
7340
7641
  } else if (subcommand === "status") {
7341
7642
  const plan = await loadPlan(config.projectRoot);
7342
7643
  if (!plan) {
7343
- console.log(chalk26.gray(" No Ralph plan found."));
7644
+ console.log(chalk27.gray(" No Ralph plan found."));
7344
7645
  } else {
7345
7646
  console.log(formatRalphStatus(plan));
7346
7647
  }
7347
7648
  } else {
7348
- console.error(chalk26.red(` Unknown: notch ralph ${subcommand}`));
7349
- console.error(chalk26.gray(" Usage: notch ralph <plan|run|status>"));
7649
+ console.error(chalk27.red(` Unknown: notch ralph ${subcommand}`));
7650
+ console.error(chalk27.gray(" Usage: notch ralph <plan|run|status>"));
7350
7651
  process.exit(1);
7351
7652
  }
7352
7653
  }
7353
7654
  main().catch((err) => {
7354
- console.error(chalk26.red(`
7655
+ console.error(chalk27.red(`
7355
7656
  Fatal: ${err.message}
7356
7657
  `));
7357
7658
  process.exit(1);