@freesyntax/notch-cli 0.4.2 → 0.4.5

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";
@@ -830,20 +830,373 @@ ${text}` };
830
830
  }
831
831
  };
832
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
+
833
1181
  // src/tools/index.ts
834
- var ALL_TOOLS = [
1182
+ var BUILTIN_TOOLS = [
835
1183
  readTool,
836
1184
  writeTool,
837
1185
  editTool,
838
1186
  shellTool,
839
1187
  gitTool,
1188
+ githubTool,
840
1189
  grepTool,
841
1190
  globTool,
842
1191
  webFetchTool
843
1192
  ];
1193
+ var mcpTools = [];
1194
+ function getAllTools() {
1195
+ return [...BUILTIN_TOOLS, ...mcpTools];
1196
+ }
844
1197
  function buildToolMap(ctx) {
845
1198
  const map = {};
846
- for (const t of ALL_TOOLS) {
1199
+ for (const t of getAllTools()) {
847
1200
  map[t.name] = tool({
848
1201
  description: t.description,
849
1202
  parameters: t.parameters,
@@ -888,7 +1241,7 @@ ${paramSummary}`
888
1241
  return map;
889
1242
  }
890
1243
  function describeTools() {
891
- return ALL_TOOLS.map(
1244
+ return getAllTools().map(
892
1245
  (t) => `- **${t.name}**: ${t.description}`
893
1246
  ).join("\n");
894
1247
  }
@@ -2311,36 +2664,49 @@ async function resolveGlob(pattern, cwd) {
2311
2664
  }
2312
2665
  }
2313
2666
 
2667
+ // src/ui/banner.ts
2668
+ import chalk6 from "chalk";
2669
+
2314
2670
  // src/ui/themes.ts
2315
2671
  import chalk5 from "chalk";
2316
2672
  var defaultTheme = {
2317
2673
  name: "Default",
2318
- description: "Classic Notch \u2014 green mantis, blue wordmark",
2319
- brand: chalk5.blueBright,
2320
- mascot: chalk5.greenBright,
2321
- mascotAccent: chalk5.yellowBright,
2322
- tagline: chalk5.cyan,
2323
- prompt: chalk5.blueBright,
2324
- border: chalk5.gray,
2325
- dim: chalk5.gray,
2326
- text: chalk5.white,
2327
- 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
2328
2693
  success: chalk5.green,
2329
2694
  warning: chalk5.yellow,
2330
2695
  error: chalk5.red,
2331
- info: chalk5.cyan,
2332
- toolName: chalk5.gray,
2333
- toolArgs: chalk5.gray,
2334
- 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"),
2335
2701
  diffAdd: chalk5.green,
2336
2702
  diffRemove: chalk5.red,
2337
- diffHeader: chalk5.cyan,
2338
- mdH1: chalk5.white.bold,
2339
- mdH2: chalk5.blueBright,
2340
- mdH3: chalk5.cyan,
2341
- mdCode: chalk5.cyan,
2342
- mdInlineCode: chalk5.bgGray.white,
2343
- 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,
2344
2710
  meterLow: chalk5.green,
2345
2711
  meterMid: chalk5.yellow,
2346
2712
  meterHigh: chalk5.red
@@ -2790,6 +3156,39 @@ function formatThemeList(activeId) {
2790
3156
  }
2791
3157
 
2792
3158
  // src/ui/banner.ts
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"
3167
+ ];
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
+ }
2793
3192
  var MANTIS = [
2794
3193
  " \u2571\u25C9\u25C9\u2572",
2795
3194
  // ╱◉◉╲ antennae + eyes
@@ -2804,11 +3203,6 @@ var MANTIS = [
2804
3203
  " \u2580\u2580 \u2580\u2580"
2805
3204
  // ▀▀ ▀▀ feet
2806
3205
  ];
2807
- var LOGO_INLINE = [
2808
- " \u2588\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2580\u2588\u2580 \u2584\u2580\u2580\u2584 \u2588 \u2588",
2809
- " \u2588 \u2580\u2584 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2580\u2580\u2588",
2810
- " \u2588 \u2580\u2588\u2588 \u2580\u2584\u2584\u2580 \u2588 \u2580\u2584\u2584\u2580 \u2588 \u2588"
2811
- ];
2812
3206
  function colorMantis(line) {
2813
3207
  const t = theme();
2814
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"));
@@ -2817,24 +3211,49 @@ function printBanner(version, modelLabel, modelId, modelSize, project) {
2817
3211
  const t = theme();
2818
3212
  const termWidth = process.stdout.columns || 80;
2819
3213
  console.log("");
2820
- for (const line of MANTIS) {
2821
- console.log(" " + colorMantis(line));
2822
- }
2823
- console.log("");
2824
- for (const line of LOGO_INLINE) {
2825
- console.log(" " + t.brand(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
- const divWidth = Math.min(50, termWidth - 4);
3218
+ const mantisStr = MANTIS.map((l) => colorMantis(l));
3219
+ const divWidth = Math.min(54, termWidth - 4);
2829
3220
  const divider = t.border(" " + "\u2500".repeat(divWidth));
2830
3221
  console.log(divider);
2831
- console.log(
2832
- t.dim(" ") + t.bold(modelLabel) + t.dim(" \u2502 v") + t.text(version) + t.dim(" \u2502 ") + t.dim("by ") + t.tagline("Driftrail")
2833
- );
2834
- 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
+ }
2835
3240
  console.log(divider);
2836
3241
  console.log("");
2837
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
+ }
2838
3257
  function printMantis() {
2839
3258
  const t = theme();
2840
3259
  console.log("");
@@ -2877,7 +3296,7 @@ function formatTokens(n) {
2877
3296
  import fs12 from "fs/promises";
2878
3297
  import path13 from "path";
2879
3298
  import os3 from "os";
2880
- import chalk6 from "chalk";
3299
+ import chalk7 from "chalk";
2881
3300
  var CACHE_FILE = path13.join(os3.homedir(), ".notch", "update-check.json");
2882
3301
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
2883
3302
  var PACKAGE_NAME = "notch-cli";
@@ -2921,7 +3340,7 @@ function isNewer(latest, current) {
2921
3340
  return false;
2922
3341
  }
2923
3342
  function formatUpdateMessage(current, latest) {
2924
- 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}
2925
3344
  `);
2926
3345
  }
2927
3346
  async function loadCache() {
@@ -3275,7 +3694,7 @@ async function exportSession(messages, outputPath, meta) {
3275
3694
  // src/init.ts
3276
3695
  import fs16 from "fs/promises";
3277
3696
  import path17 from "path";
3278
- import chalk7 from "chalk";
3697
+ import chalk8 from "chalk";
3279
3698
  var DEFAULT_CONFIG = {
3280
3699
  model: "notch-forge",
3281
3700
  temperature: 0.3,
@@ -3322,15 +3741,15 @@ async function initProject(projectRoot) {
3322
3741
  }
3323
3742
  if (!configExists) {
3324
3743
  await fs16.writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n", "utf-8");
3325
- console.log(chalk7.green(` Created ${configPath}`));
3744
+ console.log(chalk8.green(` Created ${configPath}`));
3326
3745
  } else {
3327
- console.log(chalk7.gray(` Skipped ${configPath} (already exists)`));
3746
+ console.log(chalk8.gray(` Skipped ${configPath} (already exists)`));
3328
3747
  }
3329
3748
  if (!instructionsExist) {
3330
3749
  await fs16.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS, "utf-8");
3331
- console.log(chalk7.green(` Created ${instructionsPath}`));
3750
+ console.log(chalk8.green(` Created ${instructionsPath}`));
3332
3751
  } else {
3333
- console.log(chalk7.gray(` Skipped ${instructionsPath} (already exists)`));
3752
+ console.log(chalk8.gray(` Skipped ${instructionsPath} (already exists)`));
3334
3753
  }
3335
3754
  const gitignorePath = path17.join(projectRoot, ".gitignore");
3336
3755
  try {
@@ -3340,13 +3759,13 @@ async function initProject(projectRoot) {
3340
3759
  if (additions.length > 0) {
3341
3760
  const append = "\n# Notch CLI\n" + additions.join("\n") + "\n";
3342
3761
  await fs16.appendFile(gitignorePath, append, "utf-8");
3343
- console.log(chalk7.green(` Updated .gitignore`));
3762
+ console.log(chalk8.green(` Updated .gitignore`));
3344
3763
  }
3345
3764
  } catch {
3346
3765
  }
3347
3766
  console.log("");
3348
- console.log(chalk7.cyan(" Notch initialized! Edit .notch.md to customize behavior."));
3349
- 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'));
3350
3769
  }
3351
3770
 
3352
3771
  // src/tools/diff-preview.ts
@@ -3465,183 +3884,6 @@ function findSync(oldLines, newLines, oi, ni, lookAhead) {
3465
3884
  return null;
3466
3885
  }
3467
3886
 
3468
- // src/mcp/client.ts
3469
- import { spawn } from "child_process";
3470
- import { z as z9 } from "zod";
3471
- var MCPClient = class {
3472
- constructor(config, serverName) {
3473
- this.config = config;
3474
- this.serverName = serverName;
3475
- }
3476
- process = null;
3477
- requestId = 0;
3478
- pendingRequests = /* @__PURE__ */ new Map();
3479
- buffer = "";
3480
- serverName;
3481
- _tools = [];
3482
- /**
3483
- * Start the MCP server and initialize the connection.
3484
- */
3485
- async connect() {
3486
- this.process = spawn(this.config.command, this.config.args ?? [], {
3487
- stdio: ["pipe", "pipe", "pipe"],
3488
- env: { ...process.env, ...this.config.env },
3489
- cwd: this.config.cwd
3490
- });
3491
- this.process.stdout?.setEncoding("utf-8");
3492
- this.process.stdout?.on("data", (data) => {
3493
- this.buffer += data;
3494
- this.processBuffer();
3495
- });
3496
- this.process.on("error", (err) => {
3497
- for (const [id, pending] of this.pendingRequests) {
3498
- pending.reject(new Error(`MCP server ${this.serverName} error: ${err.message}`));
3499
- this.pendingRequests.delete(id);
3500
- }
3501
- });
3502
- this.process.on("exit", (code) => {
3503
- for (const [id, pending] of this.pendingRequests) {
3504
- pending.reject(new Error(`MCP server ${this.serverName} exited with code ${code}`));
3505
- this.pendingRequests.delete(id);
3506
- }
3507
- });
3508
- await this.sendRequest("initialize", {
3509
- protocolVersion: "2024-11-05",
3510
- capabilities: {},
3511
- clientInfo: { name: "notch-cli", version: "0.3.0" }
3512
- });
3513
- this.sendNotification("notifications/initialized", {});
3514
- const result = await this.sendRequest("tools/list", {});
3515
- this._tools = result.tools ?? [];
3516
- }
3517
- /**
3518
- * Get discovered tools from this server.
3519
- */
3520
- get tools() {
3521
- return this._tools;
3522
- }
3523
- /**
3524
- * Check if the MCP server process is still alive.
3525
- */
3526
- get isAlive() {
3527
- return this.process !== null && this.process.exitCode === null && !this.process.killed;
3528
- }
3529
- /**
3530
- * Call a tool on the MCP server. Auto-reconnects if the server has crashed.
3531
- */
3532
- async callTool(name, args) {
3533
- if (!this.isAlive) {
3534
- try {
3535
- await this.connect();
3536
- } catch (err) {
3537
- throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
3538
- }
3539
- }
3540
- const result = await this.sendRequest("tools/call", { name, arguments: args });
3541
- return result;
3542
- }
3543
- /**
3544
- * Disconnect from the MCP server.
3545
- */
3546
- disconnect() {
3547
- if (this.process) {
3548
- this.process.stdin?.end();
3549
- this.process.kill();
3550
- this.process = null;
3551
- }
3552
- this.pendingRequests.clear();
3553
- }
3554
- sendRequest(method, params) {
3555
- return new Promise((resolve2, reject) => {
3556
- const id = ++this.requestId;
3557
- const msg = {
3558
- jsonrpc: "2.0",
3559
- id,
3560
- method,
3561
- params
3562
- };
3563
- this.pendingRequests.set(id, { resolve: resolve2, reject });
3564
- const data = JSON.stringify(msg);
3565
- const header = `Content-Length: ${Buffer.byteLength(data)}\r
3566
- \r
3567
- `;
3568
- this.process?.stdin?.write(header + data);
3569
- setTimeout(() => {
3570
- if (this.pendingRequests.has(id)) {
3571
- this.pendingRequests.delete(id);
3572
- reject(new Error(`MCP request ${method} timed out`));
3573
- }
3574
- }, 3e4);
3575
- });
3576
- }
3577
- sendNotification(method, params) {
3578
- const msg = {
3579
- jsonrpc: "2.0",
3580
- method,
3581
- params
3582
- };
3583
- const data = JSON.stringify(msg);
3584
- const header = `Content-Length: ${Buffer.byteLength(data)}\r
3585
- \r
3586
- `;
3587
- this.process?.stdin?.write(header + data);
3588
- }
3589
- processBuffer() {
3590
- while (this.buffer.length > 0) {
3591
- const headerEnd = this.buffer.indexOf("\r\n\r\n");
3592
- if (headerEnd === -1) break;
3593
- const header = this.buffer.slice(0, headerEnd);
3594
- const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
3595
- if (!lengthMatch) {
3596
- const nlIdx = this.buffer.indexOf("\n");
3597
- if (nlIdx === -1) break;
3598
- const line = this.buffer.slice(0, nlIdx).trim();
3599
- this.buffer = this.buffer.slice(nlIdx + 1);
3600
- if (line) this.handleMessage(line);
3601
- continue;
3602
- }
3603
- const contentLength = parseInt(lengthMatch[1], 10);
3604
- const messageStart = headerEnd + 4;
3605
- if (this.buffer.length < messageStart + contentLength) break;
3606
- const body = this.buffer.slice(messageStart, messageStart + contentLength);
3607
- this.buffer = this.buffer.slice(messageStart + contentLength);
3608
- this.handleMessage(body);
3609
- }
3610
- }
3611
- handleMessage(raw) {
3612
- try {
3613
- const msg = JSON.parse(raw);
3614
- if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
3615
- const pending = this.pendingRequests.get(msg.id);
3616
- this.pendingRequests.delete(msg.id);
3617
- if (msg.error) {
3618
- pending.reject(new Error(`MCP error: ${msg.error.message}`));
3619
- } else {
3620
- pending.resolve(msg.result);
3621
- }
3622
- }
3623
- } catch {
3624
- }
3625
- }
3626
- };
3627
- function parseMCPConfig(config) {
3628
- const servers = config?.mcpServers;
3629
- if (!servers || typeof servers !== "object") return {};
3630
- const result = {};
3631
- for (const [name, cfg] of Object.entries(servers)) {
3632
- const c = cfg;
3633
- if (c?.command) {
3634
- result[name] = {
3635
- command: c.command,
3636
- args: c.args,
3637
- env: c.env,
3638
- cwd: c.cwd
3639
- };
3640
- }
3641
- }
3642
- return result;
3643
- }
3644
-
3645
3887
  // src/ui/completions.ts
3646
3888
  import fs17 from "fs";
3647
3889
  import path18 from "path";
@@ -3729,7 +3971,9 @@ function completeFilePath(partial, cwd) {
3729
3971
 
3730
3972
  // src/index.ts
3731
3973
  import fs18 from "fs/promises";
3732
- var VERSION = "0.4.1";
3974
+ import { createRequire } from "module";
3975
+ var _require = createRequire(import.meta.url);
3976
+ var VERSION = _require("../package.json").version;
3733
3977
  var modelChoices = MODEL_IDS.join(", ");
3734
3978
  var program = new Command().name("notch").description("Notch CLI \u2014 AI-powered coding assistant by Driftrail").version(VERSION).argument("[prompt...]", "One-shot prompt (runs once and exits)").option(`-m, --model <model>`, `Notch model (${modelChoices})`).option("--base-url <url>", "Override Notch API base URL").option("--api-key <key>", "Notch API key (prefer NOTCH_API_KEY env var)").option("--no-repo-map", "Disable automatic repository mapping").option("--no-markdown", "Disable markdown rendering in output").option("--max-iterations <n>", "Max tool-call rounds per turn", "25").option("-y, --yes", "Auto-confirm destructive actions").option("--trust", "Trust mode \u2014 auto-allow all tool calls").option("--theme <theme>", `UI color theme (${THEME_IDS.join(", ")})`).option("--resume", "Resume the last session for this project").option("--session <id>", "Resume a specific session by ID").option("--cwd <dir>", "Set working directory").parse(process.argv);
3735
3979
  var opts = program.opts();
@@ -3749,7 +3993,7 @@ function printModelTable(activeModel) {
3749
3993
  `));
3750
3994
  }
3751
3995
  function printHelp() {
3752
- console.log(chalk8.gray(`
3996
+ console.log(chalk9.gray(`
3753
3997
  Commands:
3754
3998
  /model \u2014 Show available models
3755
3999
  /model <name> \u2014 Switch model (e.g., /model pyre)
@@ -3822,13 +4066,13 @@ async function main() {
3822
4066
  try {
3823
4067
  spinner.stop();
3824
4068
  const creds = await login();
3825
- console.log(chalk8.green(`
4069
+ console.log(chalk9.green(`
3826
4070
  \u2713 Signed in as ${creds.email}`));
3827
- console.log(chalk8.gray(` API key stored in ${(await import("./auth-GTGBXOSH.js")).getCredentialsPath()}
4071
+ console.log(chalk9.gray(` API key stored in ${(await import("./auth-JQX6MHJG.js")).getCredentialsPath()}
3828
4072
  `));
3829
4073
  } catch (err) {
3830
4074
  spinner.stop();
3831
- console.error(chalk8.red(`
4075
+ console.error(chalk9.red(`
3832
4076
  Login failed: ${err.message}
3833
4077
  `));
3834
4078
  process.exit(1);
@@ -3838,10 +4082,10 @@ async function main() {
3838
4082
  if (promptArgs[0] === "logout") {
3839
4083
  const creds = await loadCredentials();
3840
4084
  if (!creds) {
3841
- console.log(chalk8.gray("\n Not signed in.\n"));
4085
+ console.log(chalk9.gray("\n Not signed in.\n"));
3842
4086
  } else {
3843
4087
  await clearCredentials();
3844
- console.log(chalk8.green(`
4088
+ console.log(chalk9.green(`
3845
4089
  \u2713 Signed out (${creds.email})
3846
4090
  `));
3847
4091
  }
@@ -3850,13 +4094,13 @@ async function main() {
3850
4094
  if (promptArgs[0] === "whoami") {
3851
4095
  const creds = await loadCredentials();
3852
4096
  if (!creds) {
3853
- console.log(chalk8.gray("\n Not signed in. Run: notch login\n"));
4097
+ console.log(chalk9.gray("\n Not signed in. Run: notch login\n"));
3854
4098
  } else {
3855
4099
  const keyPreview = `${creds.token.slice(0, 12)}...`;
3856
- console.log(chalk8.gray(`
3857
- Signed in as ${chalk8.white(creds.email)}`));
3858
- console.log(chalk8.gray(` Key: ${keyPreview}`));
3859
- console.log(chalk8.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
4100
+ console.log(chalk9.gray(`
4101
+ Signed in as ${chalk9.white(creds.email)}`));
4102
+ console.log(chalk9.gray(` Key: ${keyPreview}`));
4103
+ console.log(chalk9.gray(` Since: ${new Date(creds.createdAt).toLocaleDateString()}
3860
4104
  `));
3861
4105
  }
3862
4106
  return;
@@ -3880,8 +4124,8 @@ async function main() {
3880
4124
  const config = await loadConfig(configOverrides);
3881
4125
  if (opts.model) {
3882
4126
  if (!isValidModel(opts.model)) {
3883
- console.error(chalk8.red(` Unknown model: ${opts.model}`));
3884
- console.error(chalk8.gray(` Available: ${modelChoices}`));
4127
+ console.error(chalk9.red(` Unknown model: ${opts.model}`));
4128
+ console.error(chalk9.gray(` Available: ${modelChoices}`));
3885
4129
  process.exit(1);
3886
4130
  }
3887
4131
  config.models.chat.model = opts.model;
@@ -3897,9 +4141,7 @@ async function main() {
3897
4141
  model = resolveModel(config.models.chat);
3898
4142
  } catch (err) {
3899
4143
  if (err instanceof MissingApiKeyError) {
3900
- console.log("");
3901
- console.log(" \x1B[1m\x1B[36m\u26A1 Welcome to Notch CLI\x1B[0m");
3902
- console.log("");
4144
+ printWordmark(VERSION);
3903
4145
  console.log(" To get started, you need a Notch API key.");
3904
4146
  console.log("");
3905
4147
  console.log(" \x1B[1mOption 1:\x1B[0m Log in via browser (recommended)");
@@ -3911,7 +4153,7 @@ async function main() {
3911
4153
  console.log(" \x1B[1mOption 3:\x1B[0m Pass it inline");
3912
4154
  console.log(" \x1B[33m$ notch --api-key your-key-here\x1B[0m");
3913
4155
  console.log("");
3914
- console.log(" Get your key at: \x1B[4mhttps://freesyntax.com/settings\x1B[0m");
4156
+ console.log(" Get your key at: \x1B[4mhttps://freesyntax.dev/settings\x1B[0m");
3915
4157
  console.log("");
3916
4158
  process.exit(0);
3917
4159
  }
@@ -3923,11 +4165,11 @@ async function main() {
3923
4165
  if (msg) console.log(msg);
3924
4166
  });
3925
4167
  const hookTrustPrompt = async (commands) => {
3926
- console.warn(chalk8.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
3927
- commands.forEach((cmd) => console.warn(chalk8.gray(` \u2022 ${cmd}`)));
4168
+ console.warn(chalk9.yellow("\n\u26A0 This project contains hooks in .notch.json that will run shell commands:"));
4169
+ commands.forEach((cmd) => console.warn(chalk9.gray(` \u2022 ${cmd}`)));
3928
4170
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
3929
4171
  return new Promise((resolve2) => {
3930
- rl2.question(chalk8.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
4172
+ rl2.question(chalk9.yellow("\nAllow these hooks for this project? [y/N] "), (answer) => {
3931
4173
  rl2.close();
3932
4174
  resolve2(answer.trim().toLowerCase() === "y");
3933
4175
  });
@@ -3973,9 +4215,9 @@ ${repoMapStr}` : ""
3973
4215
  const client = new MCPClient(mcpConfig, name);
3974
4216
  await client.connect();
3975
4217
  mcpClients.push(client);
3976
- console.log(chalk8.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
4218
+ console.log(chalk9.green(` MCP: Connected to ${name} (${client.tools.length} tools)`));
3977
4219
  } catch (err) {
3978
- console.log(chalk8.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
4220
+ console.log(chalk9.yellow(` MCP: Could not connect to ${name}: ${err.message}`));
3979
4221
  }
3980
4222
  }
3981
4223
  } catch {
@@ -3992,7 +4234,7 @@ ${repoMapStr}` : ""
3992
4234
  });
3993
4235
  });
3994
4236
  },
3995
- log: (msg) => console.log(chalk8.gray(` ${msg}`)),
4237
+ log: (msg) => console.log(chalk9.gray(` ${msg}`)),
3996
4238
  checkPermission: config.permissionMode === "trust" ? () => "allow" : (toolName, args) => checkPermission(permissions, toolName, args),
3997
4239
  runHook: async (event, ctx) => {
3998
4240
  if (!config.enableHooks || hookConfig.hooks.length === 0) return;
@@ -4002,7 +4244,7 @@ ${repoMapStr}` : ""
4002
4244
  });
4003
4245
  for (const r of results) {
4004
4246
  if (!r.ok) {
4005
- console.log(chalk8.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
4247
+ console.log(chalk9.yellow(` Hook failed (${r.hook.event}): ${r.error}`));
4006
4248
  }
4007
4249
  }
4008
4250
  }
@@ -4014,10 +4256,10 @@ ${repoMapStr}` : ""
4014
4256
  if (session) {
4015
4257
  messages.push(...session.messages);
4016
4258
  sessionId = session.meta.id;
4017
- console.log(chalk8.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4259
+ console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4018
4260
  `));
4019
4261
  } else {
4020
- console.log(chalk8.gray(" No session to resume.\n"));
4262
+ console.log(chalk9.gray(" No session to resume.\n"));
4021
4263
  }
4022
4264
  }
4023
4265
  const pipedInput = await readStdin();
@@ -4038,10 +4280,10 @@ Analyze the above input.`;
4038
4280
  const refContext = formatReferences(references);
4039
4281
  const finalPrompt = refContext + cleanInput;
4040
4282
  messages.push({ role: "user", content: finalPrompt });
4041
- console.log(chalk8.cyan(`> ${oneShot || "(piped input)"}
4283
+ console.log(chalk9.cyan(`> ${oneShot || "(piped input)"}
4042
4284
  `));
4043
4285
  if (references.length > 0) {
4044
- console.log(chalk8.gray(` Injected ${references.length} reference(s)
4286
+ console.log(chalk9.gray(` Injected ${references.length} reference(s)
4045
4287
  `));
4046
4288
  }
4047
4289
  const spinner = ora("Thinking...").start();
@@ -4060,13 +4302,13 @@ Analyze the above input.`;
4060
4302
  onToolCall: (name, args) => {
4061
4303
  if (spinner.isSpinning) spinner.stop();
4062
4304
  const argSummary = Object.entries(args).map(([k, v]) => `${k}=${String(v).slice(0, 60)}`).join(", ");
4063
- console.log(chalk8.gray(`
4305
+ console.log(chalk9.gray(`
4064
4306
  \u2192 ${name}(${argSummary})`));
4065
4307
  },
4066
4308
  onToolResult: (_name, result, isError) => {
4067
4309
  const preview = result.slice(0, 100).replace(/\n/g, " ");
4068
- const icon = isError ? chalk8.red("\u2717") : chalk8.green("\u2713");
4069
- console.log(chalk8.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4310
+ const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
4311
+ console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4070
4312
  }
4071
4313
  })
4072
4314
  );
@@ -4079,7 +4321,7 @@ Analyze the above input.`;
4079
4321
  model: activeModelId
4080
4322
  });
4081
4323
  costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
4082
- console.log(usage.formatLast());
4324
+ console.log(usage.formatLast() + " " + costTracker.formatLastCost());
4083
4325
  }
4084
4326
  } catch (err) {
4085
4327
  spinner.fail(`Error: ${err.message}`);
@@ -4091,7 +4333,7 @@ Analyze the above input.`;
4091
4333
  const savedPlan = await loadPlan(config.projectRoot);
4092
4334
  if (savedPlan) {
4093
4335
  ralphPlan = savedPlan;
4094
- console.log(chalk8.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
4336
+ console.log(chalk9.gray(` Ralph plan loaded (${savedPlan.tasks.length} tasks)
4095
4337
  `));
4096
4338
  }
4097
4339
  } catch {
@@ -4103,7 +4345,7 @@ Analyze the above input.`;
4103
4345
  prompt: theme().prompt("notch> "),
4104
4346
  completer: (line) => completer(line)
4105
4347
  });
4106
- console.log(chalk8.gray(" Type your request, or /help for commands.\n"));
4348
+ console.log(chalk9.gray(" Type your request, or /help for commands.\n"));
4107
4349
  rl.prompt();
4108
4350
  rl.on("line", async (line) => {
4109
4351
  const input = line.trim();
@@ -4119,7 +4361,7 @@ Analyze the above input.`;
4119
4361
  if (messages.length > 0) {
4120
4362
  try {
4121
4363
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
4122
- console.log(chalk8.gray(` Session saved: ${id}`));
4364
+ console.log(chalk9.gray(` Session saved: ${id}`));
4123
4365
  } catch {
4124
4366
  }
4125
4367
  }
@@ -4130,12 +4372,12 @@ Analyze the above input.`;
4130
4372
  }
4131
4373
  }
4132
4374
  await toolCtx.runHook?.("session-end", {});
4133
- console.log(chalk8.gray("\n Goodbye!\n"));
4375
+ console.log(chalk9.gray("\n Goodbye!\n"));
4134
4376
  process.exit(0);
4135
4377
  }
4136
4378
  if (input === "/clear") {
4137
4379
  messages.length = 0;
4138
- console.log(chalk8.gray(" Conversation cleared.\n"));
4380
+ console.log(chalk9.gray(" Conversation cleared.\n"));
4139
4381
  rl.prompt();
4140
4382
  return;
4141
4383
  }
@@ -4155,8 +4397,8 @@ Analyze the above input.`;
4155
4397
  newModel = `notch-${newModel}`;
4156
4398
  }
4157
4399
  if (!isValidModel(newModel)) {
4158
- console.log(chalk8.red(` Unknown model: ${newModel}`));
4159
- console.log(chalk8.gray(` Available: ${modelChoices}`));
4400
+ console.log(chalk9.red(` Unknown model: ${newModel}`));
4401
+ console.log(chalk9.gray(` Available: ${modelChoices}`));
4160
4402
  rl.prompt();
4161
4403
  return;
4162
4404
  }
@@ -4164,7 +4406,7 @@ Analyze the above input.`;
4164
4406
  config.models.chat.model = activeModelId;
4165
4407
  model = resolveModel(config.models.chat);
4166
4408
  const switchedInfo = MODEL_CATALOG[activeModelId];
4167
- console.log(chalk8.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
4409
+ console.log(chalk9.green(` Switched to ${switchedInfo.label} (${switchedInfo.id})
4168
4410
  `));
4169
4411
  rl.prompt();
4170
4412
  return;
@@ -4177,22 +4419,22 @@ Analyze the above input.`;
4177
4419
  statusSpinner.succeed(`${statusInfo.label} (${activeModelId}) is reachable`);
4178
4420
  } else {
4179
4421
  statusSpinner.fail(check.error ?? "API unreachable");
4180
- console.log(chalk8.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
4422
+ console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY or use --api-key, and verify your Modal endpoint is running.\n"));
4181
4423
  }
4182
4424
  rl.prompt();
4183
4425
  return;
4184
4426
  }
4185
4427
  if (input === "/undo") {
4186
4428
  if (checkpoints.undoCount === 0) {
4187
- console.log(chalk8.gray(" Nothing to undo.\n"));
4429
+ console.log(chalk9.gray(" Nothing to undo.\n"));
4188
4430
  rl.prompt();
4189
4431
  return;
4190
4432
  }
4191
4433
  const undone = await checkpoints.undo();
4192
4434
  if (undone) {
4193
4435
  const fileCount = undone.files.length;
4194
- console.log(chalk8.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
4195
- console.log(chalk8.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
4436
+ console.log(chalk9.yellow(` Undid "${undone.description}" (${fileCount} file${fileCount !== 1 ? "s" : ""} restored)`));
4437
+ console.log(chalk9.gray(` ${checkpoints.undoCount} checkpoint${checkpoints.undoCount !== 1 ? "s" : ""} remaining
4196
4438
  `));
4197
4439
  }
4198
4440
  rl.prompt();
@@ -4200,7 +4442,7 @@ Analyze the above input.`;
4200
4442
  }
4201
4443
  if (input === "/usage") {
4202
4444
  if (usage.turnCount === 0) {
4203
- console.log(chalk8.gray(" No usage yet.\n"));
4445
+ console.log(chalk9.gray(" No usage yet.\n"));
4204
4446
  } else {
4205
4447
  console.log(usage.formatSession());
4206
4448
  const currentTokens = estimateTokens(messages);
@@ -4213,7 +4455,7 @@ Analyze the above input.`;
4213
4455
  }
4214
4456
  if (input === "/cost") {
4215
4457
  if (costTracker.totalCost === 0) {
4216
- console.log(chalk8.gray(" No cost data yet.\n"));
4458
+ console.log(chalk9.gray(" No cost data yet.\n"));
4217
4459
  } else {
4218
4460
  console.log(costTracker.formatTotal());
4219
4461
  console.log(costTracker.formatByModel());
@@ -4228,7 +4470,7 @@ Analyze the above input.`;
4228
4470
  const compressed = await autoCompress2(messages, model, MODEL_CATALOG[activeModelId].contextWindow);
4229
4471
  messages.length = 0;
4230
4472
  messages.push(...compressed);
4231
- console.log(chalk8.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
4473
+ console.log(chalk9.green(` Compressed: ${before} messages \u2192 ${messages.length} messages`));
4232
4474
  console.log("");
4233
4475
  rl.prompt();
4234
4476
  return;
@@ -4236,10 +4478,10 @@ Analyze the above input.`;
4236
4478
  if (input === "/diff") {
4237
4479
  const diffs = checkpoints.allDiffs();
4238
4480
  if (diffs.length === 0) {
4239
- console.log(chalk8.gray(" No file changes this session.\n"));
4481
+ console.log(chalk9.gray(" No file changes this session.\n"));
4240
4482
  } else {
4241
4483
  for (const df of diffs) {
4242
- console.log(chalk8.cyan(` ${df.path}:`));
4484
+ console.log(chalk9.cyan(` ${df.path}:`));
4243
4485
  console.log(unifiedDiff(df.before, df.after, df.path));
4244
4486
  console.log("");
4245
4487
  }
@@ -4255,10 +4497,10 @@ Analyze the above input.`;
4255
4497
  projectRoot: config.projectRoot,
4256
4498
  outputPath: exportPath
4257
4499
  });
4258
- console.log(chalk8.green(` Exported to ${ePath}
4500
+ console.log(chalk9.green(` Exported to ${ePath}
4259
4501
  `));
4260
4502
  } catch (err) {
4261
- console.log(chalk8.red(` Export failed: ${err.message}
4503
+ console.log(chalk9.red(` Export failed: ${err.message}
4262
4504
  `));
4263
4505
  }
4264
4506
  rl.prompt();
@@ -4268,10 +4510,10 @@ Analyze the above input.`;
4268
4510
  try {
4269
4511
  const id = await saveSession(config.projectRoot, messages, { model: activeModelId });
4270
4512
  sessionId = id;
4271
- console.log(chalk8.green(` Session saved: ${id}
4513
+ console.log(chalk9.green(` Session saved: ${id}
4272
4514
  `));
4273
4515
  } catch (err) {
4274
- console.log(chalk8.red(` Save failed: ${err.message}
4516
+ console.log(chalk9.red(` Save failed: ${err.message}
4275
4517
  `));
4276
4518
  }
4277
4519
  rl.prompt();
@@ -4281,16 +4523,16 @@ Analyze the above input.`;
4281
4523
  try {
4282
4524
  const sessions = await listSessions(config.projectRoot);
4283
4525
  if (sessions.length === 0) {
4284
- console.log(chalk8.gray(" No saved sessions.\n"));
4526
+ console.log(chalk9.gray(" No saved sessions.\n"));
4285
4527
  } else {
4286
- console.log(chalk8.gray("\n Saved sessions:\n"));
4528
+ console.log(chalk9.gray("\n Saved sessions:\n"));
4287
4529
  for (const s of sessions.slice(0, 10)) {
4288
- console.log(chalk8.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
4530
+ console.log(chalk9.gray(` ${s.id} ${s.turns} turns ${s.date} ${s.model}`));
4289
4531
  }
4290
4532
  console.log("");
4291
4533
  }
4292
4534
  } catch (err) {
4293
- console.log(chalk8.red(` Error listing sessions: ${err.message}
4535
+ console.log(chalk9.red(` Error listing sessions: ${err.message}
4294
4536
  `));
4295
4537
  }
4296
4538
  rl.prompt();
@@ -4308,18 +4550,18 @@ Analyze the above input.`;
4308
4550
  const savedPlan = await loadPlan(config.projectRoot);
4309
4551
  if (savedPlan) {
4310
4552
  ralphPlan = savedPlan;
4311
- console.log(chalk8.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
4553
+ console.log(chalk9.green(` Ralph plan restored (${savedPlan.tasks.length} tasks)
4312
4554
  `));
4313
4555
  }
4314
4556
  } catch {
4315
4557
  }
4316
- console.log(chalk8.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4558
+ console.log(chalk9.green(` Resumed session ${session.meta.id} (${session.meta.turns} turns)
4317
4559
  `));
4318
4560
  } else {
4319
- console.log(chalk8.gray(" No session found.\n"));
4561
+ console.log(chalk9.gray(" No session found.\n"));
4320
4562
  }
4321
4563
  } catch (err) {
4322
- console.log(chalk8.red(` Resume failed: ${err.message}
4564
+ console.log(chalk9.red(` Resume failed: ${err.message}
4323
4565
  `));
4324
4566
  }
4325
4567
  rl.prompt();
@@ -4328,7 +4570,7 @@ Analyze the above input.`;
4328
4570
  if (input.startsWith("/search ")) {
4329
4571
  const query = input.replace("/search ", "").trim().toLowerCase();
4330
4572
  if (!query) {
4331
- console.log(chalk8.gray(" Usage: /search <query>\n"));
4573
+ console.log(chalk9.gray(" Usage: /search <query>\n"));
4332
4574
  rl.prompt();
4333
4575
  return;
4334
4576
  }
@@ -4345,14 +4587,14 @@ Analyze the above input.`;
4345
4587
  }
4346
4588
  }
4347
4589
  if (matches.length === 0) {
4348
- console.log(chalk8.gray(` No matches for "${query}"
4590
+ console.log(chalk9.gray(` No matches for "${query}"
4349
4591
  `));
4350
4592
  } else {
4351
- console.log(chalk8.gray(`
4593
+ console.log(chalk9.gray(`
4352
4594
  ${matches.length} match(es) for "${query}":
4353
4595
  `));
4354
4596
  for (const m of matches.slice(0, 10)) {
4355
- console.log(chalk8.gray(` [${m.index}] ${m.role}: ${m.preview}`));
4597
+ console.log(chalk9.gray(` [${m.index}] ${m.role}: ${m.preview}`));
4356
4598
  }
4357
4599
  console.log("");
4358
4600
  }
@@ -4366,7 +4608,7 @@ Analyze the above input.`;
4366
4608
  activePlan = await generatePlan(task, model, systemPrompt);
4367
4609
  planSpinner.succeed("Plan generated");
4368
4610
  console.log(formatPlan(activePlan));
4369
- console.log(chalk8.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
4611
+ console.log(chalk9.gray(" Use /plan approve to execute, /plan edit to modify, or /plan cancel to discard.\n"));
4370
4612
  } catch (err) {
4371
4613
  planSpinner.fail(`Plan failed: ${err.message}`);
4372
4614
  }
@@ -4375,11 +4617,11 @@ Analyze the above input.`;
4375
4617
  }
4376
4618
  if (input === "/plan approve") {
4377
4619
  if (!activePlan) {
4378
- console.log(chalk8.gray(" No active plan. Use /plan <task> to create one.\n"));
4620
+ console.log(chalk9.gray(" No active plan. Use /plan <task> to create one.\n"));
4379
4621
  rl.prompt();
4380
4622
  return;
4381
4623
  }
4382
- console.log(chalk8.green(" Executing plan...\n"));
4624
+ console.log(chalk9.green(" Executing plan...\n"));
4383
4625
  while (!isPlanComplete(activePlan)) {
4384
4626
  const stepPrompt = currentStepPrompt(activePlan);
4385
4627
  messages.push({ role: "user", content: stepPrompt });
@@ -4397,10 +4639,10 @@ Analyze the above input.`;
4397
4639
  },
4398
4640
  onToolCall: (name) => {
4399
4641
  if (planStepSpinner.isSpinning) planStepSpinner.stop();
4400
- console.log(chalk8.gray(` \u2192 ${name}`));
4642
+ console.log(chalk9.gray(` \u2192 ${name}`));
4401
4643
  },
4402
4644
  onToolResult: (_name, _result, isError) => {
4403
- console.log(isError ? chalk8.red(" \u2717") : chalk8.green(" \u2713"));
4645
+ console.log(isError ? chalk9.red(" \u2717") : chalk9.green(" \u2713"));
4404
4646
  }
4405
4647
  });
4406
4648
  console.log("\n");
@@ -4413,7 +4655,7 @@ Analyze the above input.`;
4413
4655
  }
4414
4656
  }
4415
4657
  if (activePlan && isPlanComplete(activePlan)) {
4416
- console.log(chalk8.green(" Plan completed!\n"));
4658
+ console.log(chalk9.green(" Plan completed!\n"));
4417
4659
  }
4418
4660
  activePlan = null;
4419
4661
  rl.prompt();
@@ -4421,26 +4663,26 @@ Analyze the above input.`;
4421
4663
  }
4422
4664
  if (input === "/plan edit") {
4423
4665
  if (!activePlan) {
4424
- console.log(chalk8.gray(" No active plan to edit.\n"));
4666
+ console.log(chalk9.gray(" No active plan to edit.\n"));
4425
4667
  rl.prompt();
4426
4668
  return;
4427
4669
  }
4428
4670
  console.log(formatPlan(activePlan));
4429
- console.log(chalk8.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
4430
- console.log(chalk8.gray(" or /plan approve to proceed with the current plan.\n"));
4671
+ console.log(chalk9.gray(" Type a modified plan description and use /plan <new task> to regenerate,"));
4672
+ console.log(chalk9.gray(" or /plan approve to proceed with the current plan.\n"));
4431
4673
  rl.prompt();
4432
4674
  return;
4433
4675
  }
4434
4676
  if (input === "/plan cancel") {
4435
4677
  activePlan = null;
4436
- console.log(chalk8.gray(" Plan discarded.\n"));
4678
+ console.log(chalk9.gray(" Plan discarded.\n"));
4437
4679
  rl.prompt();
4438
4680
  return;
4439
4681
  }
4440
4682
  if (input.startsWith("/agent ")) {
4441
4683
  const task = input.replace("/agent ", "").trim();
4442
4684
  const agentId = nextSubagentId();
4443
- console.log(chalk8.cyan(` Spawning subagent #${agentId}: ${task}
4685
+ console.log(chalk9.cyan(` Spawning subagent #${agentId}: ${task}
4444
4686
  `));
4445
4687
  spawnSubagent({
4446
4688
  id: agentId,
@@ -4450,14 +4692,14 @@ Analyze the above input.`;
4450
4692
  toolContext: toolCtx,
4451
4693
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4452
4694
  onComplete: (result) => {
4453
- console.log(chalk8.green(`
4695
+ console.log(chalk9.green(`
4454
4696
  Subagent #${agentId} finished:`));
4455
- console.log(chalk8.gray(` ${result.slice(0, 200)}
4697
+ console.log(chalk9.gray(` ${result.slice(0, 200)}
4456
4698
  `));
4457
4699
  rl.prompt();
4458
4700
  },
4459
4701
  onError: (err) => {
4460
- console.log(chalk8.red(`
4702
+ console.log(chalk9.red(`
4461
4703
  Subagent #${agentId} failed: ${err}
4462
4704
  `));
4463
4705
  rl.prompt();
@@ -4470,18 +4712,18 @@ Analyze the above input.`;
4470
4712
  try {
4471
4713
  const memories = await loadMemories(config.projectRoot);
4472
4714
  if (memories.length === 0) {
4473
- console.log(chalk8.gray(" No saved memories.\n"));
4715
+ console.log(chalk9.gray(" No saved memories.\n"));
4474
4716
  } else {
4475
- console.log(chalk8.gray(`
4717
+ console.log(chalk9.gray(`
4476
4718
  ${memories.length} saved memories:
4477
4719
  `));
4478
4720
  for (const m of memories) {
4479
- console.log(chalk8.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
4721
+ console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 80)}`));
4480
4722
  }
4481
4723
  console.log("");
4482
4724
  }
4483
4725
  } catch (err) {
4484
- console.log(chalk8.red(` Error: ${err.message}
4726
+ console.log(chalk9.red(` Error: ${err.message}
4485
4727
  `));
4486
4728
  }
4487
4729
  rl.prompt();
@@ -4492,16 +4734,16 @@ Analyze the above input.`;
4492
4734
  try {
4493
4735
  const results = await searchMemories(config.projectRoot, query);
4494
4736
  if (results.length === 0) {
4495
- console.log(chalk8.gray(` No memories matching "${query}"
4737
+ console.log(chalk9.gray(` No memories matching "${query}"
4496
4738
  `));
4497
4739
  } else {
4498
4740
  for (const m of results) {
4499
- console.log(chalk8.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
4741
+ console.log(chalk9.gray(` [${m.type}] ${m.content.slice(0, 100)}`));
4500
4742
  }
4501
4743
  console.log("");
4502
4744
  }
4503
4745
  } catch (err) {
4504
- console.log(chalk8.red(` Error: ${err.message}
4746
+ console.log(chalk9.red(` Error: ${err.message}
4505
4747
  `));
4506
4748
  }
4507
4749
  rl.prompt();
@@ -4513,10 +4755,10 @@ Analyze the above input.`;
4513
4755
  for (const m of memories) {
4514
4756
  await deleteMemory(config.projectRoot, m.id);
4515
4757
  }
4516
- console.log(chalk8.yellow(` Cleared ${memories.length} memories.
4758
+ console.log(chalk9.yellow(` Cleared ${memories.length} memories.
4517
4759
  `));
4518
4760
  } catch (err) {
4519
- console.log(chalk8.red(` Error: ${err.message}
4761
+ console.log(chalk9.red(` Error: ${err.message}
4520
4762
  `));
4521
4763
  }
4522
4764
  rl.prompt();
@@ -4544,27 +4786,27 @@ Analyze the above input.`;
4544
4786
  }
4545
4787
  if (input === "/ralph run") {
4546
4788
  if (!ralphPlan) {
4547
- console.log(chalk8.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
4789
+ console.log(chalk9.gray(" No Ralph plan. Use /ralph plan <goal> first.\n"));
4548
4790
  rl.prompt();
4549
4791
  return;
4550
4792
  }
4551
- console.log(chalk8.green(" Ralph is running...\n"));
4793
+ console.log(chalk9.green(" Ralph is running...\n"));
4552
4794
  try {
4553
4795
  ralphPlan = await runRalphLoop(ralphPlan, {
4554
4796
  model,
4555
4797
  systemPrompt,
4556
4798
  toolContext: toolCtx,
4557
4799
  contextWindow: MODEL_CATALOG[activeModelId].contextWindow,
4558
- onTaskStart: (task) => console.log(chalk8.cyan(` \u25B6 Task: ${task.description}`)),
4559
- onTaskComplete: (task) => console.log(chalk8.green(` \u2713 Done: ${task.description}
4800
+ onTaskStart: (task) => console.log(chalk9.cyan(` \u25B6 Task: ${task.description}`)),
4801
+ onTaskComplete: (task) => console.log(chalk9.green(` \u2713 Done: ${task.description}
4560
4802
  `)),
4561
- onTaskFail: (task, err) => console.log(chalk8.red(` \u2717 Failed: ${task.description} (${err})
4803
+ onTaskFail: (task, err) => console.log(chalk9.red(` \u2717 Failed: ${task.description} (${err})
4562
4804
  `))
4563
4805
  });
4564
4806
  await savePlan(config.projectRoot, ralphPlan);
4565
4807
  console.log(formatRalphStatus(ralphPlan));
4566
4808
  } catch (err) {
4567
- console.log(chalk8.red(` Ralph error: ${err.message}
4809
+ console.log(chalk9.red(` Ralph error: ${err.message}
4568
4810
  `));
4569
4811
  }
4570
4812
  rl.prompt();
@@ -4572,7 +4814,7 @@ Analyze the above input.`;
4572
4814
  }
4573
4815
  if (input === "/ralph status") {
4574
4816
  if (!ralphPlan) {
4575
- console.log(chalk8.gray(" No Ralph plan active.\n"));
4817
+ console.log(chalk9.gray(" No Ralph plan active.\n"));
4576
4818
  } else {
4577
4819
  console.log(formatRalphStatus(ralphPlan));
4578
4820
  }
@@ -4583,26 +4825,26 @@ Analyze the above input.`;
4583
4825
  ralphPlan = null;
4584
4826
  await deletePlan(config.projectRoot).catch(() => {
4585
4827
  });
4586
- console.log(chalk8.gray(" Ralph plan cleared.\n"));
4828
+ console.log(chalk9.gray(" Ralph plan cleared.\n"));
4587
4829
  rl.prompt();
4588
4830
  return;
4589
4831
  }
4590
4832
  if (input === "/branch") {
4591
4833
  const branchId = `branch-${branches.size + 1}`;
4592
4834
  branches.set(branchId, [...messages]);
4593
- console.log(chalk8.green(` Forked conversation as "${branchId}" (${messages.length} messages)
4835
+ console.log(chalk9.green(` Forked conversation as "${branchId}" (${messages.length} messages)
4594
4836
  `));
4595
4837
  rl.prompt();
4596
4838
  return;
4597
4839
  }
4598
4840
  if (input === "/branches") {
4599
4841
  if (branches.size === 0) {
4600
- console.log(chalk8.gray(" No conversation branches. Use /branch to fork.\n"));
4842
+ console.log(chalk9.gray(" No conversation branches. Use /branch to fork.\n"));
4601
4843
  } else {
4602
- console.log(chalk8.gray("\n Branches:\n"));
4844
+ console.log(chalk9.gray("\n Branches:\n"));
4603
4845
  for (const [name, msgs] of branches) {
4604
- const marker = name === currentBranch ? chalk8.green(" \u25CF") : " ";
4605
- console.log(chalk8.gray(` ${marker} ${name} (${msgs.length} messages)`));
4846
+ const marker = name === currentBranch ? chalk9.green(" \u25CF") : " ";
4847
+ console.log(chalk9.gray(` ${marker} ${name} (${msgs.length} messages)`));
4606
4848
  }
4607
4849
  console.log("");
4608
4850
  }
@@ -4639,8 +4881,8 @@ Analyze the above input.`;
4639
4881
  return;
4640
4882
  }
4641
4883
  if (input.startsWith("/")) {
4642
- console.log(chalk8.red(` Unknown command: ${input}`));
4643
- console.log(chalk8.gray(" Type /help for available commands.\n"));
4884
+ console.log(chalk9.red(` Unknown command: ${input}`));
4885
+ console.log(chalk9.gray(" Type /help for available commands.\n"));
4644
4886
  rl.prompt();
4645
4887
  return;
4646
4888
  }
@@ -4648,7 +4890,7 @@ Analyze the above input.`;
4648
4890
  const refContext = formatReferences(references);
4649
4891
  const finalPrompt = refContext + cleanInput;
4650
4892
  if (references.length > 0) {
4651
- console.log(chalk8.gray(` Injected ${references.length} reference(s)`));
4893
+ console.log(chalk9.gray(` Injected ${references.length} reference(s)`));
4652
4894
  }
4653
4895
  messages.push({ role: "user", content: finalPrompt });
4654
4896
  const spinner = ora("Thinking...").start();
@@ -4674,16 +4916,16 @@ Analyze the above input.`;
4674
4916
  const val = String(v);
4675
4917
  return `${k}=${val.length > 60 ? val.slice(0, 60) + "..." : val}`;
4676
4918
  }).join(", ");
4677
- console.log(chalk8.gray(`
4919
+ console.log(chalk9.gray(`
4678
4920
  \u2192 ${name}(${argSummary})`));
4679
4921
  },
4680
4922
  onToolResult: (_name, result, isError) => {
4681
4923
  const preview = result.slice(0, 100).replace(/\n/g, " ");
4682
- const icon = isError ? chalk8.red("\u2717") : chalk8.green("\u2713");
4683
- console.log(chalk8.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4924
+ const icon = isError ? chalk9.red("\u2717") : chalk9.green("\u2713");
4925
+ console.log(chalk9.gray(` ${icon} ${preview}${result.length > 100 ? "..." : ""}`));
4684
4926
  },
4685
4927
  onCompress: () => {
4686
- console.log(chalk8.yellow("\n [Context compressed to fit window]\n"));
4928
+ console.log(chalk9.yellow("\n [Context compressed to fit window]\n"));
4687
4929
  }
4688
4930
  })
4689
4931
  );
@@ -4697,7 +4939,7 @@ Analyze the above input.`;
4697
4939
  model: activeModelId
4698
4940
  });
4699
4941
  costTracker.record(activeModelId, response.usage.promptTokens, response.usage.completionTokens);
4700
- console.log(usage.formatLast());
4942
+ console.log(usage.formatLast() + " " + costTracker.formatLastCost());
4701
4943
  const currentTokens = estimateTokens(messages);
4702
4944
  const ctxWindow = MODEL_CATALOG[activeModelId].contextWindow;
4703
4945
  if (currentTokens > ctxWindow * 0.5) {
@@ -4716,7 +4958,7 @@ Analyze the above input.`;
4716
4958
  type: "auto",
4717
4959
  content: memMatch[1]
4718
4960
  });
4719
- console.log(chalk8.gray(" (Saved to memory)\n"));
4961
+ console.log(chalk9.gray(" (Saved to memory)\n"));
4720
4962
  } catch {
4721
4963
  }
4722
4964
  }
@@ -4726,13 +4968,13 @@ Analyze the above input.`;
4726
4968
  checkpoints.discard();
4727
4969
  const msg = err.message?.toLowerCase() ?? "";
4728
4970
  if (msg.includes("fetch") || msg.includes("econnrefused") || msg.includes("network")) {
4729
- console.log(chalk8.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
4971
+ console.log(chalk9.gray(" Tip: Check that your Notch endpoint is running. Use /status to verify.\n"));
4730
4972
  } else if (msg.includes("401") || msg.includes("unauthorized") || msg.includes("api key")) {
4731
- console.log(chalk8.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
4973
+ console.log(chalk9.gray(" Tip: Set NOTCH_API_KEY env var or use --api-key flag.\n"));
4732
4974
  } else if (msg.includes("429") || msg.includes("rate limit")) {
4733
- console.log(chalk8.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
4975
+ console.log(chalk9.gray(" Tip: Rate limited. Wait a moment and try again.\n"));
4734
4976
  } else {
4735
- console.log(chalk8.gray(" (The conversation history is preserved. Try again.)\n"));
4977
+ console.log(chalk9.gray(" (The conversation history is preserved. Try again.)\n"));
4736
4978
  }
4737
4979
  }
4738
4980
  rl.prompt();
@@ -4750,13 +4992,13 @@ async function handleRalphSubcommand(args, cliOpts) {
4750
4992
  cwd: config.projectRoot,
4751
4993
  requireConfirm: false,
4752
4994
  confirm: async () => true,
4753
- log: (msg) => console.log(chalk8.gray(` ${msg}`))
4995
+ log: (msg) => console.log(chalk9.gray(` ${msg}`))
4754
4996
  };
4755
4997
  const subcommand = args[0];
4756
4998
  if (subcommand === "plan") {
4757
4999
  const goal = args.slice(1).join(" ");
4758
5000
  if (!goal) {
4759
- console.error(chalk8.red(" Usage: notch ralph plan <goal>"));
5001
+ console.error(chalk9.red(" Usage: notch ralph plan <goal>"));
4760
5002
  process.exit(1);
4761
5003
  }
4762
5004
  const spinner = ora("Ralph is planning...").start();
@@ -4767,7 +5009,7 @@ async function handleRalphSubcommand(args, cliOpts) {
4767
5009
  } else if (subcommand === "run") {
4768
5010
  let plan = await loadPlan(config.projectRoot);
4769
5011
  if (!plan) {
4770
- console.error(chalk8.red(" No plan found. Run: notch ralph plan <goal>"));
5012
+ console.error(chalk9.red(" No plan found. Run: notch ralph plan <goal>"));
4771
5013
  process.exit(1);
4772
5014
  }
4773
5015
  plan = await runRalphLoop(plan, {
@@ -4775,27 +5017,27 @@ async function handleRalphSubcommand(args, cliOpts) {
4775
5017
  systemPrompt,
4776
5018
  toolContext: toolCtx,
4777
5019
  contextWindow: MODEL_CATALOG[config.models.chat.model].contextWindow,
4778
- onTaskStart: (t) => console.log(chalk8.cyan(` \u25B6 ${t.description}`)),
4779
- onTaskComplete: (t) => console.log(chalk8.green(` \u2713 ${t.description}`)),
4780
- onTaskFail: (t, e) => console.log(chalk8.red(` \u2717 ${t.description}: ${e}`))
5020
+ onTaskStart: (t) => console.log(chalk9.cyan(` \u25B6 ${t.description}`)),
5021
+ onTaskComplete: (t) => console.log(chalk9.green(` \u2713 ${t.description}`)),
5022
+ onTaskFail: (t, e) => console.log(chalk9.red(` \u2717 ${t.description}: ${e}`))
4781
5023
  });
4782
5024
  await savePlan(config.projectRoot, plan);
4783
5025
  console.log(formatRalphStatus(plan));
4784
5026
  } else if (subcommand === "status") {
4785
5027
  const plan = await loadPlan(config.projectRoot);
4786
5028
  if (!plan) {
4787
- console.log(chalk8.gray(" No Ralph plan found."));
5029
+ console.log(chalk9.gray(" No Ralph plan found."));
4788
5030
  } else {
4789
5031
  console.log(formatRalphStatus(plan));
4790
5032
  }
4791
5033
  } else {
4792
- console.error(chalk8.red(` Unknown: notch ralph ${subcommand}`));
4793
- console.error(chalk8.gray(" Usage: notch ralph <plan|run|status>"));
5034
+ console.error(chalk9.red(` Unknown: notch ralph ${subcommand}`));
5035
+ console.error(chalk9.gray(" Usage: notch ralph <plan|run|status>"));
4794
5036
  process.exit(1);
4795
5037
  }
4796
5038
  }
4797
5039
  main().catch((err) => {
4798
- console.error(chalk8.red(`
5040
+ console.error(chalk9.red(`
4799
5041
  Fatal: ${err.message}
4800
5042
  `));
4801
5043
  process.exit(1);