@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.
- package/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/_text-search.js +0 -39
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -150
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -456
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1059
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- 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
|
-
}
|