@fidacy/mcp 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/core.js +1 -1
- package/dist/index.js +604 -449
- package/dist/lib.js +3 -3
- package/dist/postinstall.d.ts +2 -0
- package/dist/postinstall.js +255 -0
- package/dist/register.d.ts +11 -0
- package/dist/setup.d.ts +27 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,573 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res, err) => function __init() {
|
|
5
|
+
if (err) throw err[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err = [e], e;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/config.ts
|
|
18
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import {
|
|
22
|
+
existsSync,
|
|
23
|
+
mkdirSync,
|
|
24
|
+
readFileSync,
|
|
25
|
+
writeFileSync
|
|
26
|
+
} from "node:fs";
|
|
27
|
+
function hasEngineKey(keyOverride) {
|
|
28
|
+
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
29
|
+
}
|
|
30
|
+
function configDir() {
|
|
31
|
+
return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
|
|
32
|
+
}
|
|
33
|
+
function configPath() {
|
|
34
|
+
return join(configDir(), "config.json");
|
|
35
|
+
}
|
|
36
|
+
function auditLogPath() {
|
|
37
|
+
if (process.env.FIDACY_AUDIT_PATH) return process.env.FIDACY_AUDIT_PATH;
|
|
38
|
+
const dir = join(configDir(), "audit");
|
|
39
|
+
try {
|
|
40
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return join(dir, "audit.log");
|
|
44
|
+
}
|
|
45
|
+
function readConfig() {
|
|
46
|
+
const p = configPath();
|
|
47
|
+
if (!existsSync(p)) return null;
|
|
48
|
+
try {
|
|
49
|
+
const raw = JSON.parse(readFileSync(p, "utf8"));
|
|
50
|
+
if (!raw || typeof raw.anon_id !== "string") return null;
|
|
51
|
+
return {
|
|
52
|
+
anon_id: raw.anon_id,
|
|
53
|
+
tier: raw.tier === "paid" ? "paid" : "free",
|
|
54
|
+
api_key: typeof raw.api_key === "string" ? raw.api_key : null,
|
|
55
|
+
mandate: raw.mandate,
|
|
56
|
+
// Install-state passthrough: dropping these on a read→write cycle would
|
|
57
|
+
// reset every once-per-install nudge into an every-time nag.
|
|
58
|
+
created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
|
|
59
|
+
nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
|
|
60
|
+
decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
|
|
61
|
+
hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
|
|
62
|
+
operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
|
|
63
|
+
registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
|
|
64
|
+
};
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function writeConfig(cfg) {
|
|
70
|
+
const dir = configDir();
|
|
71
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
72
|
+
writeFileSync(configPath(), JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
73
|
+
}
|
|
74
|
+
function ensureState() {
|
|
75
|
+
const existing = readConfig();
|
|
76
|
+
if (existing) return { config: existing, firstRun: false };
|
|
77
|
+
const config = {
|
|
78
|
+
anon_id: randomUUID2(),
|
|
79
|
+
tier: "free",
|
|
80
|
+
api_key: null,
|
|
81
|
+
mandate: { payees: [], categories: ["*"], currency: "USD", perTxMax: 2500, maxTotal: 1e4 },
|
|
82
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
writeConfig(config);
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
return { config, firstRun: true };
|
|
89
|
+
}
|
|
90
|
+
function resolveMandateRules(cfg) {
|
|
91
|
+
const m = cfg?.mandate ?? {};
|
|
92
|
+
const envList = (v) => v === void 0 ? void 0 : v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
93
|
+
const envNum = (name, v) => {
|
|
94
|
+
if (v === void 0 || v.trim() === "") return void 0;
|
|
95
|
+
const n = Number(v);
|
|
96
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
97
|
+
console.error(
|
|
98
|
+
`[fidacy] ${name}="${v}" is not a positive number, so it cannot be enforced as a cap. Ignoring it and using the safe default. Write digits only, with no thousands separator, currency symbol or unit (e.g. ${name}=2500).`
|
|
99
|
+
);
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
return n;
|
|
103
|
+
};
|
|
104
|
+
const fileNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
|
|
105
|
+
return {
|
|
106
|
+
payees: envList(process.env.FIDACY_ALLOW_PAYEES) ?? m.payees ?? [],
|
|
107
|
+
categories: envList(process.env.FIDACY_ALLOW_CATEGORIES) ?? m.categories ?? ["*"],
|
|
108
|
+
currency: process.env.FIDACY_CURRENCY ?? m.currency ?? "USD",
|
|
109
|
+
perTxMax: envNum("FIDACY_PER_TX_MAX", process.env.FIDACY_PER_TX_MAX) ?? fileNum(m.perTxMax) ?? 2500,
|
|
110
|
+
maxTotal: envNum("FIDACY_MAX_TOTAL", process.env.FIDACY_MAX_TOTAL) ?? fileNum(m.maxTotal) ?? 1e4
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
var init_config = __esm({
|
|
114
|
+
"src/config.ts"() {
|
|
115
|
+
"use strict";
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// src/telemetry.ts
|
|
120
|
+
function bandOf(amount) {
|
|
121
|
+
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
122
|
+
if (amount < 10) return "lt10";
|
|
123
|
+
if (amount < 100) return "10_99";
|
|
124
|
+
if (amount < 1e3) return "100_999";
|
|
125
|
+
if (amount < 1e4) return "1k_10k";
|
|
126
|
+
if (amount < 1e5) return "10k_100k";
|
|
127
|
+
return "gte100k";
|
|
128
|
+
}
|
|
129
|
+
function capRatioOf(amount, perTxMax) {
|
|
130
|
+
if (typeof amount !== "number" || typeof perTxMax !== "number") return void 0;
|
|
131
|
+
if (!Number.isFinite(amount) || !Number.isFinite(perTxMax) || perTxMax <= 0 || amount <= perTxMax) return void 0;
|
|
132
|
+
const r = amount / perTxMax;
|
|
133
|
+
if (r < 2) return "1_2x";
|
|
134
|
+
if (r < 5) return "2_5x";
|
|
135
|
+
if (r < 10) return "5_10x";
|
|
136
|
+
return "gt10x";
|
|
137
|
+
}
|
|
138
|
+
function telemetryEnabled() {
|
|
139
|
+
const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
|
|
140
|
+
return !(v === "1" || v === "true" || v === "yes");
|
|
141
|
+
}
|
|
142
|
+
function endpoint() {
|
|
143
|
+
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
144
|
+
return `${base}/v1/telemetry`;
|
|
145
|
+
}
|
|
146
|
+
function anonId() {
|
|
147
|
+
if (anonIdCache) return anonIdCache;
|
|
148
|
+
anonIdCache = readConfig()?.anon_id ?? null;
|
|
149
|
+
return anonIdCache;
|
|
150
|
+
}
|
|
151
|
+
function armExitFlush() {
|
|
152
|
+
if (exitHookArmed || typeof process === "undefined" || typeof process.once !== "function") return;
|
|
153
|
+
exitHookArmed = true;
|
|
154
|
+
process.once("beforeExit", () => {
|
|
155
|
+
if (buffer.length > 0) void flush();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function resultOf(status, violatedRule) {
|
|
159
|
+
if (status === "ALLOW") return "allow";
|
|
160
|
+
const r = violatedRule ?? "";
|
|
161
|
+
if (r.startsWith("payee_lookalike")) return "deny_lookalike";
|
|
162
|
+
if (r.startsWith("payee_not_in_allowlist")) return "deny_payee";
|
|
163
|
+
if (r.startsWith("duplicate_invoice")) return "deny_duplicate";
|
|
164
|
+
if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
|
|
165
|
+
if (r.startsWith("mandate_revoked") || r.startsWith("before_mandate") || r.startsWith("after_mandate"))
|
|
166
|
+
return "deny_window";
|
|
167
|
+
if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
|
|
168
|
+
return "deny_scope";
|
|
169
|
+
}
|
|
170
|
+
function record(type, result, band, capRatio, action) {
|
|
171
|
+
if (!telemetryEnabled()) return;
|
|
172
|
+
armExitFlush();
|
|
173
|
+
buffer.push({
|
|
174
|
+
type,
|
|
175
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
176
|
+
client_version: CLIENT_VERSION,
|
|
177
|
+
shell: currentShell,
|
|
178
|
+
...result ? { result } : {},
|
|
179
|
+
...band ? { band } : {},
|
|
180
|
+
...capRatio ? { cap_ratio: capRatio } : {},
|
|
181
|
+
...action ? { action } : {}
|
|
182
|
+
});
|
|
183
|
+
if (!flushTimer) {
|
|
184
|
+
flushTimer = setTimeout(() => {
|
|
185
|
+
void flush();
|
|
186
|
+
}, 2e3);
|
|
187
|
+
if (typeof flushTimer.unref === "function") flushTimer.unref();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function flush() {
|
|
191
|
+
if (flushTimer) {
|
|
192
|
+
clearTimeout(flushTimer);
|
|
193
|
+
flushTimer = null;
|
|
194
|
+
}
|
|
195
|
+
if (!telemetryEnabled() || buffer.length === 0) return;
|
|
196
|
+
const id = anonId();
|
|
197
|
+
if (!id) {
|
|
198
|
+
buffer = [];
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const events = buffer;
|
|
202
|
+
buffer = [];
|
|
203
|
+
try {
|
|
204
|
+
await fetch(endpoint(), {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { "content-type": "application/json" },
|
|
207
|
+
body: JSON.stringify({ anon_id: id, events })
|
|
208
|
+
});
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
var CLIENT_VERSION, currentShell, anonIdCache, buffer, flushTimer, exitHookArmed, recordInstall, recordAgentActive, recordDecision, recordUpgradeIntent, recordToolUse;
|
|
213
|
+
var init_telemetry = __esm({
|
|
214
|
+
"src/telemetry.ts"() {
|
|
215
|
+
"use strict";
|
|
216
|
+
init_config();
|
|
217
|
+
CLIENT_VERSION = true ? "0.6.0" : "dev";
|
|
218
|
+
currentShell = "mcp";
|
|
219
|
+
anonIdCache = null;
|
|
220
|
+
buffer = [];
|
|
221
|
+
flushTimer = null;
|
|
222
|
+
exitHookArmed = false;
|
|
223
|
+
recordInstall = () => record("install");
|
|
224
|
+
recordAgentActive = () => record("agent_active");
|
|
225
|
+
recordDecision = (status, violatedRule, amount, perTxMax) => record("decision", resultOf(status, violatedRule), bandOf(amount), capRatioOf(amount, perTxMax));
|
|
226
|
+
recordUpgradeIntent = () => record("upgrade_intent");
|
|
227
|
+
recordToolUse = (action) => record("tool_use", void 0, void 0, void 0, action);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// src/register.ts
|
|
232
|
+
function endpoint3() {
|
|
233
|
+
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
234
|
+
return `${base}/v1/register`;
|
|
235
|
+
}
|
|
236
|
+
function looksLikeEmail(s) {
|
|
237
|
+
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
|
|
238
|
+
}
|
|
239
|
+
function operatorEmail(override) {
|
|
240
|
+
const raw = (override ?? process.env.FIDACY_OPERATOR_EMAIL ?? readConfig()?.operator_email ?? "").trim();
|
|
241
|
+
return raw && looksLikeEmail(raw) ? raw : null;
|
|
242
|
+
}
|
|
243
|
+
async function registerEmail(email, shell = "mcp") {
|
|
244
|
+
const cfg = readConfig();
|
|
245
|
+
if (!cfg) return "skipped";
|
|
246
|
+
const clean = email.trim();
|
|
247
|
+
if (!looksLikeEmail(clean)) return "skipped";
|
|
248
|
+
if (cfg.registered_email && cfg.registered_email.toLowerCase() === clean.toLowerCase()) return "skipped";
|
|
249
|
+
try {
|
|
250
|
+
const res = await fetch(endpoint3(), {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: { "content-type": "application/json" },
|
|
253
|
+
body: JSON.stringify({ anon_id: cfg.anon_id, email: clean, client_version: CLIENT_VERSION3, shell })
|
|
254
|
+
});
|
|
255
|
+
if (res.status === 429) return "rate_limited";
|
|
256
|
+
if (!res.ok) return "failed";
|
|
257
|
+
const fresh = readConfig() ?? cfg;
|
|
258
|
+
writeConfig({ ...fresh, operator_email: clean, registered_email: clean });
|
|
259
|
+
return "registered";
|
|
260
|
+
} catch {
|
|
261
|
+
return "failed";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function rememberOperatorEmail(email) {
|
|
265
|
+
const cfg = readConfig();
|
|
266
|
+
if (!cfg) return;
|
|
267
|
+
const clean = email.trim();
|
|
268
|
+
if (!looksLikeEmail(clean)) return;
|
|
269
|
+
try {
|
|
270
|
+
writeConfig({ ...cfg, operator_email: clean });
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function needsOperatorEmail() {
|
|
275
|
+
const cfg = readConfig();
|
|
276
|
+
if (!cfg) return false;
|
|
277
|
+
return !cfg.registered_email && !operatorEmail();
|
|
278
|
+
}
|
|
279
|
+
function setupEmailAsk(nudgeOnce2) {
|
|
280
|
+
if (!needsOperatorEmail()) return null;
|
|
281
|
+
if (!nudgeOnce2("setup_email_ask")) return null;
|
|
282
|
+
return `Setup, once: ask your human operator for their email and then call register_email with it \u2014 it keeps this agent's protection history and lets it move into their free account. If they would rather not, drop it and do not ask again; the firewall works either way.`;
|
|
283
|
+
}
|
|
284
|
+
async function autoRegister(shell = "mcp") {
|
|
285
|
+
const email = operatorEmail();
|
|
286
|
+
if (!email) return "skipped";
|
|
287
|
+
return registerEmail(email, shell);
|
|
288
|
+
}
|
|
289
|
+
var CLIENT_VERSION3;
|
|
290
|
+
var init_register = __esm({
|
|
291
|
+
"src/register.ts"() {
|
|
292
|
+
"use strict";
|
|
293
|
+
init_config();
|
|
294
|
+
CLIENT_VERSION3 = true ? "0.6.0" : "dev";
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// src/nudges.ts
|
|
299
|
+
function claimUrl() {
|
|
300
|
+
const anon = readConfig()?.anon_id;
|
|
301
|
+
return anon ? `https://fidacy.com/claim?ref=${anon}` : null;
|
|
302
|
+
}
|
|
303
|
+
function nudgeOnce(key) {
|
|
304
|
+
const cfg = readConfig();
|
|
305
|
+
if (!cfg) return false;
|
|
306
|
+
if (cfg.nudges?.[key]) return false;
|
|
307
|
+
cfg.nudges = { ...cfg.nudges ?? {}, [key]: true };
|
|
308
|
+
try {
|
|
309
|
+
writeConfig(cfg);
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
function sessionDecisionCount() {
|
|
316
|
+
return sessionDecisions;
|
|
317
|
+
}
|
|
318
|
+
function bumpDecisionCount() {
|
|
319
|
+
sessionDecisions += 1;
|
|
320
|
+
const cfg = readConfig();
|
|
321
|
+
if (!cfg) return sessionDecisions;
|
|
322
|
+
const n = Math.max((cfg.decisions_count ?? 0) + 1, sessionDecisions);
|
|
323
|
+
cfg.decisions_count = n;
|
|
324
|
+
try {
|
|
325
|
+
writeConfig(cfg);
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
return Math.max(n, sessionDecisions);
|
|
329
|
+
}
|
|
330
|
+
function installAgeDays() {
|
|
331
|
+
const t = Date.parse(readConfig()?.created_at ?? "");
|
|
332
|
+
if (Number.isNaN(t)) return 0;
|
|
333
|
+
return Math.floor((Date.now() - t) / 864e5);
|
|
334
|
+
}
|
|
335
|
+
function decisionNudge(status, violatedRule, upgradeToolName) {
|
|
336
|
+
const count = bumpDecisionCount();
|
|
337
|
+
const url = claimUrl();
|
|
338
|
+
const claimPath = url ? `free account + free API key at ${url} (this install's history migrates)` : `call the ${upgradeToolName} tool (free account + free API key, this history migrates)`;
|
|
339
|
+
if (status === "DENY" && violatedRule?.startsWith("duplicate_invoice") && nudgeOnce("bec_catch")) {
|
|
340
|
+
return `That was a duplicate-invoice (BEC) catch, the #1 payment fraud. For your operator: ${claimPath}. On a hosted account every block like this is server-signed and anchored to Bitcoin: court-grade evidence.`;
|
|
341
|
+
}
|
|
342
|
+
if (status === "DENY" && nudgeOnce("first_deny")) {
|
|
343
|
+
return `Fidacy just blocked this payment before it moved. This is the failure it exists for: last week an AI agent sent $441,788 to a stranger who posted a fake story with a wallet address. Nothing was checking it. For your operator: ${claimPath}.`;
|
|
344
|
+
}
|
|
345
|
+
if (status === "ALLOW" && nudgeOnce("first_allow")) {
|
|
346
|
+
return `Fidacy authorized this and signed the decision. For your operator: ${claimPath}.`;
|
|
347
|
+
}
|
|
348
|
+
if (count === 15 && nudgeOnce("proof_teaser")) {
|
|
349
|
+
return `Fidacy has now checked ${count} payments on this agent and stands between it and the wrong one, the prompt-injected payee, the duplicate, the over-limit charge. That protection pauses after 20 decisions without a free key. Keep the agent covered: ${claimPath}.`;
|
|
350
|
+
}
|
|
351
|
+
if (count === 10 && nudgeOnce("milestone_10")) {
|
|
352
|
+
return `That's 10 actions gated on this install. For your operator: ${claimPath}.`;
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
function hostedWallCta(feature, billing) {
|
|
357
|
+
const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
|
|
358
|
+
if (hasEngineKey()) {
|
|
359
|
+
const where2 = billing?.billingUrl ?? "https://app.fidacy.com/billing";
|
|
360
|
+
const what = billing?.reason === "spending_cap_reached" ? `This account hit the monthly spending cap you set, so ${noun} are paused.` : billing?.detail ?? `This account's included ${noun} for the month are used up.`;
|
|
361
|
+
return `${what} The local firewall keeps deciding and blocking as normal \u2014 this only pauses ${noun}. For your operator: resolve it at ${where2}. Pay-as-you-go needs no plan and starts at $0.04 per verdict, dropping to $0.025 past 50k and $0.015 past 250k; committed plans price below that curve.`;
|
|
362
|
+
}
|
|
363
|
+
const url = claimUrl();
|
|
364
|
+
const cta = url ? `open ${url} \u2014 free account, this install's history migrates` : `call the upgrade tool \u2014 free account, your history migrates`;
|
|
365
|
+
if (nudgeOnce("hosted_wall")) {
|
|
366
|
+
return `You've used this install's free allowance for ${noun} \u2014 the proofs anyone can re-verify at fidacy.com/verify, court-grade, not a local-only check. Keep them flowing: for your operator, ${cta}.`;
|
|
367
|
+
}
|
|
368
|
+
return `Free allowance for ${noun} reached. For your operator: ${cta}.`;
|
|
369
|
+
}
|
|
370
|
+
function noKeyCta() {
|
|
371
|
+
const url = claimUrl();
|
|
372
|
+
return url ? `free in ~1 min for your operator: open ${url} (this install's history migrates), or set FIDACY_ENGINE_API_KEY if you already have one` : `free in ~1 min: call the upgrade tool (your history migrates), or set FIDACY_ENGINE_API_KEY if you already have one`;
|
|
373
|
+
}
|
|
374
|
+
function weekOneBootNudge(upgradeToolName) {
|
|
375
|
+
if (installAgeDays() >= 7 && nudgeOnce("week_1")) {
|
|
376
|
+
const url = claimUrl();
|
|
377
|
+
return url ? ` One week protected. See and claim this install's block history: ${url} (free).` : ` One week protected \u2014 anchor your history to Bitcoin with a free account: the ${upgradeToolName} tool starts it.`;
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
var sessionDecisions;
|
|
382
|
+
var init_nudges = __esm({
|
|
383
|
+
"src/nudges.ts"() {
|
|
384
|
+
"use strict";
|
|
385
|
+
init_config();
|
|
386
|
+
sessionDecisions = 0;
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// src/activation.ts
|
|
391
|
+
function decisionsUsed() {
|
|
392
|
+
return Math.max(readConfig()?.decisions_count ?? 0, sessionDecisionCount());
|
|
393
|
+
}
|
|
394
|
+
function remainingFree() {
|
|
395
|
+
return Math.max(0, FREE_DECISIONS - decisionsUsed());
|
|
396
|
+
}
|
|
397
|
+
function claimDestination() {
|
|
398
|
+
return claimUrl() ?? "https://fidacy.com/claim";
|
|
399
|
+
}
|
|
400
|
+
function activationGate(keyOverride) {
|
|
401
|
+
if (hasEngineKey(keyOverride)) return null;
|
|
402
|
+
if (decisionsUsed() < FREE_DECISIONS) return null;
|
|
403
|
+
recordUpgradeIntent();
|
|
404
|
+
const url = claimDestination();
|
|
405
|
+
return {
|
|
406
|
+
message: `DENY (activation_required). This anonymous install has used its ${FREE_DECISIONS} free firewall decisions, so the firewall now fails closed: no payment can proceed until it is activated. Activation is a free API key, about 2 minutes, no card. Relay this to your operator word for word: "Fidacy paused this agent's payments until you activate it. Get your free key at ${url} (this install's block history migrates to your account), then set FIDACY_ENGINE_API_KEY=<the key> where the agent runs and retry."`
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function trialCountdownLine(keyOverride) {
|
|
410
|
+
if (hasEngineKey(keyOverride)) return null;
|
|
411
|
+
if (!readConfig() && sessionDecisionCount() === 0) return null;
|
|
412
|
+
const left = remainingFree();
|
|
413
|
+
if (left > 5) return null;
|
|
414
|
+
const url = claimDestination();
|
|
415
|
+
if (left === 0) {
|
|
416
|
+
return ` [fidacy] That was this install's LAST free anonymous decision \u2014 the next payment will be denied until it is activated. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
|
|
417
|
+
}
|
|
418
|
+
return ` [fidacy] ${left} free anonymous decision${left === 1 ? "" : "s"} left on this install before the firewall requires activation. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
|
|
419
|
+
}
|
|
420
|
+
var FREE_DECISIONS;
|
|
421
|
+
var init_activation = __esm({
|
|
422
|
+
"src/activation.ts"() {
|
|
423
|
+
"use strict";
|
|
424
|
+
init_config();
|
|
425
|
+
init_nudges();
|
|
426
|
+
init_telemetry();
|
|
427
|
+
FREE_DECISIONS = 20;
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// src/protection.ts
|
|
432
|
+
function markHostedLapsed() {
|
|
433
|
+
const cfg = readConfig();
|
|
434
|
+
if (!cfg || cfg.hosted_lapsed_at) return;
|
|
435
|
+
cfg.hosted_lapsed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
436
|
+
try {
|
|
437
|
+
writeConfig(cfg);
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function clearHostedLapsed() {
|
|
442
|
+
const cfg = readConfig();
|
|
443
|
+
if (!cfg?.hosted_lapsed_at) return;
|
|
444
|
+
delete cfg.hosted_lapsed_at;
|
|
445
|
+
try {
|
|
446
|
+
writeConfig(cfg);
|
|
447
|
+
} catch {
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function isHostedLapsed() {
|
|
451
|
+
return Boolean(readConfig()?.hosted_lapsed_at);
|
|
452
|
+
}
|
|
453
|
+
function protectionState(decisionsUsed2, keyOverride) {
|
|
454
|
+
if (hasEngineKey(keyOverride)) return isHostedLapsed() ? "unproven" : "protected_keyed";
|
|
455
|
+
if (decisionsUsed2 >= FREE_DECISIONS) return "halted";
|
|
456
|
+
return FREE_DECISIONS - decisionsUsed2 <= 5 ? "protected_low" : "protected_trial";
|
|
457
|
+
}
|
|
458
|
+
function where() {
|
|
459
|
+
return claimUrl() ?? "https://fidacy.com/claim";
|
|
460
|
+
}
|
|
461
|
+
function protectionBadge() {
|
|
462
|
+
return `\u{1F6E1} ${BRAND} blocked this payment. Your agent was protected.`;
|
|
463
|
+
}
|
|
464
|
+
function protectionWarning(decisionsUsed2, keyOverride) {
|
|
465
|
+
const state2 = protectionState(decisionsUsed2, keyOverride);
|
|
466
|
+
const left = Math.max(0, FREE_DECISIONS - decisionsUsed2);
|
|
467
|
+
switch (state2) {
|
|
468
|
+
case "protected_trial":
|
|
469
|
+
return null;
|
|
470
|
+
// working and nothing is expiring soon: silence is the respect.
|
|
471
|
+
case "protected_low":
|
|
472
|
+
return `\u26A0 ${BRAND}: ${left} free decision${left === 1 ? "" : "s"} left, then this agent's payments STOP DEAD. Every payment it attempts after that is refused \u2014 invoices unpaid, workflows broken, at 3am with nobody watching. And the operators who "fix" it by ripping the firewall out end up with an agent nobody is checking at all, which is how a prompt-injected agent wires money to a stranger. Keep it alive, free, in ~2 minutes: ${where()} \u2014 no card, this install's block history moves with you, plus ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
|
|
473
|
+
case "halted":
|
|
474
|
+
return `\u26D4 ${BRAND} IS NOT ACTIVATED \u2014 YOUR AGENT CAN NO LONGER PAY ANYTHING. Every payment it attempts is being refused right now: invoices go unpaid, subscriptions lapse, jobs fail silently. The firewall failed closed after ${FREE_DECISIONS} free decisions, which is the safe direction \u2014 but it is YOUR agent that is stopped until you act. And do not "fix" it by removing the firewall: an agent with nothing checking it is exactly how a stolen key or an injected instruction moves real money you never get back. Activate free in ~2 minutes at ${where()}: no card, your block history moves with you, ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
|
|
475
|
+
case "unproven":
|
|
476
|
+
return `\u26D4 ${BRAND}: YOUR AGENT IS EXPOSED RIGHT NOW. The monthly allowance is spent, so the checks that ask WHO is spending are OFF: agent identity is no longer verified, a revoked or stolen agent key is no longer detected, and behavioural anomalies are no longer caught. Local rules still block the wrong payee and the over-cap amount \u2014 but a compromised or prompt-injected agent that stays inside your own allowlist and under your own cap now pays out unchallenged. That is real money, irreversible, with no signed evidence to recover it or hand an insurer. Turn the checks back on: https://app.fidacy.com/billing \u2014 pay-as-you-go, no plan, from $0.04 per payment checked. One prevented payment pays for years of it.`;
|
|
477
|
+
case "protected_keyed":
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function protectionBootLine(decisionsUsed2) {
|
|
482
|
+
const state2 = protectionState(decisionsUsed2);
|
|
483
|
+
switch (state2) {
|
|
484
|
+
case "halted":
|
|
485
|
+
return ` \u26D4 NOT ACTIVATED \u2014 your agent cannot pay until you activate at ${where()} (free, no card).`;
|
|
486
|
+
case "protected_low":
|
|
487
|
+
return ` \u26A0 ${Math.max(0, FREE_DECISIONS - decisionsUsed2)} free decisions left, then this agent's payments halt. Free key: ${where()}`;
|
|
488
|
+
case "unproven":
|
|
489
|
+
return ` \u26D4 EXPOSED \u2014 identity, revocation and anomaly checks are OFF (allowance spent). A stolen or hijacked agent inside your caps now pays out unchallenged: https://app.fidacy.com/billing`;
|
|
490
|
+
case "protected_trial":
|
|
491
|
+
return ` ${BRAND} active \u2014 ${decisionsUsed2}/${FREE_DECISIONS} free decisions used.`;
|
|
492
|
+
case "protected_keyed":
|
|
493
|
+
return ` ${BRAND} active and signing \u2014 blocks are provable to third parties.`;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
var BRAND, FREE_MONTHLY_VERDICTS;
|
|
497
|
+
var init_protection = __esm({
|
|
498
|
+
"src/protection.ts"() {
|
|
499
|
+
"use strict";
|
|
500
|
+
init_config();
|
|
501
|
+
init_activation();
|
|
502
|
+
init_nudges();
|
|
503
|
+
BRAND = "Fidacy AI Agent Firewall";
|
|
504
|
+
FREE_MONTHLY_VERDICTS = 300;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// src/setup.ts
|
|
509
|
+
var setup_exports = {};
|
|
510
|
+
__export(setup_exports, {
|
|
511
|
+
isInteractive: () => isInteractive,
|
|
512
|
+
registeredEmail: () => registeredEmail,
|
|
513
|
+
runSetup: () => runSetup
|
|
514
|
+
});
|
|
515
|
+
import { createInterface } from "node:readline/promises";
|
|
516
|
+
function isInteractive() {
|
|
517
|
+
if (process.env.CI || process.env.FIDACY_NONINTERACTIVE) return false;
|
|
518
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
519
|
+
}
|
|
520
|
+
function looksLikeEmail2(s) {
|
|
521
|
+
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
|
|
522
|
+
}
|
|
523
|
+
async function runSetup(opts = {}) {
|
|
524
|
+
if (!opts.force && !isInteractive()) return "skipped_noninteractive";
|
|
525
|
+
ensureState();
|
|
526
|
+
if (!needsOperatorEmail() && !opts.force) return "already";
|
|
527
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
528
|
+
try {
|
|
529
|
+
process.stdout.write(`
|
|
530
|
+
${BRAND}
|
|
531
|
+
`);
|
|
532
|
+
process.stdout.write(
|
|
533
|
+
" Your email keeps this agent's protection history and lets it move into a free account.\n Optional \u2014 press Enter to skip. You are never emailed without reason, and can remove it any time.\n\n"
|
|
534
|
+
);
|
|
535
|
+
const answer = (await rl.question(" Email: ")).trim();
|
|
536
|
+
if (!answer) {
|
|
537
|
+
process.stdout.write(" Skipped. The firewall is active either way.\n\n");
|
|
538
|
+
return "declined";
|
|
539
|
+
}
|
|
540
|
+
if (!looksLikeEmail2(answer)) {
|
|
541
|
+
process.stdout.write(" That does not look like an email \u2014 skipping. Run `npx @fidacy/mcp setup` to try again.\n\n");
|
|
542
|
+
return "declined";
|
|
543
|
+
}
|
|
544
|
+
const outcome = await registerEmail(answer, "mcp");
|
|
545
|
+
if (outcome === "registered" || outcome === "skipped") {
|
|
546
|
+
process.stdout.write(` Registered ${answer}. Continuing the install.
|
|
547
|
+
|
|
548
|
+
`);
|
|
549
|
+
return "registered";
|
|
550
|
+
}
|
|
551
|
+
rememberOperatorEmail(answer);
|
|
552
|
+
process.stdout.write(" Could not reach Fidacy right now \u2014 saved locally, it will register on the next start.\n\n");
|
|
553
|
+
return "failed";
|
|
554
|
+
} catch {
|
|
555
|
+
return "failed";
|
|
556
|
+
} finally {
|
|
557
|
+
rl.close();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function registeredEmail() {
|
|
561
|
+
return readConfig()?.registered_email ?? null;
|
|
562
|
+
}
|
|
563
|
+
var init_setup = __esm({
|
|
564
|
+
"src/setup.ts"() {
|
|
565
|
+
"use strict";
|
|
566
|
+
init_config();
|
|
567
|
+
init_register();
|
|
568
|
+
init_protection();
|
|
569
|
+
}
|
|
570
|
+
});
|
|
2
571
|
|
|
3
572
|
// src/index.ts
|
|
4
573
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -407,224 +976,24 @@ var FileAuditStore = class {
|
|
|
407
976
|
return this.chain.find((r) => r.decisionId === decisionId);
|
|
408
977
|
}
|
|
409
978
|
/** The full loaded chain, oldest first. Read-only; used to rehydrate core state at boot. */
|
|
410
|
-
records() {
|
|
411
|
-
return this.chain;
|
|
412
|
-
}
|
|
413
|
-
intact() {
|
|
414
|
-
let prev = "GENESIS";
|
|
415
|
-
for (const r of this.chain) {
|
|
416
|
-
const expected = sha256(`${prev}|${r.digest}|${r.seq}|${r.ts}`);
|
|
417
|
-
if (expected !== r.hash || r.prevHash !== prev)
|
|
418
|
-
return false;
|
|
419
|
-
prev = r.hash;
|
|
420
|
-
}
|
|
421
|
-
return true;
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
// src/config.ts
|
|
426
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
427
|
-
import { homedir } from "node:os";
|
|
428
|
-
import { join } from "node:path";
|
|
429
|
-
import {
|
|
430
|
-
existsSync,
|
|
431
|
-
mkdirSync,
|
|
432
|
-
readFileSync,
|
|
433
|
-
writeFileSync
|
|
434
|
-
} from "node:fs";
|
|
435
|
-
function hasEngineKey(keyOverride) {
|
|
436
|
-
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
437
|
-
}
|
|
438
|
-
function configDir() {
|
|
439
|
-
return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
|
|
440
|
-
}
|
|
441
|
-
function configPath() {
|
|
442
|
-
return join(configDir(), "config.json");
|
|
443
|
-
}
|
|
444
|
-
function auditLogPath() {
|
|
445
|
-
if (process.env.FIDACY_AUDIT_PATH) return process.env.FIDACY_AUDIT_PATH;
|
|
446
|
-
const dir = join(configDir(), "audit");
|
|
447
|
-
try {
|
|
448
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
449
|
-
} catch {
|
|
450
|
-
}
|
|
451
|
-
return join(dir, "audit.log");
|
|
452
|
-
}
|
|
453
|
-
function readConfig() {
|
|
454
|
-
const p = configPath();
|
|
455
|
-
if (!existsSync(p)) return null;
|
|
456
|
-
try {
|
|
457
|
-
const raw = JSON.parse(readFileSync(p, "utf8"));
|
|
458
|
-
if (!raw || typeof raw.anon_id !== "string") return null;
|
|
459
|
-
return {
|
|
460
|
-
anon_id: raw.anon_id,
|
|
461
|
-
tier: raw.tier === "paid" ? "paid" : "free",
|
|
462
|
-
api_key: typeof raw.api_key === "string" ? raw.api_key : null,
|
|
463
|
-
mandate: raw.mandate,
|
|
464
|
-
// Install-state passthrough: dropping these on a read→write cycle would
|
|
465
|
-
// reset every once-per-install nudge into an every-time nag.
|
|
466
|
-
created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
|
|
467
|
-
nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
|
|
468
|
-
decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
|
|
469
|
-
hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
|
|
470
|
-
operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
|
|
471
|
-
registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
|
|
472
|
-
};
|
|
473
|
-
} catch {
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function writeConfig(cfg) {
|
|
478
|
-
const dir = configDir();
|
|
479
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
480
|
-
writeFileSync(configPath(), JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
481
|
-
}
|
|
482
|
-
function ensureState() {
|
|
483
|
-
const existing = readConfig();
|
|
484
|
-
if (existing) return { config: existing, firstRun: false };
|
|
485
|
-
const config = {
|
|
486
|
-
anon_id: randomUUID2(),
|
|
487
|
-
tier: "free",
|
|
488
|
-
api_key: null,
|
|
489
|
-
mandate: { payees: [], categories: ["*"], currency: "USD", perTxMax: 2500, maxTotal: 1e4 },
|
|
490
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
491
|
-
};
|
|
492
|
-
try {
|
|
493
|
-
writeConfig(config);
|
|
494
|
-
} catch {
|
|
495
|
-
}
|
|
496
|
-
return { config, firstRun: true };
|
|
497
|
-
}
|
|
498
|
-
function resolveMandateRules(cfg) {
|
|
499
|
-
const m = cfg?.mandate ?? {};
|
|
500
|
-
const envList = (v) => v === void 0 ? void 0 : v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
501
|
-
const envNum = (name, v) => {
|
|
502
|
-
if (v === void 0 || v.trim() === "") return void 0;
|
|
503
|
-
const n = Number(v);
|
|
504
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
505
|
-
console.error(
|
|
506
|
-
`[fidacy] ${name}="${v}" is not a positive number, so it cannot be enforced as a cap. Ignoring it and using the safe default. Write digits only, with no thousands separator, currency symbol or unit (e.g. ${name}=2500).`
|
|
507
|
-
);
|
|
508
|
-
return void 0;
|
|
509
|
-
}
|
|
510
|
-
return n;
|
|
511
|
-
};
|
|
512
|
-
const fileNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
|
|
513
|
-
return {
|
|
514
|
-
payees: envList(process.env.FIDACY_ALLOW_PAYEES) ?? m.payees ?? [],
|
|
515
|
-
categories: envList(process.env.FIDACY_ALLOW_CATEGORIES) ?? m.categories ?? ["*"],
|
|
516
|
-
currency: process.env.FIDACY_CURRENCY ?? m.currency ?? "USD",
|
|
517
|
-
perTxMax: envNum("FIDACY_PER_TX_MAX", process.env.FIDACY_PER_TX_MAX) ?? fileNum(m.perTxMax) ?? 2500,
|
|
518
|
-
maxTotal: envNum("FIDACY_MAX_TOTAL", process.env.FIDACY_MAX_TOTAL) ?? fileNum(m.maxTotal) ?? 1e4
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// src/telemetry.ts
|
|
523
|
-
var CLIENT_VERSION = true ? "0.5.2" : "dev";
|
|
524
|
-
function bandOf(amount) {
|
|
525
|
-
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
526
|
-
if (amount < 10) return "lt10";
|
|
527
|
-
if (amount < 100) return "10_99";
|
|
528
|
-
if (amount < 1e3) return "100_999";
|
|
529
|
-
if (amount < 1e4) return "1k_10k";
|
|
530
|
-
if (amount < 1e5) return "10k_100k";
|
|
531
|
-
return "gte100k";
|
|
532
|
-
}
|
|
533
|
-
function capRatioOf(amount, perTxMax) {
|
|
534
|
-
if (typeof amount !== "number" || typeof perTxMax !== "number") return void 0;
|
|
535
|
-
if (!Number.isFinite(amount) || !Number.isFinite(perTxMax) || perTxMax <= 0 || amount <= perTxMax) return void 0;
|
|
536
|
-
const r = amount / perTxMax;
|
|
537
|
-
if (r < 2) return "1_2x";
|
|
538
|
-
if (r < 5) return "2_5x";
|
|
539
|
-
if (r < 10) return "5_10x";
|
|
540
|
-
return "gt10x";
|
|
541
|
-
}
|
|
542
|
-
var currentShell = "mcp";
|
|
543
|
-
function telemetryEnabled() {
|
|
544
|
-
const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
|
|
545
|
-
return !(v === "1" || v === "true" || v === "yes");
|
|
546
|
-
}
|
|
547
|
-
function endpoint() {
|
|
548
|
-
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
549
|
-
return `${base}/v1/telemetry`;
|
|
550
|
-
}
|
|
551
|
-
var anonIdCache = null;
|
|
552
|
-
function anonId() {
|
|
553
|
-
if (anonIdCache) return anonIdCache;
|
|
554
|
-
anonIdCache = readConfig()?.anon_id ?? null;
|
|
555
|
-
return anonIdCache;
|
|
556
|
-
}
|
|
557
|
-
var buffer = [];
|
|
558
|
-
var flushTimer = null;
|
|
559
|
-
var exitHookArmed = false;
|
|
560
|
-
function armExitFlush() {
|
|
561
|
-
if (exitHookArmed || typeof process === "undefined" || typeof process.once !== "function") return;
|
|
562
|
-
exitHookArmed = true;
|
|
563
|
-
process.once("beforeExit", () => {
|
|
564
|
-
if (buffer.length > 0) void flush();
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
function resultOf(status, violatedRule) {
|
|
568
|
-
if (status === "ALLOW") return "allow";
|
|
569
|
-
const r = violatedRule ?? "";
|
|
570
|
-
if (r.startsWith("payee_lookalike")) return "deny_lookalike";
|
|
571
|
-
if (r.startsWith("payee_not_in_allowlist")) return "deny_payee";
|
|
572
|
-
if (r.startsWith("duplicate_invoice")) return "deny_duplicate";
|
|
573
|
-
if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
|
|
574
|
-
if (r.startsWith("mandate_revoked") || r.startsWith("before_mandate") || r.startsWith("after_mandate"))
|
|
575
|
-
return "deny_window";
|
|
576
|
-
if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
|
|
577
|
-
return "deny_scope";
|
|
578
|
-
}
|
|
579
|
-
function record(type, result, band, capRatio, action) {
|
|
580
|
-
if (!telemetryEnabled()) return;
|
|
581
|
-
armExitFlush();
|
|
582
|
-
buffer.push({
|
|
583
|
-
type,
|
|
584
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
585
|
-
client_version: CLIENT_VERSION,
|
|
586
|
-
shell: currentShell,
|
|
587
|
-
...result ? { result } : {},
|
|
588
|
-
...band ? { band } : {},
|
|
589
|
-
...capRatio ? { cap_ratio: capRatio } : {},
|
|
590
|
-
...action ? { action } : {}
|
|
591
|
-
});
|
|
592
|
-
if (!flushTimer) {
|
|
593
|
-
flushTimer = setTimeout(() => {
|
|
594
|
-
void flush();
|
|
595
|
-
}, 2e3);
|
|
596
|
-
if (typeof flushTimer.unref === "function") flushTimer.unref();
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
var recordInstall = () => record("install");
|
|
600
|
-
var recordAgentActive = () => record("agent_active");
|
|
601
|
-
var recordDecision = (status, violatedRule, amount, perTxMax) => record("decision", resultOf(status, violatedRule), bandOf(amount), capRatioOf(amount, perTxMax));
|
|
602
|
-
var recordUpgradeIntent = () => record("upgrade_intent");
|
|
603
|
-
var recordToolUse = (action) => record("tool_use", void 0, void 0, void 0, action);
|
|
604
|
-
async function flush() {
|
|
605
|
-
if (flushTimer) {
|
|
606
|
-
clearTimeout(flushTimer);
|
|
607
|
-
flushTimer = null;
|
|
608
|
-
}
|
|
609
|
-
if (!telemetryEnabled() || buffer.length === 0) return;
|
|
610
|
-
const id = anonId();
|
|
611
|
-
if (!id) {
|
|
612
|
-
buffer = [];
|
|
613
|
-
return;
|
|
979
|
+
records() {
|
|
980
|
+
return this.chain;
|
|
614
981
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
}
|
|
623
|
-
|
|
982
|
+
intact() {
|
|
983
|
+
let prev = "GENESIS";
|
|
984
|
+
for (const r of this.chain) {
|
|
985
|
+
const expected = sha256(`${prev}|${r.digest}|${r.seq}|${r.ts}`);
|
|
986
|
+
if (expected !== r.hash || r.prevHash !== prev)
|
|
987
|
+
return false;
|
|
988
|
+
prev = r.hash;
|
|
989
|
+
}
|
|
990
|
+
return true;
|
|
624
991
|
}
|
|
625
|
-
}
|
|
992
|
+
};
|
|
626
993
|
|
|
627
994
|
// src/core.ts
|
|
995
|
+
init_config();
|
|
996
|
+
init_telemetry();
|
|
628
997
|
function buildMandate() {
|
|
629
998
|
if (process.env.FIDACY_MANDATE_JSON) {
|
|
630
999
|
try {
|
|
@@ -881,8 +1250,13 @@ async function findArtifacts(sha2562, cfg) {
|
|
|
881
1250
|
return await engineFetch("GET", `/v1/artifacts?sha256=${sha2562}`, cfg);
|
|
882
1251
|
}
|
|
883
1252
|
|
|
1253
|
+
// src/index.ts
|
|
1254
|
+
init_config();
|
|
1255
|
+
init_telemetry();
|
|
1256
|
+
|
|
884
1257
|
// src/provision.ts
|
|
885
|
-
|
|
1258
|
+
init_config();
|
|
1259
|
+
var CLIENT_VERSION2 = true ? "0.6.0" : "dev";
|
|
886
1260
|
function provisionEnabled() {
|
|
887
1261
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
888
1262
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -918,57 +1292,12 @@ async function autoProvision() {
|
|
|
918
1292
|
}
|
|
919
1293
|
}
|
|
920
1294
|
|
|
921
|
-
// src/
|
|
922
|
-
|
|
923
|
-
function endpoint3() {
|
|
924
|
-
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
925
|
-
return `${base}/v1/register`;
|
|
926
|
-
}
|
|
927
|
-
function looksLikeEmail(s) {
|
|
928
|
-
return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
|
|
929
|
-
}
|
|
930
|
-
function operatorEmail(override) {
|
|
931
|
-
const raw = (override ?? process.env.FIDACY_OPERATOR_EMAIL ?? readConfig()?.operator_email ?? "").trim();
|
|
932
|
-
return raw && looksLikeEmail(raw) ? raw : null;
|
|
933
|
-
}
|
|
934
|
-
async function registerEmail(email, shell = "mcp") {
|
|
935
|
-
const cfg = readConfig();
|
|
936
|
-
if (!cfg) return "skipped";
|
|
937
|
-
const clean = email.trim();
|
|
938
|
-
if (!looksLikeEmail(clean)) return "skipped";
|
|
939
|
-
if (cfg.registered_email && cfg.registered_email.toLowerCase() === clean.toLowerCase()) return "skipped";
|
|
940
|
-
try {
|
|
941
|
-
const res = await fetch(endpoint3(), {
|
|
942
|
-
method: "POST",
|
|
943
|
-
headers: { "content-type": "application/json" },
|
|
944
|
-
body: JSON.stringify({ anon_id: cfg.anon_id, email: clean, client_version: CLIENT_VERSION3, shell })
|
|
945
|
-
});
|
|
946
|
-
if (res.status === 429) return "rate_limited";
|
|
947
|
-
if (!res.ok) return "failed";
|
|
948
|
-
const fresh = readConfig() ?? cfg;
|
|
949
|
-
writeConfig({ ...fresh, operator_email: clean, registered_email: clean });
|
|
950
|
-
return "registered";
|
|
951
|
-
} catch {
|
|
952
|
-
return "failed";
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
function needsOperatorEmail() {
|
|
956
|
-
const cfg = readConfig();
|
|
957
|
-
if (!cfg) return false;
|
|
958
|
-
return !cfg.registered_email && !operatorEmail();
|
|
959
|
-
}
|
|
960
|
-
function setupEmailAsk(nudgeOnce2) {
|
|
961
|
-
if (!needsOperatorEmail()) return null;
|
|
962
|
-
if (!nudgeOnce2("setup_email_ask")) return null;
|
|
963
|
-
return `Setup, once: ask your human operator for their email and then call register_email with it \u2014 it keeps this agent's protection history and lets it move into their free account. If they would rather not, drop it and do not ask again; the firewall works either way.`;
|
|
964
|
-
}
|
|
965
|
-
async function autoRegister(shell = "mcp") {
|
|
966
|
-
const email = operatorEmail();
|
|
967
|
-
if (!email) return "skipped";
|
|
968
|
-
return registerEmail(email, shell);
|
|
969
|
-
}
|
|
1295
|
+
// src/index.ts
|
|
1296
|
+
init_register();
|
|
970
1297
|
|
|
971
1298
|
// src/upgrade.ts
|
|
1299
|
+
init_config();
|
|
1300
|
+
init_telemetry();
|
|
972
1301
|
function upgradeUrl() {
|
|
973
1302
|
const base = (process.env.FIDACY_WEB_URL ?? "https://fidacy.com").replace(/\/$/, "");
|
|
974
1303
|
const anon = readConfig()?.anon_id ?? "";
|
|
@@ -980,190 +1309,10 @@ function requestUpgrade() {
|
|
|
980
1309
|
return { url: upgradeUrl(), anonId: anonId2 };
|
|
981
1310
|
}
|
|
982
1311
|
|
|
983
|
-
// src/
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
function nudgeOnce(key) {
|
|
989
|
-
const cfg = readConfig();
|
|
990
|
-
if (!cfg) return false;
|
|
991
|
-
if (cfg.nudges?.[key]) return false;
|
|
992
|
-
cfg.nudges = { ...cfg.nudges ?? {}, [key]: true };
|
|
993
|
-
try {
|
|
994
|
-
writeConfig(cfg);
|
|
995
|
-
} catch {
|
|
996
|
-
return false;
|
|
997
|
-
}
|
|
998
|
-
return true;
|
|
999
|
-
}
|
|
1000
|
-
var sessionDecisions = 0;
|
|
1001
|
-
function sessionDecisionCount() {
|
|
1002
|
-
return sessionDecisions;
|
|
1003
|
-
}
|
|
1004
|
-
function bumpDecisionCount() {
|
|
1005
|
-
sessionDecisions += 1;
|
|
1006
|
-
const cfg = readConfig();
|
|
1007
|
-
if (!cfg) return sessionDecisions;
|
|
1008
|
-
const n = Math.max((cfg.decisions_count ?? 0) + 1, sessionDecisions);
|
|
1009
|
-
cfg.decisions_count = n;
|
|
1010
|
-
try {
|
|
1011
|
-
writeConfig(cfg);
|
|
1012
|
-
} catch {
|
|
1013
|
-
}
|
|
1014
|
-
return Math.max(n, sessionDecisions);
|
|
1015
|
-
}
|
|
1016
|
-
function installAgeDays() {
|
|
1017
|
-
const t = Date.parse(readConfig()?.created_at ?? "");
|
|
1018
|
-
if (Number.isNaN(t)) return 0;
|
|
1019
|
-
return Math.floor((Date.now() - t) / 864e5);
|
|
1020
|
-
}
|
|
1021
|
-
function decisionNudge(status, violatedRule, upgradeToolName) {
|
|
1022
|
-
const count = bumpDecisionCount();
|
|
1023
|
-
const url = claimUrl();
|
|
1024
|
-
const claimPath = url ? `free account + free API key at ${url} (this install's history migrates)` : `call the ${upgradeToolName} tool (free account + free API key, this history migrates)`;
|
|
1025
|
-
if (status === "DENY" && violatedRule?.startsWith("duplicate_invoice") && nudgeOnce("bec_catch")) {
|
|
1026
|
-
return `That was a duplicate-invoice (BEC) catch, the #1 payment fraud. For your operator: ${claimPath}. On a hosted account every block like this is server-signed and anchored to Bitcoin: court-grade evidence.`;
|
|
1027
|
-
}
|
|
1028
|
-
if (status === "DENY" && nudgeOnce("first_deny")) {
|
|
1029
|
-
return `Fidacy just blocked this payment before it moved. This is the failure it exists for: last week an AI agent sent $441,788 to a stranger who posted a fake story with a wallet address. Nothing was checking it. For your operator: ${claimPath}.`;
|
|
1030
|
-
}
|
|
1031
|
-
if (status === "ALLOW" && nudgeOnce("first_allow")) {
|
|
1032
|
-
return `Fidacy authorized this and signed the decision. For your operator: ${claimPath}.`;
|
|
1033
|
-
}
|
|
1034
|
-
if (count === 15 && nudgeOnce("proof_teaser")) {
|
|
1035
|
-
return `Fidacy has now checked ${count} payments on this agent and stands between it and the wrong one, the prompt-injected payee, the duplicate, the over-limit charge. That protection pauses after 20 decisions without a free key. Keep the agent covered: ${claimPath}.`;
|
|
1036
|
-
}
|
|
1037
|
-
if (count === 10 && nudgeOnce("milestone_10")) {
|
|
1038
|
-
return `That's 10 actions gated on this install. For your operator: ${claimPath}.`;
|
|
1039
|
-
}
|
|
1040
|
-
return null;
|
|
1041
|
-
}
|
|
1042
|
-
function hostedWallCta(feature, billing) {
|
|
1043
|
-
const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
|
|
1044
|
-
if (hasEngineKey()) {
|
|
1045
|
-
const where2 = billing?.billingUrl ?? "https://app.fidacy.com/billing";
|
|
1046
|
-
const what = billing?.reason === "spending_cap_reached" ? `This account hit the monthly spending cap you set, so ${noun} are paused.` : billing?.detail ?? `This account's included ${noun} for the month are used up.`;
|
|
1047
|
-
return `${what} The local firewall keeps deciding and blocking as normal \u2014 this only pauses ${noun}. For your operator: resolve it at ${where2}. Pay-as-you-go needs no plan and starts at $0.04 per verdict, dropping to $0.025 past 50k and $0.015 past 250k; committed plans price below that curve.`;
|
|
1048
|
-
}
|
|
1049
|
-
const url = claimUrl();
|
|
1050
|
-
const cta = url ? `open ${url} \u2014 free account, this install's history migrates` : `call the upgrade tool \u2014 free account, your history migrates`;
|
|
1051
|
-
if (nudgeOnce("hosted_wall")) {
|
|
1052
|
-
return `You've used this install's free allowance for ${noun} \u2014 the proofs anyone can re-verify at fidacy.com/verify, court-grade, not a local-only check. Keep them flowing: for your operator, ${cta}.`;
|
|
1053
|
-
}
|
|
1054
|
-
return `Free allowance for ${noun} reached. For your operator: ${cta}.`;
|
|
1055
|
-
}
|
|
1056
|
-
function noKeyCta() {
|
|
1057
|
-
const url = claimUrl();
|
|
1058
|
-
return url ? `free in ~1 min for your operator: open ${url} (this install's history migrates), or set FIDACY_ENGINE_API_KEY if you already have one` : `free in ~1 min: call the upgrade tool (your history migrates), or set FIDACY_ENGINE_API_KEY if you already have one`;
|
|
1059
|
-
}
|
|
1060
|
-
function weekOneBootNudge(upgradeToolName) {
|
|
1061
|
-
if (installAgeDays() >= 7 && nudgeOnce("week_1")) {
|
|
1062
|
-
const url = claimUrl();
|
|
1063
|
-
return url ? ` One week protected. See and claim this install's block history: ${url} (free).` : ` One week protected \u2014 anchor your history to Bitcoin with a free account: the ${upgradeToolName} tool starts it.`;
|
|
1064
|
-
}
|
|
1065
|
-
return null;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// src/activation.ts
|
|
1069
|
-
var FREE_DECISIONS = 20;
|
|
1070
|
-
function decisionsUsed() {
|
|
1071
|
-
return Math.max(readConfig()?.decisions_count ?? 0, sessionDecisionCount());
|
|
1072
|
-
}
|
|
1073
|
-
function remainingFree() {
|
|
1074
|
-
return Math.max(0, FREE_DECISIONS - decisionsUsed());
|
|
1075
|
-
}
|
|
1076
|
-
function claimDestination() {
|
|
1077
|
-
return claimUrl() ?? "https://fidacy.com/claim";
|
|
1078
|
-
}
|
|
1079
|
-
function activationGate(keyOverride) {
|
|
1080
|
-
if (hasEngineKey(keyOverride)) return null;
|
|
1081
|
-
if (decisionsUsed() < FREE_DECISIONS) return null;
|
|
1082
|
-
recordUpgradeIntent();
|
|
1083
|
-
const url = claimDestination();
|
|
1084
|
-
return {
|
|
1085
|
-
message: `DENY (activation_required). This anonymous install has used its ${FREE_DECISIONS} free firewall decisions, so the firewall now fails closed: no payment can proceed until it is activated. Activation is a free API key, about 2 minutes, no card. Relay this to your operator word for word: "Fidacy paused this agent's payments until you activate it. Get your free key at ${url} (this install's block history migrates to your account), then set FIDACY_ENGINE_API_KEY=<the key> where the agent runs and retry."`
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
function trialCountdownLine(keyOverride) {
|
|
1089
|
-
if (hasEngineKey(keyOverride)) return null;
|
|
1090
|
-
if (!readConfig() && sessionDecisionCount() === 0) return null;
|
|
1091
|
-
const left = remainingFree();
|
|
1092
|
-
if (left > 5) return null;
|
|
1093
|
-
const url = claimDestination();
|
|
1094
|
-
if (left === 0) {
|
|
1095
|
-
return ` [fidacy] That was this install's LAST free anonymous decision \u2014 the next payment will be denied until it is activated. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
|
|
1096
|
-
}
|
|
1097
|
-
return ` [fidacy] ${left} free anonymous decision${left === 1 ? "" : "s"} left on this install before the firewall requires activation. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
// src/protection.ts
|
|
1101
|
-
var BRAND = "Fidacy AI Agent Firewall";
|
|
1102
|
-
var FREE_MONTHLY_VERDICTS = 300;
|
|
1103
|
-
function markHostedLapsed() {
|
|
1104
|
-
const cfg = readConfig();
|
|
1105
|
-
if (!cfg || cfg.hosted_lapsed_at) return;
|
|
1106
|
-
cfg.hosted_lapsed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1107
|
-
try {
|
|
1108
|
-
writeConfig(cfg);
|
|
1109
|
-
} catch {
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
function clearHostedLapsed() {
|
|
1113
|
-
const cfg = readConfig();
|
|
1114
|
-
if (!cfg?.hosted_lapsed_at) return;
|
|
1115
|
-
delete cfg.hosted_lapsed_at;
|
|
1116
|
-
try {
|
|
1117
|
-
writeConfig(cfg);
|
|
1118
|
-
} catch {
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
function isHostedLapsed() {
|
|
1122
|
-
return Boolean(readConfig()?.hosted_lapsed_at);
|
|
1123
|
-
}
|
|
1124
|
-
function protectionState(decisionsUsed2, keyOverride) {
|
|
1125
|
-
if (hasEngineKey(keyOverride)) return isHostedLapsed() ? "unproven" : "protected_keyed";
|
|
1126
|
-
if (decisionsUsed2 >= FREE_DECISIONS) return "halted";
|
|
1127
|
-
return FREE_DECISIONS - decisionsUsed2 <= 5 ? "protected_low" : "protected_trial";
|
|
1128
|
-
}
|
|
1129
|
-
function where() {
|
|
1130
|
-
return claimUrl() ?? "https://fidacy.com/claim";
|
|
1131
|
-
}
|
|
1132
|
-
function protectionBadge() {
|
|
1133
|
-
return `\u{1F6E1} ${BRAND} blocked this payment. Your agent was protected.`;
|
|
1134
|
-
}
|
|
1135
|
-
function protectionWarning(decisionsUsed2, keyOverride) {
|
|
1136
|
-
const state2 = protectionState(decisionsUsed2, keyOverride);
|
|
1137
|
-
const left = Math.max(0, FREE_DECISIONS - decisionsUsed2);
|
|
1138
|
-
switch (state2) {
|
|
1139
|
-
case "protected_trial":
|
|
1140
|
-
return null;
|
|
1141
|
-
// working and nothing is expiring soon: silence is the respect.
|
|
1142
|
-
case "protected_low":
|
|
1143
|
-
return `\u26A0 ${BRAND}: ${left} free decision${left === 1 ? "" : "s"} left, then this agent's payments STOP DEAD. Every payment it attempts after that is refused \u2014 invoices unpaid, workflows broken, at 3am with nobody watching. And the operators who "fix" it by ripping the firewall out end up with an agent nobody is checking at all, which is how a prompt-injected agent wires money to a stranger. Keep it alive, free, in ~2 minutes: ${where()} \u2014 no card, this install's block history moves with you, plus ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
|
|
1144
|
-
case "halted":
|
|
1145
|
-
return `\u26D4 ${BRAND} IS NOT ACTIVATED \u2014 YOUR AGENT CAN NO LONGER PAY ANYTHING. Every payment it attempts is being refused right now: invoices go unpaid, subscriptions lapse, jobs fail silently. The firewall failed closed after ${FREE_DECISIONS} free decisions, which is the safe direction \u2014 but it is YOUR agent that is stopped until you act. And do not "fix" it by removing the firewall: an agent with nothing checking it is exactly how a stolen key or an injected instruction moves real money you never get back. Activate free in ~2 minutes at ${where()}: no card, your block history moves with you, ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
|
|
1146
|
-
case "unproven":
|
|
1147
|
-
return `\u26D4 ${BRAND}: YOUR AGENT IS EXPOSED RIGHT NOW. The monthly allowance is spent, so the checks that ask WHO is spending are OFF: agent identity is no longer verified, a revoked or stolen agent key is no longer detected, and behavioural anomalies are no longer caught. Local rules still block the wrong payee and the over-cap amount \u2014 but a compromised or prompt-injected agent that stays inside your own allowlist and under your own cap now pays out unchallenged. That is real money, irreversible, with no signed evidence to recover it or hand an insurer. Turn the checks back on: https://app.fidacy.com/billing \u2014 pay-as-you-go, no plan, from $0.04 per payment checked. One prevented payment pays for years of it.`;
|
|
1148
|
-
case "protected_keyed":
|
|
1149
|
-
return null;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
function protectionBootLine(decisionsUsed2) {
|
|
1153
|
-
const state2 = protectionState(decisionsUsed2);
|
|
1154
|
-
switch (state2) {
|
|
1155
|
-
case "halted":
|
|
1156
|
-
return ` \u26D4 NOT ACTIVATED \u2014 your agent cannot pay until you activate at ${where()} (free, no card).`;
|
|
1157
|
-
case "protected_low":
|
|
1158
|
-
return ` \u26A0 ${Math.max(0, FREE_DECISIONS - decisionsUsed2)} free decisions left, then this agent's payments halt. Free key: ${where()}`;
|
|
1159
|
-
case "unproven":
|
|
1160
|
-
return ` \u26D4 EXPOSED \u2014 identity, revocation and anomaly checks are OFF (allowance spent). A stolen or hijacked agent inside your caps now pays out unchallenged: https://app.fidacy.com/billing`;
|
|
1161
|
-
case "protected_trial":
|
|
1162
|
-
return ` ${BRAND} active \u2014 ${decisionsUsed2}/${FREE_DECISIONS} free decisions used.`;
|
|
1163
|
-
case "protected_keyed":
|
|
1164
|
-
return ` ${BRAND} active and signing \u2014 blocks are provable to third parties.`;
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1312
|
+
// src/index.ts
|
|
1313
|
+
init_nudges();
|
|
1314
|
+
init_activation();
|
|
1315
|
+
init_protection();
|
|
1167
1316
|
|
|
1168
1317
|
// src/reporting.ts
|
|
1169
1318
|
function withinDays(records, days, now = Date.now()) {
|
|
@@ -1279,6 +1428,7 @@ function explainRule(rule, payee) {
|
|
|
1279
1428
|
}
|
|
1280
1429
|
|
|
1281
1430
|
// src/banner.ts
|
|
1431
|
+
init_activation();
|
|
1282
1432
|
var fancy = () => {
|
|
1283
1433
|
if (process.env.NO_COLOR) return false;
|
|
1284
1434
|
if (process.env.FORCE_COLOR) return true;
|
|
@@ -1433,7 +1583,7 @@ function renderAlertLine(alerts) {
|
|
|
1433
1583
|
var state = ensureState();
|
|
1434
1584
|
var core = makeCore();
|
|
1435
1585
|
var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
|
|
1436
|
-
var SERVER_VERSION = true ? "0.
|
|
1586
|
+
var SERVER_VERSION = true ? "0.6.0" : "dev";
|
|
1437
1587
|
var bootClaim = claimUrl();
|
|
1438
1588
|
var server = new McpServer(
|
|
1439
1589
|
// The commercial identity. `name` is the stable slug hosts key on; `title` is
|
|
@@ -1859,6 +2009,11 @@ server.registerTool(
|
|
|
1859
2009
|
}
|
|
1860
2010
|
);
|
|
1861
2011
|
async function main() {
|
|
2012
|
+
if (process.argv.includes("setup")) {
|
|
2013
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
2014
|
+
await runSetup2({ force: true });
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
1862
2017
|
const transport = new StdioServerTransport();
|
|
1863
2018
|
await server.connect(transport);
|
|
1864
2019
|
if (state.firstRun) recordInstall();
|