@fidacy/mcp 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.js CHANGED
@@ -643,7 +643,7 @@ function resolveMandateRules(cfg) {
643
643
  }
644
644
 
645
645
  // src/telemetry.ts
646
- var CLIENT_VERSION = true ? "0.5.2" : "dev";
646
+ var CLIENT_VERSION = true ? "0.6.1" : "dev";
647
647
  function bandOf(amount) {
648
648
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
649
649
  if (amount < 10) return "lt10";
@@ -831,7 +831,7 @@ function requestUpgrade() {
831
831
  }
832
832
 
833
833
  // src/provision.ts
834
- var CLIENT_VERSION2 = true ? "0.5.2" : "dev";
834
+ var CLIENT_VERSION2 = true ? "0.6.1" : "dev";
835
835
  function provisionEnabled() {
836
836
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
837
837
  return !(v === "1" || v === "true" || v === "yes");
@@ -994,7 +994,7 @@ function activationBootLine() {
994
994
  }
995
995
 
996
996
  // src/register.ts
997
- var CLIENT_VERSION3 = true ? "0.5.2" : "dev";
997
+ var CLIENT_VERSION3 = true ? "0.6.1" : "dev";
998
998
  function endpoint3() {
999
999
  const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
1000
1000
  return `${base}/v1/register`;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,266 @@
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 } 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 configDir() {
28
+ return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
29
+ }
30
+ function configPath() {
31
+ return join(configDir(), "config.json");
32
+ }
33
+ function readConfig() {
34
+ const p = configPath();
35
+ if (!existsSync(p)) return null;
36
+ try {
37
+ const raw = JSON.parse(readFileSync(p, "utf8"));
38
+ if (!raw || typeof raw.anon_id !== "string") return null;
39
+ return {
40
+ anon_id: raw.anon_id,
41
+ tier: raw.tier === "paid" ? "paid" : "free",
42
+ api_key: typeof raw.api_key === "string" ? raw.api_key : null,
43
+ mandate: raw.mandate,
44
+ // Install-state passthrough: dropping these on a read→write cycle would
45
+ // reset every once-per-install nudge into an every-time nag.
46
+ created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
47
+ nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
48
+ decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
49
+ hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
50
+ operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
51
+ registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
52
+ };
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ function writeConfig(cfg) {
58
+ const dir = configDir();
59
+ mkdirSync(dir, { recursive: true, mode: 448 });
60
+ writeFileSync(configPath(), JSON.stringify(cfg, null, 2), { mode: 384 });
61
+ }
62
+ function ensureState() {
63
+ const existing = readConfig();
64
+ if (existing) return { config: existing, firstRun: false };
65
+ const config = {
66
+ anon_id: randomUUID(),
67
+ tier: "free",
68
+ api_key: null,
69
+ mandate: { payees: [], categories: ["*"], currency: "USD", perTxMax: 2500, maxTotal: 1e4 },
70
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
71
+ };
72
+ try {
73
+ writeConfig(config);
74
+ } catch {
75
+ }
76
+ return { config, firstRun: true };
77
+ }
78
+ var init_config = __esm({
79
+ "src/config.ts"() {
80
+ "use strict";
81
+ }
82
+ });
83
+
84
+ // src/register.ts
85
+ function endpoint() {
86
+ const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
87
+ return `${base}/v1/register`;
88
+ }
89
+ function looksLikeEmail(s) {
90
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
91
+ }
92
+ function operatorEmail(override) {
93
+ const raw = (override ?? process.env.FIDACY_OPERATOR_EMAIL ?? readConfig()?.operator_email ?? "").trim();
94
+ return raw && looksLikeEmail(raw) ? raw : null;
95
+ }
96
+ async function registerEmail(email, shell = "mcp") {
97
+ const cfg = readConfig();
98
+ if (!cfg) return "skipped";
99
+ const clean = email.trim();
100
+ if (!looksLikeEmail(clean)) return "skipped";
101
+ if (cfg.registered_email && cfg.registered_email.toLowerCase() === clean.toLowerCase()) return "skipped";
102
+ try {
103
+ const res = await fetch(endpoint(), {
104
+ method: "POST",
105
+ headers: { "content-type": "application/json" },
106
+ body: JSON.stringify({ anon_id: cfg.anon_id, email: clean, client_version: CLIENT_VERSION, shell })
107
+ });
108
+ if (res.status === 429) return "rate_limited";
109
+ if (!res.ok) return "failed";
110
+ const fresh = readConfig() ?? cfg;
111
+ writeConfig({ ...fresh, operator_email: clean, registered_email: clean });
112
+ return "registered";
113
+ } catch {
114
+ return "failed";
115
+ }
116
+ }
117
+ function rememberOperatorEmail(email) {
118
+ const cfg = readConfig();
119
+ if (!cfg) return;
120
+ const clean = email.trim();
121
+ if (!looksLikeEmail(clean)) return;
122
+ try {
123
+ writeConfig({ ...cfg, operator_email: clean });
124
+ } catch {
125
+ }
126
+ }
127
+ function needsOperatorEmail() {
128
+ const cfg = readConfig();
129
+ if (!cfg) return false;
130
+ return !cfg.registered_email && !operatorEmail();
131
+ }
132
+ var CLIENT_VERSION;
133
+ var init_register = __esm({
134
+ "src/register.ts"() {
135
+ "use strict";
136
+ init_config();
137
+ CLIENT_VERSION = true ? "0.6.1" : "dev";
138
+ }
139
+ });
140
+
141
+ // src/nudges.ts
142
+ var init_nudges = __esm({
143
+ "src/nudges.ts"() {
144
+ "use strict";
145
+ init_config();
146
+ }
147
+ });
148
+
149
+ // src/telemetry.ts
150
+ var init_telemetry = __esm({
151
+ "src/telemetry.ts"() {
152
+ "use strict";
153
+ init_config();
154
+ }
155
+ });
156
+
157
+ // src/activation.ts
158
+ var init_activation = __esm({
159
+ "src/activation.ts"() {
160
+ "use strict";
161
+ init_config();
162
+ init_nudges();
163
+ init_telemetry();
164
+ }
165
+ });
166
+
167
+ // src/protection.ts
168
+ var BRAND;
169
+ var init_protection = __esm({
170
+ "src/protection.ts"() {
171
+ "use strict";
172
+ init_config();
173
+ init_activation();
174
+ init_nudges();
175
+ BRAND = "Fidacy AI Agent Firewall";
176
+ }
177
+ });
178
+
179
+ // src/setup.ts
180
+ var setup_exports = {};
181
+ __export(setup_exports, {
182
+ isInteractive: () => isInteractive,
183
+ registeredEmail: () => registeredEmail,
184
+ runSetup: () => runSetup
185
+ });
186
+ import { createInterface } from "node:readline/promises";
187
+ function isInteractive() {
188
+ if (process.env.CI || process.env.FIDACY_NONINTERACTIVE) return false;
189
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
190
+ }
191
+ function looksLikeEmail2(s) {
192
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
193
+ }
194
+ async function runSetup(opts = {}) {
195
+ if (!isInteractive()) {
196
+ if (opts.force) {
197
+ process.stdout.write(
198
+ `
199
+ ${BRAND}: this needs an interactive terminal.
200
+ Run \`npx @fidacy/mcp setup\` directly in your shell, or set FIDACY_OPERATOR_EMAIL.
201
+
202
+ `
203
+ );
204
+ }
205
+ return "skipped_noninteractive";
206
+ }
207
+ ensureState();
208
+ if (!needsOperatorEmail() && !opts.force) return "already";
209
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
210
+ try {
211
+ process.stdout.write(`
212
+ ${BRAND}
213
+ `);
214
+ process.stdout.write(
215
+ " 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"
216
+ );
217
+ const answer = (await rl.question(" Email: ")).trim();
218
+ if (!answer) {
219
+ process.stdout.write(" Skipped. The firewall is active either way.\n\n");
220
+ return "declined";
221
+ }
222
+ if (!looksLikeEmail2(answer)) {
223
+ process.stdout.write(" That does not look like an email \u2014 skipping. Run `npx @fidacy/mcp setup` to try again.\n\n");
224
+ return "declined";
225
+ }
226
+ const outcome = await registerEmail(answer, "mcp");
227
+ if (outcome === "registered" || outcome === "skipped") {
228
+ process.stdout.write(` Registered ${answer}. Continuing the install.
229
+
230
+ `);
231
+ return "registered";
232
+ }
233
+ rememberOperatorEmail(answer);
234
+ process.stdout.write(" Could not reach Fidacy right now \u2014 saved locally, it will register on the next start.\n\n");
235
+ return "failed";
236
+ } catch {
237
+ return "failed";
238
+ } finally {
239
+ rl.close();
240
+ }
241
+ }
242
+ function registeredEmail() {
243
+ return readConfig()?.registered_email ?? null;
244
+ }
245
+ var init_setup = __esm({
246
+ "src/setup.ts"() {
247
+ "use strict";
248
+ init_config();
249
+ init_register();
250
+ init_protection();
251
+ }
252
+ });
253
+
254
+ // src/postinstall.ts
255
+ async function main() {
256
+ try {
257
+ const { runSetup: runSetup2, isInteractive: isInteractive2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
258
+ if (!isInteractive2()) return;
259
+ await runSetup2();
260
+ } catch {
261
+ }
262
+ }
263
+ void main().then(
264
+ () => process.exit(0),
265
+ () => process.exit(0)
266
+ );
@@ -6,6 +6,17 @@ export declare function operatorEmail(override?: string): string | null;
6
6
  * we do not re-send every boot. Returns the outcome for tests/logs.
7
7
  */
8
8
  export declare function registerEmail(email: string, shell?: string): Promise<"registered" | "skipped" | "rate_limited" | "failed">;
9
+ /**
10
+ * Persist the address the operator typed WITHOUT claiming it reached us.
11
+ *
12
+ * The install prompt can run while the machine is offline or the engine is down.
13
+ * Telling the operator "saved locally, it will register later" is only true if we
14
+ * actually save it: `registerEmail` writes config on success only, so a failed
15
+ * post used to lose the address entirely and the promise was hollow. Storing
16
+ * `operator_email` (and NOT `registered_email`) is what makes the next boot's
17
+ * autoRegister pick it up and finish the job.
18
+ */
19
+ export declare function rememberOperatorEmail(email: string): void;
9
20
  /**
10
21
  * True when nobody has given us an email for this install yet. Goes false the
11
22
  * moment an address is provided or registered, so the ask never becomes a nag.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * INTERACTIVE SETUP — the terminal field.
3
+ *
4
+ * Asks the operator for their email in the terminal, registers it, and then the
5
+ * install proceeds. Two entry points share this:
6
+ * • `npx @fidacy/mcp setup` — explicit, always runs.
7
+ * • the postinstall hook — runs automatically, but ONLY when a human is there.
8
+ *
9
+ * THE TTY RULE, which is not a preference: an MCP server is started by its host
10
+ * as `npx -y @fidacy/mcp`, and installs also run inside Docker builds and CI.
11
+ * None of those have a terminal. A prompt in that context does not fail — it
12
+ * BLOCKS FOREVER waiting for input that can never come, hanging the host, the
13
+ * build, the pipeline. So the prompt is gated on a real interactive stdin AND
14
+ * stdout; everywhere else this returns immediately and the install continues
15
+ * untouched. The field is still there for every human who installs by hand.
16
+ */
17
+ /** True only when a real person is on the other end of this process. */
18
+ export declare function isInteractive(): boolean;
19
+ /**
20
+ * Run the terminal ask. Returns what happened, for the caller's messaging.
21
+ * Never throws: a broken tty, an EOF, or a Ctrl-C leaves the install healthy.
22
+ */
23
+ export declare function runSetup(opts?: {
24
+ force?: boolean;
25
+ }): Promise<"registered" | "declined" | "already" | "skipped_noninteractive" | "failed">;
26
+ /** Whether an email is already on record for this install (for status output). */
27
+ export declare function registeredEmail(): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "description": "Fidacy action firewall for AI agents. Mandate-gated payment authorization as an MCP server.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Fidacy (ZeepCode Group Technology LLC) <hello@fidacy.com> (https://fidacy.com)",
@@ -74,6 +74,7 @@
74
74
  "dev": "tsx watch src/index.ts",
75
75
  "start": "node dist/index.js",
76
76
  "typecheck": "tsc --noEmit",
77
- "test": "vitest run"
77
+ "test": "vitest run",
78
+ "postinstall": "node -e \"try{require('fs').accessSync('dist/postinstall.js')}catch{process.exit(0)};import('./dist/postinstall.js')\" || exit 0"
78
79
  }
79
80
  }