@freesyntax/notch-cli 0.4.1 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  clearCredentials,
4
4
  loadCredentials,
5
5
  login
6
- } from "./chunk-TJS4W4R5.js";
6
+ } from "./chunk-FIFC4V2R.js";
7
7
  import {
8
8
  autoCompress,
9
9
  estimateTokens
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  // src/index.ts
13
13
  import { Command } from "commander";
14
- import chalk8 from "chalk";
14
+ import chalk9 from "chalk";
15
15
  import ora from "ora";
16
16
  import * as readline from "readline";
17
17
  import * as nodePath from "path";
@@ -22,6 +22,12 @@ import path from "path";
22
22
 
23
23
  // src/providers/registry.ts
24
24
  import { createOpenAI } from "@ai-sdk/openai";
25
+ var MissingApiKeyError = class extends Error {
26
+ constructor() {
27
+ super("NOTCH_API_KEY is not set");
28
+ this.name = "MissingApiKeyError";
29
+ }
30
+ };
25
31
  var MODEL_CATALOG = {
26
32
  "notch-cinder": {
27
33
  id: "notch-cinder",
@@ -74,9 +80,7 @@ function resolveModel(config) {
74
80
  const baseUrl = config.baseUrl ?? process.env.NOTCH_BASE_URL ?? info.baseUrl;
75
81
  const apiKey = config.apiKey ?? process.env.NOTCH_API_KEY;
76
82
  if (!apiKey) {
77
- throw new Error(
78
- "NOTCH_API_KEY is not set. Set it via the NOTCH_API_KEY environment variable or --api-key flag."
79
- );
83
+ throw new MissingApiKeyError();
80
84
  }
81
85
  const provider = createOpenAI({
82
86
  apiKey,
@@ -826,20 +830,373 @@ ${text}` };
826
830
  }
827
831
  };
828
832
 
833
+ // src/tools/github.ts
834
+ import { z as z9 } from "zod";
835
+ var GITHUB_API = "https://api.github.com";
836
+ function getToken() {
837
+ return process.env.GITHUB_TOKEN || process.env.GH_TOKEN || null;
838
+ }
839
+ async function ghFetch(path19, opts2 = {}) {
840
+ const token = getToken();
841
+ const headers = {
842
+ "Accept": "application/vnd.github+json",
843
+ "X-GitHub-Api-Version": "2022-11-28",
844
+ ...opts2.headers ?? {}
845
+ };
846
+ if (token) headers["Authorization"] = `Bearer ${token}`;
847
+ return fetch(`${GITHUB_API}${path19}`, { ...opts2, headers });
848
+ }
849
+ var parameters9 = z9.object({
850
+ action: z9.enum([
851
+ "create_pr",
852
+ "list_prs",
853
+ "list_issues",
854
+ "repo_info",
855
+ "create_issue",
856
+ "pr_status"
857
+ ]).describe("The GitHub operation to perform"),
858
+ owner: z9.string().describe("Repository owner (user or org)"),
859
+ repo: z9.string().describe("Repository name"),
860
+ // PR fields
861
+ title: z9.string().optional().describe("PR or issue title"),
862
+ body: z9.string().optional().describe("PR or issue body/description"),
863
+ head: z9.string().optional().describe("PR source branch"),
864
+ base: z9.string().optional().describe("PR target branch (default: main)"),
865
+ // Issue fields
866
+ labels: z9.array(z9.string()).optional().describe("Labels for issue"),
867
+ // Filter
868
+ state: z9.enum(["open", "closed", "all"]).optional().describe("Filter by state"),
869
+ pr_number: z9.number().optional().describe("PR number for status check")
870
+ });
871
+ async function execute(args, _ctx) {
872
+ const token = getToken();
873
+ if (!token) {
874
+ return {
875
+ content: "No GitHub token found. Set GITHUB_TOKEN or GH_TOKEN environment variable.\nYou can create a token at https://github.com/settings/tokens",
876
+ isError: true
877
+ };
878
+ }
879
+ try {
880
+ switch (args.action) {
881
+ case "repo_info": {
882
+ const res = await ghFetch(`/repos/${args.owner}/${args.repo}`);
883
+ if (!res.ok) return { content: `GitHub API error: ${res.status} ${await res.text()}`, isError: true };
884
+ const repo = await res.json();
885
+ return {
886
+ content: [
887
+ `Repository: ${repo.full_name}`,
888
+ `Description: ${repo.description || "None"}`,
889
+ `Stars: ${repo.stargazers_count} | Forks: ${repo.forks_count} | Open issues: ${repo.open_issues_count}`,
890
+ `Default branch: ${repo.default_branch}`,
891
+ `Language: ${repo.language || "Unknown"}`,
892
+ `Private: ${repo.private}`,
893
+ `URL: ${repo.html_url}`
894
+ ].join("\n")
895
+ };
896
+ }
897
+ case "list_prs": {
898
+ const state = args.state ?? "open";
899
+ const res = await ghFetch(`/repos/${args.owner}/${args.repo}/pulls?state=${state}&per_page=15`);
900
+ if (!res.ok) return { content: `GitHub API error: ${res.status}`, isError: true };
901
+ const prs = await res.json();
902
+ if (prs.length === 0) return { content: `No ${state} pull requests.` };
903
+ const lines = prs.map(
904
+ (pr) => `#${pr.number} [${pr.state}] ${pr.title} (${pr.head.ref} \u2192 ${pr.base.ref}) by @${pr.user.login}`
905
+ );
906
+ return { content: lines.join("\n") };
907
+ }
908
+ case "list_issues": {
909
+ const state = args.state ?? "open";
910
+ const res = await ghFetch(`/repos/${args.owner}/${args.repo}/issues?state=${state}&per_page=15`);
911
+ if (!res.ok) return { content: `GitHub API error: ${res.status}`, isError: true };
912
+ const issues = await res.json();
913
+ const realIssues = issues.filter((i) => !i.pull_request);
914
+ if (realIssues.length === 0) return { content: `No ${state} issues.` };
915
+ const lines = realIssues.map((i) => {
916
+ const labels = i.labels.map((l) => l.name).join(", ");
917
+ return `#${i.number} [${i.state}] ${i.title}${labels ? ` [${labels}]` : ""} by @${i.user.login}`;
918
+ });
919
+ return { content: lines.join("\n") };
920
+ }
921
+ case "create_pr": {
922
+ if (!args.title || !args.head) {
923
+ return { content: "title and head branch are required for create_pr", isError: true };
924
+ }
925
+ const res = await ghFetch(`/repos/${args.owner}/${args.repo}/pulls`, {
926
+ method: "POST",
927
+ body: JSON.stringify({
928
+ title: args.title,
929
+ body: args.body ?? "",
930
+ head: args.head,
931
+ base: args.base ?? "main"
932
+ })
933
+ });
934
+ if (!res.ok) {
935
+ const err = await res.json();
936
+ return { content: `Failed to create PR: ${err.message || res.status}`, isError: true };
937
+ }
938
+ const pr = await res.json();
939
+ return { content: `PR #${pr.number} created: ${pr.html_url}` };
940
+ }
941
+ case "create_issue": {
942
+ if (!args.title) {
943
+ return { content: "title is required for create_issue", isError: true };
944
+ }
945
+ const res = await ghFetch(`/repos/${args.owner}/${args.repo}/issues`, {
946
+ method: "POST",
947
+ body: JSON.stringify({
948
+ title: args.title,
949
+ body: args.body ?? "",
950
+ labels: args.labels ?? []
951
+ })
952
+ });
953
+ if (!res.ok) {
954
+ const err = await res.json();
955
+ return { content: `Failed to create issue: ${err.message || res.status}`, isError: true };
956
+ }
957
+ const issue = await res.json();
958
+ return { content: `Issue #${issue.number} created: ${issue.html_url}` };
959
+ }
960
+ case "pr_status": {
961
+ if (!args.pr_number) {
962
+ return { content: "pr_number is required for pr_status", isError: true };
963
+ }
964
+ const res = await ghFetch(`/repos/${args.owner}/${args.repo}/pulls/${args.pr_number}`);
965
+ if (!res.ok) return { content: `GitHub API error: ${res.status}`, isError: true };
966
+ const pr = await res.json();
967
+ const checksRes = await ghFetch(`/repos/${args.owner}/${args.repo}/commits/${pr.head.sha}/check-runs`);
968
+ let checksInfo = "";
969
+ if (checksRes.ok) {
970
+ const checks = await checksRes.json();
971
+ if (checks.check_runs?.length) {
972
+ checksInfo = "\nChecks:\n" + checks.check_runs.map(
973
+ (c) => ` ${c.status === "completed" ? c.conclusion === "success" ? "\u2713" : "\u2717" : "\u25CC"} ${c.name}: ${c.conclusion ?? c.status}`
974
+ ).join("\n");
975
+ }
976
+ }
977
+ return {
978
+ content: [
979
+ `PR #${pr.number}: ${pr.title}`,
980
+ `State: ${pr.state} | Mergeable: ${pr.mergeable ?? "checking..."}`,
981
+ `${pr.head.ref} \u2192 ${pr.base.ref}`,
982
+ `Author: @${pr.user.login}`,
983
+ `+${pr.additions} -${pr.deletions} (${pr.changed_files} files)`,
984
+ `Reviews: ${pr.requested_reviewers?.length ?? 0} requested`,
985
+ checksInfo,
986
+ `URL: ${pr.html_url}`
987
+ ].join("\n")
988
+ };
989
+ }
990
+ default:
991
+ return { content: `Unknown action: ${args.action}`, isError: true };
992
+ }
993
+ } catch (err) {
994
+ return { content: `GitHub error: ${err.message}`, isError: true };
995
+ }
996
+ }
997
+ var githubTool = {
998
+ name: "github",
999
+ description: "GitHub operations \u2014 create PRs, list issues, check PR status, view repo info. Requires GITHUB_TOKEN env var.",
1000
+ parameters: parameters9,
1001
+ execute
1002
+ };
1003
+
1004
+ // src/mcp/client.ts
1005
+ import { spawn } from "child_process";
1006
+ import { z as z10 } from "zod";
1007
+ var MCPClient = class {
1008
+ constructor(config, serverName) {
1009
+ this.config = config;
1010
+ this.serverName = serverName;
1011
+ }
1012
+ process = null;
1013
+ requestId = 0;
1014
+ pendingRequests = /* @__PURE__ */ new Map();
1015
+ buffer = "";
1016
+ serverName;
1017
+ _tools = [];
1018
+ /**
1019
+ * Start the MCP server and initialize the connection.
1020
+ */
1021
+ async connect() {
1022
+ this.process = spawn(this.config.command, this.config.args ?? [], {
1023
+ stdio: ["pipe", "pipe", "pipe"],
1024
+ env: { ...process.env, ...this.config.env },
1025
+ cwd: this.config.cwd
1026
+ });
1027
+ this.process.stdout?.setEncoding("utf-8");
1028
+ this.process.stdout?.on("data", (data) => {
1029
+ this.buffer += data;
1030
+ this.processBuffer();
1031
+ });
1032
+ this.process.on("error", (err) => {
1033
+ for (const [id, pending] of this.pendingRequests) {
1034
+ pending.reject(new Error(`MCP server ${this.serverName} error: ${err.message}`));
1035
+ this.pendingRequests.delete(id);
1036
+ }
1037
+ });
1038
+ this.process.on("exit", (code) => {
1039
+ for (const [id, pending] of this.pendingRequests) {
1040
+ pending.reject(new Error(`MCP server ${this.serverName} exited with code ${code}`));
1041
+ this.pendingRequests.delete(id);
1042
+ }
1043
+ });
1044
+ await this.sendRequest("initialize", {
1045
+ protocolVersion: "2024-11-05",
1046
+ capabilities: {},
1047
+ clientInfo: { name: "notch-cli", version: "0.3.0" }
1048
+ });
1049
+ this.sendNotification("notifications/initialized", {});
1050
+ const result = await this.sendRequest("tools/list", {});
1051
+ this._tools = result.tools ?? [];
1052
+ }
1053
+ /**
1054
+ * Get discovered tools from this server.
1055
+ */
1056
+ get tools() {
1057
+ return this._tools;
1058
+ }
1059
+ /**
1060
+ * Check if the MCP server process is still alive.
1061
+ */
1062
+ get isAlive() {
1063
+ return this.process !== null && this.process.exitCode === null && !this.process.killed;
1064
+ }
1065
+ /**
1066
+ * Call a tool on the MCP server. Auto-reconnects if the server has crashed.
1067
+ */
1068
+ async callTool(name, args) {
1069
+ if (!this.isAlive) {
1070
+ try {
1071
+ await this.connect();
1072
+ } catch (err) {
1073
+ throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
1074
+ }
1075
+ }
1076
+ const result = await this.sendRequest("tools/call", { name, arguments: args });
1077
+ return result;
1078
+ }
1079
+ /**
1080
+ * Disconnect from the MCP server.
1081
+ */
1082
+ disconnect() {
1083
+ if (this.process) {
1084
+ this.process.stdin?.end();
1085
+ this.process.kill();
1086
+ this.process = null;
1087
+ }
1088
+ this.pendingRequests.clear();
1089
+ }
1090
+ sendRequest(method, params) {
1091
+ return new Promise((resolve2, reject) => {
1092
+ const id = ++this.requestId;
1093
+ const msg = {
1094
+ jsonrpc: "2.0",
1095
+ id,
1096
+ method,
1097
+ params
1098
+ };
1099
+ this.pendingRequests.set(id, { resolve: resolve2, reject });
1100
+ const data = JSON.stringify(msg);
1101
+ const header = `Content-Length: ${Buffer.byteLength(data)}\r
1102
+ \r
1103
+ `;
1104
+ this.process?.stdin?.write(header + data);
1105
+ setTimeout(() => {
1106
+ if (this.pendingRequests.has(id)) {
1107
+ this.pendingRequests.delete(id);
1108
+ reject(new Error(`MCP request ${method} timed out`));
1109
+ }
1110
+ }, 3e4);
1111
+ });
1112
+ }
1113
+ sendNotification(method, params) {
1114
+ const msg = {
1115
+ jsonrpc: "2.0",
1116
+ method,
1117
+ params
1118
+ };
1119
+ const data = JSON.stringify(msg);
1120
+ const header = `Content-Length: ${Buffer.byteLength(data)}\r
1121
+ \r
1122
+ `;
1123
+ this.process?.stdin?.write(header + data);
1124
+ }
1125
+ processBuffer() {
1126
+ while (this.buffer.length > 0) {
1127
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
1128
+ if (headerEnd === -1) break;
1129
+ const header = this.buffer.slice(0, headerEnd);
1130
+ const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
1131
+ if (!lengthMatch) {
1132
+ const nlIdx = this.buffer.indexOf("\n");
1133
+ if (nlIdx === -1) break;
1134
+ const line = this.buffer.slice(0, nlIdx).trim();
1135
+ this.buffer = this.buffer.slice(nlIdx + 1);
1136
+ if (line) this.handleMessage(line);
1137
+ continue;
1138
+ }
1139
+ const contentLength = parseInt(lengthMatch[1], 10);
1140
+ const messageStart = headerEnd + 4;
1141
+ if (this.buffer.length < messageStart + contentLength) break;
1142
+ const body = this.buffer.slice(messageStart, messageStart + contentLength);
1143
+ this.buffer = this.buffer.slice(messageStart + contentLength);
1144
+ this.handleMessage(body);
1145
+ }
1146
+ }
1147
+ handleMessage(raw) {
1148
+ try {
1149
+ const msg = JSON.parse(raw);
1150
+ if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
1151
+ const pending = this.pendingRequests.get(msg.id);
1152
+ this.pendingRequests.delete(msg.id);
1153
+ if (msg.error) {
1154
+ pending.reject(new Error(`MCP error: ${msg.error.message}`));
1155
+ } else {
1156
+ pending.resolve(msg.result);
1157
+ }
1158
+ }
1159
+ } catch {
1160
+ }
1161
+ }
1162
+ };
1163
+ function parseMCPConfig(config) {
1164
+ const servers = config?.mcpServers;
1165
+ if (!servers || typeof servers !== "object") return {};
1166
+ const result = {};
1167
+ for (const [name, cfg] of Object.entries(servers)) {
1168
+ const c = cfg;
1169
+ if (c?.command) {
1170
+ result[name] = {
1171
+ command: c.command,
1172
+ args: c.args,
1173
+ env: c.env,
1174
+ cwd: c.cwd
1175
+ };
1176
+ }
1177
+ }
1178
+ return result;
1179
+ }
1180
+
829
1181
  // src/tools/index.ts
830
- var ALL_TOOLS = [
1182
+ var BUILTIN_TOOLS = [
831
1183
  readTool,
832
1184
  writeTool,
833
1185
  editTool,
834
1186
  shellTool,
835
1187
  gitTool,
1188
+ githubTool,
836
1189
  grepTool,
837
1190
  globTool,
838
1191
  webFetchTool
839
1192
  ];
1193
+ var mcpTools = [];
1194
+ function getAllTools() {
1195
+ return [...BUILTIN_TOOLS, ...mcpTools];
1196
+ }
840
1197
  function buildToolMap(ctx) {
841
1198
  const map = {};
842
- for (const t of ALL_TOOLS) {
1199
+ for (const t of getAllTools()) {
843
1200
  map[t.name] = tool({
844
1201
  description: t.description,
845
1202
  parameters: t.parameters,
@@ -884,7 +1241,7 @@ ${paramSummary}`
884
1241
  return map;
885
1242
  }
886
1243
  function describeTools() {
887
- return ALL_TOOLS.map(
1244
+ return getAllTools().map(
888
1245
  (t) => `- **${t.name}**: ${t.description}`
889
1246
  ).join("\n");
890
1247
  }
@@ -2307,36 +2664,49 @@ async function resolveGlob(pattern, cwd) {
2307
2664
  }
2308
2665
  }
2309
2666
 
2667
+ // src/ui/banner.ts
2668
+ import chalk6 from "chalk";
2669
+
2310
2670
  // src/ui/themes.ts
2311
2671
  import chalk5 from "chalk";
2312
2672
  var defaultTheme = {
2313
2673
  name: "Default",
2314
- description: "Classic Notch \u2014 green mantis, blue wordmark",
2315
- brand: chalk5.blueBright,
2316
- mascot: chalk5.greenBright,
2317
- mascotAccent: chalk5.yellowBright,
2318
- tagline: chalk5.cyan,
2319
- prompt: chalk5.blueBright,
2320
- border: chalk5.gray,
2321
- dim: chalk5.gray,
2322
- text: chalk5.white,
2323
- bold: chalk5.white.bold,
2674
+ description: "FreeSyntax \u2014 silver, white, monochrome",
2675
+ brand: chalk5.hex("#D4D4D4"),
2676
+ // silver (banner uses gradient override)
2677
+ mascot: chalk5.hex("#AAAAAA"),
2678
+ // medium gray mantis
2679
+ mascotAccent: chalk5.hex("#FFFFFF"),
2680
+ // white eyes
2681
+ tagline: chalk5.hex("#777777"),
2682
+ // muted gray
2683
+ prompt: chalk5.hex("#CCCCCC"),
2684
+ // silver prompt
2685
+ border: chalk5.hex("#444444"),
2686
+ // dark border
2687
+ dim: chalk5.hex("#666666"),
2688
+ // muted text
2689
+ text: chalk5.hex("#D4D4D4"),
2690
+ // silver body text
2691
+ bold: chalk5.hex("#FFFFFF").bold,
2692
+ // pure white emphasis
2324
2693
  success: chalk5.green,
2325
2694
  warning: chalk5.yellow,
2326
2695
  error: chalk5.red,
2327
- info: chalk5.cyan,
2328
- toolName: chalk5.gray,
2329
- toolArgs: chalk5.gray,
2330
- toolResult: chalk5.gray,
2696
+ info: chalk5.hex("#BBBBBB"),
2697
+ // light gray info
2698
+ toolName: chalk5.hex("#AAAAAA"),
2699
+ toolArgs: chalk5.hex("#777777"),
2700
+ toolResult: chalk5.hex("#555555"),
2331
2701
  diffAdd: chalk5.green,
2332
2702
  diffRemove: chalk5.red,
2333
- diffHeader: chalk5.cyan,
2334
- mdH1: chalk5.white.bold,
2335
- mdH2: chalk5.blueBright,
2336
- mdH3: chalk5.cyan,
2337
- mdCode: chalk5.cyan,
2338
- mdInlineCode: chalk5.bgGray.white,
2339
- mdLink: chalk5.blue.underline,
2703
+ diffHeader: chalk5.hex("#CCCCCC"),
2704
+ mdH1: chalk5.hex("#FFFFFF").bold,
2705
+ mdH2: chalk5.hex("#D4D4D4"),
2706
+ mdH3: chalk5.hex("#AAAAAA"),
2707
+ mdCode: chalk5.hex("#BBBBBB"),
2708
+ mdInlineCode: chalk5.hex("#CCCCCC"),
2709
+ mdLink: chalk5.hex("#D4D4D4").underline,
2340
2710
  meterLow: chalk5.green,
2341
2711
  meterMid: chalk5.yellow,
2342
2712
  meterHigh: chalk5.red
@@ -2786,59 +3156,104 @@ function formatThemeList(activeId) {
2786
3156
  }
2787
3157
 
2788
3158
  // src/ui/banner.ts
2789
- var MANTIS = [
2790
- " \u25C9\u2588\u2580\u2580\u2580\u2580\u2588\u25C9",
2791
- // head + eyes
2792
- " \u2580\u2588\u2584\u2584\u2588\u2580",
2793
- // jaw
2794
- " \u2590\u258C",
2795
- // thin neck
2796
- " \u2588\u2580 \u2590\u258C \u2580\u2588",
2797
- // forelegs out
2798
- " \u2580\u2588\u2584\u2588\u2588\u2584\u2588\u2580",
2799
- // forelegs folding (prayer)
2800
- " \u2590\u2588\u2588\u258C",
2801
- // thorax
2802
- " \u2590\u2588\u2588\u258C",
2803
- // abdomen
2804
- " \u2590\u2588\u2588\u258C",
2805
- // abdomen
2806
- " \u2584\u2580 \u2580\u2584",
2807
- // legs splay
2808
- " \u2580 \u2580"
2809
- // feet
3159
+ var NOTCH_LARGE = [
3160
+ "\u2588\u2588\u2584 \u2588 \u2584\u2588\u2588\u2580\u2580\u2588\u2588\u2584 \u2580\u2588\u2588\u2580\u2580\u2580\u2580 \u2584\u2588\u2588\u2580\u2580\u2588\u2588\u2584 \u2588\u2588 \u2588\u2588",
3161
+ "\u2588\u2580\u2588\u2588 \u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2580\u2588\u2588\u2580\u2588",
3162
+ "\u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588 \u2580 \u2588",
3163
+ "\u2588 \u2580\u2588\u2584\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588 \u2588",
3164
+ "\u2588 \u2580\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588 \u2588",
3165
+ "\u2588 \u2588\u2588 \u2580\u2588\u2588\u2584\u2584\u2588\u2588\u2580 \u2588\u2588 \u2580\u2588\u2588\u2584\u2584\u2588\u2588\u2580 \u2588 \u2588",
3166
+ "\u2580 \u2580 \u2580\u2580\u2580\u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580 \u2580"
2810
3167
  ];
2811
- var LOGO_INLINE = [
2812
- " \u2588\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2580\u2588\u2580 \u2584\u2580\u2580\u2584 \u2588 \u2588",
2813
- " \u2588 \u2580\u2584 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2580\u2580\u2588",
2814
- " \u2588 \u2580\u2588\u2588 \u2580\u2584\u2584\u2580 \u2588 \u2580\u2584\u2584\u2580 \u2588 \u2588"
3168
+ var ROW_COLORS = [
3169
+ "#666666",
3170
+ // dim silver
3171
+ "#888888",
3172
+ // medium gray
3173
+ "#AAAAAA",
3174
+ // silver
3175
+ "#CCCCCC",
3176
+ // light silver
3177
+ "#DDDDDD",
3178
+ // near white
3179
+ "#EEEEEE",
3180
+ // bright
3181
+ "#FFFFFF"
3182
+ // pure white
3183
+ ];
3184
+ function colorBannerLine(line, rowIndex) {
3185
+ const t = theme();
3186
+ if (t.name !== "Default") {
3187
+ return t.brand(line);
3188
+ }
3189
+ const color = ROW_COLORS[rowIndex] ?? "#CCCCCC";
3190
+ return chalk6.hex(color)(line);
3191
+ }
3192
+ var MANTIS = [
3193
+ " \u2571\u25C9\u25C9\u2572",
3194
+ // ╱◉◉╲ antennae + eyes
3195
+ " \u2588\u2588\u2588",
3196
+ // ███ head
3197
+ " \u2590\u2588\u258C",
3198
+ // ▐█▌ neck
3199
+ " \u2584\u2580\u2590\u2588\u258C\u2580\u2584",
3200
+ // ▄▀▐█▌▀▄ prayer arms
3201
+ " \u2590\u2588\u258C",
3202
+ // ▐█▌ abdomen
3203
+ " \u2580\u2580 \u2580\u2580"
3204
+ // ▀▀ ▀▀ feet
2815
3205
  ];
2816
3206
  function colorMantis(line) {
2817
3207
  const t = theme();
2818
- return line.replace(/\u25c9/g, t.mascotAccent("\u25C9")).replace(/[\u2588]/g, (ch) => t.mascot(ch)).replace(/[\u2584\u2580]/g, (ch) => t.mascot(ch)).replace(/\u2590/g, t.mascot("\u2590")).replace(/\u258c/g, t.mascot("\u258C"));
3208
+ return line.replace(/[\u2571\u2572]/g, (ch) => t.mascot(ch)).replace(/\u25c9/g, t.mascotAccent("\u25C9")).replace(/[\u2588]/g, (ch) => t.mascot(ch)).replace(/[\u2584\u2580]/g, (ch) => t.mascot(ch)).replace(/\u2590/g, t.mascot("\u2590")).replace(/\u258c/g, t.mascot("\u258C"));
2819
3209
  }
2820
3210
  function printBanner(version, modelLabel, modelId, modelSize, project) {
2821
3211
  const t = theme();
2822
3212
  const termWidth = process.stdout.columns || 80;
2823
3213
  console.log("");
2824
- for (const line of MANTIS) {
2825
- console.log(" " + colorMantis(line));
3214
+ for (let i = 0; i < NOTCH_LARGE.length; i++) {
3215
+ console.log(" " + colorBannerLine(NOTCH_LARGE[i], i));
2826
3216
  }
2827
3217
  console.log("");
2828
- for (const line of LOGO_INLINE) {
2829
- console.log(" " + t.brand(line));
2830
- }
2831
- console.log("");
2832
- const divWidth = Math.min(50, termWidth - 4);
3218
+ const mantisStr = MANTIS.map((l) => colorMantis(l));
3219
+ const divWidth = Math.min(54, termWidth - 4);
2833
3220
  const divider = t.border(" " + "\u2500".repeat(divWidth));
2834
3221
  console.log(divider);
2835
- console.log(
2836
- t.dim(" ") + t.bold(modelLabel) + t.dim(" \u2502 v") + t.text(version) + t.dim(" \u2502 ") + t.dim("by ") + t.tagline("Driftrail")
2837
- );
2838
- console.log(t.dim(` ${project}`));
3222
+ const info = [
3223
+ t.dim(" ") + t.bold(modelLabel) + t.dim(" \u2502 v") + t.text(version),
3224
+ t.dim(" ") + t.dim(project),
3225
+ t.dim(" ") + t.dim("by ") + t.tagline("Driftrail")
3226
+ ];
3227
+ for (let i = 0; i < Math.max(mantisStr.length, info.length); i++) {
3228
+ const left = i < mantisStr.length ? " " + mantisStr[i] : "";
3229
+ const right = i < info.length ? info[i] : "";
3230
+ if (left && right) {
3231
+ const rawLeft = " " + MANTIS[i];
3232
+ const pad = Math.max(0, 16 - rawLeft.length);
3233
+ console.log(left + " ".repeat(pad) + right);
3234
+ } else if (left) {
3235
+ console.log(left);
3236
+ } else {
3237
+ console.log(" " + " ".repeat(14) + right);
3238
+ }
3239
+ }
2839
3240
  console.log(divider);
2840
3241
  console.log("");
2841
3242
  }
3243
+ function printWordmark(version) {
3244
+ const t = theme();
3245
+ const termWidth = process.stdout.columns || 80;
3246
+ console.log("");
3247
+ for (let i = 0; i < NOTCH_LARGE.length; i++) {
3248
+ console.log(" " + colorBannerLine(NOTCH_LARGE[i], i));
3249
+ }
3250
+ console.log("");
3251
+ const divWidth = Math.min(54, termWidth - 4);
3252
+ console.log(t.border(" " + "\u2500".repeat(divWidth)));
3253
+ console.log(t.dim(" v" + version) + t.dim(" \u2502 ") + t.tagline("by Driftrail"));
3254
+ console.log(t.border(" " + "\u2500".repeat(divWidth)));
3255
+ console.log("");
3256
+ }
2842
3257
  function printMantis() {
2843
3258
  const t = theme();
2844
3259
  console.log("");
@@ -2881,7 +3296,7 @@ function formatTokens(n) {
2881
3296
  import fs12 from "fs/promises";
2882
3297
  import path13 from "path";
2883
3298
  import os3 from "os";
2884
- import chalk6 from "chalk";
3299
+ import chalk7 from "chalk";
2885
3300
  var CACHE_FILE = path13.join(os3.homedir(), ".notch", "update-check.json");
2886
3301
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
2887
3302
  var PACKAGE_NAME = "notch-cli";
@@ -2925,7 +3340,7 @@ function isNewer(latest, current) {
2925
3340
  return false;
2926
3341
  }
2927
3342
  function formatUpdateMessage(current, latest) {
2928
- return chalk6.yellow(` Update available: ${current} -> ${latest}. Run: npm update -g ${PACKAGE_NAME}
3343
+ return chalk7.yellow(` Update available: ${current} -> ${latest}. Run: npm update -g ${PACKAGE_NAME}
2929
3344
  `);
2930
3345
  }
2931
3346
  async function loadCache() {
@@ -3279,7 +3694,7 @@ async function exportSession(messages, outputPath, meta) {
3279
3694
  // src/init.ts
3280
3695
  import fs16 from "fs/promises";
3281
3696
  import path17 from "path";
3282
- import chalk7 from "chalk";
3697
+ import chalk8 from "chalk";
3283
3698
  var DEFAULT_CONFIG = {
3284
3699
  model: "notch-forge",
3285
3700
  temperature: 0.3,
@@ -3326,15 +3741,15 @@ async function initProject(projectRoot) {
3326
3741
  }
3327
3742
  if (!configExists) {
3328
3743
  await fs16.writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
3329
- console.log(chalk7.green(` Created ${configPath}`));
3744
+ console.log(chalk8.green(` Created ${configPath}`));
3330
3745
  } else {
3331
- console.log(chalk7.gray(` Skipped ${configPath} (already exists)`));
3746
+ console.log(chalk8.gray(` Skipped ${configPath} (already exists)`));
3332
3747
  }
3333
3748
  if (!instructionsExist) {
3334
3749
  await fs16.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS, "utf-8");
3335
- console.log(chalk7.green(` Created ${instructionsPath}`));
3750
+ console.log(chalk8.green(` Created ${instructionsPath}`));
3336
3751
  } else {
3337
- console.log(chalk7.gray(` Skipped ${instructionsPath} (already exists)`));
3752
+ console.log(chalk8.gray(` Skipped ${instructionsPath} (already exists)`));
3338
3753
  }
3339
3754
  const gitignorePath = path17.join(projectRoot, ".gitignore");
3340
3755
  try {
@@ -3344,13 +3759,13 @@ async function initProject(projectRoot) {
3344
3759
  if (additions.length > 0) {
3345
3760
  const append = "\n# Notch CLI\n" + additions.join("\n") + "\n";
3346
3761
  await fs16.appendFile(gitignorePath, append, "utf-8");
3347
- console.log(chalk7.green(` Updated .gitignore`));
3762
+ console.log(chalk8.green(` Updated .gitignore`));
3348
3763
  }
3349
3764
  } catch {
3350
3765
  }
3351
3766
  console.log("");
3352
- console.log(chalk7.cyan(" Notch initialized! Edit .notch.md to customize behavior."));
3353
- console.log(chalk7.gray(' Run "notch" to start.\n'));
3767
+ console.log(chalk8.cyan(" Notch initialized! Edit .notch.md to customize behavior."));
3768
+ console.log(chalk8.gray(' Run "notch" to start.\n'));
3354
3769
  }
3355
3770
 
3356
3771
  // src/tools/diff-preview.ts
@@ -3469,183 +3884,6 @@ function findSync(oldLines, newLines, oi, ni, lookAhead) {
3469
3884
  return null;
3470
3885
  }
3471
3886
 
3472
- // src/mcp/client.ts
3473
- import { spawn } from "child_process";
3474
- import { z as z9 } from "zod";
3475
- var MCPClient = class {
3476
- constructor(config, serverName) {
3477
- this.config = config;
3478
- this.serverName = serverName;
3479
- }
3480
- process = null;
3481
- requestId = 0;
3482
- pendingRequests = /* @__PURE__ */ new Map();
3483
- buffer = "";
3484
- serverName;
3485
- _tools = [];
3486
- /**
3487
- * Start the MCP server and initialize the connection.
3488
- */
3489
- async connect() {
3490
- this.process = spawn(this.config.command, this.config.args ?? [], {
3491
- stdio: ["pipe", "pipe", "pipe"],
3492
- env: { ...process.env, ...this.config.env },
3493
- cwd: this.config.cwd
3494
- });
3495
- this.process.stdout?.setEncoding("utf-8");
3496
- this.process.stdout?.on("data", (data) => {
3497
- this.buffer += data;
3498
- this.processBuffer();
3499
- });
3500
- this.process.on("error", (err) => {
3501
- for (const [id, pending] of this.pendingRequests) {
3502
- pending.reject(new Error(`MCP server ${this.serverName} error: ${err.message}`));
3503
- this.pendingRequests.delete(id);
3504
- }
3505
- });
3506
- this.process.on("exit", (code) => {
3507
- for (const [id, pending] of this.pendingRequests) {
3508
- pending.reject(new Error(`MCP server ${this.serverName} exited with code ${code}`));
3509
- this.pendingRequests.delete(id);
3510
- }
3511
- });
3512
- await this.sendRequest("initialize", {
3513
- protocolVersion: "2024-11-05",
3514
- capabilities: {},
3515
- clientInfo: { name: "notch-cli", version: "0.3.0" }
3516
- });
3517
- this.sendNotification("notifications/initialized", {});
3518
- const result = await this.sendRequest("tools/list", {});
3519
- this._tools = result.tools ?? [];
3520
- }
3521
- /**
3522
- * Get discovered tools from this server.
3523
- */
3524
- get tools() {
3525
- return this._tools;
3526
- }
3527
- /**
3528
- * Check if the MCP server process is still alive.
3529
- */
3530
- get isAlive() {
3531
- return this.process !== null && this.process.exitCode === null && !this.process.killed;
3532
- }
3533
- /**
3534
- * Call a tool on the MCP server. Auto-reconnects if the server has crashed.
3535
- */
3536
- async callTool(name, args) {
3537
- if (!this.isAlive) {
3538
- try {
3539
- await this.connect();
3540
- } catch (err) {
3541
- throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
3542
- }
3543
- }
3544
- const result = await this.sendRequest("tools/call", { name, arguments: args });
3545
- return result;
3546
- }
3547
- /**
3548
- * Disconnect from the MCP server.
3549
- */
3550
- disconnect() {
3551
- if (this.process) {
3552
- this.process.stdin?.end();
3553
- this.process.kill();
3554
- this.process = null;
3555
- }
3556
- this.pendingRequests.clear();
3557
- }
3558
- sendRequest(method, params) {
3559
- return new Promise((resolve2, reject) => {
3560
- const id = ++this.requestId;
3561
- const msg = {
3562
- jsonrpc: "2.0",
3563
- id,
3564
- method,
3565
- params
3566
- };
3567
- this.pendingRequests.set(id, { resolve: resolve2, reject });
3568
- const data = JSON.stringify(msg);
3569
- const header = `Content-Length: ${Buffer.byteLength(data)}\r
3570
- \r
3571
- `;
3572
- this.process?.stdin?.write(header + data);
3573
- setTimeout(() => {
3574
- if (this.pendingRequests.has(id)) {
3575
- this.pendingRequests.delete(id);
3576
- reject(new Error(`MCP request ${method} timed out`));
3577
- }
3578
- }, 3e4);
3579
- });
3580
- }
3581
- sendNotification(method, params) {
3582
- const msg = {
3583
- jsonrpc: "2.0",
3584
- method,
3585
- params
3586
- };
3587
- const data = JSON.stringify(msg);
3588
- const header = `Content-Length: ${Buffer.byteLength(data)}\r
3589
- \r
3590
- `;
3591
- this.process?.stdin?.write(header + data);
3592
- }
3593
- processBuffer() {
3594
- while (this.buffer.length > 0) {
3595
- const headerEnd = this.buffer.indexOf("\r\n\r\n");
3596
- if (headerEnd === -1) break;
3597
- const header = this.buffer.slice(0, headerEnd);
3598
- const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
3599
- if (!lengthMatch) {
3600
- const nlIdx = this.buffer.indexOf("\n");
3601
- if (nlIdx === -1) break;
3602
- const line = this.buffer.slice(0, nlIdx).trim();
3603
- this.buffer = this.buffer.slice(nlIdx + 1);
3604
- if (line) this.handleMessage(line);
3605
- continue;
3606
- }
3607
- const contentLength = parseInt(lengthMatch[1], 10);
3608
- const messageStart = headerEnd + 4;
3609
- if (this.buffer.length < messageStart + contentLength) break;
3610
- const body = this.buffer.slice(messageStart, messageStart + contentLength);
3611
- this.buffer = this.buffer.slice(messageStart + contentLength);
3612
- this.handleMessage(body);
3613
- }
3614
- }
3615
- handleMessage(raw) {
3616
- try {
3617
- const msg = JSON.parse(raw);
3618
- if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
3619
- const pending = this.pendingRequests.get(msg.id);
3620
- this.pendingRequests.delete(msg.id);
3621
- if (msg.error) {
3622
- pending.reject(new Error(`MCP error: ${msg.error.message}`));
3623
- } else {
3624
- pending.resolve(msg.result);
3625
- }
3626
- }
3627
- } catch {
3628
- }
3629
- }
3630
- };
3631
- function parseMCPConfig(config) {
3632
- const servers = config?.mcpServers;
3633
- if (!servers || typeof servers !== "object") return {};
3634
- const result = {};
3635
- for (const [name, cfg] of Object.entries(servers)) {
3636
- const c = cfg;
3637
- if (c?.command) {
3638
- result[name] = {
3639
- command: c.command,
3640
- args: c.args,
3641
- env: c.env,
3642
- cwd: c.cwd
3643
- };
3644
- }
3645
- }
3646
- return result;
3647
- }
3648
-
3649
3887
  // src/ui/completions.ts
3650
3888
  import fs17 from "fs";
3651
3889
  import path18 from "path";
@@ -3753,7 +3991,7 @@ function printModelTable(activeModel) {
3753
3991
  `));
3754
3992
  }
3755
3993
  function printHelp() {
3756
- console.log(chalk8.gray(`
3994
+ console.log(chalk9.gray(`
3757
3995
  Commands:
3758
3996
  /model \u2014 Show available models
3759
3997
  /model <name> \u2014 Switch model (e.g., /model pyre)
@@ -3826,13 +4064,13 @@ async function main() {
3826
4064
  try {
3827
4065
  spinner.stop();
3828
4066
  const creds = await login();
3829
- console.log(chalk8.green(`
4067
+ console.log(chalk9.green(`
3830
4068
  \u2713 Signed in as ${creds.email}`));
3831
- console.log(chalk8.gray(` API key stored in ${(await import("./auth-GTGBXOSH.js")).getCredentialsPath()}
4069
+ console.log(chalk9.gray(` API key stored in ${(await import("./auth-JQX6MHJG.js")).getCredentialsPath()}
3832
4070
  `));
3833
4071
  } catch (err) {
3834
4072
  spinner.stop();
3835
- console.error(chalk8.red(`
4073
+ console.error(chalk9.red(`
3836
4074
  Login failed: ${err.message}
3837
4075
  `));
3838
4076
  process.exit(1);
@@ -3842,10 +4080,10 @@ async function main() {
3842
4080
  if (promptArgs[0] === "logout") {
3843
4081
  const creds = await loadCredentials();
3844
4082
  if (!creds) {
3845
- console.log(chalk8.gray("\n Not signed in.\n"));
4083
+ console.log(chalk9.gray("\n Not signed in.\n"));
3846
4084
  } else {
3847
4085
  await clearCredentials();
3848
- console.log(chalk8.green(`
4086
+ console.log(chalk9.green(`
3849
4087
  \u2713 Signed out (${creds.email})
3850
4088
  `));
3851
4089
  }
@@ -3854,13 +4092,13 @@ async function main() {
3854
4092
  if (promptArgs[0] === "whoami") {
3855
4093
  const creds = await loadCredentials();
3856
4094
  if (!creds) {
3857
- console.log(chalk8.gray("\n Not signed in. Run: notch login\n"));
4095
+ console.log(chalk9.gray("\n Not signed in. Run: notch login\n"));
3858
4096
  } else {
3859
4097
  const keyPreview = `${creds.token.slice(0, 12)}...`;
3860
- console.log(chalk8.gray(`
3861
- Signed in as ${chalk8.white(creds.email)}`));
3862
- console.log(chalk8.gray(` Key: ${keyPreview}`));
3863
- console.log(chalk8.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
4098
+ console.log(chalk9.gray(`
4099
+ Signed in as ${chalk9.white(creds.email)}`));
4100
+ console.log(chalk9.gray(` Key: ${keyPreview}`));
4101
+ console.log(chalk9.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
3864
4102
  `));
3865
4103
  }
3866
4104
  return;
@@ -3884,8 +4122,8 @@ async function main() {
3884
4122
  const config = await loadConfig(configOverrides);
3885
4123
  if (opts.model) {
3886
4124
  if (!isValidModel(opts.model)) {
3887
- console.error(chalk8.red(` Unknown model: ${opts.model}`));
3888
- console.error(chalk8.gray(` Available: ${modelChoices}`));
4125
+ console.error(chalk9.red(` Unknown model: ${opts.model}`));
4126
+ console.error(chalk9.gray(` Available: ${modelChoices}`));
3889
4127
  process.exit(1);
3890
4128
  }
3891
4129
  config.models.chat.model = opts.model;
@@ -3896,18 +4134,40 @@ async function main() {
3896
4134
  setTheme(config.theme);
3897
4135
  }
3898
4136
  let activeModelId = config.models.chat.model;
3899
- let model = resolveModel(config.models.chat);
4137
+ let model;
4138
+ try {
4139
+ model = resolveModel(config.models.chat);
4140
+ } catch (err) {
4141
+ if (err instanceof MissingApiKeyError) {
4142
+ printWordmark(VERSION);
4143
+ console.log(" To get started, you need a Notch API key.");
4144
+ console.log("");
4145
+ console.log(" \x1B[1mOption 1:\x1B[0m Log in via browser (recommended)");
4146
+ console.log(" \x1B[33m$ notch login\x1B[0m");
4147
+ console.log("");
4148
+ console.log(" \x1B[1mOption 2:\x1B[0m Set your API key directly");
4149
+ console.log(" \x1B[33m$ export NOTCH_API_KEY=your-key-here\x1B[0m");
4150
+ console.log("");
4151
+ console.log(" \x1B[1mOption 3:\x1B[0m Pass it inline");
4152
+ console.log(" \x1B[33m$ notch --api-key your-key-here\x1B[0m");
4153
+ console.log("");
4154
+ console.log(" Get your key at: \x1B[4mhttps://freesyntax.dev/settings\x1B[0m");
4155
+ console.log("");
4156
+ process.exit(0);
4157
+ }
4158
+ throw err;
4159
+ }
3900
4160
  const info = MODEL_CATALOG[activeModelId];
3901
4161
  printBanner(VERSION, info.label, info.id, info.size, config.projectRoot);
3902
4162
  checkForUpdates(VERSION).then((msg) => {
3903
4163
  if (msg) console.log(msg);
3904
4164
  });
3905
4165
  const hookTrustPrompt = async (commands) => {
3906
- console.warn(chalk8.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
3907
- commands.forEach((cmd) => console.warn(chalk8.gray(` \u2022 ${cmd}`)));
4166
+ console.warn(chalk9.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
4167
+ commands.forEach((cmd) => console.warn(chalk9.gray(` \u2022 ${cmd}`)));
3908
4168
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
3909
4169
  return new Promise((resolve2) => {
3910
- rl2.question(chalk8.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
4170
+ rl2.question(chalk9.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
3911
4171
  rl2.close();
3912
4172
  resolve2(answer.trim().toLowerCase() === "y");
3913
4173
  });
@@ -3953,9 +4213,9 @@ ${repoMapStr}` : ""
3953
4213
  const client = new MCPClient(mcpConfig, name);
3954
4214
  await client.connect();
3955
4215
  mcpClients.push(client);
3956
- console.log(chalk8.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
4216
+ console.log(chalk9.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
3957
4217
  } catch (err) {
3958
- console.log(chalk8.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
4218
+ console.log(chalk9.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
3959
4219
  }
3960
4220
  }
3961
4221
  } catch {
@@ -3972,7 +4232,7 @@ ${repoMapStr}` : ""
3972
4232
  });
3973
4233
  });
3974
4234
  },
3975
- log: (msg) => console.log(chalk8.gray(` ${msg}`)),
4235
+ log: (msg) => console.log(chalk9.gray(` ${msg}`)),
3976
4236
  checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
3977
4237
  runHook: async (event, ctx) => {
3978
4238
  if (!config.enableHooks || hookConfig.hooks.length === 0) return;
@@ -3982,7 +4242,7 @@ ${repoMapStr}` : ""
3982
4242
  });
3983
4243
  for (const r of results) {
3984
4244
  if (!r.ok) {
3985
- console.log(chalk8.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
4245
+ console.log(chalk9.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
3986
4246
  }
3987
4247
  }
3988
4248
  }
@@ -3994,10 +4254,10 @@ ${repoMapStr}` : ""
3994
4254
  if (session) {
3995
4255
  messages.push(...session.messages);
3996
4256
  sessionId = session.meta.id;
3997
- console.log(chalk8.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4257
+ console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
3998
4258
  `));
3999
4259
  } else {
4000
- console.log(chalk8.gray(" No session to resume.\n"));
4260
+ console.log(chalk9.gray(" No session to resume.\n"));
4001
4261
  }
4002
4262
  }
4003
4263
  const pipedInput = await readStdin();
@@ -4018,10 +4278,10 @@ Analyze the above input.`;
4018
4278
  const refContext = formatReferences(references);
4019
4279
  const finalPrompt = refContext + cleanInput;
4020
4280
  messages.push({ role: "user", content: finalPrompt });
4021
- console.log(chalk8.cyan(`> ${oneShot || "(piped input)"}
4281
+ console.log(chalk9.cyan(`> ${oneShot || "(piped input)"}
4022
4282
  `));
4023
4283
  if (references.length > 0) {
4024
- console.log(chalk8.gray(` Injected ${references.length} reference(s)
4284
+ console.log(chalk9.gray(` Injected ${references.length} reference(s)
4025
4285
  `));
4026
4286
  }
4027
4287
  const spinner = ora("Thinking...").start();
@@ -4040,13 +4300,13 @@ Analyze the above input.`;
4040
4300
  onToolCall: (name, args) => {
4041
4301
  if (spinner.isSpinning) spinner.stop();
4042
4302
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
4043
- console.log(chalk8.gray(`
4303
+ console.log(chalk9.gray(`
4044
4304
  \u2192 ${name}(${argSummary})`));
4045
4305
  },
4046
4306
  onToolResult: (_name, result, isError) => {
4047
4307
  const preview = result.slice(0, 100).replace(/\n/g, " ");
4048
- const icon = isError ? chalk8.red("\u2717") : chalk8.green("\u2713");
4049
- console.log(chalk8.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4308
+ const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
4309
+ console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4050
4310
  }
4051
4311
  })
4052
4312
  );
@@ -4059,7 +4319,7 @@ Analyze the above input.`;
4059
4319
  model: activeModelId
4060
4320
  });
4061
4321
  costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
4062
- console.log(usage.formatLast());
4322
+ console.log(usage.formatLast() + " " + costTracker.formatLastCost());
4063
4323
  }
4064
4324
  } catch (err) {
4065
4325
  spinner.fail(`Error: ${err.message}`);
@@ -4071,7 +4331,7 @@ Analyze the above input.`;
4071
4331
  const savedPlan = await loadPlan(config.projectRoot);
4072
4332
  if (savedPlan) {
4073
4333
  ralphPlan = savedPlan;
4074
- console.log(chalk8.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
4334
+ console.log(chalk9.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
4075
4335
  `));
4076
4336
  }
4077
4337
  } catch {
@@ -4083,7 +4343,7 @@ Analyze the above input.`;
4083
4343
  prompt: theme().prompt("notch> "),
4084
4344
  completer: (line) => completer(line)
4085
4345
  });
4086
- console.log(chalk8.gray(" Type your request, or /help for commands.\n"));
4346
+ console.log(chalk9.gray(" Type your request, or /help for commands.\n"));
4087
4347
  rl.prompt();
4088
4348
  rl.on("line", async (line) => {
4089
4349
  const input = line.trim();
@@ -4099,7 +4359,7 @@ Analyze the above input.`;
4099
4359
  if (messages.length > 0) {
4100
4360
  try {
4101
4361
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
4102
- console.log(chalk8.gray(` Session saved: ${id}`));
4362
+ console.log(chalk9.gray(` Session saved: ${id}`));
4103
4363
  } catch {
4104
4364
  }
4105
4365
  }
@@ -4110,12 +4370,12 @@ Analyze the above input.`;
4110
4370
  }
4111
4371
  }
4112
4372
  await toolCtx.runHook?.("session-end", {});
4113
- console.log(chalk8.gray("\n Goodbye!\n"));
4373
+ console.log(chalk9.gray("\n Goodbye!\n"));
4114
4374
  process.exit(0);
4115
4375
  }
4116
4376
  if (input === "/clear") {
4117
4377
  messages.length = 0;
4118
- console.log(chalk8.gray(" Conversation cleared.\n"));
4378
+ console.log(chalk9.gray(" Conversation cleared.\n"));
4119
4379
  rl.prompt();
4120
4380
  return;
4121
4381
  }
@@ -4135,8 +4395,8 @@ Analyze the above input.`;
4135
4395
  newModel = `notch-${newModel}`;
4136
4396
  }
4137
4397
  if (!isValidModel(newModel)) {
4138
- console.log(chalk8.red(` Unknown model: ${newModel}`));
4139
- console.log(chalk8.gray(` Available: ${modelChoices}`));
4398
+ console.log(chalk9.red(` Unknown model: ${newModel}`));
4399
+ console.log(chalk9.gray(` Available: ${modelChoices}`));
4140
4400
  rl.prompt();
4141
4401
  return;
4142
4402
  }
@@ -4144,7 +4404,7 @@ Analyze the above input.`;
4144
4404
  config.models.chat.model = activeModelId;
4145
4405
  model = resolveModel(config.models.chat);
4146
4406
  const switchedInfo = MODEL_CATALOG[activeModelId];
4147
- console.log(chalk8.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
4407
+ console.log(chalk9.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
4148
4408
  `));
4149
4409
  rl.prompt();
4150
4410
  return;
@@ -4157,22 +4417,22 @@ Analyze the above input.`;
4157
4417
  statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
4158
4418
  } else {
4159
4419
  statusSpinner.fail(check.error ?? "API unreachable");
4160
- console.log(chalk8.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
4420
+ console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
4161
4421
  }
4162
4422
  rl.prompt();
4163
4423
  return;
4164
4424
  }
4165
4425
  if (input === "/undo") {
4166
4426
  if (checkpoints.undoCount === 0) {
4167
- console.log(chalk8.gray(" Nothing to undo.\n"));
4427
+ console.log(chalk9.gray(" Nothing to undo.\n"));
4168
4428
  rl.prompt();
4169
4429
  return;
4170
4430
  }
4171
4431
  const undone = await checkpoints.undo();
4172
4432
  if (undone) {
4173
4433
  const fileCount = undone.files.length;
4174
- console.log(chalk8.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
4175
- console.log(chalk8.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
4434
+ console.log(chalk9.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
4435
+ console.log(chalk9.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
4176
4436
  `));
4177
4437
  }
4178
4438
  rl.prompt();
@@ -4180,7 +4440,7 @@ Analyze the above input.`;
4180
4440
  }
4181
4441
  if (input === "/usage") {
4182
4442
  if (usage.turnCount === 0) {
4183
- console.log(chalk8.gray(" No usage yet.\n"));
4443
+ console.log(chalk9.gray(" No usage yet.\n"));
4184
4444
  } else {
4185
4445
  console.log(usage.formatSession());
4186
4446
  const currentTokens = estimateTokens(messages);
@@ -4193,7 +4453,7 @@ Analyze the above input.`;
4193
4453
  }
4194
4454
  if (input === "/cost") {
4195
4455
  if (costTracker.totalCost === 0) {
4196
- console.log(chalk8.gray(" No cost data yet.\n"));
4456
+ console.log(chalk9.gray(" No cost data yet.\n"));
4197
4457
  } else {
4198
4458
  console.log(costTracker.formatTotal());
4199
4459
  console.log(costTracker.formatByModel());
@@ -4208,7 +4468,7 @@ Analyze the above input.`;
4208
4468
  const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
4209
4469
  messages.length = 0;
4210
4470
  messages.push(...compressed);
4211
- console.log(chalk8.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
4471
+ console.log(chalk9.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
4212
4472
  console.log("");
4213
4473
  rl.prompt();
4214
4474
  return;
@@ -4216,10 +4476,10 @@ Analyze the above input.`;
4216
4476
  if (input === "/diff") {
4217
4477
  const diffs = checkpoints.allDiffs();
4218
4478
  if (diffs.length === 0) {
4219
- console.log(chalk8.gray(" No file changes this session.\n"));
4479
+ console.log(chalk9.gray(" No file changes this session.\n"));
4220
4480
  } else {
4221
4481
  for (const df of diffs) {
4222
- console.log(chalk8.cyan(` ${df.path}:`));
4482
+ console.log(chalk9.cyan(` ${df.path}:`));
4223
4483
  console.log(unifiedDiff(df.before, df.after, df.path));
4224
4484
  console.log("");
4225
4485
  }
@@ -4235,10 +4495,10 @@ Analyze the above input.`;
4235
4495
  projectRoot: config.projectRoot,
4236
4496
  outputPath: exportPath
4237
4497
  });
4238
- console.log(chalk8.green(` Exported to ${ePath}
4498
+ console.log(chalk9.green(` Exported to ${ePath}
4239
4499
  `));
4240
4500
  } catch (err) {
4241
- console.log(chalk8.red(` Export failed: ${err.message}
4501
+ console.log(chalk9.red(` Export failed: ${err.message}
4242
4502
  `));
4243
4503
  }
4244
4504
  rl.prompt();
@@ -4248,10 +4508,10 @@ Analyze the above input.`;
4248
4508
  try {
4249
4509
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
4250
4510
  sessionId = id;
4251
- console.log(chalk8.green(` Session saved: ${id}
4511
+ console.log(chalk9.green(` Session saved: ${id}
4252
4512
  `));
4253
4513
  } catch (err) {
4254
- console.log(chalk8.red(` Save failed: ${err.message}
4514
+ console.log(chalk9.red(` Save failed: ${err.message}
4255
4515
  `));
4256
4516
  }
4257
4517
  rl.prompt();
@@ -4261,16 +4521,16 @@ Analyze the above input.`;
4261
4521
  try {
4262
4522
  const sessions = await listSessions(config.projectRoot);
4263
4523
  if (sessions.length === 0) {
4264
- console.log(chalk8.gray(" No saved sessions.\n"));
4524
+ console.log(chalk9.gray(" No saved sessions.\n"));
4265
4525
  } else {
4266
- console.log(chalk8.gray("\n Saved sessions:\n"));
4526
+ console.log(chalk9.gray("\n Saved sessions:\n"));
4267
4527
  for (const s of sessions.slice(0, 10)) {
4268
- console.log(chalk8.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
4528
+ console.log(chalk9.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
4269
4529
  }
4270
4530
  console.log("");
4271
4531
  }
4272
4532
  } catch (err) {
4273
- console.log(chalk8.red(` Error listing sessions: ${err.message}
4533
+ console.log(chalk9.red(` Error listing sessions: ${err.message}
4274
4534
  `));
4275
4535
  }
4276
4536
  rl.prompt();
@@ -4288,18 +4548,18 @@ Analyze the above input.`;
4288
4548
  const savedPlan = await loadPlan(config.projectRoot);
4289
4549
  if (savedPlan) {
4290
4550
  ralphPlan = savedPlan;
4291
- console.log(chalk8.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
4551
+ console.log(chalk9.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
4292
4552
  `));
4293
4553
  }
4294
4554
  } catch {
4295
4555
  }
4296
- console.log(chalk8.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4556
+ console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4297
4557
  `));
4298
4558
  } else {
4299
- console.log(chalk8.gray(" No session found.\n"));
4559
+ console.log(chalk9.gray(" No session found.\n"));
4300
4560
  }
4301
4561
  } catch (err) {
4302
- console.log(chalk8.red(` Resume failed: ${err.message}
4562
+ console.log(chalk9.red(` Resume failed: ${err.message}
4303
4563
  `));
4304
4564
  }
4305
4565
  rl.prompt();
@@ -4308,7 +4568,7 @@ Analyze the above input.`;
4308
4568
  if (input.startsWith("/search ")) {
4309
4569
  const query = input.replace("/search ", "").trim().toLowerCase();
4310
4570
  if (!query) {
4311
- console.log(chalk8.gray(" Usage: /search <query>\n"));
4571
+ console.log(chalk9.gray(" Usage: /search <query>\n"));
4312
4572
  rl.prompt();
4313
4573
  return;
4314
4574
  }
@@ -4325,14 +4585,14 @@ Analyze the above input.`;
4325
4585
  }
4326
4586
  }
4327
4587
  if (matches.length === 0) {
4328
- console.log(chalk8.gray(` No matches for "${query}"
4588
+ console.log(chalk9.gray(` No matches for "${query}"
4329
4589
  `));
4330
4590
  } else {
4331
- console.log(chalk8.gray(`
4591
+ console.log(chalk9.gray(`
4332
4592
  ${matches.length} match(es) for "${query}":
4333
4593
  `));
4334
4594
  for (const m of matches.slice(0, 10)) {
4335
- console.log(chalk8.gray(` [${m.index}] ${m.role}: ${m.preview}`));
4595
+ console.log(chalk9.gray(` [${m.index}] ${m.role}: ${m.preview}`));
4336
4596
  }
4337
4597
  console.log("");
4338
4598
  }
@@ -4346,7 +4606,7 @@ Analyze the above input.`;
4346
4606
  activePlan = await generatePlan(task, model, systemPrompt);
4347
4607
  planSpinner.succeed("Plan generated");
4348
4608
  console.log(formatPlan(activePlan));
4349
- console.log(chalk8.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
4609
+ console.log(chalk9.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
4350
4610
  } catch (err) {
4351
4611
  planSpinner.fail(`Plan failed: ${err.message}`);
4352
4612
  }
@@ -4355,11 +4615,11 @@ Analyze the above input.`;
4355
4615
  }
4356
4616
  if (input === "/plan approve") {
4357
4617
  if (!activePlan) {
4358
- console.log(chalk8.gray(" No active plan. Use /plan <task> to create one.\n"));
4618
+ console.log(chalk9.gray(" No active plan. Use /plan <task> to create one.\n"));
4359
4619
  rl.prompt();
4360
4620
  return;
4361
4621
  }
4362
- console.log(chalk8.green(" Executing plan...\n"));
4622
+ console.log(chalk9.green(" Executing plan...\n"));
4363
4623
  while (!isPlanComplete(activePlan)) {
4364
4624
  const stepPrompt = currentStepPrompt(activePlan);
4365
4625
  messages.push({ role: "user", content: stepPrompt });
@@ -4377,10 +4637,10 @@ Analyze the above input.`;
4377
4637
  },
4378
4638
  onToolCall: (name) => {
4379
4639
  if (planStepSpinner.isSpinning) planStepSpinner.stop();
4380
- console.log(chalk8.gray(` \u2192 ${name}`));
4640
+ console.log(chalk9.gray(` \u2192 ${name}`));
4381
4641
  },
4382
4642
  onToolResult: (_name, _result, isError) => {
4383
- console.log(isError ? chalk8.red(" \u2717") : chalk8.green(" \u2713"));
4643
+ console.log(isError ? chalk9.red(" \u2717") : chalk9.green(" \u2713"));
4384
4644
  }
4385
4645
  });
4386
4646
  console.log("\n");
@@ -4393,7 +4653,7 @@ Analyze the above input.`;
4393
4653
  }
4394
4654
  }
4395
4655
  if (activePlan && isPlanComplete(activePlan)) {
4396
- console.log(chalk8.green(" Plan completed!\n"));
4656
+ console.log(chalk9.green(" Plan completed!\n"));
4397
4657
  }
4398
4658
  activePlan = null;
4399
4659
  rl.prompt();
@@ -4401,26 +4661,26 @@ Analyze the above input.`;
4401
4661
  }
4402
4662
  if (input === "/plan edit") {
4403
4663
  if (!activePlan) {
4404
- console.log(chalk8.gray(" No active plan to edit.\n"));
4664
+ console.log(chalk9.gray(" No active plan to edit.\n"));
4405
4665
  rl.prompt();
4406
4666
  return;
4407
4667
  }
4408
4668
  console.log(formatPlan(activePlan));
4409
- console.log(chalk8.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
4410
- console.log(chalk8.gray(" or /plan approve to proceed with the current plan.\n"));
4669
+ console.log(chalk9.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
4670
+ console.log(chalk9.gray(" or /plan approve to proceed with the current plan.\n"));
4411
4671
  rl.prompt();
4412
4672
  return;
4413
4673
  }
4414
4674
  if (input === "/plan cancel") {
4415
4675
  activePlan = null;
4416
- console.log(chalk8.gray(" Plan discarded.\n"));
4676
+ console.log(chalk9.gray(" Plan discarded.\n"));
4417
4677
  rl.prompt();
4418
4678
  return;
4419
4679
  }
4420
4680
  if (input.startsWith("/agent ")) {
4421
4681
  const task = input.replace("/agent ", "").trim();
4422
4682
  const agentId = nextSubagentId();
4423
- console.log(chalk8.cyan(` Spawning subagent #${agentId}: ${task}
4683
+ console.log(chalk9.cyan(` Spawning subagent #${agentId}: ${task}
4424
4684
  `));
4425
4685
  spawnSubagent({
4426
4686
  id: agentId,
@@ -4430,14 +4690,14 @@ Analyze the above input.`;
4430
4690
  toolContext: toolCtx,
4431
4691
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4432
4692
  onComplete: (result) => {
4433
- console.log(chalk8.green(`
4693
+ console.log(chalk9.green(`
4434
4694
  Subagent #${agentId} finished:`));
4435
- console.log(chalk8.gray(` ${result.slice(0, 200)}
4695
+ console.log(chalk9.gray(` ${result.slice(0, 200)}
4436
4696
  `));
4437
4697
  rl.prompt();
4438
4698
  },
4439
4699
  onError: (err) => {
4440
- console.log(chalk8.red(`
4700
+ console.log(chalk9.red(`
4441
4701
  Subagent #${agentId} failed: ${err}
4442
4702
  `));
4443
4703
  rl.prompt();
@@ -4450,18 +4710,18 @@ Analyze the above input.`;
4450
4710
  try {
4451
4711
  const memories = await loadMemories(config.projectRoot);
4452
4712
  if (memories.length === 0) {
4453
- console.log(chalk8.gray(" No saved memories.\n"));
4713
+ console.log(chalk9.gray(" No saved memories.\n"));
4454
4714
  } else {
4455
- console.log(chalk8.gray(`
4715
+ console.log(chalk9.gray(`
4456
4716
  ${memories.length} saved memories:
4457
4717
  `));
4458
4718
  for (const m of memories) {
4459
- console.log(chalk8.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
4719
+ console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
4460
4720
  }
4461
4721
  console.log("");
4462
4722
  }
4463
4723
  } catch (err) {
4464
- console.log(chalk8.red(` Error: ${err.message}
4724
+ console.log(chalk9.red(` Error: ${err.message}
4465
4725
  `));
4466
4726
  }
4467
4727
  rl.prompt();
@@ -4472,16 +4732,16 @@ Analyze the above input.`;
4472
4732
  try {
4473
4733
  const results = await searchMemories(config.projectRoot, query);
4474
4734
  if (results.length === 0) {
4475
- console.log(chalk8.gray(` No memories matching "${query}"
4735
+ console.log(chalk9.gray(` No memories matching "${query}"
4476
4736
  `));
4477
4737
  } else {
4478
4738
  for (const m of results) {
4479
- console.log(chalk8.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
4739
+ console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
4480
4740
  }
4481
4741
  console.log("");
4482
4742
  }
4483
4743
  } catch (err) {
4484
- console.log(chalk8.red(` Error: ${err.message}
4744
+ console.log(chalk9.red(` Error: ${err.message}
4485
4745
  `));
4486
4746
  }
4487
4747
  rl.prompt();
@@ -4493,10 +4753,10 @@ Analyze the above input.`;
4493
4753
  for (const m of memories) {
4494
4754
  await deleteMemory(config.projectRoot, m.id);
4495
4755
  }
4496
- console.log(chalk8.yellow(` Cleared ${memories.length} memories.
4756
+ console.log(chalk9.yellow(` Cleared ${memories.length} memories.
4497
4757
  `));
4498
4758
  } catch (err) {
4499
- console.log(chalk8.red(` Error: ${err.message}
4759
+ console.log(chalk9.red(` Error: ${err.message}
4500
4760
  `));
4501
4761
  }
4502
4762
  rl.prompt();
@@ -4524,27 +4784,27 @@ Analyze the above input.`;
4524
4784
  }
4525
4785
  if (input === "/ralph run") {
4526
4786
  if (!ralphPlan) {
4527
- console.log(chalk8.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
4787
+ console.log(chalk9.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
4528
4788
  rl.prompt();
4529
4789
  return;
4530
4790
  }
4531
- console.log(chalk8.green(" Ralph is running...\n"));
4791
+ console.log(chalk9.green(" Ralph is running...\n"));
4532
4792
  try {
4533
4793
  ralphPlan = await runRalphLoop(ralphPlan, {
4534
4794
  model,
4535
4795
  systemPrompt,
4536
4796
  toolContext: toolCtx,
4537
4797
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4538
- onTaskStart: (task) => console.log(chalk8.cyan(` \u25B6 Task: ${task.description}`)),
4539
- onTaskComplete: (task) => console.log(chalk8.green(` \u2713 Done: ${task.description}
4798
+ onTaskStart: (task) => console.log(chalk9.cyan(` \u25B6 Task: ${task.description}`)),
4799
+ onTaskComplete: (task) => console.log(chalk9.green(` \u2713 Done: ${task.description}
4540
4800
  `)),
4541
- onTaskFail: (task, err) => console.log(chalk8.red(` \u2717 Failed: ${task.description} (${err})
4801
+ onTaskFail: (task, err) => console.log(chalk9.red(` \u2717 Failed: ${task.description} (${err})
4542
4802
  `))
4543
4803
  });
4544
4804
  await savePlan(config.projectRoot, ralphPlan);
4545
4805
  console.log(formatRalphStatus(ralphPlan));
4546
4806
  } catch (err) {
4547
- console.log(chalk8.red(` Ralph error: ${err.message}
4807
+ console.log(chalk9.red(` Ralph error: ${err.message}
4548
4808
  `));
4549
4809
  }
4550
4810
  rl.prompt();
@@ -4552,7 +4812,7 @@ Analyze the above input.`;
4552
4812
  }
4553
4813
  if (input === "/ralph status") {
4554
4814
  if (!ralphPlan) {
4555
- console.log(chalk8.gray(" No Ralph plan active.\n"));
4815
+ console.log(chalk9.gray(" No Ralph plan active.\n"));
4556
4816
  } else {
4557
4817
  console.log(formatRalphStatus(ralphPlan));
4558
4818
  }
@@ -4563,26 +4823,26 @@ Analyze the above input.`;
4563
4823
  ralphPlan = null;
4564
4824
  await deletePlan(config.projectRoot).catch(() => {
4565
4825
  });
4566
- console.log(chalk8.gray(" Ralph plan cleared.\n"));
4826
+ console.log(chalk9.gray(" Ralph plan cleared.\n"));
4567
4827
  rl.prompt();
4568
4828
  return;
4569
4829
  }
4570
4830
  if (input === "/branch") {
4571
4831
  const branchId = `branch-${branches.size + 1}`;
4572
4832
  branches.set(branchId, [...messages]);
4573
- console.log(chalk8.green(` Forked conversation as "${branchId}" (${messages.length} messages)
4833
+ console.log(chalk9.green(` Forked conversation as "${branchId}" (${messages.length} messages)
4574
4834
  `));
4575
4835
  rl.prompt();
4576
4836
  return;
4577
4837
  }
4578
4838
  if (input === "/branches") {
4579
4839
  if (branches.size === 0) {
4580
- console.log(chalk8.gray(" No conversation branches. Use /branch to fork.\n"));
4840
+ console.log(chalk9.gray(" No conversation branches. Use /branch to fork.\n"));
4581
4841
  } else {
4582
- console.log(chalk8.gray("\n Branches:\n"));
4842
+ console.log(chalk9.gray("\n Branches:\n"));
4583
4843
  for (const [name, msgs] of branches) {
4584
- const marker = name === currentBranch ? chalk8.green(" \u25CF") : " ";
4585
- console.log(chalk8.gray(` ${marker} ${name} (${msgs.length} messages)`));
4844
+ const marker = name === currentBranch ? chalk9.green(" \u25CF") : " ";
4845
+ console.log(chalk9.gray(` ${marker} ${name} (${msgs.length} messages)`));
4586
4846
  }
4587
4847
  console.log("");
4588
4848
  }
@@ -4619,8 +4879,8 @@ Analyze the above input.`;
4619
4879
  return;
4620
4880
  }
4621
4881
  if (input.startsWith("/")) {
4622
- console.log(chalk8.red(` Unknown command: ${input}`));
4623
- console.log(chalk8.gray(" Type /help for available commands.\n"));
4882
+ console.log(chalk9.red(` Unknown command: ${input}`));
4883
+ console.log(chalk9.gray(" Type /help for available commands.\n"));
4624
4884
  rl.prompt();
4625
4885
  return;
4626
4886
  }
@@ -4628,7 +4888,7 @@ Analyze the above input.`;
4628
4888
  const refContext = formatReferences(references);
4629
4889
  const finalPrompt = refContext + cleanInput;
4630
4890
  if (references.length > 0) {
4631
- console.log(chalk8.gray(` Injected ${references.length} reference(s)`));
4891
+ console.log(chalk9.gray(` Injected ${references.length} reference(s)`));
4632
4892
  }
4633
4893
  messages.push({ role: "user", content: finalPrompt });
4634
4894
  const spinner = ora("Thinking...").start();
@@ -4654,16 +4914,16 @@ Analyze the above input.`;
4654
4914
  const val = String(v);
4655
4915
  return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
4656
4916
  }).join(", ");
4657
- console.log(chalk8.gray(`
4917
+ console.log(chalk9.gray(`
4658
4918
  \u2192 ${name}(${argSummary})`));
4659
4919
  },
4660
4920
  onToolResult: (_name, result, isError) => {
4661
4921
  const preview = result.slice(0, 100).replace(/\n/g, " ");
4662
- const icon = isError ? chalk8.red("\u2717") : chalk8.green("\u2713");
4663
- console.log(chalk8.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4922
+ const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
4923
+ console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4664
4924
  },
4665
4925
  onCompress: () => {
4666
- console.log(chalk8.yellow("\n [Context compressed to fit window]\n"));
4926
+ console.log(chalk9.yellow("\n [Context compressed to fit window]\n"));
4667
4927
  }
4668
4928
  })
4669
4929
  );
@@ -4677,7 +4937,7 @@ Analyze the above input.`;
4677
4937
  model: activeModelId
4678
4938
  });
4679
4939
  costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
4680
- console.log(usage.formatLast());
4940
+ console.log(usage.formatLast() + " " + costTracker.formatLastCost());
4681
4941
  const currentTokens = estimateTokens(messages);
4682
4942
  const ctxWindow = MODEL_CATALOG[activeModelId].contextWindow;
4683
4943
  if (currentTokens > ctxWindow * 0.5) {
@@ -4696,7 +4956,7 @@ Analyze the above input.`;
4696
4956
  type: "auto",
4697
4957
  content: memMatch[1]
4698
4958
  });
4699
- console.log(chalk8.gray(" (Saved to memory)\n"));
4959
+ console.log(chalk9.gray(" (Saved to memory)\n"));
4700
4960
  } catch {
4701
4961
  }
4702
4962
  }
@@ -4706,13 +4966,13 @@ Analyze the above input.`;
4706
4966
  checkpoints.discard();
4707
4967
  const msg = err.message?.toLowerCase() ?? "";
4708
4968
  if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
4709
- console.log(chalk8.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
4969
+ console.log(chalk9.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
4710
4970
  } else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
4711
- console.log(chalk8.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
4971
+ console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
4712
4972
  } else if (msg.includes("429") || msg.includes("rate limit")) {
4713
- console.log(chalk8.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
4973
+ console.log(chalk9.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
4714
4974
  } else {
4715
- console.log(chalk8.gray(" (The conversation history is preserved. Try again.)\n"));
4975
+ console.log(chalk9.gray(" (The conversation history is preserved. Try again.)\n"));
4716
4976
  }
4717
4977
  }
4718
4978
  rl.prompt();
@@ -4730,13 +4990,13 @@ async function handleRalphSubcommand(args, cliOpts) {
4730
4990
  cwd: config.projectRoot,
4731
4991
  requireConfirm: false,
4732
4992
  confirm: async () => true,
4733
- log: (msg) => console.log(chalk8.gray(` ${msg}`))
4993
+ log: (msg) => console.log(chalk9.gray(` ${msg}`))
4734
4994
  };
4735
4995
  const subcommand = args[0];
4736
4996
  if (subcommand === "plan") {
4737
4997
  const goal = args.slice(1).join(" ");
4738
4998
  if (!goal) {
4739
- console.error(chalk8.red(" Usage: notch ralph plan <goal>"));
4999
+ console.error(chalk9.red(" Usage: notch ralph plan <goal>"));
4740
5000
  process.exit(1);
4741
5001
  }
4742
5002
  const spinner = ora("Ralph is planning...").start();
@@ -4747,7 +5007,7 @@ async function handleRalphSubcommand(args, cliOpts) {
4747
5007
  } else if (subcommand === "run") {
4748
5008
  let plan = await loadPlan(config.projectRoot);
4749
5009
  if (!plan) {
4750
- console.error(chalk8.red(" No plan found. Run: notch ralph plan <goal>"));
5010
+ console.error(chalk9.red(" No plan found. Run: notch ralph plan <goal>"));
4751
5011
  process.exit(1);
4752
5012
  }
4753
5013
  plan = await runRalphLoop(plan, {
@@ -4755,27 +5015,27 @@ async function handleRalphSubcommand(args, cliOpts) {
4755
5015
  systemPrompt,
4756
5016
  toolContext: toolCtx,
4757
5017
  contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
4758
- onTaskStart: (t) => console.log(chalk8.cyan(` \u25B6 ${t.description}`)),
4759
- onTaskComplete: (t) => console.log(chalk8.green(` \u2713 ${t.description}`)),
4760
- onTaskFail: (t, e) => console.log(chalk8.red(` \u2717 ${t.description}: ${e}`))
5018
+ onTaskStart: (t) => console.log(chalk9.cyan(` \u25B6 ${t.description}`)),
5019
+ onTaskComplete: (t) => console.log(chalk9.green(` \u2713 ${t.description}`)),
5020
+ onTaskFail: (t, e) => console.log(chalk9.red(` \u2717 ${t.description}: ${e}`))
4761
5021
  });
4762
5022
  await savePlan(config.projectRoot, plan);
4763
5023
  console.log(formatRalphStatus(plan));
4764
5024
  } else if (subcommand === "status") {
4765
5025
  const plan = await loadPlan(config.projectRoot);
4766
5026
  if (!plan) {
4767
- console.log(chalk8.gray(" No Ralph plan found."));
5027
+ console.log(chalk9.gray(" No Ralph plan found."));
4768
5028
  } else {
4769
5029
  console.log(formatRalphStatus(plan));
4770
5030
  }
4771
5031
  } else {
4772
- console.error(chalk8.red(` Unknown: notch ralph ${subcommand}`));
4773
- console.error(chalk8.gray(" Usage: notch ralph <plan|run|status>"));
5032
+ console.error(chalk9.red(` Unknown: notch ralph ${subcommand}`));
5033
+ console.error(chalk9.gray(" Usage: notch ralph <plan|run|status>"));
4774
5034
  process.exit(1);
4775
5035
  }
4776
5036
  }
4777
5037
  main().catch((err) => {
4778
- console.error(chalk8.red(`
5038
+ console.error(chalk9.red(`
4779
5039
  Fatal: ${err.message}
4780
5040
  `));
4781
5041
  process.exit(1);