@finchagentic/mcp 4.6.1 → 4.6.2

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 (58) hide show
  1. package/package.json +5 -6
  2. package/dist/_http-cache.js +0 -96
  3. package/dist/_text-search.js +0 -39
  4. package/dist/agent-loop.js +0 -301
  5. package/dist/annotations.js +0 -122
  6. package/dist/cli.js +0 -1391
  7. package/dist/clink-input.js +0 -15
  8. package/dist/config.js +0 -132
  9. package/dist/convex.js +0 -175
  10. package/dist/dex-pair.js +0 -54
  11. package/dist/enrichment-router.js +0 -315
  12. package/dist/index.js +0 -258
  13. package/dist/llm.js +0 -298
  14. package/dist/local-memory-file.js +0 -150
  15. package/dist/local-memory.js +0 -135
  16. package/dist/local-vault.js +0 -456
  17. package/dist/output-schemas.js +0 -605
  18. package/dist/project.js +0 -36
  19. package/dist/prompts.js +0 -111
  20. package/dist/public-url.js +0 -107
  21. package/dist/resources.js +0 -111
  22. package/dist/server.js +0 -322
  23. package/dist/signal-gate.js +0 -57
  24. package/dist/token-decimals.js +0 -26
  25. package/dist/token-gate.js +0 -88
  26. package/dist/tool-filter.js +0 -53
  27. package/dist/tools/_solidity-scan.js +0 -313
  28. package/dist/tools/agents.js +0 -441
  29. package/dist/tools/automation.js +0 -354
  30. package/dist/tools/base-mcp.js +0 -466
  31. package/dist/tools/base.js +0 -283
  32. package/dist/tools/chronicle.js +0 -268
  33. package/dist/tools/coder.js +0 -94
  34. package/dist/tools/deep-research.js +0 -1421
  35. package/dist/tools/defi.js +0 -292
  36. package/dist/tools/equity.js +0 -372
  37. package/dist/tools/events.js +0 -182
  38. package/dist/tools/github.js +0 -564
  39. package/dist/tools/insider.js +0 -264
  40. package/dist/tools/insight.js +0 -630
  41. package/dist/tools/market.js +0 -555
  42. package/dist/tools/memory.js +0 -1059
  43. package/dist/tools/miroshark.js +0 -350
  44. package/dist/tools/monitor.js +0 -319
  45. package/dist/tools/os.js +0 -236
  46. package/dist/tools/packets.js +0 -296
  47. package/dist/tools/research-chain.js +0 -226
  48. package/dist/tools/research-compare.js +0 -280
  49. package/dist/tools/research.js +0 -188
  50. package/dist/tools/rh-bridge.js +0 -148
  51. package/dist/tools/rh-mcp.js +0 -1448
  52. package/dist/tools/rh-orders.js +0 -556
  53. package/dist/tools/scanner.js +0 -564
  54. package/dist/tools/stake.js +0 -369
  55. package/dist/tools/vault.js +0 -1020
  56. package/dist/tools/wallet.js +0 -200
  57. package/dist/types.js +0 -2
  58. package/dist/wallet.js +0 -372
package/dist/cli.js DELETED
@@ -1,1391 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- const readline = __importStar(require("readline"));
38
- const fs = __importStar(require("fs"));
39
- const os = __importStar(require("os"));
40
- const path = __importStar(require("path"));
41
- const agent_loop_js_1 = require("./agent-loop.js");
42
- const server_js_1 = require("./server.js");
43
- const tool_filter_js_1 = require("./tool-filter.js");
44
- const config_js_1 = require("./config.js");
45
- const local_memory_js_1 = require("./local-memory.js");
46
- const clink_input_js_1 = require("./clink-input.js");
47
- const wallet_js_1 = require("./wallet.js");
48
- const child_process = __importStar(require("child_process"));
49
- (0, config_js_1.hydrateEnvFromConfig)();
50
- // Derived tool counts - single source of truth, kept in sync with the
51
- // actual registered tools. Updates here propagate to banner, login,
52
- // and doctor without manual edits.
53
- const TOTAL_TOOL_COUNT = server_js_1.ALL_TOOLS.length;
54
- const CORE_TOOL_COUNT = (() => {
55
- const prev = process.env.FINCH_TOOLS;
56
- try {
57
- process.env.FINCH_TOOLS = "core";
58
- return (0, tool_filter_js_1.filterTools)(server_js_1.ALL_TOOLS).length;
59
- }
60
- finally {
61
- if (prev === undefined)
62
- delete process.env.FINCH_TOOLS;
63
- else
64
- process.env.FINCH_TOOLS = prev;
65
- }
66
- })();
67
- const CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
68
- const PKG_VERSION = (() => {
69
- try {
70
- const raw = fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8");
71
- return JSON.parse(raw).version ?? "unknown";
72
- }
73
- catch {
74
- return "unknown";
75
- }
76
- })();
77
- // Read wallet address from the encrypted JSON without decrypting -
78
- // ethers stores the address in plaintext inside the keystore file.
79
- function getLocalWalletAddress() {
80
- try {
81
- const walletPath = path.join(os.homedir(), ".finch", "wallet.json");
82
- if (!fs.existsSync(walletPath))
83
- return null;
84
- const data = JSON.parse(fs.readFileSync(walletPath, "utf8"));
85
- const addr = data.address;
86
- if (!addr)
87
- return null;
88
- return addr.startsWith("0x") ? addr : `0x${addr}`;
89
- }
90
- catch {
91
- return null;
92
- }
93
- }
94
- async function loginWithApiKey(rl) {
95
- const ask = (q) => new Promise(resolve => rl.question(q, resolve));
96
- console.log(` ${C.dim}Generate an API key at app.finchagentic.com → Settings → API Keys${C.reset}`);
97
- console.log(` ${C.dim}Or set env var: set FINCH_API_KEY=finch_sk_... (or noel_sk_...)${C.reset}`);
98
- let apiKey = (await ask(` API key (finch_sk_... / noel_sk_...): `)).trim();
99
- if (!apiKey)
100
- return;
101
- // Deduplicates doubled input from a known Clink v1.7.6 terminal bug (e.g.
102
- // "finch_sk_xxfinch_sk_xx" → "finch_sk_xx"); also strips non-ASCII Clink can inject.
103
- apiKey = (0, clink_input_js_1.dedupClinkInput)(apiKey).replace(/[^\x20-\x7E]/g, "").trim();
104
- if (!(0, config_js_1.isApiKey)(apiKey)) {
105
- console.log(`\n ${C.red}✗${C.reset} API key must start with "finch_sk_" or "noel_sk_". Got: "${apiKey.slice(0, 20)}..."\n`);
106
- return;
107
- }
108
- process.stdout.write(` Authenticating...`);
109
- const res = await fetch(`${CONVEX_SITE}/auth/apikey/login`, {
110
- method: "POST",
111
- headers: { "Content-Type": "application/json" },
112
- body: JSON.stringify({ apiKey }),
113
- });
114
- const data = await res.json();
115
- if (!res.ok || !data.token) {
116
- console.log(`\n ${C.red}✗${C.reset} ${data.error ?? "Invalid API key"}\n`);
117
- return;
118
- }
119
- const email = data.email ?? "api-key-user";
120
- const name = data.displayName ?? undefined;
121
- (0, config_js_1.writeConfig)({ sessionToken: data.token, email, name, walletAddress: data.walletAddress });
122
- printLoginSuccess({ email, name, walletAddress: data.walletAddress });
123
- }
124
- async function loginFlow(loginRl) {
125
- const rl = loginRl ?? readline.createInterface({ input: process.stdin, output: process.stdout });
126
- // Check env var first — skip prompt entirely
127
- const envKey = process.env.FINCH_API_KEY;
128
- if (envKey && (0, config_js_1.isApiKey)(envKey)) {
129
- console.log(`\n ${C.dim}Found FINCH_API_KEY in environment — authenticating...${C.reset}`);
130
- process.stdout.write(` Authenticating...`);
131
- const res = await fetch(`${CONVEX_SITE}/auth/apikey/login`, {
132
- method: "POST",
133
- headers: { "Content-Type": "application/json" },
134
- body: JSON.stringify({ apiKey: envKey }),
135
- });
136
- const data = await res.json();
137
- if (res.ok && data.token) {
138
- const email = data.email ?? "api-key-user";
139
- const name = data.displayName ?? undefined;
140
- (0, config_js_1.writeConfig)({ sessionToken: data.token, email, name, walletAddress: data.walletAddress });
141
- printLoginSuccess({ email, name, walletAddress: data.walletAddress });
142
- rl.close();
143
- return;
144
- }
145
- console.log(`\n ${C.red}✗${C.reset} Env var FINCH_API_KEY is invalid. Falling back to manual login.\n`);
146
- }
147
- console.log(`\n ${C.cyan}${C.bold}Sign in to Finch${C.reset}\n`);
148
- await loginWithApiKey(rl);
149
- rl.close();
150
- }
151
- // ── Setup wizard: BYOK LLM provider + local memory ────────────────────────────
152
- const PROVIDER_CHOICES = {
153
- "1": { configKey: "bankrApiKey", envVar: "BANKR_API_KEY", label: "Bankr" },
154
- "2": { configKey: "anthropicApiKey", envVar: "ANTHROPIC_API_KEY", label: "Anthropic" },
155
- "3": { configKey: "openaiApiKey", envVar: "OPENAI_API_KEY", label: "OpenAI" },
156
- };
157
- const LOCAL_MEMORY_INSTALL_CMD = ["npx", ["-y", "supermemory", "local"]];
158
- const LOCAL_MEMORY_POLL_MS = 2000;
159
- const LOCAL_MEMORY_POLL_MAX_MS = 45000;
160
- const LOCAL_MEMORY_KEY_RE = /\b(sm_[A-Za-z0-9_-]{8,})\b/;
161
- async function setupFlow() {
162
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
163
- const ask = (q) => new Promise(resolve => rl.question(q, resolve));
164
- console.log(`\n ${C.cyan}${C.bold}finch setup${C.reset}`);
165
- console.log(` ${C.dim}Bring your own LLM key and/or run memory fully local - zero cost, zero lock-in.${C.reset}\n`);
166
- // ── Step 1: LLM provider ──────────────────────────────────────────────────
167
- // Say plainly that skipping is fine. Tools return data and structure for the
168
- // calling model; only `finch run` and scheduled agents need a key of
169
- // their own, because nothing else is there to do the thinking.
170
- console.log(` ${C.dim}Optional. Every MCP tool works without a key - your client's model does the reasoning.${C.reset}`);
171
- console.log(` ${C.dim}A key is what lets finch think on its own: \`finch run\` and scheduled agents.${C.reset}\n`);
172
- console.log(` ${C.dim}[1] Bankr [2] Anthropic [3] OpenAI [4] Custom endpoint (self-hosted/VPS) [5] Skip${C.reset}\n`);
173
- const providerChoice = (0, clink_input_js_1.dedupClinkInput)((await ask(` Choose [1-5]: `)).trim());
174
- const provider = PROVIDER_CHOICES[providerChoice];
175
- // Tracks whatever key got configured in this step, regardless of which
176
- // branch set it, so step 2 can forward it to the local-memory subprocess
177
- // env uniformly instead of only handling the named-provider case.
178
- let chosenProviderKey;
179
- let chosenProviderEnvVar;
180
- if (provider) {
181
- const key = (0, clink_input_js_1.dedupClinkInput)((await ask(` ${provider.label} API key: `)).trim()).replace(/[^\x20-\x7E]/g, "").trim();
182
- if (key) {
183
- (0, config_js_1.writeConfig)({ [provider.configKey]: key });
184
- process.env[provider.envVar] = key;
185
- chosenProviderKey = key;
186
- chosenProviderEnvVar = provider.envVar;
187
- console.log(`\n ${C.green}✓${C.reset} Saved. ${provider.label} will be used for LLM calls in the interactive shell and MCP server.`);
188
- console.log(` ${C.dim}(Stored in ~/.finch/config.json, mode 0600 - same protection as your session token.)${C.reset}\n`);
189
- }
190
- else {
191
- console.log(`\n ${C.dim}No key entered - skipped.${C.reset}\n`);
192
- }
193
- }
194
- else if (providerChoice === "4") {
195
- console.log(` ${C.dim}Any OpenAI Chat Completions-compatible endpoint works (LiteLLM, vLLM, Ollama, OpenRouter, your own VPS gateway).${C.reset}`);
196
- const baseUrl = (0, clink_input_js_1.dedupClinkInput)((await ask(` Base URL (e.g. https://your-vps:8000/v1): `)).trim()).replace(/[^\x20-\x7E]/g, "").trim().replace(/\/+$/, "");
197
- if (baseUrl) {
198
- const key = (0, clink_input_js_1.dedupClinkInput)((await ask(` API key (leave blank if your endpoint doesn't need one): `)).trim()).replace(/[^\x20-\x7E]/g, "").trim();
199
- (0, config_js_1.writeConfig)({ openaiBaseUrl: baseUrl, openaiApiKey: key || "local" });
200
- process.env.OPENAI_BASE_URL = baseUrl;
201
- process.env.OPENAI_API_KEY = key || "local";
202
- chosenProviderKey = key || "local";
203
- chosenProviderEnvVar = "OPENAI_API_KEY";
204
- console.log(`\n ${C.green}✓${C.reset} Saved. Requests route to ${baseUrl} using the OpenAI protocol.\n`);
205
- }
206
- else {
207
- console.log(`\n ${C.dim}No URL entered - skipped.${C.reset}\n`);
208
- }
209
- }
210
- else {
211
- console.log(`\n ${C.dim}Skipped - using Finch proxy.${C.reset}\n`);
212
- }
213
- // ── Step 2: local memory ──────────────────────────────────────────────────
214
- console.log(` ${C.cyan}${C.bold}Local memory${C.reset} ${C.dim}(self-hosted - your choice of backend)${C.reset}`);
215
- console.log(` ${C.dim}[1] Simple file-based (recommended) - zero dependency, JSON on disk at ~/.finch/memory/, keyword search only.${C.reset}`);
216
- console.log(` ${C.dim}[2] Supermemory server - real semantic/embedding search, but you run a separate process yourself.${C.reset}`);
217
- console.log(` ${C.dim}[3] Skip - memory tools use the Finch-hosted proxy.${C.reset}\n`);
218
- const memChoice = (0, clink_input_js_1.dedupClinkInput)((await ask(` Choose [1-3]: `)).trim());
219
- if (memChoice === "1") {
220
- (0, config_js_1.writeConfig)({ memoryBackend: "local-file" });
221
- console.log(`\n ${C.green}✓${C.reset} Local file memory enabled at ~/.finch/memory/. No server, no key - memory tools run fully local.\n`);
222
- }
223
- else if (memChoice === "2") {
224
- const url = "http://localhost:6767";
225
- console.log(`\n ${C.dim}Checking ${url}...${C.reset}`);
226
- let reachable = await isLocalMemoryReachableRaw(url);
227
- let installerError;
228
- if (!reachable) {
229
- console.log(` ${C.dim}Not running - launching \`npx -y supermemory local\` in the background...${C.reset}`);
230
- const providerEnv = chosenProviderEnvVar && chosenProviderKey ? { [chosenProviderEnvVar]: chosenProviderKey } : {};
231
- const result = await launchLocalMemoryServer(url, providerEnv);
232
- reachable = result.reachable;
233
- installerError = result.installerError;
234
- }
235
- if (reachable) {
236
- const key = await readLocalMemoryKey();
237
- if (key) {
238
- (0, config_js_1.writeConfig)({ memoryBackend: "local", supermemoryUrl: url, supermemoryApiKey: key });
239
- console.log(`\n ${C.green}✓${C.reset} Local memory enabled at ${url}. Memory tools now run fully local - zero cost, private to this machine.\n`);
240
- }
241
- else {
242
- console.log(`\n ${C.yellow}⚠${C.reset} Server is running but its API key couldn't be auto-detected.`);
243
- const manualKey = (0, clink_input_js_1.dedupClinkInput)((await ask(` Paste the key printed by the server (starts with sm_), or press Enter to skip: `)).trim());
244
- if (manualKey) {
245
- (0, config_js_1.writeConfig)({ memoryBackend: "local", supermemoryUrl: url, supermemoryApiKey: manualKey });
246
- console.log(`\n ${C.green}✓${C.reset} Local memory enabled at ${url}.\n`);
247
- }
248
- else {
249
- console.log(`\n ${C.dim}Skipped - memory tools will keep using the Finch proxy. Run \`finch setup\` again once you have the key.${C.reset}\n`);
250
- }
251
- }
252
- }
253
- else if (installerError) {
254
- console.log(`\n ${C.yellow}⚠${C.reset} Local install failed: ${installerError}`);
255
- console.log(` ${C.dim}Memory tools keep using the Finch proxy until this is resolved. Run \`finch setup\` again after fixing it.${C.reset}\n`);
256
- }
257
- else {
258
- console.log(`\n ${C.yellow}⚠${C.reset} Couldn't reach a local supermemory server after ${LOCAL_MEMORY_POLL_MAX_MS / 1000}s.`);
259
- console.log(` ${C.dim}Install it yourself in another terminal: ${C.cyan}npx -y supermemory local${C.reset}`);
260
- console.log(` ${C.dim}Then run \`finch setup\` again - memory tools keep using the Finch proxy until then.${C.reset}\n`);
261
- }
262
- }
263
- else {
264
- console.log(`\n ${C.dim}Skipped - memory tools use the Finch-hosted proxy.${C.reset}\n`);
265
- }
266
- // ── Step 3: local vault ───────────────────────────────────────────────────
267
- // Unlike local memory, this needs no server - it's a plain on-disk store, so
268
- // enabling it is a config flip. Data lives under ~/.finch/vault/.
269
- console.log(` ${C.cyan}${C.bold}Local vault${C.reset} ${C.dim}(versioned artifacts on your own disk - ~/.finch/vault/)${C.reset}`);
270
- console.log(` ${C.dim}No account, no Convex, no cost. vault_save/read/search/history/diff run fully local.${C.reset}\n`);
271
- const enableVault = (0, clink_input_js_1.dedupClinkInput)((await ask(` Enable local vault? [y/N]: `)).trim()).toLowerCase();
272
- if (enableVault === "y" || enableVault === "yes") {
273
- (0, config_js_1.writeConfig)({ vaultBackend: "local" });
274
- console.log(`\n ${C.green}✓${C.reset} Local vault enabled. All vault tools now store on this machine - you own the data, no account needed.\n`);
275
- }
276
- else {
277
- console.log(`\n ${C.dim}Skipped - vault tools use the Finch-hosted store (needs \`finch login\`).${C.reset}\n`);
278
- }
279
- rl.close();
280
- }
281
- async function isLocalMemoryReachableRaw(url) {
282
- try {
283
- const res = await fetch(url, { signal: AbortSignal.timeout(1500) });
284
- return res.status < 500;
285
- }
286
- catch {
287
- return false;
288
- }
289
- }
290
- // Spawns the local supermemory server detached so it survives after this CLI
291
- // process exits (same lifecycle model as a local daemon, e.g. Ollama).
292
- // Captures stdout briefly to look for a printed sm_... key (the binary's
293
- // first-boot wizard prints one), then stops watching it - the process itself
294
- // keeps running in the background regardless of whether we caught the key.
295
- let capturedLocalMemoryKey;
296
- // Known installer failure signatures worth surfacing verbatim instead of a
297
- // generic "couldn't reach" message. Confirmed on Windows: the installer
298
- // shells out via WSL, and fails immediately (before ever listening on the
299
- // port) if WSL isn't installed/registered - waiting out the full poll
300
- // window for that case just wastes the user's time.
301
- const KNOWN_INSTALLER_FAILURES = [
302
- { re: /unsupported OS/i, hint: "The installer doesn't support this shell/OS directly (seen from Git Bash/MINGW on Windows)." },
303
- { re: /REGDB_E_CLASSNOTREG|Class not registered/i, hint: "The installer needs WSL (Windows Subsystem for Linux) to run its install script, and WSL isn't set up on this machine. Install it with `wsl --install` (needs a restart), then try again." },
304
- ];
305
- async function launchLocalMemoryServer(url, providerEnv) {
306
- const [cmd, args] = LOCAL_MEMORY_INSTALL_CMD;
307
- let installerError;
308
- // Authoritative "did the process die" signal - independent of whether the
309
- // regex hints below happen to match. A spawn failure (e.g. npx not found)
310
- // emits an async 'error' event rather than throwing synchronously, so it
311
- // is NOT caught by the try/catch around spawn() itself; without this
312
- // listener it becomes an uncaught exception that crashes the whole CLI.
313
- let exited = false;
314
- // Accumulated across all chunks (not tested per-chunk) so a known error
315
- // string split across two writes still matches.
316
- let outputBuffer = "";
317
- try {
318
- const child = child_process.spawn(cmd, [...args], {
319
- detached: true,
320
- stdio: ["ignore", "pipe", "pipe"],
321
- env: { ...process.env, ...providerEnv },
322
- });
323
- child.on("error", (err) => {
324
- exited = true;
325
- if (!installerError)
326
- installerError = `Couldn't launch \`${cmd}\`: ${err.message}`;
327
- });
328
- child.on("exit", () => { exited = true; });
329
- const onData = (chunk) => {
330
- outputBuffer += chunk.toString("utf8");
331
- const keyMatch = outputBuffer.match(LOCAL_MEMORY_KEY_RE);
332
- if (keyMatch)
333
- capturedLocalMemoryKey = keyMatch[1];
334
- if (!installerError) {
335
- const known = KNOWN_INSTALLER_FAILURES.find((k) => k.re.test(outputBuffer));
336
- if (known)
337
- installerError = known.hint;
338
- }
339
- };
340
- child.stdout?.on("data", onData);
341
- child.stderr?.on("data", onData);
342
- child.unref();
343
- }
344
- catch (err) {
345
- return { reachable: false, installerError: `Couldn't launch \`${cmd}\`: ${err.message}` };
346
- }
347
- const deadline = Date.now() + LOCAL_MEMORY_POLL_MAX_MS;
348
- while (Date.now() < deadline) {
349
- if (await isLocalMemoryReachableRaw(url))
350
- return { reachable: true };
351
- // Fail fast once the process has died (confirmed via 'error'/'exit') or
352
- // a known error signature was recognized - polling further won't help.
353
- if (installerError || exited)
354
- return { reachable: false, installerError };
355
- await new Promise((r) => setTimeout(r, LOCAL_MEMORY_POLL_MS));
356
- }
357
- return { reachable: false, installerError };
358
- }
359
- async function readLocalMemoryKey() {
360
- if (capturedLocalMemoryKey)
361
- return capturedLocalMemoryKey;
362
- // Fall back to the documented on-disk location ("everything lives in
363
- // ./.supermemory") - exact path may need adjusting once verified against
364
- // the real binary; this covers the most likely spot.
365
- try {
366
- const candidate = path.join(os.homedir(), ".supermemory", "config.json");
367
- if (fs.existsSync(candidate)) {
368
- const data = JSON.parse(fs.readFileSync(candidate, "utf8"));
369
- const key = data?.apiKey ?? data?.api_key ?? data?.key;
370
- if (typeof key === "string" && LOCAL_MEMORY_KEY_RE.test(key))
371
- return key;
372
- }
373
- }
374
- catch { /* best-effort only */ }
375
- return undefined;
376
- }
377
- // ── ANSI ─────────────────────────────────────────────────────────────────────
378
- const C = {
379
- reset: "\x1b[0m",
380
- bold: "\x1b[1m",
381
- dim: "\x1b[2m",
382
- green: "\x1b[32m",
383
- cyan: "\x1b[36m",
384
- violet: "\x1b[35m",
385
- red: "\x1b[31m",
386
- yellow: "\x1b[33m",
387
- white: "\x1b[97m",
388
- bg: "\x1b[48;5;17m",
389
- };
390
- // ── Banner ────────────────────────────────────────────────────────────────────
391
- //
392
- // ███╗ ██╗ ██████╗ ███████╗██╗ ██████╗██╗ █████╗ ██╗ ██╗
393
- // ████╗ ██║██╔═══██╗██╔════╝██║ ██╔════╝██║ ██╔══██╗██║ ██║
394
- // ██╔██╗ ██║██║ ██║█████╗ ██║ ██║ ██║ ███████║██║ █╗ ██║
395
- // ██║╚██╗██║██║ ██║██╔══╝ ██║ ██║ ██║ ██╔══██║██║███╗██║
396
- // ██║ ╚████║╚██████╔╝███████╗███████╗╚██████╗███████╗██║ ██║╚███╔███╔╝
397
- // ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝
398
- const LOGO_LINES = [
399
- ` ███████╗ ██╗ ███╗ ██╗ ██████╗ ██╗ ██╗`,
400
- ` ██╔════╝ ██║ ████╗ ██║ ██╔════╝ ██║ ██║`,
401
- ` █████╗ ██║ ██╔██╗ ██║ ██║ ███████║`,
402
- ` ██╔══╝ ██║ ██║╚██╗██║ ██║ ██╔══██║`,
403
- ` ██║ ██║ ██║ ╚████║ ╚██████╗ ██║ ██║`,
404
- ` ╚═╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝`,
405
- ];
406
- const SEP = ` ${"─".repeat(70)}`;
407
- function buildBanner() {
408
- const cfg = (0, config_js_1.readConfig)();
409
- // Every base_mcp_*/rh_mcp_* tool signs locally via getOrCreateWallet()
410
- // (~/.finch/wallet.json) on BOTH Base and Robinhood Chain (RH reuses the
411
- // same local address) - this CLI never touches the account's custodial
412
- // webapp wallet. Showing that other address here would mismatch what
413
- // every tool call actually reports and operates on.
414
- const walletAddr = getLocalWalletAddress();
415
- const provider = process.env.BANKR_API_KEY ? "Bankr"
416
- : process.env.ANTHROPIC_API_KEY ? "Anthropic"
417
- : process.env.OPENAI_API_KEY ? "OpenAI"
418
- : "Finch proxy";
419
- const logo = LOGO_LINES.map(l => `${C.cyan}${C.bold}${l}${C.reset}`).join("\n");
420
- // ── meta row ──
421
- const meta = `\n${SEP}\n ${C.dim}v${PKG_VERSION} · ${TOTAL_TOOL_COUNT} tools · finchagentic.com${C.reset}\n${SEP}`;
422
- // ── auth block ──
423
- let authBlock;
424
- if (cfg.sessionToken) {
425
- const displayName = cfg.name ? `${C.white}${C.bold}${cfg.name}${C.reset}` : "";
426
- const displayEmail = cfg.email ? `${C.dim}${cfg.email}${C.reset}` : "";
427
- const nameLine = displayName
428
- ? ` ${C.green}●${C.reset} ${displayName} ${displayEmail}`
429
- : ` ${C.green}●${C.reset} ${displayEmail || `${C.green}Signed in${C.reset}`}`;
430
- const walletNote = "Base + Robinhood Chain · local device wallet · keys never leave your machine";
431
- const walletLine = walletAddr
432
- ? ` ${C.dim}Wallet${C.reset} ${C.cyan}${walletAddr.slice(0, 6)}...${walletAddr.slice(-4)}${C.reset} ${C.dim}· ${walletNote}${C.reset}`
433
- : ` ${C.dim}Wallet not created yet · auto-creates on first DeFi call${C.reset}`;
434
- const llmLine = ` ${C.dim}LLM ${C.reset}${C.green}${provider}${C.reset} ${C.dim}· ${TOTAL_TOOL_COUNT} tools active${C.reset}`;
435
- authBlock = `\n${nameLine}\n${walletLine}\n${llmLine}`;
436
- }
437
- else {
438
- authBlock = [
439
- ``,
440
- ` ${C.yellow}○${C.reset} ${C.yellow}Not signed in${C.reset} ${C.dim}- tools that need your account will fail${C.reset}`,
441
- ` ${C.dim}Run ${C.reset}${C.cyan}/login${C.reset}${C.dim} to unlock all ${TOTAL_TOOL_COUNT} tools${C.reset}`,
442
- ` ${C.dim}LLM ${provider} · basic tools still work${C.reset}`,
443
- ].join("\n");
444
- }
445
- const hint = `\n${SEP}\n ${C.dim}Type anything to chat · /help · Ctrl+C to exit${C.reset}\n`;
446
- return `\n${logo}\n${meta}\n${authBlock}\n${hint}`;
447
- }
448
- // ── Post-login success block ──────────────────────────────────────────────────
449
- function printLoginSuccess({ email, name }) {
450
- // Always show the local per-device wallet: every base_mcp_*/rh_mcp_* tool
451
- // signs with it on BOTH chains. The account's custodial webapp wallet is a
452
- // separate address this CLI never touches - showing it here would mismatch
453
- // what every subsequent tool call actually reports.
454
- const walletAddr = getLocalWalletAddress();
455
- const displayName = name ?? "";
456
- console.log(`\n${SEP}`);
457
- if (displayName) {
458
- console.log(` ${C.green}✓${C.reset} ${C.white}${C.bold}${displayName}${C.reset} ${C.dim}${email}${C.reset}`);
459
- }
460
- else {
461
- console.log(` ${C.green}✓${C.reset} ${C.green}${C.bold}Signed in${C.reset} ${C.dim}as ${email}${C.reset}`);
462
- }
463
- if (walletAddr) {
464
- const note = "Base + Robinhood Chain · local device wallet";
465
- console.log(` ${C.dim}Wallet${C.reset} ${C.cyan}${walletAddr.slice(0, 6)}...${walletAddr.slice(-4)}${C.reset} ${C.dim}· ${note}${C.reset}`);
466
- }
467
- else {
468
- console.log(` ${C.dim}Wallet auto-creates on first DeFi tool call${C.reset}`);
469
- }
470
- console.log(` ${C.dim}Token saved to ~/.finch/config.json${C.reset}`);
471
- console.log(` ${C.dim}All ${TOTAL_TOOL_COUNT} tools unlocked${C.reset}`);
472
- console.log(`${SEP}\n`);
473
- }
474
- // ── Spinner ───────────────────────────────────────────────────────────────────
475
- function spinner(label) {
476
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
477
- let i = 0;
478
- const iv = setInterval(() => {
479
- process.stdout.write(`\r ${C.dim}${frames[i % frames.length]} ${label}${C.reset} `);
480
- i++;
481
- }, 80);
482
- return () => {
483
- clearInterval(iv);
484
- process.stdout.write("\r" + " ".repeat(label.length + 12) + "\r");
485
- };
486
- }
487
- // ── Help ─────────────────────────────────────────────────────────────────────
488
- function printHelp() {
489
- const cfg = (0, config_js_1.readConfig)();
490
- const authLine = cfg.email
491
- ? ` ${C.dim}Signed in as ${C.reset}${C.green}${cfg.email}${C.reset}`
492
- : ` ${C.yellow}⚠${C.reset} ${C.dim}Not signed in - run /login to unlock all tools${C.reset}`;
493
- console.log(`
494
- ${C.cyan}Commands:${C.reset}
495
- /login Sign in to unlock all ${TOTAL_TOOL_COUNT} tools
496
- /logout Sign out and clear saved token
497
- /clear Clear conversation history
498
- /tools List all available tools
499
- /quit Exit
500
-
501
- ${authLine}
502
-
503
- ${C.dim}Examples:
504
- remember that I prefer concise answers
505
- search the web for recent AI news
506
- save a note to my vault
507
- research "top AI agent frameworks in 2025"
508
- spawn an agent to monitor competitor releases weekly${C.reset}
509
- `);
510
- }
511
- // ── Version check ─────────────────────────────────────────────────────────────
512
- async function checkForUpdate() {
513
- try {
514
- const res = await fetch("https://registry.npmjs.org/@finchagentic/mcp/latest", {
515
- signal: AbortSignal.timeout(5000),
516
- });
517
- if (!res.ok)
518
- return;
519
- const data = await res.json();
520
- const latest = data.version;
521
- if (!latest || latest === PKG_VERSION)
522
- return;
523
- const sep = ` ${"─".repeat(58)}`;
524
- console.log(`\n${sep}`);
525
- console.log(` ${C.yellow}⚠${C.reset} Update available: ${C.yellow}v${PKG_VERSION}${C.reset} → ${C.cyan}v${latest}${C.reset}`);
526
- console.log(` ${C.dim}npm install -g @finchagentic/mcp@${latest}${C.reset} ${C.dim}or restart your MCP client${C.reset}`);
527
- console.log(`${sep}\n`);
528
- }
529
- catch {
530
- // silently ignore
531
- }
532
- }
533
- // ── Main ─────────────────────────────────────────────────────────────────────
534
- async function main() {
535
- // Auto-login from env var if set and not already logged in
536
- const cfg = (0, config_js_1.readConfig)();
537
- const envKey = process.env.FINCH_API_KEY;
538
- if (!cfg.sessionToken && envKey && (0, config_js_1.isApiKey)(envKey)) {
539
- process.stdout.write(` ${C.dim}Auto-login from FINCH_API_KEY...${C.reset} `);
540
- try {
541
- const res = await fetch(`${CONVEX_SITE}/auth/apikey/login`, {
542
- method: "POST",
543
- headers: { "Content-Type": "application/json" },
544
- body: JSON.stringify({ apiKey: envKey }),
545
- });
546
- const data = await res.json();
547
- if (res.ok && data.token) {
548
- (0, config_js_1.writeConfig)({ sessionToken: data.token, email: data.email ?? "api-key-user", name: data.displayName ?? undefined, walletAddress: data.walletAddress });
549
- console.log(`${C.green}✓${C.reset} ${C.dim}Signed in as ${data.email}${C.reset}`);
550
- }
551
- else {
552
- console.log(`${C.red}✗${C.reset} ${C.dim}Invalid API key in env var${C.reset}`);
553
- }
554
- }
555
- catch (e) {
556
- console.log(`${C.red}✗${C.reset} ${C.dim}Login failed: ${e.message}${C.reset}`);
557
- }
558
- }
559
- process.stdout.write(buildBanner());
560
- // Once, here, so it doesn't interrupt the first tool reply mid-conversation.
561
- (0, wallet_js_1.warnIfNoPassphrase)();
562
- // Check for updates in background - shows after banner, doesn't block prompt
563
- checkForUpdate().catch(() => { });
564
- const history = [];
565
- const rl = readline.createInterface({
566
- input: process.stdin,
567
- output: process.stdout,
568
- prompt: `${C.green}>${C.reset} `,
569
- });
570
- rl.prompt();
571
- rl.on("line", async (raw) => {
572
- const line = raw.trim();
573
- if (!line) {
574
- rl.prompt();
575
- return;
576
- }
577
- // Built-in commands
578
- if (line === "/quit" || line === "/exit") {
579
- console.log(`\n ${C.dim}Goodbye.${C.reset}\n`);
580
- process.exit(0);
581
- }
582
- if (line === "/clear") {
583
- history.length = 0;
584
- console.log(` ${C.dim}History cleared.${C.reset}\n`);
585
- rl.prompt();
586
- return;
587
- }
588
- if (line === "/help") {
589
- printHelp();
590
- rl.prompt();
591
- return;
592
- }
593
- if (line === "/login") {
594
- rl.pause();
595
- // Create fresh readline for login — reusing rl causes input conflicts
596
- const loginRl = readline.createInterface({ input: process.stdin, output: process.stdout });
597
- await loginFlow(loginRl);
598
- loginRl.close();
599
- rl.resume();
600
- rl.prompt();
601
- return;
602
- }
603
- if (line === "/logout") {
604
- (0, config_js_1.writeConfig)({ sessionToken: undefined, email: undefined });
605
- console.log(` ${C.dim}Logged out. Token cleared from ~/.finch/config.json${C.reset}\n`);
606
- rl.prompt();
607
- return;
608
- }
609
- if (line === "/tools") {
610
- console.log(`\n ${C.cyan}${server_js_1.ALL_TOOLS.length} tools:${C.reset}`);
611
- for (const t of server_js_1.ALL_TOOLS) {
612
- console.log(` ${C.dim}·${C.reset} ${t.name} ${C.dim}${(t.description ?? "").slice(0, 60)}${C.reset}`);
613
- }
614
- console.log();
615
- rl.prompt();
616
- return;
617
- }
618
- // Agent call
619
- const stop = spinner("thinking");
620
- try {
621
- const result = await (0, agent_loop_js_1.runAgent)(line, history, (toolName) => {
622
- stop();
623
- process.stdout.write(` ${C.dim}✦ ${toolName}${C.reset}\n`);
624
- });
625
- stop();
626
- // Update conversation history (keep last 20 turns)
627
- history.push({ role: "user", content: line });
628
- history.push({ role: "assistant", content: result.text });
629
- while (history.length > 20)
630
- history.splice(0, 2);
631
- // Output
632
- console.log();
633
- const lines = result.text.split("\n");
634
- for (const l of lines) {
635
- console.log(` ${l}`);
636
- }
637
- console.log();
638
- }
639
- catch (err) {
640
- stop();
641
- console.log(`\n ${C.red}✗${C.reset} ${err.message}\n`);
642
- }
643
- rl.prompt();
644
- });
645
- rl.on("close", () => {
646
- console.log(`\n ${C.dim}Goodbye.${C.reset}\n`);
647
- process.exit(0);
648
- });
649
- }
650
- function resolveClients() {
651
- const home = os.homedir();
652
- const plat = os.platform();
653
- const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
654
- const defs = [
655
- {
656
- name: "Claude Desktop", serversKey: "mcpServers", entryStyle: "standard",
657
- paths: {
658
- darwin: path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
659
- win32: path.join(appdata, "Claude", "claude_desktop_config.json"),
660
- linux: path.join(home, ".config", "Claude", "claude_desktop_config.json"),
661
- },
662
- },
663
- {
664
- name: "Cursor", serversKey: "mcpServers", entryStyle: "standard",
665
- paths: {
666
- darwin: path.join(home, ".cursor", "mcp.json"),
667
- win32: path.join(home, ".cursor", "mcp.json"),
668
- linux: path.join(home, ".cursor", "mcp.json"),
669
- },
670
- },
671
- {
672
- name: "Windsurf", serversKey: "mcpServers", entryStyle: "standard",
673
- paths: {
674
- darwin: path.join(home, ".codeium", "windsurf", "mcp_config.json"),
675
- win32: path.join(home, ".codeium", "windsurf", "mcp_config.json"),
676
- linux: path.join(home, ".codeium", "windsurf", "mcp_config.json"),
677
- },
678
- },
679
- // VS Code: user-level mcp.json uses the top-level `servers` key (NOT
680
- // mcpServers) and each entry needs `"type": "stdio"`.
681
- {
682
- name: "VS Code", serversKey: "servers", entryStyle: "vscode",
683
- paths: {
684
- darwin: path.join(home, "Library", "Application Support", "Code", "User", "mcp.json"),
685
- win32: path.join(appdata, "Code", "User", "mcp.json"),
686
- linux: path.join(home, ".config", "Code", "User", "mcp.json"),
687
- },
688
- },
689
- {
690
- name: "VS Code Insiders", serversKey: "servers", entryStyle: "vscode",
691
- paths: {
692
- darwin: path.join(home, "Library", "Application Support", "Code - Insiders", "User", "mcp.json"),
693
- win32: path.join(appdata, "Code - Insiders", "User", "mcp.json"),
694
- linux: path.join(home, ".config", "Code - Insiders", "User", "mcp.json"),
695
- },
696
- },
697
- // Zed: MCP servers live in settings.json under `context_servers`, and each
698
- // entry needs `"source": "custom"`. (Not a separate mcp.json file.)
699
- {
700
- name: "Zed", serversKey: "context_servers", entryStyle: "zed",
701
- paths: {
702
- darwin: path.join(home, ".config", "zed", "settings.json"),
703
- win32: path.join(appdata, "Zed", "settings.json"),
704
- linux: path.join(home, ".config", "zed", "settings.json"),
705
- },
706
- },
707
- ];
708
- return defs
709
- .map((d) => {
710
- const configPath = d.paths[plat] ?? d.paths.linux;
711
- return { name: d.name, configPath, serversKey: d.serversKey, entryStyle: d.entryStyle };
712
- })
713
- .filter((c) => {
714
- // Include if the config file exists OR the parent directory exists (app installed but not yet configured)
715
- return fs.existsSync(c.configPath) || fs.existsSync(path.dirname(c.configPath));
716
- });
717
- }
718
- // `npx -y @finchagentic/mcp@version` alone fails with "could not determine
719
- // executable to run" - the package ships two bins (finch, finch-mcp)
720
- // and neither matches the unscoped package name npx resolves by default.
721
- // -p ... finch-mcp names the actual MCP-protocol server explicitly
722
- // (not `finch`, which is the human-interactive REPL and would break the
723
- // JSON-RPC handshake if a host spawned it instead). Pinned to the currently
724
- // installed version, never @latest - matches the security boundary
725
- // documented in the README (wallet/credential access needs a stable,
726
- // reviewable version, not a moving target).
727
- function buildEntry(style) {
728
- const base = {
729
- command: "npx",
730
- args: ["-y", "-p", `@finchagentic/mcp@${PKG_VERSION}`, "finch-mcp"],
731
- env: {},
732
- };
733
- if (style === "vscode")
734
- return { type: "stdio", ...base };
735
- if (style === "zed")
736
- return { source: "custom", ...base };
737
- return base;
738
- }
739
- // Returns "skipped" (distinct from "error") when an existing config file can't
740
- // be parsed - overwriting it would destroy the user's real config (VS Code
741
- // mcp.json and Zed settings.json are JSONC and routinely contain comments,
742
- // which JSON.parse rejects). In that case we leave the file untouched and the
743
- // caller prints a manual-add snippet instead.
744
- function installIntoConfig(client) {
745
- try {
746
- let json = {};
747
- if (fs.existsSync(client.configPath)) {
748
- const raw = fs.readFileSync(client.configPath, "utf8");
749
- try {
750
- json = JSON.parse(raw);
751
- }
752
- catch {
753
- return "skipped"; // don't clobber an unparseable (likely JSONC) file
754
- }
755
- }
756
- else {
757
- // Ensure parent dir exists
758
- fs.mkdirSync(path.dirname(client.configPath), { recursive: true });
759
- }
760
- const key = client.serversKey;
761
- if (!json[key] || typeof json[key] !== "object")
762
- json[key] = {};
763
- const existed = !!json[key].finch;
764
- json[key].finch = buildEntry(client.entryStyle);
765
- fs.writeFileSync(client.configPath, JSON.stringify(json, null, 2), "utf8");
766
- return existed ? "updated" : "added";
767
- }
768
- catch {
769
- return "error";
770
- }
771
- }
772
- async function installFlow() {
773
- console.log(`\n ${C.cyan}${C.bold}finch install${C.reset}\n`);
774
- console.log(` ${C.dim}Scanning for MCP-compatible apps...${C.reset}\n`);
775
- const clients = resolveClients();
776
- if (clients.length === 0) {
777
- console.log(` ${C.yellow}No MCP-compatible apps found.${C.reset}`);
778
- console.log(` ${C.dim}Install Claude Desktop, Cursor, or Windsurf, then run this again.${C.reset}\n`);
779
- console.log(` ${C.dim}Or add manually to your app's MCP config:${C.reset}`);
780
- console.log(` ${C.dim} "finch": { "command": "npx", "args": ["-y", "-p", "@finchagentic/mcp@${PKG_VERSION}", "finch-mcp"] }${C.reset}\n`);
781
- return;
782
- }
783
- let installed = 0;
784
- const toRestart = [];
785
- const manual = [];
786
- for (const client of clients) {
787
- const result = installIntoConfig(client);
788
- const short = client.configPath.replace(os.homedir(), "~");
789
- if (result === "added") {
790
- console.log(` ${C.green}✓${C.reset} ${C.bold}${client.name}${C.reset} ${C.dim}→ added${C.reset}`);
791
- console.log(` ${C.dim}${short}${C.reset}`);
792
- installed++;
793
- toRestart.push(client.name);
794
- }
795
- else if (result === "updated") {
796
- console.log(` ${C.green}↑${C.reset} ${C.bold}${client.name}${C.reset} ${C.dim}→ updated (re-pinned to v${PKG_VERSION})${C.reset}`);
797
- console.log(` ${C.dim}${short}${C.reset}`);
798
- installed++;
799
- toRestart.push(client.name);
800
- }
801
- else if (result === "skipped") {
802
- // Existing config couldn't be parsed (likely has comments) - never
803
- // overwrite it. Show the user exactly what to paste under which key.
804
- console.log(` ${C.yellow}⚠${C.reset} ${C.bold}${client.name}${C.reset} ${C.dim}→ has an existing config we won't overwrite${C.reset}`);
805
- console.log(` ${C.dim}${short}${C.reset}`);
806
- manual.push(` ${C.dim}${client.name}: add under ${C.reset}${C.cyan}"${client.serversKey}"${C.reset}${C.dim}:${C.reset}\n ${C.dim}${JSON.stringify({ finch: buildEntry(client.entryStyle) })}${C.reset}`);
807
- }
808
- else {
809
- console.log(` ${C.yellow}✗${C.reset} ${C.bold}${client.name}${C.reset} ${C.dim}→ write failed (check permissions)${C.reset}`);
810
- }
811
- }
812
- if (manual.length) {
813
- console.log(`\n ${C.dim}Add these by hand (we didn't touch your existing config):${C.reset}`);
814
- for (const m of manual)
815
- console.log(m);
816
- }
817
- console.log(`\n ${C.dim}${"─".repeat(52)}${C.reset}\n`);
818
- if (installed === 0) {
819
- console.log(` ${C.yellow}Nothing was installed. Check file permissions.${C.reset}\n`);
820
- return;
821
- }
822
- console.log(` ${C.green}${C.bold}✓ Finch installed in ${installed} app${installed === 1 ? "" : "s"}.${C.reset}\n`);
823
- if (toRestart.length > 0) {
824
- console.log(` ${C.dim}Restart to activate: ${toRestart.join(" · ")}${C.reset}`);
825
- }
826
- // Tool palette: the server exposes the CORE subset by default to keep the
827
- // client's context lean. Everything else stays callable by name, and power
828
- // users can flip on the full surface. Surfaced here so the count a user sees
829
- // in their client (CORE) doesn't silently contradict the "all tools" framing.
830
- console.log(`\n ${C.dim}Your client will see the ${CORE_TOOL_COUNT} core tools by default. ` +
831
- `Add ${C.reset}${C.cyan}"env": { "FINCH_TOOLS": "all" }${C.reset}${C.dim} to the finch entry for all ${TOTAL_TOOL_COUNT}.${C.reset}`);
832
- const cfg = (0, config_js_1.readConfig)();
833
- if (!cfg.sessionToken) {
834
- console.log(`\n ${C.dim}Next step - sign in to unlock all tools:${C.reset}`);
835
- console.log(` ${C.cyan} finch login${C.reset}\n`);
836
- }
837
- else {
838
- console.log(`\n ${C.dim}Already signed in as ${cfg.email ?? "user"}.${C.reset}`);
839
- console.log(` ${C.dim}Open your MCP client and start using Finch.${C.reset}\n`);
840
- }
841
- }
842
- async function doctorFlow() {
843
- console.log(`\n ${C.cyan}${C.bold}finch doctor${C.reset} ${C.dim}v${PKG_VERSION}${C.reset}\n`);
844
- console.log(` ${C.dim}Running diagnostic - this takes ~5 seconds.${C.reset}\n`);
845
- const checks = [];
846
- const cfg = (0, config_js_1.readConfig)();
847
- const authHeader = cfg.sessionToken ? { Authorization: `Bearer ${cfg.sessionToken}` } : {};
848
- // 1. LLM provider
849
- const provider = process.env.BANKR_API_KEY ? "bankr"
850
- : process.env.ANTHROPIC_API_KEY ? "anthropic"
851
- : process.env.OPENAI_API_KEY ? "openai"
852
- : "finch-proxy";
853
- // Tools never call an LLM of their own - they return data and structure for
854
- // the calling model. A key is what makes *finch itself* a host: the CLI
855
- // agent loop and scheduled agents, which have no client model to borrow.
856
- checks.push({
857
- name: "LLM provider",
858
- status: "✓",
859
- detail: provider === "finch-proxy"
860
- ? `none set · all ${TOTAL_TOOL_COUNT} tools work without one`
861
- : `${provider} (direct) · used by \`finch run\` and scheduled agents`,
862
- fix: provider === "finch-proxy"
863
- ? `Only needed for \`finch run\` and scheduled agents — \`finch setup\` adds one`
864
- : undefined,
865
- });
866
- // 2. Backend reachable
867
- try {
868
- const t0 = Date.now();
869
- const res = await fetch(`${CONVEX_SITE}/memory/profile`, {
870
- headers: authHeader,
871
- signal: AbortSignal.timeout(8000),
872
- });
873
- const latency = Date.now() - t0;
874
- if (res.status === 401) {
875
- checks.push({
876
- name: "Backend reachable", status: "⚠",
877
- detail: `${CONVEX_SITE} → 401 (not signed in)`,
878
- fix: `Run \`finch login\` to authenticate.`,
879
- });
880
- }
881
- else if (res.ok) {
882
- checks.push({
883
- name: "Backend reachable", status: "✓",
884
- detail: `${CONVEX_SITE} → ${res.status} (${latency}ms)`,
885
- });
886
- }
887
- else {
888
- checks.push({
889
- name: "Backend reachable", status: "✗",
890
- detail: `${CONVEX_SITE} → ${res.status}`,
891
- fix: `Check FINCH_CONVEX_URL env var, or wait if the service is down.`,
892
- });
893
- }
894
- }
895
- catch (err) {
896
- checks.push({
897
- name: "Backend reachable", status: "✗",
898
- detail: `${CONVEX_SITE} → ${err.message}`,
899
- fix: `Network issue or wrong URL. Default: https://befitting-porcupine-276.convex.site`,
900
- });
901
- }
902
- // 3. Auth state
903
- if (cfg.sessionToken) {
904
- checks.push({
905
- name: "Authentication",
906
- status: "✓",
907
- detail: cfg.email
908
- ? `Signed in as ${cfg.email}`
909
- : `Session token present`,
910
- });
911
- }
912
- else {
913
- checks.push({
914
- name: "Authentication",
915
- status: "⚠",
916
- detail: `No session token - running with local wallet signature only`,
917
- fix: `Run \`finch login\` for persistent identity across MCP clients.`,
918
- });
919
- }
920
- // 4. Local wallet
921
- const walletPath = path.join(os.homedir(), ".finch", "wallet.json");
922
- if (fs.existsSync(walletPath)) {
923
- try {
924
- const wallet = JSON.parse(fs.readFileSync(walletPath, "utf8"));
925
- const addr = wallet.address ? (wallet.address.startsWith("0x") ? wallet.address : `0x${wallet.address}`) : "unknown";
926
- checks.push({
927
- name: "Local wallet", status: "✓",
928
- detail: `${addr.slice(0, 6)}...${addr.slice(-4)} at ~/.finch/wallet.json`,
929
- });
930
- }
931
- catch {
932
- checks.push({
933
- name: "Local wallet", status: "⚠",
934
- detail: `wallet.json present but unreadable`,
935
- fix: `Delete ~/.finch/wallet.json and re-run finch - new wallet auto-creates.`,
936
- });
937
- }
938
- }
939
- else {
940
- checks.push({
941
- name: "Local wallet", status: "⚠",
942
- detail: `Not created yet`,
943
- fix: `Auto-creates on first DeFi tool call (base_mcp_balance, etc).`,
944
- });
945
- }
946
- // 5. Profile entries
947
- if (cfg.sessionToken) {
948
- try {
949
- const res = await fetch(`${CONVEX_SITE}/vault/profile-context?maxChars=100`, {
950
- headers: authHeader,
951
- signal: AbortSignal.timeout(6000),
952
- });
953
- const data = await res.json();
954
- if (data?.hasProfile) {
955
- checks.push({
956
- name: "Profile context", status: "✓",
957
- detail: `Profile entries found - Claude auto-loads your context across sessions`,
958
- });
959
- }
960
- else {
961
- checks.push({
962
- name: "Profile context", status: "⚠",
963
- detail: `No profile entries yet`,
964
- fix: `Run: vault_save type=memory key=profile/business content="<who you are, what you build>"`,
965
- });
966
- }
967
- }
968
- catch {
969
- checks.push({
970
- name: "Profile context", status: "⚠",
971
- detail: `Could not fetch (backend issue)`,
972
- });
973
- }
974
- }
975
- // 6. Tool palette mode
976
- const toolMode = process.env.FINCH_TOOLS ?? "core";
977
- checks.push({
978
- name: "Tool palette",
979
- status: "✓",
980
- detail: `Mode: ${toolMode}${toolMode === "core" ? ` (default - ${CORE_TOOL_COUNT} essential tools)` : toolMode === "all" ? ` (power user - ${TOTAL_TOOL_COUNT} tools)` : ` (custom subset)`}`,
981
- fix: toolMode === "core"
982
- ? `Set FINCH_TOOLS=all to expose all ${TOTAL_TOOL_COUNT} tools (raises LLM context cost).`
983
- : undefined,
984
- });
985
- // 7. MEV-protect broadcast (optional belt-and-suspenders for swaps)
986
- if (process.env.FINCH_BROADCAST_RPC) {
987
- const host = (() => {
988
- try {
989
- return new URL(process.env.FINCH_BROADCAST_RPC).host;
990
- }
991
- catch {
992
- return "custom";
993
- }
994
- })();
995
- checks.push({
996
- name: "MEV-protect", status: "✓",
997
- detail: `Broadcasts routed through ${host} (private/MEV-protected)`,
998
- });
999
- }
1000
- else {
1001
- checks.push({
1002
- name: "MEV-protect", status: "⚠",
1003
- detail: `Standard Base RPC (sequencer is centralized, MEV is naturally low)`,
1004
- fix: `Optional: set FINCH_BROADCAST_RPC=<private-relay-url> for belt-and-suspenders routing.`,
1005
- });
1006
- }
1007
- // 8. GITHUB_TOKEN (optional but affects github_search_code)
1008
- if (process.env.GITHUB_TOKEN) {
1009
- checks.push({
1010
- name: "GitHub token", status: "✓",
1011
- detail: `GITHUB_TOKEN set - github_search_code + private repos work`,
1012
- });
1013
- }
1014
- else {
1015
- checks.push({
1016
- name: "GitHub token", status: "⚠",
1017
- detail: `No GITHUB_TOKEN - github_search_code disabled, other tools rate-limited to 60/hr`,
1018
- fix: `Optional. Create at https://github.com/settings/tokens (scopes: public_repo) and add to MCP env.`,
1019
- });
1020
- }
1021
- // 9. Local memory (self-hosted) - optional, zero-cost alternative to the
1022
- // Convex-proxied cloud memory backend. Two flavors: zero-dependency file
1023
- // store, or a separately-run supermemory server.
1024
- const localMemCfg = (0, local_memory_js_1.getLocalMemoryConfig)();
1025
- if (localMemCfg?.kind === "file") {
1026
- checks.push({
1027
- name: "Local memory", status: "✓",
1028
- detail: `file-based at ~/.finch/memory - memory tools run fully local, zero cost, no server`,
1029
- });
1030
- }
1031
- else if (localMemCfg?.kind === "supermemory") {
1032
- const reachable = await (0, local_memory_js_1.isLocalMemoryReachable)(localMemCfg);
1033
- checks.push(reachable ? {
1034
- name: "Local memory", status: "✓",
1035
- detail: `supermemory reachable at ${localMemCfg.url} - memory tools run fully local, zero cost`,
1036
- } : {
1037
- name: "Local memory", status: "⚠",
1038
- detail: `memoryBackend is "local" but ${localMemCfg.url} isn't reachable`,
1039
- fix: `Start the local server (see \`npx supermemory local\`), or run \`finch setup\` again.`,
1040
- });
1041
- }
1042
- else {
1043
- checks.push({
1044
- name: "Local memory", status: "⚠",
1045
- detail: `Not configured - memory tools use the Finch-hosted proxy`,
1046
- fix: `Run \`finch setup\` to switch to a free, self-hosted local memory backend.`,
1047
- });
1048
- }
1049
- // 10. Local vault (user-owned, on-disk) - the account-free alternative to the
1050
- // Convex-hosted vault.
1051
- if (cfg.vaultBackend === "local") {
1052
- const vaultDir = path.join(os.homedir(), ".finch", "vault");
1053
- let entryCount = 0;
1054
- try {
1055
- const idx = JSON.parse(fs.readFileSync(path.join(vaultDir, "index.json"), "utf8"));
1056
- entryCount = Object.keys(idx.entries ?? {}).length;
1057
- }
1058
- catch { /* no index yet */ }
1059
- checks.push({
1060
- name: "Local vault", status: "✓",
1061
- detail: `on-disk at ~/.finch/vault (${entryCount} entr${entryCount === 1 ? "y" : "ies"}) - you own the data, no account needed`,
1062
- });
1063
- }
1064
- else {
1065
- checks.push({
1066
- name: "Local vault", status: "⚠",
1067
- detail: `Not configured - vault tools use the Finch-hosted store (needs login)`,
1068
- fix: `Run \`finch setup\` to store vault artifacts locally, no account required.`,
1069
- });
1070
- }
1071
- // ── Render ────────────────────────────────────────────────────────────────
1072
- const colorFor = (s) => s === "✓" ? C.green : s === "⚠" ? C.yellow : C.red;
1073
- const widest = Math.max(...checks.map((c) => c.name.length));
1074
- for (const c of checks) {
1075
- const pad = c.name.padEnd(widest);
1076
- console.log(` ${colorFor(c.status)}${c.status}${C.reset} ${C.cyan}${pad}${C.reset} ${c.detail}`);
1077
- if (c.fix)
1078
- console.log(` ${" ".repeat(widest)} ${C.dim}→ ${c.fix}${C.reset}`);
1079
- }
1080
- const counts = { "✓": 0, "⚠": 0, "✗": 0 };
1081
- checks.forEach((c) => counts[c.status]++);
1082
- console.log("");
1083
- console.log(` ${C.dim}Summary:${C.reset} ${C.green}${counts["✓"]} ok${C.reset} · ${C.yellow}${counts["⚠"]} warning${C.reset} · ${C.red}${counts["✗"]} critical${C.reset}`);
1084
- console.log("");
1085
- if (counts["✗"] > 0) {
1086
- console.log(` ${C.red}Critical issues found - fix the lines above before relying on finch.${C.reset}\n`);
1087
- process.exit(1);
1088
- }
1089
- else if (counts["⚠"] > 0) {
1090
- console.log(` ${C.dim}Warnings are non-blocking - finch works but could be smoother.${C.reset}\n`);
1091
- }
1092
- else {
1093
- console.log(` ${C.green}All systems healthy.${C.reset}\n`);
1094
- }
1095
- }
1096
- // ── vault - inspect the local, user-owned vault ──────────────────────────────
1097
- function vaultFlow() {
1098
- const cfg = (0, config_js_1.readConfig)();
1099
- const dir = path.join(os.homedir(), ".finch", "vault");
1100
- const shortDir = dir.replace(os.homedir(), "~");
1101
- const SEP_V = ` ${"─".repeat(52)}`;
1102
- console.log(`\n ${C.cyan}${C.bold}finch vault${C.reset}\n`);
1103
- if (cfg.vaultBackend !== "local") {
1104
- console.log(` ${C.yellow}○${C.reset} Local vault is ${C.yellow}off${C.reset} - vault tools use the hosted store (needs \`finch login\`).`);
1105
- console.log(` ${C.dim}Turn it on:${C.reset} ${C.cyan}finch setup${C.reset} ${C.dim}(answer "y" to local vault) - then your data lives here, on your machine.${C.reset}\n`);
1106
- return;
1107
- }
1108
- let entries = 0, versions = 0, creds = 0;
1109
- try {
1110
- const idx = JSON.parse(fs.readFileSync(path.join(dir, "index.json"), "utf8"));
1111
- const e = idx.entries ?? {};
1112
- entries = Object.keys(e).length;
1113
- versions = Object.values(e).reduce((n, x) => n + (x?.versions?.length ?? 0), 0);
1114
- }
1115
- catch { /* no index yet */ }
1116
- try {
1117
- creds = Object.keys(JSON.parse(fs.readFileSync(path.join(dir, "credentials.json"), "utf8"))).length;
1118
- }
1119
- catch { /* none */ }
1120
- console.log(` ${C.green}●${C.reset} Local vault is ${C.green}on${C.reset} - your data, on your machine, no account.`);
1121
- console.log(`${SEP_V}`);
1122
- console.log(` ${C.dim}Location ${C.reset}${shortDir}`);
1123
- console.log(` ${C.dim}Contents ${C.reset}${entries} entr${entries === 1 ? "y" : "ies"} ${C.dim}·${C.reset} ${versions} version${versions === 1 ? "" : "s"} ${C.dim}·${C.reset} ${creds} credential${creds === 1 ? "" : "s"} ${C.dim}(encrypted)${C.reset}`);
1124
- console.log(`${SEP_V}`);
1125
- console.log(` ${C.dim}It's a plain folder - back it up or sync it however you like:${C.reset}`);
1126
- console.log(` ${C.cyan}cp -r ${shortDir} ~/backup${C.reset} ${C.dim}# copy${C.reset}`);
1127
- console.log(` ${C.cyan}git -C ${shortDir} init${C.reset} ${C.dim}# version-control it${C.reset}`);
1128
- console.log(` ${C.dim}Nothing leaves this machine unless you move it.${C.reset}\n`);
1129
- }
1130
- // ── Robinhood Chain order scheduler ──────────────────────────────────────────
1131
- //
1132
- // rh_dca_create/rh_bracket_create (src/tools/rh-orders.ts) only ever write a
1133
- // LOCAL pending order - nothing executes it. Only rh_orders_tick does, and
1134
- // per that file's own header comment, "an always-on scheduler must call
1135
- // rh_orders_tick {execute:true} periodically" - which this package never
1136
- // actually set up. A user who creates a daily DCA and closes their laptop
1137
- // got nothing, silently: no error, no warning, the order just sat there.
1138
- // These three subcommands are that missing scheduler, reusing the existing
1139
- // tick/lock/kill-switch logic as-is - "wire up the missing piece", not
1140
- // "build order execution from scratch".
1141
- async function runOrdersTick(execute) {
1142
- const { handleRhOrderTool } = await Promise.resolve().then(() => __importStar(require("./tools/rh-orders.js")));
1143
- const result = await handleRhOrderTool("rh_orders_tick", { execute });
1144
- const text = result?.content?.[0]?.type === "text" ? result.content[0].text : "(no output)";
1145
- return { text, isError: !!result?.isError };
1146
- }
1147
- function installOrdersScheduler() {
1148
- // Version-pinned npx invocation, not a hardcoded path to this process's
1149
- // own binary - matches this package's own "always pin the version, never
1150
- // @latest" security guidance (README), and survives regardless of whether
1151
- // the user got here via a global install or a one-off `npx -p`, whose
1152
- // resolved path may not exist by the time the scheduler fires later.
1153
- const invocation = `npx -y -p @finchagentic/mcp@${PKG_VERSION} finch orders tick --execute`;
1154
- if (process.platform === "win32") {
1155
- const taskName = "FinchOrdersTick";
1156
- try {
1157
- child_process.execSync(`schtasks /Create /TN "${taskName}" /TR "${invocation}" /SC MINUTE /MO 15 /F`, { stdio: "pipe" });
1158
- console.log(`\n ${C.green}✓${C.reset} Registered Windows Task Scheduler task "${taskName}" - ticks every 15 min, survives reboots.`);
1159
- console.log(` ${C.dim}Remove it:${C.reset} schtasks /Delete /TN "${taskName}" /F`);
1160
- }
1161
- catch (err) {
1162
- console.error(` ${C.red}✗ schtasks failed: ${err.message}${C.reset}`);
1163
- console.error(` ${C.dim}Try running as Administrator, or use \`finch orders daemon\` instead.${C.reset}\n`);
1164
- process.exit(1);
1165
- }
1166
- }
1167
- else {
1168
- const logFile = path.join(os.homedir(), ".finch", "orders-tick.log");
1169
- const line = `*/15 * * * * ${invocation} >> ${logFile} 2>&1`;
1170
- try {
1171
- const existing = (() => {
1172
- try {
1173
- return child_process.execSync("crontab -l", { stdio: ["pipe", "pipe", "ignore"] }).toString();
1174
- }
1175
- catch {
1176
- return "";
1177
- } // no crontab yet for this user - not an error
1178
- })();
1179
- if (existing.includes("finch orders tick")) {
1180
- console.log(`\n ${C.yellow}○${C.reset} A finch orders tick entry already exists in crontab - not adding a duplicate.\n`);
1181
- return;
1182
- }
1183
- const next = existing.trim().length > 0 ? `${existing.trim()}\n${line}\n` : `${line}\n`;
1184
- child_process.execSync("crontab -", { input: next });
1185
- console.log(`\n ${C.green}✓${C.reset} Added a crontab entry - ticks every 15 min, survives reboots.`);
1186
- console.log(` ${C.dim}Remove it:${C.reset} crontab -e ${C.dim}(delete the finch orders line)${C.reset}`);
1187
- console.log(` ${C.dim}Logs:${C.reset} ${logFile}`);
1188
- }
1189
- catch (err) {
1190
- console.error(` ${C.red}✗ crontab failed: ${err.message}${C.reset}`);
1191
- console.error(` ${C.dim}Try \`finch orders daemon\` instead.${C.reset}\n`);
1192
- process.exit(1);
1193
- }
1194
- }
1195
- console.log(` ${C.dim}Only the local wallet's own default key is needed for signing - set FINCH_WALLET_PASSPHRASE as a persistent system env var (not just your shell rc file) if you use one, since scheduled tasks don't inherit an interactive shell's environment.${C.reset}\n`);
1196
- }
1197
- async function ordersFlow() {
1198
- const sub = process.argv[3];
1199
- if (sub === "tick") {
1200
- const execute = process.argv.includes("--execute");
1201
- const { text, isError } = await runOrdersTick(execute);
1202
- console.log(`\n${text}\n`);
1203
- if (isError)
1204
- process.exit(1);
1205
- return;
1206
- }
1207
- if (sub === "daemon") {
1208
- const intervalFlag = process.argv.indexOf("--interval");
1209
- const intervalMinutes = intervalFlag !== -1 ? Number(process.argv[intervalFlag + 1]) : 15;
1210
- if (!(intervalMinutes > 0)) {
1211
- console.error(` ${C.red}✗ --interval must be a positive number of minutes${C.reset}\n`);
1212
- process.exit(1);
1213
- }
1214
- console.log(`\n ${C.cyan}${C.bold}finch orders daemon${C.reset} ${C.dim}- ticking every ${intervalMinutes}m, Ctrl+C to stop${C.reset}`);
1215
- console.log(` ${C.dim}Kill-switch: RH_ORDERS_DISABLED=1 or create ~/.finch/rh-orders.OFF${C.reset}`);
1216
- console.log(` ${C.dim}Note: this only runs while this process stays alive - for something that${C.reset}`);
1217
- console.log(` ${C.dim}survives reboots/terminal closure, use \`finch orders install-scheduler\` instead.${C.reset}\n`);
1218
- let running = false; // skip a tick if the previous one is still in flight (slow RPC), don't queue up
1219
- const tick = async () => {
1220
- if (running)
1221
- return;
1222
- running = true;
1223
- const ts = new Date().toISOString();
1224
- try {
1225
- const { text } = await runOrdersTick(true);
1226
- console.log(` ${C.dim}[${ts}]${C.reset}`);
1227
- console.log(text.split("\n").map((l) => ` ${l}`).join("\n") + "\n");
1228
- }
1229
- catch (err) {
1230
- console.error(` ${C.red}[${ts}] tick error: ${err.message ?? err}${C.reset}\n`);
1231
- }
1232
- finally {
1233
- running = false;
1234
- }
1235
- };
1236
- await tick();
1237
- const timer = setInterval(tick, intervalMinutes * 60000);
1238
- const shutdown = () => {
1239
- clearInterval(timer);
1240
- console.log(`\n ${C.dim}Stopped.${C.reset}\n`);
1241
- process.exit(0);
1242
- };
1243
- process.on("SIGINT", shutdown);
1244
- process.on("SIGTERM", shutdown);
1245
- return; // process stays alive via the interval timer
1246
- }
1247
- if (sub === "install-scheduler") {
1248
- installOrdersScheduler();
1249
- return;
1250
- }
1251
- console.log(`
1252
- ${C.cyan}${C.bold}finch orders${C.reset} ${C.dim}- Robinhood Chain DCA/TP-SL orders (rh_dca_create/rh_bracket_create)${C.reset}
1253
- ${C.dim}do nothing on their own until one of these is running - creating an order${C.reset}
1254
- ${C.dim}only ever writes it locally, nothing executes it automatically by itself.${C.reset}
1255
-
1256
- ${C.cyan}Commands:${C.reset}
1257
- finch orders tick [--execute] Run one tick now and exit (preview by default)
1258
- finch orders daemon [--interval N] Run in the foreground, ticking every N min (default 15)
1259
- finch orders install-scheduler Register an OS-level recurring task (Task Scheduler / cron)
1260
- `);
1261
- }
1262
- // ── Entry point ───────────────────────────────────────────────────────────────
1263
- const cmd = process.argv[2];
1264
- if (cmd === "install") {
1265
- installFlow().catch((err) => {
1266
- console.error(` ${C.red}✗ install error: ${err.message}${C.reset}`);
1267
- process.exit(1);
1268
- });
1269
- }
1270
- else if (cmd === "login") {
1271
- // loginFlow() already checks FINCH_API_KEY first and falls back to the
1272
- // interactive prompt - no need to duplicate that check here.
1273
- loginFlow().catch((err) => {
1274
- console.error(` ${C.red}✗ login error: ${err.message}${C.reset}`);
1275
- process.exit(1);
1276
- });
1277
- }
1278
- else if (cmd === "doctor") {
1279
- doctorFlow().catch((err) => {
1280
- console.error(` ${C.red}✗ doctor error: ${err.message}${C.reset}`);
1281
- process.exit(1);
1282
- });
1283
- }
1284
- else if (cmd === "setup") {
1285
- setupFlow().catch((err) => {
1286
- console.error(` ${C.red}✗ setup error: ${err.message}${C.reset}`);
1287
- process.exit(1);
1288
- });
1289
- }
1290
- else if (cmd === "vault") {
1291
- vaultFlow();
1292
- }
1293
- else if (cmd === "orders") {
1294
- ordersFlow().catch((err) => {
1295
- console.error(` ${C.red}✗ orders error: ${err.message}${C.reset}`);
1296
- process.exit(1);
1297
- });
1298
- }
1299
- else if (cmd === "logout") {
1300
- const cfg = (0, config_js_1.readConfig)();
1301
- if (!cfg.sessionToken) {
1302
- console.log(`\n ${C.dim}Already signed out.${C.reset}\n`);
1303
- }
1304
- else {
1305
- (0, config_js_1.writeConfig)({ sessionToken: undefined, email: undefined });
1306
- console.log(`\n ${C.green}✓${C.reset} Signed out${cfg.email ? ` (${cfg.email})` : ""}. Token cleared from ~/.finch/config.json\n`);
1307
- }
1308
- }
1309
- else if (cmd === "status") {
1310
- const cfg = (0, config_js_1.readConfig)();
1311
- // Every base_mcp_*/rh_mcp_* tool signs locally on BOTH chains - the
1312
- // account's custodial webapp wallet is a separate address this CLI never
1313
- // touches, so always show the local one to match what tools report.
1314
- const walletAddr = getLocalWalletAddress();
1315
- const provider = process.env.BANKR_API_KEY ? "Bankr"
1316
- : process.env.ANTHROPIC_API_KEY ? "Anthropic"
1317
- : process.env.OPENAI_API_KEY ? "OpenAI"
1318
- : "Finch proxy";
1319
- const SEP_S = ` ${"─".repeat(52)}`;
1320
- console.log(`\n${SEP_S}`);
1321
- if (cfg.sessionToken) {
1322
- const displayName = cfg.name ? `${C.white}${C.bold}${cfg.name}${C.reset} ` : "";
1323
- const displayEmail = cfg.email ? `${C.dim}${cfg.email}${C.reset}` : "";
1324
- console.log(` ${C.green}●${C.reset} ${displayName}${displayEmail}`);
1325
- if (walletAddr) {
1326
- const note = "Base + Robinhood Chain · local device wallet";
1327
- console.log(` ${C.dim}Wallet ${C.reset}${C.cyan}${walletAddr.slice(0, 6)}...${walletAddr.slice(-4)}${C.reset} ${C.dim}· ${note}${C.reset}`);
1328
- }
1329
- else {
1330
- console.log(` ${C.dim}Wallet not created yet${C.reset}`);
1331
- }
1332
- console.log(` ${C.dim}LLM ${C.reset}${C.green}${provider}${C.reset} ${C.dim}· ${TOTAL_TOOL_COUNT} tools · v${PKG_VERSION}${C.reset}`);
1333
- }
1334
- else {
1335
- console.log(` ${C.yellow}○${C.reset} ${C.yellow}Not signed in${C.reset}`);
1336
- console.log(` ${C.dim}→ run \`finch login\` to unlock all ${TOTAL_TOOL_COUNT} tools${C.reset}`);
1337
- console.log(` ${C.dim}LLM ${provider} · v${PKG_VERSION}${C.reset}`);
1338
- }
1339
- console.log(`${SEP_S}\n`);
1340
- }
1341
- else if (cmd === "run") {
1342
- const prompt = process.argv.slice(3).join(" ");
1343
- if (!prompt) {
1344
- console.error(` ${C.red}✗ usage: finch run "your prompt"${C.reset}\n`);
1345
- process.exit(1);
1346
- }
1347
- (0, wallet_js_1.warnIfNoPassphrase)();
1348
- (async () => {
1349
- const history = [];
1350
- const stop = spinner("thinking");
1351
- try {
1352
- const result = await (0, agent_loop_js_1.runAgent)(prompt, history, (toolName) => {
1353
- stop();
1354
- process.stdout.write(` ${C.dim}✦ ${toolName}${C.reset}\n`);
1355
- });
1356
- stop();
1357
- console.log(`\n${result.text}\n`);
1358
- }
1359
- catch (err) {
1360
- stop();
1361
- console.error(` ${C.red}✗ run error: ${err.message || err}${C.reset}\n`);
1362
- process.exit(1);
1363
- }
1364
- })();
1365
- }
1366
- else if (cmd === "help" || cmd === "--help" || cmd === "-h") {
1367
- console.log(`
1368
- ${C.cyan}${C.bold}finch${C.reset} ${C.dim}runtime layer for Agentic AI · terminal CLI${C.reset}
1369
-
1370
- ${C.cyan}Commands:${C.reset}
1371
- finch Start interactive AI terminal
1372
- finch run "..." Run a single prompt and exit (scripting/CI-friendly)
1373
- finch install Auto-configure all detected MCP clients
1374
- finch login Sign in to unlock all tools
1375
- finch logout Sign out and clear saved token
1376
- finch status Show auth state and version (quick check)
1377
- finch doctor Run a full health check + suggest fixes
1378
- finch setup Configure your own LLM key and/or local memory + vault
1379
- finch vault Show your local vault (location, contents, backup)
1380
- finch orders Run/schedule Robinhood Chain DCA & TP-SL order ticks
1381
- finch help Show this help
1382
-
1383
- ${C.dim}Claude Code / Cursor / Windsurf / Codex / Aeon / Antigravity / Zed — anywhere MCP runs.${C.reset}
1384
- `);
1385
- }
1386
- else {
1387
- main().catch(err => {
1388
- console.error(`Fatal: ${err.message}`);
1389
- process.exit(1);
1390
- });
1391
- }