whale-code 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/bin/swagmanager-mcp.js +7 -0
  2. package/dist/cli/app.js +30 -2
  3. package/dist/cli/chat/ChatApp.d.ts +4 -4
  4. package/dist/cli/chat/ChatApp.js +114 -44
  5. package/dist/cli/chat/ChatInput.d.ts +13 -6
  6. package/dist/cli/chat/ChatInput.js +433 -89
  7. package/dist/cli/chat/MemoryManager.d.ts +15 -0
  8. package/dist/cli/chat/MemoryManager.js +61 -0
  9. package/dist/cli/chat/MessageList.d.ts +8 -0
  10. package/dist/cli/chat/MessageList.js +1 -1
  11. package/dist/cli/chat/NodeManager.d.ts +30 -0
  12. package/dist/cli/chat/NodeManager.js +89 -0
  13. package/dist/cli/chat/NodeSelector.d.ts +19 -0
  14. package/dist/cli/chat/NodeSelector.js +37 -0
  15. package/dist/cli/chat/PlanApproval.d.ts +17 -0
  16. package/dist/cli/chat/PlanApproval.js +82 -0
  17. package/dist/cli/chat/SessionManager.d.ts +16 -0
  18. package/dist/cli/chat/SessionManager.js +43 -0
  19. package/dist/cli/chat/SlashMenu.d.ts +38 -0
  20. package/dist/cli/chat/SlashMenu.js +208 -0
  21. package/dist/cli/chat/StatusBar.d.ts +16 -0
  22. package/dist/cli/chat/StatusBar.js +22 -0
  23. package/dist/cli/chat/ThemeSelector.d.ts +14 -0
  24. package/dist/cli/chat/ThemeSelector.js +29 -0
  25. package/dist/cli/chat/ToolIndicator.d.ts +8 -0
  26. package/dist/cli/chat/ToolIndicator.js +33 -9
  27. package/dist/cli/chat/hooks/useAgentLoop.d.ts +2 -1
  28. package/dist/cli/chat/hooks/useAgentLoop.js +22 -17
  29. package/dist/cli/chat/hooks/useSlashCommands.d.ts +19 -0
  30. package/dist/cli/chat/hooks/useSlashCommands.js +254 -15
  31. package/dist/cli/commands/config-cmd.js +4 -25
  32. package/dist/cli/commands/db.d.ts +13 -0
  33. package/dist/cli/commands/db.js +243 -0
  34. package/dist/cli/commands/doctor.js +6 -9
  35. package/dist/cli/commands/mcp.js +1 -20
  36. package/dist/cli/services/agent-events.d.ts +22 -1
  37. package/dist/cli/services/agent-events.js +9 -0
  38. package/dist/cli/services/agent-loop.js +66 -2
  39. package/dist/cli/services/agent-worker-base.js +21 -6
  40. package/dist/cli/services/api-retry.d.ts +25 -0
  41. package/dist/cli/services/api-retry.js +91 -0
  42. package/dist/cli/services/auth-service.d.ts +1 -1
  43. package/dist/cli/services/auth-service.js +40 -19
  44. package/dist/cli/services/background-processes.js +26 -2
  45. package/dist/cli/services/config-store.d.ts +13 -1
  46. package/dist/cli/services/config-store.js +116 -13
  47. package/dist/cli/services/format-server-response.js +12 -6
  48. package/dist/cli/services/ink-resize-fix.d.ts +18 -0
  49. package/dist/cli/services/ink-resize-fix.js +66 -0
  50. package/dist/cli/services/interactive-tools.d.ts +14 -0
  51. package/dist/cli/services/interactive-tools.js +47 -2
  52. package/dist/cli/services/keybinding-manager.js +1 -1
  53. package/dist/cli/services/local-tools.js +35 -2
  54. package/dist/cli/services/server-tools.js +175 -3
  55. package/dist/cli/services/subagent.js +15 -3
  56. package/dist/cli/services/system-prompt.js +5 -3
  57. package/dist/cli/services/task-decomposer.d.ts +35 -0
  58. package/dist/cli/services/task-decomposer.js +199 -0
  59. package/dist/cli/services/team-lead.d.ts +18 -0
  60. package/dist/cli/services/team-lead.js +80 -0
  61. package/dist/cli/services/teammate.js +5 -5
  62. package/dist/cli/services/telemetry.d.ts +8 -2
  63. package/dist/cli/services/telemetry.js +116 -92
  64. package/dist/cli/services/tools/agent-tools.d.ts +1 -0
  65. package/dist/cli/services/tools/agent-tools.js +50 -4
  66. package/dist/cli/services/tools/file-ops.d.ts +2 -0
  67. package/dist/cli/services/tools/file-ops.js +71 -19
  68. package/dist/cli/services/tools/shell-exec.js +22 -12
  69. package/dist/cli/shared/Theme.d.ts +1 -2
  70. package/dist/cli/shared/Theme.js +1 -1
  71. package/dist/cli/shared/WhaleBanner.d.ts +4 -1
  72. package/dist/cli/shared/WhaleBanner.js +12 -8
  73. package/dist/cli/shared/markdown.d.ts +5 -4
  74. package/dist/cli/shared/markdown.js +376 -334
  75. package/dist/cli/shared/theme-manager.d.ts +27 -0
  76. package/dist/cli/shared/theme-manager.js +178 -0
  77. package/dist/cli/shared/theme-presets.d.ts +16 -0
  78. package/dist/cli/shared/theme-presets.js +265 -0
  79. package/dist/index.js +0 -51
  80. package/dist/node/adapters/imessage.d.ts +10 -0
  81. package/dist/node/adapters/imessage.js +45 -6
  82. package/dist/node/cli.js +459 -8
  83. package/dist/node/config.d.ts +17 -0
  84. package/dist/node/gateway-client.d.ts +55 -0
  85. package/dist/node/gateway-client.js +201 -0
  86. package/dist/node/portal/clipboard.d.ts +28 -0
  87. package/dist/node/portal/clipboard.js +183 -0
  88. package/dist/node/portal/discovery.d.ts +29 -0
  89. package/dist/node/portal/discovery.js +61 -0
  90. package/dist/node/portal/forward.d.ts +30 -0
  91. package/dist/node/portal/forward.js +90 -0
  92. package/dist/node/portal/index.d.ts +47 -0
  93. package/dist/node/portal/index.js +250 -0
  94. package/dist/node/portal/multiplexer.d.ts +48 -0
  95. package/dist/node/portal/multiplexer.js +207 -0
  96. package/dist/node/portal/permissions.d.ts +36 -0
  97. package/dist/node/portal/permissions.js +131 -0
  98. package/dist/node/portal/protocol.d.ts +140 -0
  99. package/dist/node/portal/protocol.js +193 -0
  100. package/dist/node/portal/screen.d.ts +18 -0
  101. package/dist/node/portal/screen.js +93 -0
  102. package/dist/node/portal/session.d.ts +68 -0
  103. package/dist/node/portal/session.js +127 -0
  104. package/dist/node/portal/shell.d.ts +26 -0
  105. package/dist/node/portal/shell.js +142 -0
  106. package/dist/node/portal/stream.d.ts +43 -0
  107. package/dist/node/portal/stream.js +90 -0
  108. package/dist/node/portal/transfer.d.ts +33 -0
  109. package/dist/node/portal/transfer.js +231 -0
  110. package/dist/node/portal/ui.d.ts +16 -0
  111. package/dist/node/portal/ui.js +148 -0
  112. package/dist/node/remote-desktop/compile-helper.d.ts +13 -0
  113. package/dist/node/remote-desktop/compile-helper.js +73 -0
  114. package/dist/node/remote-desktop/index.d.ts +67 -0
  115. package/dist/node/remote-desktop/index.js +220 -0
  116. package/dist/node/remote-desktop/protocol.d.ts +96 -0
  117. package/dist/node/remote-desktop/protocol.js +67 -0
  118. package/dist/node/runtime.d.ts +8 -1
  119. package/dist/node/runtime.js +117 -9
  120. package/dist/server/handlers/__test-utils__/test-db.d.ts +25 -0
  121. package/dist/server/handlers/__test-utils__/test-db.js +128 -0
  122. package/dist/server/handlers/api-keys.js +26 -2
  123. package/dist/server/handlers/browser.d.ts +0 -4
  124. package/dist/server/handlers/browser.js +0 -46
  125. package/dist/server/handlers/catalog.js +37 -14
  126. package/dist/server/handlers/clickhouse.d.ts +10 -0
  127. package/dist/server/handlers/clickhouse.js +215 -0
  128. package/dist/server/handlers/comms.d.ts +308 -4
  129. package/dist/server/handlers/comms.js +444 -11
  130. package/dist/server/handlers/creations.js +1 -1
  131. package/dist/server/handlers/crm.d.ts +54 -8
  132. package/dist/server/handlers/crm.js +353 -68
  133. package/dist/server/handlers/embeddings.js +3 -3
  134. package/dist/server/handlers/enrichment.js +39 -55
  135. package/dist/server/handlers/inventory.js +1 -1
  136. package/dist/server/handlers/kali.d.ts +9 -1
  137. package/dist/server/handlers/kali.js +50 -1
  138. package/dist/server/handlers/media.d.ts +8 -0
  139. package/dist/server/handlers/media.js +902 -0
  140. package/dist/server/handlers/meta-ads.js +6 -3
  141. package/dist/server/handlers/nodes.d.ts +2 -0
  142. package/dist/server/handlers/nodes.js +331 -40
  143. package/dist/server/handlers/operations.d.ts +4 -6
  144. package/dist/server/handlers/operations.js +99 -38
  145. package/dist/server/handlers/platform.js +224 -107
  146. package/dist/server/handlers/remove-bg.d.ts +6 -0
  147. package/dist/server/handlers/remove-bg.js +96 -0
  148. package/dist/server/handlers/storefront.d.ts +6 -0
  149. package/dist/server/handlers/storefront.js +477 -0
  150. package/dist/server/handlers/supply-chain.js +21 -3
  151. package/dist/server/handlers/workflow-steps.js +87 -31
  152. package/dist/server/handlers/workflows.js +4 -1
  153. package/dist/server/index.js +334 -88
  154. package/dist/server/lib/clickhouse-buffer.d.ts +48 -0
  155. package/dist/server/lib/clickhouse-buffer.js +175 -0
  156. package/dist/server/lib/clickhouse-client.d.ts +112 -0
  157. package/dist/server/lib/clickhouse-client.js +141 -0
  158. package/dist/server/lib/coa-renderer.d.ts +91 -0
  159. package/dist/server/lib/coa-renderer.js +411 -0
  160. package/dist/server/lib/compaction-service.js +45 -1
  161. package/dist/server/lib/pdf-renderer.d.ts +143 -0
  162. package/dist/server/lib/pdf-renderer.js +867 -0
  163. package/dist/server/lib/react-pdf-layout.d.ts +40 -0
  164. package/dist/server/lib/react-pdf-layout.js +437 -0
  165. package/dist/server/lib/server-agent-loop.d.ts +2 -0
  166. package/dist/server/lib/server-agent-loop.js +61 -15
  167. package/dist/server/lib/server-subagent.d.ts +3 -0
  168. package/dist/server/lib/server-subagent.js +7 -4
  169. package/dist/server/lib/supabase-client.js +51 -3
  170. package/dist/server/lib/template-resolver.js +14 -4
  171. package/dist/server/lib/utils.js +15 -0
  172. package/dist/server/local-agent-gateway.d.ts +44 -0
  173. package/dist/server/local-agent-gateway.js +389 -49
  174. package/dist/server/providers/anthropic.js +12 -2
  175. package/dist/server/providers/gemini.js +17 -2
  176. package/dist/server/proxy-handlers.js +151 -0
  177. package/dist/server/tool-router.d.ts +2 -2
  178. package/dist/server/tool-router.js +25 -35
  179. package/dist/shared/agent-core.d.ts +5 -2
  180. package/dist/shared/agent-core.js +30 -4
  181. package/dist/shared/api-client.js +54 -3
  182. package/dist/shared/sse-parser.d.ts +1 -1
  183. package/dist/shared/sse-parser.js +5 -2
  184. package/dist/shared/tool-dispatch.js +1 -1
  185. package/package.json +16 -10
  186. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +0 -11
  187. package/dist/server/handlers/__test-utils__/mock-supabase.js +0 -393
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Theme Manager — shiki syntax highlighting + theme switching
3
+ *
4
+ * Synchronous shiki highlighter using the JavaScript regex engine.
5
+ * Manages active theme state and persists preference to config store.
6
+ */
7
+ import { type ThemePreset } from "./theme-presets.js";
8
+ export type { ThemePreset } from "./theme-presets.js";
9
+ /**
10
+ * Highlight code synchronously using shiki.
11
+ * Returns ANSI-colored string. Falls back to plain text for unknown languages.
12
+ */
13
+ export declare function highlightCode(code: string, lang: string): string;
14
+ export declare function setRebuildCallback(cb: () => void): void;
15
+ /**
16
+ * Switch the active theme. Mutates `colors` in place so all importers see the change.
17
+ * Returns true if theme was found and applied.
18
+ */
19
+ export declare function switchTheme(presetId: string): boolean;
20
+ /** Get the current theme ID */
21
+ export declare function getCurrentThemeId(): string;
22
+ /** Get all available theme presets */
23
+ export declare function getThemePresets(): ThemePreset[];
24
+ /**
25
+ * Initialize theme from saved preference. Call once at startup.
26
+ */
27
+ export declare function initTheme(): void;
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Theme Manager — shiki syntax highlighting + theme switching
3
+ *
4
+ * Synchronous shiki highlighter using the JavaScript regex engine.
5
+ * Manages active theme state and persists preference to config store.
6
+ */
7
+ import chalk from "chalk";
8
+ import { createHighlighterCoreSync } from "shiki/core";
9
+ import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
10
+ // Bundled themes (tree-shaken imports)
11
+ import catppuccinMocha from "shiki/themes/catppuccin-mocha.mjs";
12
+ import tokyoNight from "shiki/themes/tokyo-night.mjs";
13
+ import dracula from "shiki/themes/dracula.mjs";
14
+ import nord from "shiki/themes/nord.mjs";
15
+ import oneDarkPro from "shiki/themes/one-dark-pro.mjs";
16
+ // Bundled languages (tree-shaken imports)
17
+ import langTypescript from "shiki/langs/typescript.mjs";
18
+ import langJavascript from "shiki/langs/javascript.mjs";
19
+ import langTsx from "shiki/langs/tsx.mjs";
20
+ import langJsx from "shiki/langs/jsx.mjs";
21
+ import langPython from "shiki/langs/python.mjs";
22
+ import langJson from "shiki/langs/json.mjs";
23
+ import langBash from "shiki/langs/bash.mjs";
24
+ import langCss from "shiki/langs/css.mjs";
25
+ import langHtml from "shiki/langs/html.mjs";
26
+ import langXml from "shiki/langs/xml.mjs";
27
+ import langRust from "shiki/langs/rust.mjs";
28
+ import langGo from "shiki/langs/go.mjs";
29
+ import langSql from "shiki/langs/sql.mjs";
30
+ import langYaml from "shiki/langs/yaml.mjs";
31
+ import langToml from "shiki/langs/toml.mjs";
32
+ import langMarkdown from "shiki/langs/markdown.mjs";
33
+ import langSwift from "shiki/langs/swift.mjs";
34
+ import langDiff from "shiki/langs/diff.mjs";
35
+ import { colors } from "./Theme.js";
36
+ import { THEME_PRESETS, DEFAULT_THEME_ID, getPresetById } from "./theme-presets.js";
37
+ import { loadPreferences, savePreferences } from "../services/config-store.js";
38
+ // ============================================================================
39
+ // Shiki highlighter — created synchronously at module load
40
+ // ============================================================================
41
+ const allThemes = [catppuccinMocha, tokyoNight, dracula, nord, oneDarkPro];
42
+ const allLangs = [
43
+ langTypescript, langJavascript, langTsx, langJsx,
44
+ langPython, langJson, langBash, langCss, langHtml, langXml,
45
+ langRust, langGo, langSql, langYaml, langToml,
46
+ langMarkdown, langSwift, langDiff,
47
+ ];
48
+ const highlighter = createHighlighterCoreSync({
49
+ themes: [...allThemes],
50
+ langs: [...allLangs],
51
+ engine: createJavaScriptRegexEngine(),
52
+ });
53
+ // Set of known language IDs for fast lookup
54
+ const KNOWN_LANGS = new Set(highlighter.getLoadedLanguages());
55
+ // Map common aliases to shiki language IDs
56
+ const LANG_ALIASES = {
57
+ ts: "typescript",
58
+ js: "javascript",
59
+ py: "python",
60
+ rb: "ruby",
61
+ sh: "bash",
62
+ shell: "bash",
63
+ zsh: "bash",
64
+ yml: "yaml",
65
+ md: "markdown",
66
+ rs: "rust",
67
+ htm: "html",
68
+ jsonc: "json",
69
+ terminal: "bash",
70
+ };
71
+ // ============================================================================
72
+ // State
73
+ // ============================================================================
74
+ let currentThemeId = DEFAULT_THEME_ID;
75
+ // ============================================================================
76
+ // Syntax highlighting
77
+ // ============================================================================
78
+ /**
79
+ * Highlight code synchronously using shiki.
80
+ * Returns ANSI-colored string. Falls back to plain text for unknown languages.
81
+ */
82
+ export function highlightCode(code, lang) {
83
+ // Resolve language alias
84
+ const resolvedLang = LANG_ALIASES[lang] || lang;
85
+ if (!resolvedLang || !KNOWN_LANGS.has(resolvedLang)) {
86
+ return code; // plain text fallback
87
+ }
88
+ const preset = getPresetById(currentThemeId);
89
+ const shikiTheme = preset?.shikiTheme || "catppuccin-mocha";
90
+ try {
91
+ const result = highlighter.codeToTokens(code, {
92
+ lang: resolvedLang,
93
+ theme: shikiTheme,
94
+ });
95
+ // Convert tokens to chalk ANSI
96
+ const lines = [];
97
+ for (const tokenLine of result.tokens) {
98
+ const parts = [];
99
+ for (const token of tokenLine) {
100
+ if (token.color) {
101
+ let styled = chalk.hex(token.color)(token.content);
102
+ // Apply font styles
103
+ if (token.fontStyle) {
104
+ if (token.fontStyle & 1)
105
+ styled = chalk.italic(chalk.hex(token.color)(token.content));
106
+ if (token.fontStyle & 2)
107
+ styled = chalk.bold(chalk.hex(token.color)(token.content));
108
+ }
109
+ parts.push(styled);
110
+ }
111
+ else {
112
+ parts.push(token.content);
113
+ }
114
+ }
115
+ lines.push(parts.join(""));
116
+ }
117
+ return lines.join("\n");
118
+ }
119
+ catch {
120
+ return code; // fallback on any error
121
+ }
122
+ }
123
+ // ============================================================================
124
+ // Theme switching
125
+ // ============================================================================
126
+ /** Rebuild callback — set by markdown.ts to rebuild chalk instances */
127
+ let rebuildCallback = null;
128
+ export function setRebuildCallback(cb) {
129
+ rebuildCallback = cb;
130
+ }
131
+ /**
132
+ * Switch the active theme. Mutates `colors` in place so all importers see the change.
133
+ * Returns true if theme was found and applied.
134
+ */
135
+ export function switchTheme(presetId) {
136
+ const preset = getPresetById(presetId);
137
+ if (!preset)
138
+ return false;
139
+ currentThemeId = preset.id;
140
+ // Mutate colors in place — all modules import the same object
141
+ Object.assign(colors, preset.chrome);
142
+ // Persist preference
143
+ try {
144
+ const prefs = loadPreferences();
145
+ prefs.theme = preset.id;
146
+ savePreferences(prefs);
147
+ }
148
+ catch { /* best effort */ }
149
+ // Rebuild markdown renderer chalk instances
150
+ if (rebuildCallback)
151
+ rebuildCallback();
152
+ return true;
153
+ }
154
+ /** Get the current theme ID */
155
+ export function getCurrentThemeId() {
156
+ return currentThemeId;
157
+ }
158
+ /** Get all available theme presets */
159
+ export function getThemePresets() {
160
+ return THEME_PRESETS;
161
+ }
162
+ /**
163
+ * Initialize theme from saved preference. Call once at startup.
164
+ */
165
+ export function initTheme() {
166
+ try {
167
+ const prefs = loadPreferences();
168
+ if (prefs.theme) {
169
+ const preset = getPresetById(prefs.theme);
170
+ if (preset) {
171
+ currentThemeId = preset.id;
172
+ Object.assign(colors, preset.chrome);
173
+ // Rebuild will be called after markdown module loads
174
+ }
175
+ }
176
+ }
177
+ catch { /* use default */ }
178
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Theme Presets — bundled color themes for whale CLI
3
+ *
4
+ * Each preset defines:
5
+ * - shikiTheme: the shiki theme ID for syntax highlighting
6
+ * - chrome: full UI color overrides (all keys from DEFAULT_COLORS)
7
+ */
8
+ export interface ThemePreset {
9
+ id: string;
10
+ label: string;
11
+ shikiTheme: string;
12
+ chrome: Record<string, string>;
13
+ }
14
+ export declare const DEFAULT_THEME_ID = "catppuccin-mocha";
15
+ export declare const THEME_PRESETS: ThemePreset[];
16
+ export declare function getPresetById(id: string): ThemePreset | undefined;
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Theme Presets — bundled color themes for whale CLI
3
+ *
4
+ * Each preset defines:
5
+ * - shikiTheme: the shiki theme ID for syntax highlighting
6
+ * - chrome: full UI color overrides (all keys from DEFAULT_COLORS)
7
+ */
8
+ export const DEFAULT_THEME_ID = "catppuccin-mocha";
9
+ export const THEME_PRESETS = [
10
+ // ── Catppuccin Mocha — warm modern dark ──
11
+ {
12
+ id: "catppuccin-mocha",
13
+ label: "Catppuccin Mocha",
14
+ shikiTheme: "catppuccin-mocha",
15
+ chrome: {
16
+ brand: "#89B4FA",
17
+ brandDim: "#74C7EC",
18
+ success: "#A6E3A1",
19
+ error: "#F38BA8",
20
+ warning: "#FAB387",
21
+ info: "#94E2D5",
22
+ pink: "#F5C2E7",
23
+ purple: "#CBA6F7",
24
+ indigo: "#B4BEFE",
25
+ mint: "#94E2D5",
26
+ teal: "#89DCEB",
27
+ lavender: "#B4BEFE",
28
+ roseGold: "#F5C2E7",
29
+ text: "#CDD6F4",
30
+ secondary: "#A6ADC8",
31
+ tertiary: "#6C7086",
32
+ quaternary: "#45475A",
33
+ muted: "#A6ADC8",
34
+ dim: "#9399B2",
35
+ subtle: "#6C7086",
36
+ border: "#45475A",
37
+ user: "#CDD6F4",
38
+ assistant: "#CBA6F7",
39
+ tool: "#89B4FA",
40
+ localTool: "#CBA6F7",
41
+ serverTool: "#F5C2E7",
42
+ gain: "#A6E3A1",
43
+ loss: "#F38BA8",
44
+ panel: "#1E1E2E",
45
+ separator: "#45475A",
46
+ diffAddedBg: "#1E3A2F",
47
+ diffRemovedBg: "#3A1E2A",
48
+ diffWordAdded: "#2E5A45",
49
+ diffWordRemoved: "#5A2E3A",
50
+ },
51
+ },
52
+ // ── Tokyo Night — cool blue-toned ──
53
+ {
54
+ id: "tokyo-night",
55
+ label: "Tokyo Night",
56
+ shikiTheme: "tokyo-night",
57
+ chrome: {
58
+ brand: "#7AA2F7",
59
+ brandDim: "#3D59A1",
60
+ success: "#9ECE6A",
61
+ error: "#F7768E",
62
+ warning: "#E0AF68",
63
+ info: "#7DCFFF",
64
+ pink: "#F7768E",
65
+ purple: "#BB9AF7",
66
+ indigo: "#7AA2F7",
67
+ mint: "#73DACA",
68
+ teal: "#2AC3DE",
69
+ lavender: "#C0CAF5",
70
+ roseGold: "#FF9E64",
71
+ text: "#C0CAF5",
72
+ secondary: "#9AA5CE",
73
+ tertiary: "#565F89",
74
+ quaternary: "#3B4261",
75
+ muted: "#9AA5CE",
76
+ dim: "#787C99",
77
+ subtle: "#565F89",
78
+ border: "#3B4261",
79
+ user: "#C0CAF5",
80
+ assistant: "#BB9AF7",
81
+ tool: "#7AA2F7",
82
+ localTool: "#BB9AF7",
83
+ serverTool: "#F7768E",
84
+ gain: "#9ECE6A",
85
+ loss: "#F7768E",
86
+ panel: "#1A1B26",
87
+ separator: "#3B4261",
88
+ diffAddedBg: "#1A2E1F",
89
+ diffRemovedBg: "#2E1A22",
90
+ diffWordAdded: "#2A4A32",
91
+ diffWordRemoved: "#4A2A35",
92
+ },
93
+ },
94
+ // ── Dracula — bold purple-heavy ──
95
+ {
96
+ id: "dracula",
97
+ label: "Dracula",
98
+ shikiTheme: "dracula",
99
+ chrome: {
100
+ brand: "#BD93F9",
101
+ brandDim: "#6272A4",
102
+ success: "#50FA7B",
103
+ error: "#FF5555",
104
+ warning: "#FFB86C",
105
+ info: "#8BE9FD",
106
+ pink: "#FF79C6",
107
+ purple: "#BD93F9",
108
+ indigo: "#6272A4",
109
+ mint: "#8BE9FD",
110
+ teal: "#8BE9FD",
111
+ lavender: "#BD93F9",
112
+ roseGold: "#FF79C6",
113
+ text: "#F8F8F2",
114
+ secondary: "#BFBFBF",
115
+ tertiary: "#6272A4",
116
+ quaternary: "#44475A",
117
+ muted: "#BFBFBF",
118
+ dim: "#6272A4",
119
+ subtle: "#6272A4",
120
+ border: "#44475A",
121
+ user: "#F8F8F2",
122
+ assistant: "#BD93F9",
123
+ tool: "#8BE9FD",
124
+ localTool: "#BD93F9",
125
+ serverTool: "#FF79C6",
126
+ gain: "#50FA7B",
127
+ loss: "#FF5555",
128
+ panel: "#282A36",
129
+ separator: "#44475A",
130
+ diffAddedBg: "#1A3A1A",
131
+ diffRemovedBg: "#3A1A1A",
132
+ diffWordAdded: "#2E5A2E",
133
+ diffWordRemoved: "#5A2E2E",
134
+ },
135
+ },
136
+ // ── Nord — muted Scandinavian ──
137
+ {
138
+ id: "nord",
139
+ label: "Nord",
140
+ shikiTheme: "nord",
141
+ chrome: {
142
+ brand: "#81A1C1",
143
+ brandDim: "#5E81AC",
144
+ success: "#A3BE8C",
145
+ error: "#BF616A",
146
+ warning: "#EBCB8B",
147
+ info: "#88C0D0",
148
+ pink: "#B48EAD",
149
+ purple: "#B48EAD",
150
+ indigo: "#81A1C1",
151
+ mint: "#8FBCBB",
152
+ teal: "#88C0D0",
153
+ lavender: "#B48EAD",
154
+ roseGold: "#D08770",
155
+ text: "#ECEFF4",
156
+ secondary: "#D8DEE9",
157
+ tertiary: "#4C566A",
158
+ quaternary: "#3B4252",
159
+ muted: "#D8DEE9",
160
+ dim: "#616E88",
161
+ subtle: "#4C566A",
162
+ border: "#3B4252",
163
+ user: "#ECEFF4",
164
+ assistant: "#B48EAD",
165
+ tool: "#81A1C1",
166
+ localTool: "#B48EAD",
167
+ serverTool: "#D08770",
168
+ gain: "#A3BE8C",
169
+ loss: "#BF616A",
170
+ panel: "#2E3440",
171
+ separator: "#3B4252",
172
+ diffAddedBg: "#2A3A2A",
173
+ diffRemovedBg: "#3A2A2A",
174
+ diffWordAdded: "#3A4A3A",
175
+ diffWordRemoved: "#4A3A3A",
176
+ },
177
+ },
178
+ // ── One Dark Pro — VS Code classic ──
179
+ {
180
+ id: "one-dark-pro",
181
+ label: "One Dark Pro",
182
+ shikiTheme: "one-dark-pro",
183
+ chrome: {
184
+ brand: "#61AFEF",
185
+ brandDim: "#528BFF",
186
+ success: "#98C379",
187
+ error: "#E06C75",
188
+ warning: "#D19A66",
189
+ info: "#56B6C2",
190
+ pink: "#E06C75",
191
+ purple: "#C678DD",
192
+ indigo: "#61AFEF",
193
+ mint: "#56B6C2",
194
+ teal: "#56B6C2",
195
+ lavender: "#C678DD",
196
+ roseGold: "#E5C07B",
197
+ text: "#ABB2BF",
198
+ secondary: "#828997",
199
+ tertiary: "#5C6370",
200
+ quaternary: "#3E4452",
201
+ muted: "#828997",
202
+ dim: "#636D83",
203
+ subtle: "#5C6370",
204
+ border: "#3E4452",
205
+ user: "#ABB2BF",
206
+ assistant: "#C678DD",
207
+ tool: "#61AFEF",
208
+ localTool: "#C678DD",
209
+ serverTool: "#E06C75",
210
+ gain: "#98C379",
211
+ loss: "#E06C75",
212
+ panel: "#282C34",
213
+ separator: "#3E4452",
214
+ diffAddedBg: "#1E3A1E",
215
+ diffRemovedBg: "#3A1E1E",
216
+ diffWordAdded: "#2E5A2E",
217
+ diffWordRemoved: "#5A2E2E",
218
+ },
219
+ },
220
+ // ── Apple Dark — original whale CLI colors (preserved as-is) ──
221
+ {
222
+ id: "apple-dark",
223
+ label: "Apple Dark",
224
+ shikiTheme: "one-dark-pro", // closest VS Code match for syntax
225
+ chrome: {
226
+ brand: "#0A84FF",
227
+ brandDim: "#0071E3",
228
+ success: "#30D158",
229
+ error: "#FF453A",
230
+ warning: "#FF9F0A",
231
+ info: "#64D2FF",
232
+ pink: "#FF375F",
233
+ purple: "#BF5AF2",
234
+ indigo: "#5E5CE6",
235
+ mint: "#66D4CF",
236
+ teal: "#6AC4DC",
237
+ lavender: "#D4BBFF",
238
+ roseGold: "#FFB5C2",
239
+ text: "#F5F5F7",
240
+ secondary: "#A1A1A6",
241
+ tertiary: "#6E6E73",
242
+ quaternary: "#48484A",
243
+ muted: "#A1A1A6",
244
+ dim: "#86868B",
245
+ subtle: "#6E6E73",
246
+ border: "#38383A",
247
+ user: "#F5F5F7",
248
+ assistant: "#BF5AF2",
249
+ tool: "#0A84FF",
250
+ localTool: "#BF5AF2",
251
+ serverTool: "#FF375F",
252
+ gain: "#30D158",
253
+ loss: "#FF453A",
254
+ panel: "#1C1C1E",
255
+ separator: "#38383A",
256
+ diffAddedBg: "#005f00",
257
+ diffRemovedBg: "#5f0000",
258
+ diffWordAdded: "#008700",
259
+ diffWordRemoved: "#870000",
260
+ },
261
+ },
262
+ ];
263
+ export function getPresetById(id) {
264
+ return THEME_PRESETS.find((p) => p.id === id);
265
+ }
package/dist/index.js CHANGED
@@ -334,7 +334,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
334
334
  };
335
335
  }
336
336
  // ── Remote tool execution ──
337
- const startTime = Date.now();
338
337
  const traceId = crypto.randomUUID();
339
338
  // Allow tool-level store_id override (e.g. multi-store users switching context)
340
339
  const effectiveStoreId = toolArgs.store_id || STORE_ID || undefined;
@@ -343,9 +342,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
343
342
  }
344
343
  console.error(`[MCP] Executing: ${toolName} → Fly.io [${traceId.slice(0, 8)}] store=${effectiveStoreId?.slice(0, 8) || "none"}`);
345
344
  const result = await executeToolRemote(toolName, toolArgs, effectiveStoreId, traceId);
346
- const durationMs = Date.now() - startTime;
347
- // Fire-and-forget telemetry
348
- logMcpToolCall(toolName, toolArgs.action, result.success, durationMs, traceId, result.error).catch(() => { });
349
345
  if (result.success) {
350
346
  return {
351
347
  content: [{
@@ -366,53 +362,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
366
362
  };
367
363
  }
368
364
  });
369
- /**
370
- * Fire-and-forget MCP proxy telemetry — logs to audit_logs as a third service
371
- * ("mcp-proxy") alongside "whale-cli" and "agent-server", capturing the full
372
- * network round-trip latency from MCP client → Fly.io server → response.
373
- */
374
- async function logMcpToolCall(toolName, action, success, durationMs, traceId, error) {
375
- if (!supabase)
376
- return;
377
- try {
378
- const now = new Date();
379
- const startTime = new Date(now.getTime() - durationMs);
380
- const bytes = new Uint8Array(8);
381
- crypto.getRandomValues(bytes);
382
- const spanId = Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
383
- await supabase.from("audit_logs").insert({
384
- action: `mcp.tool.${toolName}${action ? `.${action}` : ""}`,
385
- severity: success ? "info" : "error",
386
- store_id: STORE_ID || null,
387
- user_id: USER_ID,
388
- user_email: USER_EMAIL,
389
- resource_type: "whale_code_mcp",
390
- resource_id: toolName,
391
- request_id: traceId,
392
- conversation_id: SESSION_ID,
393
- source: "whale_code_mcp",
394
- details: {
395
- session_id: SESSION_ID,
396
- source: "whale_code_mcp",
397
- tool_name: toolName,
398
- action: action || null,
399
- },
400
- error_message: error || null,
401
- duration_ms: durationMs,
402
- trace_id: traceId,
403
- span_id: spanId,
404
- span_kind: "CLIENT",
405
- service_name: "whale-code-mcp",
406
- service_version: PKG_VERSION,
407
- status_code: success ? "OK" : "ERROR",
408
- start_time: startTime.toISOString(),
409
- end_time: now.toISOString(),
410
- });
411
- }
412
- catch {
413
- // Telemetry must never break tool execution
414
- }
415
- }
416
365
  // ============================================================================
417
366
  // LOCAL AGENT (auto-spawned — shares this process with MCP server)
418
367
  // ============================================================================
@@ -9,9 +9,19 @@ export declare class IMessageAdapter extends BaseAdapter {
9
9
  readonly name: string;
10
10
  private process;
11
11
  private config;
12
+ private restartAttempts;
13
+ private maxRestartAttempts;
14
+ private lastError;
15
+ private restartTimer;
12
16
  constructor(name: string, config?: IMessageConfig);
13
17
  start(): Promise<void>;
14
18
  stop(): Promise<void>;
19
+ /** Manual restart — resets attempt counter and restarts */
20
+ restart(): void;
21
+ /** Get the last stderr error message */
22
+ getLastError(): string | null;
23
+ /** Get current restart attempt count */
24
+ getRestartAttempts(): number;
15
25
  sendMessage(msg: OutboundMessage): Promise<boolean>;
16
26
  private handleLine;
17
27
  }
@@ -1,10 +1,18 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
2
3
  import { BaseAdapter } from "./base.js";
4
+ // Resolve imsg binary — prefer /opt/homebrew/bin (macOS ARM) then fall back to PATH
5
+ const IMSG_BIN = existsSync("/opt/homebrew/bin/imsg") ? "/opt/homebrew/bin/imsg" : "imsg";
3
6
  export class IMessageAdapter extends BaseAdapter {
4
7
  type = "imessage";
5
8
  name;
6
9
  process = null;
7
10
  config;
11
+ // Restart resilience
12
+ restartAttempts = 0;
13
+ maxRestartAttempts = 5;
14
+ lastError = null;
15
+ restartTimer = null;
8
16
  constructor(name, config = {}) {
9
17
  super();
10
18
  this.name = name;
@@ -16,7 +24,7 @@ export class IMessageAdapter extends BaseAdapter {
16
24
  this.running = true;
17
25
  // Spawn imsg watch as a child process
18
26
  const args = ["watch", "--json"];
19
- this.process = spawn("imsg", args, { stdio: ["ignore", "pipe", "pipe"] });
27
+ this.process = spawn(IMSG_BIN, args, { stdio: ["ignore", "pipe", "pipe"] });
20
28
  let buffer = "";
21
29
  this.process.stdout?.on("data", (chunk) => {
22
30
  buffer += chunk.toString();
@@ -32,27 +40,56 @@ export class IMessageAdapter extends BaseAdapter {
32
40
  }
33
41
  });
34
42
  this.process.stderr?.on("data", (chunk) => {
35
- console.error(`[imessage] stderr:`, chunk.toString().trim());
43
+ const msg = chunk.toString().trim();
44
+ this.lastError = msg;
45
+ console.error(`[imessage] stderr:`, msg);
36
46
  });
37
47
  this.process.on("exit", (code) => {
38
48
  console.log(`[imessage] Process exited with code ${code}`);
39
49
  const wasRunning = this.running;
40
50
  this.running = false;
41
- // Auto-restart after 5s if unexpected exit while we were supposed to be running
51
+ // Auto-restart with exponential backoff if unexpected exit
42
52
  if (code !== 0 && wasRunning) {
43
- console.log(`[imessage] Restarting in 5s...`);
44
- setTimeout(() => this.start(), 5000);
53
+ if (this.restartAttempts >= this.maxRestartAttempts) {
54
+ console.error(`[imessage] Max restart attempts (${this.maxRestartAttempts}) reached. Giving up. Last error: ${this.lastError || "unknown"}`);
55
+ return;
56
+ }
57
+ this.restartAttempts++;
58
+ const delay = Math.min(5000 * Math.pow(2, this.restartAttempts - 1), 80_000);
59
+ console.log(`[imessage] Restarting in ${delay / 1000}s (attempt ${this.restartAttempts}/${this.maxRestartAttempts}, last error: ${this.lastError || "unknown"})...`);
60
+ this.restartTimer = setTimeout(() => {
61
+ this.restartTimer = null;
62
+ this.start();
63
+ }, delay);
45
64
  }
46
65
  });
47
66
  console.log(`[imessage] Watching for messages...`);
48
67
  }
49
68
  async stop() {
50
69
  this.running = false;
70
+ if (this.restartTimer) {
71
+ clearTimeout(this.restartTimer);
72
+ this.restartTimer = null;
73
+ }
51
74
  if (this.process) {
52
75
  this.process.kill("SIGTERM");
53
76
  this.process = null;
54
77
  }
55
78
  }
79
+ /** Manual restart — resets attempt counter and restarts */
80
+ restart() {
81
+ this.restartAttempts = 0;
82
+ this.lastError = null;
83
+ this.stop().then(() => this.start());
84
+ }
85
+ /** Get the last stderr error message */
86
+ getLastError() {
87
+ return this.lastError;
88
+ }
89
+ /** Get current restart attempt count */
90
+ getRestartAttempts() {
91
+ return this.restartAttempts;
92
+ }
56
93
  async sendMessage(msg) {
57
94
  try {
58
95
  const text = msg.content.substring(0, this.config.max_message_length || 4000);
@@ -61,7 +98,7 @@ export class IMessageAdapter extends BaseAdapter {
61
98
  console.error("[imessage] No chat_id to send to");
62
99
  return false;
63
100
  }
64
- const proc = spawn("imsg", ["send", "--chat", String(chatId), text]);
101
+ const proc = spawn(IMSG_BIN, ["send", "--chat-id", String(chatId), "--text", text]);
65
102
  return new Promise((resolve) => {
66
103
  proc.on("exit", (code) => {
67
104
  if (code === 0) {
@@ -109,6 +146,8 @@ export class IMessageAdapter extends BaseAdapter {
109
146
  attachments: data.attachments,
110
147
  },
111
148
  };
149
+ // Successful message receipt — reset restart counter
150
+ this.restartAttempts = 0;
112
151
  this.stats.messages_in++;
113
152
  this.stats.last_message_at = new Date().toISOString();
114
153
  if (this.onInboundMessage) {