@hasna/wallets 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +19 -0
  2. package/dist/cli/index.js +15353 -8679
  3. package/dist/db/agents.d.ts +2 -2
  4. package/dist/db/agents.d.ts.map +1 -1
  5. package/dist/db/audit.d.ts +31 -0
  6. package/dist/db/audit.d.ts.map +1 -0
  7. package/dist/db/cards.d.ts +4 -2
  8. package/dist/db/cards.d.ts.map +1 -1
  9. package/dist/db/database.d.ts +2 -1
  10. package/dist/db/database.d.ts.map +1 -1
  11. package/dist/db/providers.d.ts +3 -3
  12. package/dist/db/providers.d.ts.map +1 -1
  13. package/dist/db/transactions.d.ts +2 -2
  14. package/dist/db/transactions.d.ts.map +1 -1
  15. package/dist/index.d.ts +12 -12
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4934 -216
  18. package/dist/lib/cache.d.ts +3 -0
  19. package/dist/lib/cache.d.ts.map +1 -0
  20. package/dist/lib/config.d.ts +12 -5
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/doctor.d.ts +2 -2
  23. package/dist/lib/doctor.d.ts.map +1 -1
  24. package/dist/lib/format.d.ts +11 -2
  25. package/dist/lib/format.d.ts.map +1 -1
  26. package/dist/lib/logger.d.ts +25 -0
  27. package/dist/lib/logger.d.ts.map +1 -0
  28. package/dist/mcp/http.d.ts +14 -0
  29. package/dist/mcp/http.d.ts.map +1 -0
  30. package/dist/mcp/index.d.ts +2 -1
  31. package/dist/mcp/index.d.ts.map +1 -1
  32. package/dist/mcp/index.js +1753 -695
  33. package/dist/providers/agentcard.d.ts +8 -1
  34. package/dist/providers/agentcard.d.ts.map +1 -1
  35. package/dist/providers/index.d.ts +6 -5
  36. package/dist/providers/index.d.ts.map +1 -1
  37. package/dist/providers/registry.d.ts +1 -1
  38. package/dist/providers/registry.d.ts.map +1 -1
  39. package/dist/types/index.d.ts +16 -1
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/package.json +83 -73
package/dist/mcp/index.js CHANGED
@@ -20,7 +20,7 @@ var __export = (target, all) => {
20
20
  var require_package = __commonJS((exports, module) => {
21
21
  module.exports = {
22
22
  name: "@hasna/wallets",
23
- version: "0.1.6",
23
+ version: "0.1.7",
24
24
  description: "Universal wallet management for AI agents - CLI + MCP server with multi-provider support",
25
25
  type: "module",
26
26
  main: "dist/index.js",
@@ -35,18 +35,21 @@ var require_package = __commonJS((exports, module) => {
35
35
  import: "./dist/index.js"
36
36
  }
37
37
  },
38
- files: [
39
- "dist",
40
- "LICENSE",
41
- "README.md"
42
- ],
38
+ files: ["dist", "LICENSE", "README.md"],
43
39
  scripts: {
44
40
  build: "bun build src/cli/index.ts --outdir dist/cli --target bun --external chalk --external commander --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external @modelcontextprotocol/sdk && bun build src/index.ts --outdir dist --target bun && tsc --emitDeclarationOnly --outDir dist",
45
41
  typecheck: "tsc --noEmit",
46
42
  test: "bun test",
43
+ "test:coverage": "bun test --coverage",
44
+ dev: "bun run dev:cli",
47
45
  "dev:cli": "bun run src/cli/index.ts",
48
46
  "dev:mcp": "bun run src/mcp/index.ts",
49
- postinstall: "mkdir -p $HOME/.hasna/wallets 2>/dev/null || true"
47
+ "dev:watch": "bun --watch run src/cli/index.ts",
48
+ "dev:mcp:watch": "bun --watch run src/mcp/index.ts",
49
+ lint: "biome check src/",
50
+ "lint:fix": "biome check --write .",
51
+ prepack: "bun run build",
52
+ postinstall: "mkdir -p $HOME/.hasna/wallets 2>/dev/null || true && lefthook install"
50
53
  },
51
54
  keywords: [
52
55
  "wallets",
@@ -85,10 +88,13 @@ var require_package = __commonJS((exports, module) => {
85
88
  "@modelcontextprotocol/sdk": "^1.12.1",
86
89
  chalk: "^5.4.1",
87
90
  commander: "^13.1.0",
91
+ jmespath: "^0.16.0",
88
92
  zod: "^3.24.2"
89
93
  },
90
94
  devDependencies: {
95
+ "@biomejs/biome": "^1.9.4",
91
96
  "@types/bun": "^1.2.4",
97
+ lefthook: "^2.1.4",
92
98
  typescript: "^5.7.3"
93
99
  }
94
100
  };
@@ -4071,6 +4077,33 @@ var coerce = {
4071
4077
  date: (arg) => ZodDate.create({ ...arg, coerce: true })
4072
4078
  };
4073
4079
  var NEVER = INVALID;
4080
+ // src/lib/cache.ts
4081
+ var cache = new Map;
4082
+ function cached(key, ttlMs, fn) {
4083
+ const entry = cache.get(key);
4084
+ if (entry && entry.expiresAt > Date.now()) {
4085
+ return entry.value;
4086
+ }
4087
+ const value = fn();
4088
+ cache.set(key, { value, expiresAt: Date.now() + ttlMs });
4089
+ return value;
4090
+ }
4091
+ function cacheClear(prefix) {
4092
+ if (!prefix) {
4093
+ cache.clear();
4094
+ return;
4095
+ }
4096
+ for (const key of cache.keys()) {
4097
+ if (key.startsWith(prefix))
4098
+ cache.delete(key);
4099
+ }
4100
+ }
4101
+
4102
+ // src/db/database.ts
4103
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
4104
+ import { homedir as homedir3 } from "os";
4105
+ import { join as join3 } from "path";
4106
+
4074
4107
  // node_modules/@hasna/cloud/dist/index.js
4075
4108
  import { createRequire } from "module";
4076
4109
  import { Database } from "bun:sqlite";
@@ -4097,10 +4130,10 @@ var __toESMCache_esm;
4097
4130
  var __toESM = (mod, isNodeMode, target) => {
4098
4131
  var canCache = mod != null && typeof mod === "object";
4099
4132
  if (canCache) {
4100
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
4101
- var cached = cache.get(mod);
4102
- if (cached)
4103
- return cached;
4133
+ var cache2 = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
4134
+ var cached2 = cache2.get(mod);
4135
+ if (cached2)
4136
+ return cached2;
4104
4137
  }
4105
4138
  target = mod != null ? __create(__getProtoOf(mod)) : {};
4106
4139
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target;
@@ -4111,7 +4144,7 @@ var __toESM = (mod, isNodeMode, target) => {
4111
4144
  enumerable: true
4112
4145
  });
4113
4146
  if (canCache)
4114
- cache.set(mod, to);
4147
+ cache2.set(mod, to);
4115
4148
  return to;
4116
4149
  };
4117
4150
  var __commonJS2 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
@@ -13007,9 +13040,6 @@ function ensureFeedbackTable(db) {
13007
13040
  }
13008
13041
 
13009
13042
  // src/db/database.ts
13010
- import { homedir as homedir3 } from "os";
13011
- import { join as join3 } from "path";
13012
- import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
13013
13043
  var _db = null;
13014
13044
  var _adapter = null;
13015
13045
  var MIGRATIONS = [
@@ -13084,7 +13114,30 @@ var MIGRATIONS = [
13084
13114
  },
13085
13115
  {
13086
13116
  id: 2,
13087
- sql: `ALTER TABLE agents ADD COLUMN active_project_id TEXT;`
13117
+ sql: "ALTER TABLE agents ADD COLUMN active_project_id TEXT;"
13118
+ },
13119
+ {
13120
+ id: 3,
13121
+ sql: "ALTER TABLE cards ADD COLUMN idempotency_key TEXT; CREATE UNIQUE INDEX IF NOT EXISTS idx_cards_idempotency ON cards(idempotency_key) WHERE idempotency_key IS NOT NULL;"
13122
+ },
13123
+ {
13124
+ id: 4,
13125
+ sql: `
13126
+ CREATE TABLE IF NOT EXISTS audit_log (
13127
+ id TEXT PRIMARY KEY,
13128
+ action TEXT NOT NULL CHECK(action IN ('create', 'update', 'delete')),
13129
+ entity_type TEXT NOT NULL CHECK(entity_type IN ('card', 'provider', 'transaction', 'agent')),
13130
+ entity_id TEXT NOT NULL,
13131
+ actor_id TEXT,
13132
+ actor_name TEXT,
13133
+ changes TEXT DEFAULT '{}',
13134
+ metadata TEXT DEFAULT '{}',
13135
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
13136
+ );
13137
+ CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity_type, entity_id);
13138
+ CREATE INDEX IF NOT EXISTS idx_audit_actor ON audit_log(actor_id);
13139
+ CREATE INDEX IF NOT EXISTS idx_audit_action ON audit_log(action);
13140
+ `
13088
13141
  }
13089
13142
  ];
13090
13143
  function runMigrations(db) {
@@ -13108,10 +13161,10 @@ function runMigrations(db) {
13108
13161
  }
13109
13162
  }
13110
13163
  function resolveDbPath() {
13111
- if (process.env["HASNA_WALLETS_DB_PATH"])
13112
- return process.env["HASNA_WALLETS_DB_PATH"];
13113
- if (process.env["WALLETS_DB_PATH"])
13114
- return process.env["WALLETS_DB_PATH"];
13164
+ if (process.env.HASNA_WALLETS_DB_PATH)
13165
+ return process.env.HASNA_WALLETS_DB_PATH;
13166
+ if (process.env.WALLETS_DB_PATH)
13167
+ return process.env.WALLETS_DB_PATH;
13115
13168
  const home = homedir3();
13116
13169
  migrateDotfile("wallets");
13117
13170
  const newDir = join3(home, ".hasna", "wallets");
@@ -13148,84 +13201,66 @@ function resolvePartialId(db, table, partialId) {
13148
13201
  return null;
13149
13202
  }
13150
13203
 
13151
- // src/db/providers.ts
13152
- function rowToProvider(row) {
13153
- return {
13154
- ...row,
13155
- status: row.status,
13156
- config: JSON.parse(row.config || "{}"),
13157
- metadata: JSON.parse(row.metadata || "{}")
13158
- };
13204
+ // src/db/agents.ts
13205
+ var AGENT_LIST_TTL = 30000;
13206
+ function rowToAgent(row) {
13207
+ return { ...row };
13159
13208
  }
13160
- function createProvider(input, db) {
13209
+ function registerAgent(input, db) {
13161
13210
  const d = db || getDatabase();
13211
+ const normalizedName = input.name.trim().toLowerCase();
13212
+ const existing = getAgentByName(normalizedName, d);
13213
+ if (existing) {
13214
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [
13215
+ now(),
13216
+ existing.id
13217
+ ]);
13218
+ cacheClear("agents:");
13219
+ return getAgent(existing.id, d);
13220
+ }
13162
13221
  const id = shortId();
13163
- d.run(`INSERT INTO providers (id, name, type, config, metadata) VALUES (?, ?, ?, ?, ?)`, [
13222
+ d.run("INSERT INTO agents (id, name, description) VALUES (?, ?, ?)", [
13164
13223
  id,
13165
- input.name,
13166
- input.type,
13167
- JSON.stringify(input.config || {}),
13168
- JSON.stringify(input.metadata || {})
13224
+ normalizedName,
13225
+ input.description ?? null
13169
13226
  ]);
13170
- return getProvider(id, d);
13227
+ cacheClear("agents:");
13228
+ return getAgent(id, d);
13171
13229
  }
13172
- function getProvider(id, db) {
13230
+ function getAgent(id, db) {
13173
13231
  const d = db || getDatabase();
13174
- const row = d.query("SELECT * FROM providers WHERE id = ?").get(id);
13175
- return row ? rowToProvider(row) : null;
13232
+ const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
13233
+ return row ? rowToAgent(row) : null;
13176
13234
  }
13177
- function getProviderByName(name, db) {
13235
+ function getAgentByName(name, db) {
13178
13236
  const d = db || getDatabase();
13179
- const row = d.query("SELECT * FROM providers WHERE name = ?").get(name);
13180
- return row ? rowToProvider(row) : null;
13237
+ const normalizedName = name.trim().toLowerCase();
13238
+ const row = d.query("SELECT * FROM agents WHERE name = ?").get(normalizedName);
13239
+ return row ? rowToAgent(row) : null;
13181
13240
  }
13182
- function listProviders(db) {
13183
- const d = db || getDatabase();
13184
- const rows = d.query("SELECT * FROM providers ORDER BY created_at DESC").all();
13185
- return rows.map(rowToProvider);
13241
+ function listAgents(db) {
13242
+ return cached("agents:list", AGENT_LIST_TTL, () => {
13243
+ const d = db || getDatabase();
13244
+ const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
13245
+ return rows.map(rowToAgent);
13246
+ });
13186
13247
  }
13187
- function updateProvider(id, input, db) {
13248
+ function heartbeatAgent(idOrName, db) {
13188
13249
  const d = db || getDatabase();
13189
- const provider = getProvider(id, d);
13190
- if (!provider)
13191
- throw new Error(`Provider not found: ${id}`);
13192
- const sets = [];
13193
- const params = [];
13194
- if (input.name !== undefined) {
13195
- sets.push("name = ?");
13196
- params.push(input.name);
13197
- }
13198
- if (input.config !== undefined) {
13199
- sets.push("config = ?");
13200
- params.push(JSON.stringify(input.config));
13201
- }
13202
- if (input.metadata !== undefined) {
13203
- sets.push("metadata = ?");
13204
- params.push(JSON.stringify(input.metadata));
13205
- }
13206
- if (input.status !== undefined) {
13207
- sets.push("status = ?");
13208
- params.push(input.status);
13209
- }
13210
- if (sets.length > 0) {
13211
- sets.push("updated_at = ?");
13212
- params.push(now());
13213
- params.push(id);
13214
- d.run(`UPDATE providers SET ${sets.join(", ")} WHERE id = ?`, params);
13215
- }
13216
- return getProvider(id, d);
13250
+ const agent = getAgent(idOrName, d) ?? getAgentByName(idOrName, d);
13251
+ if (!agent)
13252
+ return null;
13253
+ d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
13254
+ return getAgent(agent.id, d);
13217
13255
  }
13218
- function ensureProvider(name, type, config, db) {
13256
+ function setAgentFocus(idOrName, projectId, db) {
13219
13257
  const d = db || getDatabase();
13220
- const existing = getProviderByName(name, d);
13221
- if (existing) {
13222
- if (config) {
13223
- return updateProvider(existing.id, { config }, d);
13224
- }
13225
- d.run("UPDATE providers SET updated_at = ? WHERE id = ?", [now(), existing.id]);
13226
- return getProvider(existing.id, d);
13227
- }
13228
- return createProvider({ name, type, config }, d);
13258
+ const agent = getAgent(idOrName, d) ?? getAgentByName(idOrName, d);
13259
+ if (!agent)
13260
+ return null;
13261
+ d.run("UPDATE agents SET active_project_id = ?, last_seen_at = ? WHERE id = ?", [projectId, now(), agent.id]);
13262
+ cacheClear("agents:");
13263
+ return getAgent(agent.id, d);
13229
13264
  }
13230
13265
 
13231
13266
  // src/db/cards.ts
@@ -13240,8 +13275,8 @@ function rowToCard(row) {
13240
13275
  function createCardRecord(input, db) {
13241
13276
  const d = db || getDatabase();
13242
13277
  const id = uuid();
13243
- d.run(`INSERT INTO cards (id, provider_id, external_id, name, last_four, brand, status, currency, balance, funded_amount, spending_limit, agent_id, metadata, expires_at)
13244
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
13278
+ d.run(`INSERT INTO cards (id, provider_id, external_id, name, last_four, brand, status, currency, balance, funded_amount, spending_limit, agent_id, metadata, expires_at, idempotency_key)
13279
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
13245
13280
  id,
13246
13281
  input.provider_id,
13247
13282
  input.external_id,
@@ -13255,7 +13290,8 @@ function createCardRecord(input, db) {
13255
13290
  input.spending_limit ?? null,
13256
13291
  input.agent_id ?? null,
13257
13292
  JSON.stringify(input.metadata || {}),
13258
- input.expires_at ?? null
13293
+ input.expires_at ?? null,
13294
+ input.idempotency_key ?? null
13259
13295
  ]);
13260
13296
  return getCard(id, d);
13261
13297
  }
@@ -13264,6 +13300,11 @@ function getCard(id, db) {
13264
13300
  const row = d.query("SELECT * FROM cards WHERE id = ?").get(id);
13265
13301
  return row ? rowToCard(row) : null;
13266
13302
  }
13303
+ function getCardByIdempotencyKey(idempotencyKey, db) {
13304
+ const d = db || getDatabase();
13305
+ const row = d.query("SELECT * FROM cards WHERE idempotency_key = ?").get(idempotencyKey);
13306
+ return row ? rowToCard(row) : null;
13307
+ }
13267
13308
  function listCards(filter = {}, db) {
13268
13309
  const d = db || getDatabase();
13269
13310
  const conditions = [];
@@ -13323,10 +13364,102 @@ function updateCard(id, input, db) {
13323
13364
  }
13324
13365
  function updateCardBalance(id, balance, db) {
13325
13366
  const d = db || getDatabase();
13326
- d.run("UPDATE cards SET balance = ?, updated_at = ? WHERE id = ?", [balance, now(), id]);
13367
+ d.run("UPDATE cards SET balance = ?, updated_at = ? WHERE id = ?", [
13368
+ balance,
13369
+ now(),
13370
+ id
13371
+ ]);
13327
13372
  return getCard(id, d);
13328
13373
  }
13329
13374
 
13375
+ // src/db/providers.ts
13376
+ var PROVIDER_LIST_TTL = 30000;
13377
+ function rowToProvider(row) {
13378
+ return {
13379
+ ...row,
13380
+ status: row.status,
13381
+ config: JSON.parse(row.config || "{}"),
13382
+ metadata: JSON.parse(row.metadata || "{}")
13383
+ };
13384
+ }
13385
+ function createProvider(input, db) {
13386
+ const d = db || getDatabase();
13387
+ const id = shortId();
13388
+ d.run("INSERT INTO providers (id, name, type, config, metadata) VALUES (?, ?, ?, ?, ?)", [
13389
+ id,
13390
+ input.name,
13391
+ input.type,
13392
+ JSON.stringify(input.config || {}),
13393
+ JSON.stringify(input.metadata || {})
13394
+ ]);
13395
+ cacheClear("providers:");
13396
+ return getProvider(id, d);
13397
+ }
13398
+ function getProvider(id, db) {
13399
+ const d = db || getDatabase();
13400
+ const row = d.query("SELECT * FROM providers WHERE id = ?").get(id);
13401
+ return row ? rowToProvider(row) : null;
13402
+ }
13403
+ function getProviderByName(name, db) {
13404
+ const d = db || getDatabase();
13405
+ const row = d.query("SELECT * FROM providers WHERE name = ?").get(name);
13406
+ return row ? rowToProvider(row) : null;
13407
+ }
13408
+ function listProviders(db) {
13409
+ return cached("providers:list", PROVIDER_LIST_TTL, () => {
13410
+ const d = db || getDatabase();
13411
+ const rows = d.query("SELECT * FROM providers ORDER BY created_at DESC").all();
13412
+ return rows.map(rowToProvider);
13413
+ });
13414
+ }
13415
+ function updateProvider(id, input, db) {
13416
+ const d = db || getDatabase();
13417
+ const provider = getProvider(id, d);
13418
+ if (!provider)
13419
+ throw new Error(`Provider not found: ${id}`);
13420
+ const sets = [];
13421
+ const params = [];
13422
+ if (input.name !== undefined) {
13423
+ sets.push("name = ?");
13424
+ params.push(input.name);
13425
+ }
13426
+ if (input.config !== undefined) {
13427
+ sets.push("config = ?");
13428
+ params.push(JSON.stringify(input.config));
13429
+ }
13430
+ if (input.metadata !== undefined) {
13431
+ sets.push("metadata = ?");
13432
+ params.push(JSON.stringify(input.metadata));
13433
+ }
13434
+ if (input.status !== undefined) {
13435
+ sets.push("status = ?");
13436
+ params.push(input.status);
13437
+ }
13438
+ if (sets.length > 0) {
13439
+ sets.push("updated_at = ?");
13440
+ params.push(now());
13441
+ params.push(id);
13442
+ d.run(`UPDATE providers SET ${sets.join(", ")} WHERE id = ?`, params);
13443
+ cacheClear("providers:");
13444
+ }
13445
+ return getProvider(id, d);
13446
+ }
13447
+ function ensureProvider(name, type, config, db) {
13448
+ const d = db || getDatabase();
13449
+ const existing = getProviderByName(name, d);
13450
+ if (existing) {
13451
+ if (config) {
13452
+ return updateProvider(existing.id, { config }, d);
13453
+ }
13454
+ d.run("UPDATE providers SET updated_at = ? WHERE id = ?", [
13455
+ now(),
13456
+ existing.id
13457
+ ]);
13458
+ return getProvider(existing.id, d);
13459
+ }
13460
+ return createProvider({ name, type, config }, d);
13461
+ }
13462
+
13330
13463
  // src/db/transactions.ts
13331
13464
  function rowToTransaction(row) {
13332
13465
  return {
@@ -13364,53 +13497,688 @@ function listTransactions(filter = {}, db) {
13364
13497
  return rows.map(rowToTransaction);
13365
13498
  }
13366
13499
 
13367
- // src/db/agents.ts
13368
- function rowToAgent(row) {
13369
- return { ...row };
13370
- }
13371
- function registerAgent(input, db) {
13372
- const d = db || getDatabase();
13373
- const normalizedName = input.name.trim().toLowerCase();
13374
- const existing = getAgentByName(normalizedName, d);
13375
- if (existing) {
13376
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), existing.id]);
13377
- return getAgent(existing.id, d);
13500
+ // src/lib/config.ts
13501
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync, writeFileSync } from "fs";
13502
+ import { homedir as homedir5 } from "os";
13503
+ import { join as join5 } from "path";
13504
+
13505
+ // node_modules/chalk/source/vendor/ansi-styles/index.js
13506
+ var ANSI_BACKGROUND_OFFSET = 10;
13507
+ var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
13508
+ var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
13509
+ var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
13510
+ var styles = {
13511
+ modifier: {
13512
+ reset: [0, 0],
13513
+ bold: [1, 22],
13514
+ dim: [2, 22],
13515
+ italic: [3, 23],
13516
+ underline: [4, 24],
13517
+ overline: [53, 55],
13518
+ inverse: [7, 27],
13519
+ hidden: [8, 28],
13520
+ strikethrough: [9, 29]
13521
+ },
13522
+ color: {
13523
+ black: [30, 39],
13524
+ red: [31, 39],
13525
+ green: [32, 39],
13526
+ yellow: [33, 39],
13527
+ blue: [34, 39],
13528
+ magenta: [35, 39],
13529
+ cyan: [36, 39],
13530
+ white: [37, 39],
13531
+ blackBright: [90, 39],
13532
+ gray: [90, 39],
13533
+ grey: [90, 39],
13534
+ redBright: [91, 39],
13535
+ greenBright: [92, 39],
13536
+ yellowBright: [93, 39],
13537
+ blueBright: [94, 39],
13538
+ magentaBright: [95, 39],
13539
+ cyanBright: [96, 39],
13540
+ whiteBright: [97, 39]
13541
+ },
13542
+ bgColor: {
13543
+ bgBlack: [40, 49],
13544
+ bgRed: [41, 49],
13545
+ bgGreen: [42, 49],
13546
+ bgYellow: [43, 49],
13547
+ bgBlue: [44, 49],
13548
+ bgMagenta: [45, 49],
13549
+ bgCyan: [46, 49],
13550
+ bgWhite: [47, 49],
13551
+ bgBlackBright: [100, 49],
13552
+ bgGray: [100, 49],
13553
+ bgGrey: [100, 49],
13554
+ bgRedBright: [101, 49],
13555
+ bgGreenBright: [102, 49],
13556
+ bgYellowBright: [103, 49],
13557
+ bgBlueBright: [104, 49],
13558
+ bgMagentaBright: [105, 49],
13559
+ bgCyanBright: [106, 49],
13560
+ bgWhiteBright: [107, 49]
13561
+ }
13562
+ };
13563
+ var modifierNames = Object.keys(styles.modifier);
13564
+ var foregroundColorNames = Object.keys(styles.color);
13565
+ var backgroundColorNames = Object.keys(styles.bgColor);
13566
+ var colorNames = [...foregroundColorNames, ...backgroundColorNames];
13567
+ function assembleStyles() {
13568
+ const codes = new Map;
13569
+ for (const [groupName, group] of Object.entries(styles)) {
13570
+ for (const [styleName, style] of Object.entries(group)) {
13571
+ styles[styleName] = {
13572
+ open: `\x1B[${style[0]}m`,
13573
+ close: `\x1B[${style[1]}m`
13574
+ };
13575
+ group[styleName] = styles[styleName];
13576
+ codes.set(style[0], style[1]);
13577
+ }
13578
+ Object.defineProperty(styles, groupName, {
13579
+ value: group,
13580
+ enumerable: false
13581
+ });
13582
+ }
13583
+ Object.defineProperty(styles, "codes", {
13584
+ value: codes,
13585
+ enumerable: false
13586
+ });
13587
+ styles.color.close = "\x1B[39m";
13588
+ styles.bgColor.close = "\x1B[49m";
13589
+ styles.color.ansi = wrapAnsi16();
13590
+ styles.color.ansi256 = wrapAnsi256();
13591
+ styles.color.ansi16m = wrapAnsi16m();
13592
+ styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
13593
+ styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
13594
+ styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
13595
+ Object.defineProperties(styles, {
13596
+ rgbToAnsi256: {
13597
+ value(red, green, blue) {
13598
+ if (red === green && green === blue) {
13599
+ if (red < 8) {
13600
+ return 16;
13601
+ }
13602
+ if (red > 248) {
13603
+ return 231;
13604
+ }
13605
+ return Math.round((red - 8) / 247 * 24) + 232;
13606
+ }
13607
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
13608
+ },
13609
+ enumerable: false
13610
+ },
13611
+ hexToRgb: {
13612
+ value(hex) {
13613
+ const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
13614
+ if (!matches) {
13615
+ return [0, 0, 0];
13616
+ }
13617
+ let [colorString] = matches;
13618
+ if (colorString.length === 3) {
13619
+ colorString = [...colorString].map((character) => character + character).join("");
13620
+ }
13621
+ const integer = Number.parseInt(colorString, 16);
13622
+ return [
13623
+ integer >> 16 & 255,
13624
+ integer >> 8 & 255,
13625
+ integer & 255
13626
+ ];
13627
+ },
13628
+ enumerable: false
13629
+ },
13630
+ hexToAnsi256: {
13631
+ value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
13632
+ enumerable: false
13633
+ },
13634
+ ansi256ToAnsi: {
13635
+ value(code) {
13636
+ if (code < 8) {
13637
+ return 30 + code;
13638
+ }
13639
+ if (code < 16) {
13640
+ return 90 + (code - 8);
13641
+ }
13642
+ let red;
13643
+ let green;
13644
+ let blue;
13645
+ if (code >= 232) {
13646
+ red = ((code - 232) * 10 + 8) / 255;
13647
+ green = red;
13648
+ blue = red;
13649
+ } else {
13650
+ code -= 16;
13651
+ const remainder = code % 36;
13652
+ red = Math.floor(code / 36) / 5;
13653
+ green = Math.floor(remainder / 6) / 5;
13654
+ blue = remainder % 6 / 5;
13655
+ }
13656
+ const value = Math.max(red, green, blue) * 2;
13657
+ if (value === 0) {
13658
+ return 30;
13659
+ }
13660
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
13661
+ if (value === 2) {
13662
+ result += 60;
13663
+ }
13664
+ return result;
13665
+ },
13666
+ enumerable: false
13667
+ },
13668
+ rgbToAnsi: {
13669
+ value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
13670
+ enumerable: false
13671
+ },
13672
+ hexToAnsi: {
13673
+ value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
13674
+ enumerable: false
13675
+ }
13676
+ });
13677
+ return styles;
13678
+ }
13679
+ var ansiStyles = assembleStyles();
13680
+ var ansi_styles_default = ansiStyles;
13681
+
13682
+ // node_modules/chalk/source/vendor/supports-color/index.js
13683
+ import process2 from "process";
13684
+ import os from "os";
13685
+ import tty from "tty";
13686
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
13687
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
13688
+ const position = argv.indexOf(prefix + flag);
13689
+ const terminatorPosition = argv.indexOf("--");
13690
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
13691
+ }
13692
+ var { env } = process2;
13693
+ var flagForceColor;
13694
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
13695
+ flagForceColor = 0;
13696
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
13697
+ flagForceColor = 1;
13698
+ }
13699
+ function envForceColor() {
13700
+ if ("FORCE_COLOR" in env) {
13701
+ if (env.FORCE_COLOR === "true") {
13702
+ return 1;
13703
+ }
13704
+ if (env.FORCE_COLOR === "false") {
13705
+ return 0;
13706
+ }
13707
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
13378
13708
  }
13379
- const id = shortId();
13380
- d.run("INSERT INTO agents (id, name, description) VALUES (?, ?, ?)", [id, normalizedName, input.description ?? null]);
13381
- return getAgent(id, d);
13382
- }
13383
- function getAgent(id, db) {
13384
- const d = db || getDatabase();
13385
- const row = d.query("SELECT * FROM agents WHERE id = ?").get(id);
13386
- return row ? rowToAgent(row) : null;
13387
13709
  }
13388
- function getAgentByName(name, db) {
13389
- const d = db || getDatabase();
13390
- const normalizedName = name.trim().toLowerCase();
13391
- const row = d.query("SELECT * FROM agents WHERE name = ?").get(normalizedName);
13392
- return row ? rowToAgent(row) : null;
13710
+ function translateLevel(level) {
13711
+ if (level === 0) {
13712
+ return false;
13713
+ }
13714
+ return {
13715
+ level,
13716
+ hasBasic: true,
13717
+ has256: level >= 2,
13718
+ has16m: level >= 3
13719
+ };
13393
13720
  }
13394
- function listAgents(db) {
13395
- const d = db || getDatabase();
13396
- const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
13397
- return rows.map(rowToAgent);
13721
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
13722
+ const noFlagForceColor = envForceColor();
13723
+ if (noFlagForceColor !== undefined) {
13724
+ flagForceColor = noFlagForceColor;
13725
+ }
13726
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
13727
+ if (forceColor === 0) {
13728
+ return 0;
13729
+ }
13730
+ if (sniffFlags) {
13731
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
13732
+ return 3;
13733
+ }
13734
+ if (hasFlag("color=256")) {
13735
+ return 2;
13736
+ }
13737
+ }
13738
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) {
13739
+ return 1;
13740
+ }
13741
+ if (haveStream && !streamIsTTY && forceColor === undefined) {
13742
+ return 0;
13743
+ }
13744
+ const min = forceColor || 0;
13745
+ if (env.TERM === "dumb") {
13746
+ return min;
13747
+ }
13748
+ if (process2.platform === "win32") {
13749
+ const osRelease = os.release().split(".");
13750
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
13751
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
13752
+ }
13753
+ return 1;
13754
+ }
13755
+ if ("CI" in env) {
13756
+ if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => (key in env))) {
13757
+ return 3;
13758
+ }
13759
+ if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => (sign in env)) || env.CI_NAME === "codeship") {
13760
+ return 1;
13761
+ }
13762
+ return min;
13763
+ }
13764
+ if ("TEAMCITY_VERSION" in env) {
13765
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
13766
+ }
13767
+ if (env.COLORTERM === "truecolor") {
13768
+ return 3;
13769
+ }
13770
+ if (env.TERM === "xterm-kitty") {
13771
+ return 3;
13772
+ }
13773
+ if (env.TERM === "xterm-ghostty") {
13774
+ return 3;
13775
+ }
13776
+ if (env.TERM === "wezterm") {
13777
+ return 3;
13778
+ }
13779
+ if ("TERM_PROGRAM" in env) {
13780
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
13781
+ switch (env.TERM_PROGRAM) {
13782
+ case "iTerm.app": {
13783
+ return version >= 3 ? 3 : 2;
13784
+ }
13785
+ case "Apple_Terminal": {
13786
+ return 2;
13787
+ }
13788
+ }
13789
+ }
13790
+ if (/-256(color)?$/i.test(env.TERM)) {
13791
+ return 2;
13792
+ }
13793
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
13794
+ return 1;
13795
+ }
13796
+ if ("COLORTERM" in env) {
13797
+ return 1;
13798
+ }
13799
+ return min;
13398
13800
  }
13399
- function heartbeatAgent(idOrName, db) {
13400
- const d = db || getDatabase();
13401
- const agent = getAgent(idOrName, d) ?? getAgentByName(idOrName, d);
13402
- if (!agent)
13403
- return null;
13404
- d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [now(), agent.id]);
13405
- return getAgent(agent.id, d);
13801
+ function createSupportsColor(stream, options = {}) {
13802
+ const level = _supportsColor(stream, {
13803
+ streamIsTTY: stream && stream.isTTY,
13804
+ ...options
13805
+ });
13806
+ return translateLevel(level);
13406
13807
  }
13407
- function setAgentFocus(idOrName, projectId, db) {
13408
- const d = db || getDatabase();
13409
- const agent = getAgent(idOrName, d) ?? getAgentByName(idOrName, d);
13410
- if (!agent)
13411
- return null;
13412
- d.run("UPDATE agents SET active_project_id = ?, last_seen_at = ? WHERE id = ?", [projectId, now(), agent.id]);
13413
- return getAgent(agent.id, d);
13808
+ var supportsColor = {
13809
+ stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
13810
+ stderr: createSupportsColor({ isTTY: tty.isatty(2) })
13811
+ };
13812
+ var supports_color_default = supportsColor;
13813
+
13814
+ // node_modules/chalk/source/utilities.js
13815
+ function stringReplaceAll(string, substring, replacer) {
13816
+ let index = string.indexOf(substring);
13817
+ if (index === -1) {
13818
+ return string;
13819
+ }
13820
+ const substringLength = substring.length;
13821
+ let endIndex = 0;
13822
+ let returnValue = "";
13823
+ do {
13824
+ returnValue += string.slice(endIndex, index) + substring + replacer;
13825
+ endIndex = index + substringLength;
13826
+ index = string.indexOf(substring, endIndex);
13827
+ } while (index !== -1);
13828
+ returnValue += string.slice(endIndex);
13829
+ return returnValue;
13830
+ }
13831
+ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
13832
+ let endIndex = 0;
13833
+ let returnValue = "";
13834
+ do {
13835
+ const gotCR = string[index - 1] === "\r";
13836
+ returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? `\r
13837
+ ` : `
13838
+ `) + postfix;
13839
+ endIndex = index + 1;
13840
+ index = string.indexOf(`
13841
+ `, endIndex);
13842
+ } while (index !== -1);
13843
+ returnValue += string.slice(endIndex);
13844
+ return returnValue;
13845
+ }
13846
+
13847
+ // node_modules/chalk/source/index.js
13848
+ var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
13849
+ var GENERATOR = Symbol("GENERATOR");
13850
+ var STYLER = Symbol("STYLER");
13851
+ var IS_EMPTY = Symbol("IS_EMPTY");
13852
+ var levelMapping = [
13853
+ "ansi",
13854
+ "ansi",
13855
+ "ansi256",
13856
+ "ansi16m"
13857
+ ];
13858
+ var styles2 = Object.create(null);
13859
+ var applyOptions = (object, options = {}) => {
13860
+ if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
13861
+ throw new Error("The `level` option should be an integer from 0 to 3");
13862
+ }
13863
+ const colorLevel = stdoutColor ? stdoutColor.level : 0;
13864
+ object.level = options.level === undefined ? colorLevel : options.level;
13865
+ };
13866
+ var chalkFactory = (options) => {
13867
+ const chalk = (...strings) => strings.join(" ");
13868
+ applyOptions(chalk, options);
13869
+ Object.setPrototypeOf(chalk, createChalk.prototype);
13870
+ return chalk;
13871
+ };
13872
+ function createChalk(options) {
13873
+ return chalkFactory(options);
13874
+ }
13875
+ Object.setPrototypeOf(createChalk.prototype, Function.prototype);
13876
+ for (const [styleName, style] of Object.entries(ansi_styles_default)) {
13877
+ styles2[styleName] = {
13878
+ get() {
13879
+ const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
13880
+ Object.defineProperty(this, styleName, { value: builder });
13881
+ return builder;
13882
+ }
13883
+ };
13884
+ }
13885
+ styles2.visible = {
13886
+ get() {
13887
+ const builder = createBuilder(this, this[STYLER], true);
13888
+ Object.defineProperty(this, "visible", { value: builder });
13889
+ return builder;
13890
+ }
13891
+ };
13892
+ var getModelAnsi = (model, level, type, ...arguments_) => {
13893
+ if (model === "rgb") {
13894
+ if (level === "ansi16m") {
13895
+ return ansi_styles_default[type].ansi16m(...arguments_);
13896
+ }
13897
+ if (level === "ansi256") {
13898
+ return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
13899
+ }
13900
+ return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
13901
+ }
13902
+ if (model === "hex") {
13903
+ return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
13904
+ }
13905
+ return ansi_styles_default[type][model](...arguments_);
13906
+ };
13907
+ var usedModels = ["rgb", "hex", "ansi256"];
13908
+ for (const model of usedModels) {
13909
+ styles2[model] = {
13910
+ get() {
13911
+ const { level } = this;
13912
+ return function(...arguments_) {
13913
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
13914
+ return createBuilder(this, styler, this[IS_EMPTY]);
13915
+ };
13916
+ }
13917
+ };
13918
+ const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
13919
+ styles2[bgModel] = {
13920
+ get() {
13921
+ const { level } = this;
13922
+ return function(...arguments_) {
13923
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
13924
+ return createBuilder(this, styler, this[IS_EMPTY]);
13925
+ };
13926
+ }
13927
+ };
13928
+ }
13929
+ var proto = Object.defineProperties(() => {}, {
13930
+ ...styles2,
13931
+ level: {
13932
+ enumerable: true,
13933
+ get() {
13934
+ return this[GENERATOR].level;
13935
+ },
13936
+ set(level) {
13937
+ this[GENERATOR].level = level;
13938
+ }
13939
+ }
13940
+ });
13941
+ var createStyler = (open, close, parent) => {
13942
+ let openAll;
13943
+ let closeAll;
13944
+ if (parent === undefined) {
13945
+ openAll = open;
13946
+ closeAll = close;
13947
+ } else {
13948
+ openAll = parent.openAll + open;
13949
+ closeAll = close + parent.closeAll;
13950
+ }
13951
+ return {
13952
+ open,
13953
+ close,
13954
+ openAll,
13955
+ closeAll,
13956
+ parent
13957
+ };
13958
+ };
13959
+ var createBuilder = (self, _styler, _isEmpty) => {
13960
+ const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
13961
+ Object.setPrototypeOf(builder, proto);
13962
+ builder[GENERATOR] = self;
13963
+ builder[STYLER] = _styler;
13964
+ builder[IS_EMPTY] = _isEmpty;
13965
+ return builder;
13966
+ };
13967
+ var applyStyle = (self, string) => {
13968
+ if (self.level <= 0 || !string) {
13969
+ return self[IS_EMPTY] ? "" : string;
13970
+ }
13971
+ let styler = self[STYLER];
13972
+ if (styler === undefined) {
13973
+ return string;
13974
+ }
13975
+ const { openAll, closeAll } = styler;
13976
+ if (string.includes("\x1B")) {
13977
+ while (styler !== undefined) {
13978
+ string = stringReplaceAll(string, styler.close, styler.open);
13979
+ styler = styler.parent;
13980
+ }
13981
+ }
13982
+ const lfIndex = string.indexOf(`
13983
+ `);
13984
+ if (lfIndex !== -1) {
13985
+ string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
13986
+ }
13987
+ return openAll + string + closeAll;
13988
+ };
13989
+ Object.defineProperties(createChalk.prototype, styles2);
13990
+ var chalk = createChalk();
13991
+ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
13992
+ var source_default = chalk;
13993
+
13994
+ // src/lib/config.ts
13995
+ var WalletsConfigSchema = exports_external.object({
13996
+ default_provider: exports_external.string().optional(),
13997
+ default_currency: exports_external.string().optional()
13998
+ });
13999
+ var _configCache = null;
14000
+ var _configCachePath = null;
14001
+ function _invalidateCache() {
14002
+ _configCache = null;
14003
+ _configCachePath = null;
14004
+ }
14005
+ function _loadConfigUncached() {
14006
+ const configPath = getConfigPath();
14007
+ if (!existsSync3(configPath)) {
14008
+ return {};
14009
+ }
14010
+ try {
14011
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
14012
+ return WalletsConfigSchema.parse(raw);
14013
+ } catch (e) {
14014
+ if (e instanceof exports_external.ZodError) {
14015
+ console.error(source_default.yellow(`Config validation errors: ${e.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join(", ")}`));
14016
+ }
14017
+ return {};
14018
+ }
14019
+ }
14020
+ function getConfigDir() {
14021
+ if (process.env.WALLETS_CONFIG_DIR)
14022
+ return process.env.WALLETS_CONFIG_DIR;
14023
+ const home = homedir5();
14024
+ const newDir = join5(home, ".hasna", "wallets");
14025
+ const legacyDir = join5(home, ".wallets");
14026
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
14027
+ return legacyDir;
14028
+ }
14029
+ return newDir;
14030
+ }
14031
+ function getConfigPath() {
14032
+ return join5(getConfigDir(), "config.json");
14033
+ }
14034
+ function loadConfig() {
14035
+ const configPath = getConfigPath();
14036
+ if (_configCache !== null && _configCachePath === configPath) {
14037
+ return _configCache;
14038
+ }
14039
+ _configCache = _loadConfigUncached();
14040
+ _configCachePath = configPath;
14041
+ return _configCache;
14042
+ }
14043
+ function saveConfig(config) {
14044
+ const configDir = getConfigDir();
14045
+ if (!existsSync3(configDir)) {
14046
+ mkdirSync3(configDir, { recursive: true });
14047
+ }
14048
+ writeFileSync(join5(configDir, "config.json"), `${JSON.stringify(config, null, 2)}
14049
+ `);
14050
+ _invalidateCache();
14051
+ }
14052
+ function getProviderConfig(providerName) {
14053
+ const provider = getProviderByName(providerName);
14054
+ return provider?.config;
14055
+ }
14056
+ function setProviderConfig(providerName, providerConfig) {
14057
+ const provider = getProviderByName(providerName);
14058
+ if (!provider)
14059
+ return;
14060
+ updateProvider(provider.id, { config: providerConfig });
14061
+ }
14062
+
14063
+ // src/lib/doctor.ts
14064
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
14065
+ import { join as join6 } from "path";
14066
+ function runDoctor(fix = false) {
14067
+ const checks = [];
14068
+ const fixed = [];
14069
+ const configDir = getConfigDir();
14070
+ if (!existsSync4(configDir)) {
14071
+ if (fix) {
14072
+ mkdirSync4(configDir, { recursive: true });
14073
+ fixed.push(`Created config directory at ${configDir}`);
14074
+ }
14075
+ }
14076
+ checks.push({
14077
+ name: "Config directory",
14078
+ status: existsSync4(configDir) ? "ok" : fix ? "ok" : "warn",
14079
+ message: existsSync4(configDir) ? `Found at ${configDir}` : fix ? `Created at ${configDir}` : `Not found at ${configDir}. Run 'wallets provider add' to create it.`
14080
+ });
14081
+ const configPath = getConfigPath();
14082
+ if (!existsSync4(configPath)) {
14083
+ if (fix) {
14084
+ saveConfig({});
14085
+ fixed.push(`Created empty config at ${configPath}`);
14086
+ }
14087
+ }
14088
+ if (existsSync4(configPath)) {
14089
+ try {
14090
+ const config = loadConfig();
14091
+ checks.push({
14092
+ name: "Config file",
14093
+ status: "ok",
14094
+ message: `Valid config at ${configPath}`
14095
+ });
14096
+ if (config.default_provider) {
14097
+ checks.push({
14098
+ name: "Default provider",
14099
+ status: "ok",
14100
+ message: `Set to '${config.default_provider}'`
14101
+ });
14102
+ } else {
14103
+ checks.push({
14104
+ name: "Default provider",
14105
+ status: "warn",
14106
+ message: "No default provider set. Use 'wallets provider add' to register one."
14107
+ });
14108
+ }
14109
+ } catch {
14110
+ checks.push({
14111
+ name: "Config file",
14112
+ status: "error",
14113
+ message: `Invalid JSON at ${configPath}`
14114
+ });
14115
+ }
14116
+ } else {
14117
+ checks.push({
14118
+ name: "Config file",
14119
+ status: "warn",
14120
+ message: `Not found at ${configPath}. Run 'wallets provider add' to create it.`
14121
+ });
14122
+ }
14123
+ try {
14124
+ const db = getDatabase();
14125
+ const providers = listProviders(db);
14126
+ checks.push({
14127
+ name: "Database",
14128
+ status: "ok",
14129
+ message: `Connected. ${providers.length} provider(s) registered.`
14130
+ });
14131
+ for (const provider of providers) {
14132
+ const hasConfig = provider.config && Object.keys(provider.config).length > 0;
14133
+ if (provider.type === "agentcard") {
14134
+ const hasJwt = provider.config && "jwt" in provider.config;
14135
+ checks.push({
14136
+ name: `Provider: ${provider.name}`,
14137
+ status: hasJwt ? "ok" : "error",
14138
+ message: hasJwt ? `AgentCard configured (status: ${provider.status})` : "Missing JWT token. Run 'wallets provider add agentcard' to configure."
14139
+ });
14140
+ } else {
14141
+ checks.push({
14142
+ name: `Provider: ${provider.name}`,
14143
+ status: hasConfig ? "ok" : "warn",
14144
+ message: `Type: ${provider.type}, Status: ${provider.status}${hasConfig ? "" : " (no config)"}`
14145
+ });
14146
+ }
14147
+ }
14148
+ if (providers.length === 0) {
14149
+ checks.push({
14150
+ name: "Providers",
14151
+ status: "warn",
14152
+ message: "No providers registered. Use 'wallets provider add' to add one."
14153
+ });
14154
+ }
14155
+ } catch (e) {
14156
+ checks.push({
14157
+ name: "Database",
14158
+ status: "error",
14159
+ message: `Failed to connect: ${e instanceof Error ? e.message : String(e)}`
14160
+ });
14161
+ }
14162
+ try {
14163
+ const binPath = join6(import.meta.dir, "../../dist/mcp/index.js");
14164
+ const srcPath = join6(import.meta.dir, "../mcp/index.ts");
14165
+ const mcpAvailable = existsSync4(binPath) || existsSync4(srcPath);
14166
+ checks.push({
14167
+ name: "MCP server",
14168
+ status: mcpAvailable ? "ok" : "warn",
14169
+ message: mcpAvailable ? "MCP server binary available" : "MCP server not found. Run 'bun run build' to compile."
14170
+ });
14171
+ } catch {
14172
+ checks.push({
14173
+ name: "MCP server",
14174
+ status: "warn",
14175
+ message: "Could not check MCP server status."
14176
+ });
14177
+ }
14178
+ return {
14179
+ checks,
14180
+ healthy: checks.every((c) => c.status !== "error")
14181
+ };
13414
14182
  }
13415
14183
 
13416
14184
  // src/types/index.ts
@@ -13477,29 +14245,140 @@ class AgentNotFoundError extends Error {
13477
14245
  }
13478
14246
  }
13479
14247
 
13480
- // src/providers/agentcard.ts
13481
- class AgentCardProvider {
13482
- name = "agentcard";
13483
- type = "agentcard";
13484
- jwt;
13485
- baseUrl;
13486
- constructor(config) {
13487
- this.jwt = config.jwt;
13488
- this.baseUrl = config.baseUrl || "https://api.agentcard.sh";
13489
- }
13490
- async request(path, options = {}) {
13491
- const url = `${this.baseUrl}${path}`;
13492
- const res = await fetch(url, {
13493
- ...options,
13494
- headers: {
13495
- "Content-Type": "application/json",
13496
- Authorization: `Bearer ${this.jwt}`,
13497
- ...options.headers || {}
13498
- }
13499
- });
13500
- if (!res.ok) {
13501
- const body = await res.text();
13502
- throw new ProviderError("agentcard", `HTTP ${res.status}: ${body}`);
14248
+ // src/lib/format.ts
14249
+ function formatCard(card) {
14250
+ const id = card.id.slice(0, 8);
14251
+ const last4 = card.last_four ? `*${card.last_four}` : "----";
14252
+ const category = card.metadata?.category ? `[${card.metadata.category}] ` : "";
14253
+ return `${id} ${card.status.padEnd(8)} ${last4.padEnd(6)} $${card.balance.toFixed(2).padStart(10)} ${category}${card.name}`;
14254
+ }
14255
+ function formatProvider(provider) {
14256
+ return `${provider.id.slice(0, 8)} ${provider.status.padEnd(8)} ${provider.type.padEnd(12)} ${provider.name}`;
14257
+ }
14258
+ function formatTransaction(tx) {
14259
+ const id = tx.id.slice(0, 8);
14260
+ const sign = tx.type === "refund" || tx.type === "load" ? "+" : "-";
14261
+ return `${id} ${tx.type.padEnd(10)} ${tx.status.padEnd(10)} ${sign}$${tx.amount.toFixed(2).padStart(9)} ${tx.merchant || tx.description || ""}`;
14262
+ }
14263
+ function formatDoctorCheck(check) {
14264
+ const icon = check.status === "ok" ? "[ok]" : check.status === "warn" ? "[!!]" : "[ERR]";
14265
+ return `${icon} ${check.name}: ${check.message}`;
14266
+ }
14267
+ function formatError(error, includeStack = false) {
14268
+ return JSON.stringify(formatErrorStructured(error, includeStack));
14269
+ }
14270
+ function formatErrorStructured(error, includeStack = false) {
14271
+ const base = { timestamp: new Date().toISOString() };
14272
+ if (error instanceof ProviderNotFoundError) {
14273
+ return {
14274
+ ...base,
14275
+ type: "ProviderNotFoundError",
14276
+ code: ProviderNotFoundError.code,
14277
+ message: error.message,
14278
+ suggestion: ProviderNotFoundError.suggestion,
14279
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14280
+ };
14281
+ }
14282
+ if (error instanceof CardNotFoundError) {
14283
+ return {
14284
+ ...base,
14285
+ type: "CardNotFoundError",
14286
+ code: CardNotFoundError.code,
14287
+ message: error.message,
14288
+ suggestion: CardNotFoundError.suggestion,
14289
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14290
+ };
14291
+ }
14292
+ if (error instanceof ProviderError) {
14293
+ return {
14294
+ ...base,
14295
+ type: "ProviderError",
14296
+ code: ProviderError.code,
14297
+ message: error.message,
14298
+ suggestion: ProviderError.suggestion,
14299
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14300
+ };
14301
+ }
14302
+ if (error instanceof InsufficientFundsError) {
14303
+ return {
14304
+ ...base,
14305
+ type: "InsufficientFundsError",
14306
+ code: InsufficientFundsError.code,
14307
+ message: error.message,
14308
+ suggestion: InsufficientFundsError.suggestion,
14309
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14310
+ };
14311
+ }
14312
+ if (error instanceof ConfigError) {
14313
+ return {
14314
+ ...base,
14315
+ type: "ConfigError",
14316
+ code: ConfigError.code,
14317
+ message: error.message,
14318
+ suggestion: ConfigError.suggestion,
14319
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14320
+ };
14321
+ }
14322
+ if (error instanceof AgentNotFoundError) {
14323
+ return {
14324
+ ...base,
14325
+ type: "AgentNotFoundError",
14326
+ code: AgentNotFoundError.code,
14327
+ message: error.message,
14328
+ suggestion: AgentNotFoundError.suggestion,
14329
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14330
+ };
14331
+ }
14332
+ if (error instanceof WalletError) {
14333
+ return {
14334
+ ...base,
14335
+ type: "WalletError",
14336
+ code: WalletError.code,
14337
+ message: error.message,
14338
+ suggestion: WalletError.suggestion,
14339
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14340
+ };
14341
+ }
14342
+ if (error instanceof Error) {
14343
+ return {
14344
+ ...base,
14345
+ type: error.name || "Error",
14346
+ code: "UNKNOWN_ERROR",
14347
+ message: error.message,
14348
+ ...includeStack && error.stack ? { stack: error.stack } : {}
14349
+ };
14350
+ }
14351
+ return {
14352
+ ...base,
14353
+ type: "Unknown",
14354
+ code: "UNKNOWN_ERROR",
14355
+ message: String(error)
14356
+ };
14357
+ }
14358
+
14359
+ // src/providers/agentcard.ts
14360
+ class AgentCardProvider {
14361
+ name = "agentcard";
14362
+ type = "agentcard";
14363
+ jwt;
14364
+ baseUrl;
14365
+ constructor(config) {
14366
+ this.jwt = config.jwt;
14367
+ this.baseUrl = config.baseUrl || "https://api.agentcard.sh";
14368
+ }
14369
+ async request(path, options = {}) {
14370
+ const url = `${this.baseUrl}${path}`;
14371
+ const res = await fetch(url, {
14372
+ ...options,
14373
+ headers: {
14374
+ "Content-Type": "application/json",
14375
+ Authorization: `Bearer ${this.jwt}`,
14376
+ ...options.headers || {}
14377
+ }
14378
+ });
14379
+ if (!res.ok) {
14380
+ const body = await res.text();
14381
+ throw new ProviderError("agentcard", `HTTP ${res.status}: ${body}`);
13503
14382
  }
13504
14383
  return res.json();
13505
14384
  }
@@ -13525,7 +14404,8 @@ class AgentCardProvider {
13525
14404
  expires_at: null,
13526
14405
  created_at: new Date().toISOString(),
13527
14406
  updated_at: new Date().toISOString(),
13528
- funding_url: data.funding_url
14407
+ funding_url: data.funding_url,
14408
+ idempotency_key: null
13529
14409
  };
13530
14410
  }
13531
14411
  async listCards() {
@@ -13546,7 +14426,8 @@ class AgentCardProvider {
13546
14426
  metadata: {},
13547
14427
  expires_at: null,
13548
14428
  created_at: c.created_at || new Date().toISOString(),
13549
- updated_at: new Date().toISOString()
14429
+ updated_at: new Date().toISOString(),
14430
+ idempotency_key: null
13550
14431
  }));
13551
14432
  }
13552
14433
  async getCardDetails(externalId) {
@@ -13571,7 +14452,8 @@ class AgentCardProvider {
13571
14452
  pan: data.pan,
13572
14453
  cvv: data.cvv,
13573
14454
  exp_month: data.exp_month,
13574
- exp_year: data.exp_year
14455
+ exp_year: data.exp_year,
14456
+ idempotency_key: null
13575
14457
  };
13576
14458
  }
13577
14459
  async getBalance(externalId) {
@@ -13584,6 +14466,39 @@ class AgentCardProvider {
13584
14466
  async closeCard(externalId) {
13585
14467
  await this.request(`/cards/${externalId}/close`, { method: "POST" });
13586
14468
  }
14469
+ async freezeCard(externalId) {
14470
+ await this.request(`/cards/${externalId}/freeze`, { method: "POST" });
14471
+ }
14472
+ async unfreezeCard(externalId) {
14473
+ await this.request(`/cards/${externalId}/unfreeze`, { method: "POST" });
14474
+ }
14475
+ async topUpCard(externalId, amount) {
14476
+ const data = await this.request(`/cards/${externalId}/topup`, {
14477
+ method: "POST",
14478
+ body: JSON.stringify({ amount })
14479
+ });
14480
+ return {
14481
+ balance: data.balance,
14482
+ currency: data.currency || "USD"
14483
+ };
14484
+ }
14485
+ async getTransactions(externalId) {
14486
+ const data = await this.request(`/cards/${externalId}/transactions`);
14487
+ return data.map((tx) => ({
14488
+ id: tx.id,
14489
+ card_id: "",
14490
+ provider_id: "",
14491
+ external_id: tx.id,
14492
+ type: tx.type || "purchase",
14493
+ status: tx.status || "completed",
14494
+ amount: tx.amount,
14495
+ currency: tx.currency || "USD",
14496
+ merchant: tx.merchant || null,
14497
+ description: tx.description || null,
14498
+ metadata: {},
14499
+ created_at: tx.created_at || new Date().toISOString()
14500
+ }));
14501
+ }
13587
14502
  }
13588
14503
 
13589
14504
  // src/providers/registry.ts
@@ -13606,574 +14521,717 @@ function createProviderInstance(type) {
13606
14521
  registerProviderFactory("agentcard", () => {
13607
14522
  throw new Error("AgentCard requires config. Use createAgentCardProvider() instead.");
13608
14523
  });
14524
+ var PROVIDER_INSTANCE_TTL_MS = 300000;
14525
+ var instanceCache = new Map;
14526
+ function makeInstanceCacheKey(type, config) {
14527
+ const sorted = Object.keys(config).sort().reduce((acc, k) => {
14528
+ acc[k] = config[k];
14529
+ return acc;
14530
+ }, {});
14531
+ return `${type}:${JSON.stringify(sorted)}`;
14532
+ }
13609
14533
  function getProviderInstance(type, config) {
14534
+ const key = makeInstanceCacheKey(type, config);
14535
+ const cached2 = instanceCache.get(key);
14536
+ if (cached2 && cached2.expiresAt > Date.now()) {
14537
+ return cached2.instance;
14538
+ }
14539
+ let instance;
13610
14540
  switch (type) {
13611
14541
  case "agentcard":
13612
- return new AgentCardProvider({ jwt: config["jwt"], baseUrl: config["baseUrl"] });
14542
+ instance = new AgentCardProvider({
14543
+ jwt: config.jwt,
14544
+ baseUrl: config.baseUrl
14545
+ });
14546
+ break;
13613
14547
  default:
13614
- return createProviderInstance(type);
13615
- }
13616
- }
13617
-
13618
- // src/lib/config.ts
13619
- import { homedir as homedir5 } from "os";
13620
- import { join as join5 } from "path";
13621
- import { existsSync as existsSync3, readFileSync, writeFileSync, mkdirSync as mkdirSync3 } from "fs";
13622
- function getConfigDir() {
13623
- if (process.env["WALLETS_CONFIG_DIR"])
13624
- return process.env["WALLETS_CONFIG_DIR"];
13625
- const home = homedir5();
13626
- const newDir = join5(home, ".hasna", "wallets");
13627
- const legacyDir = join5(home, ".wallets");
13628
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
13629
- return legacyDir;
13630
- }
13631
- return newDir;
13632
- }
13633
- function getConfigPath() {
13634
- return join5(getConfigDir(), "config.json");
13635
- }
13636
- function loadConfig() {
13637
- const configPath = getConfigPath();
13638
- if (!existsSync3(configPath)) {
13639
- return {};
13640
- }
13641
- try {
13642
- return JSON.parse(readFileSync(configPath, "utf-8"));
13643
- } catch {
13644
- return {};
13645
- }
13646
- }
13647
- function saveConfig(config) {
13648
- const configDir = getConfigDir();
13649
- if (!existsSync3(configDir)) {
13650
- mkdirSync3(configDir, { recursive: true });
14548
+ instance = createProviderInstance(type);
13651
14549
  }
13652
- writeFileSync(join5(configDir, "config.json"), JSON.stringify(config, null, 2) + `
13653
- `);
13654
- }
13655
- function getProviderConfig(providerName) {
13656
- const config = loadConfig();
13657
- return config.providers?.[providerName];
13658
- }
13659
- function setProviderConfig(providerName, providerConfig) {
13660
- const config = loadConfig();
13661
- if (!config.providers)
13662
- config.providers = {};
13663
- config.providers[providerName] = providerConfig;
13664
- saveConfig(config);
14550
+ instanceCache.set(key, {
14551
+ instance,
14552
+ expiresAt: Date.now() + PROVIDER_INSTANCE_TTL_MS
14553
+ });
14554
+ return instance;
14555
+ }
14556
+
14557
+ // src/mcp/http.ts
14558
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
14559
+ var DEFAULT_MCP_HTTP_PORT = 8882;
14560
+ var MCP_HTTP_HOST = "127.0.0.1";
14561
+ function isStdioMode(args) {
14562
+ return args.includes("--stdio") || process.env.MCP_STDIO === "1";
14563
+ }
14564
+ function resolveMcpHttpPort(args) {
14565
+ const portIdx = args.indexOf("--port");
14566
+ if (portIdx >= 0 && args[portIdx + 1])
14567
+ return Number(args[portIdx + 1]);
14568
+ const envPort = process.env.MCP_HTTP_PORT;
14569
+ if (envPort)
14570
+ return Number(envPort);
14571
+ return DEFAULT_MCP_HTTP_PORT;
14572
+ }
14573
+ async function handleMcpRequest(req, buildServer) {
14574
+ const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
14575
+ const server = await buildServer();
14576
+ await server.connect(transport);
14577
+ return transport.handleRequest(req);
14578
+ }
14579
+ function startMcpHttpServer(options) {
14580
+ const { name, port, buildServer } = options;
14581
+ const server = Bun.serve({
14582
+ hostname: MCP_HTTP_HOST,
14583
+ port,
14584
+ async fetch(req) {
14585
+ const url = new URL(req.url);
14586
+ if (url.pathname === "/health" && req.method === "GET")
14587
+ return Response.json({ status: "ok", name });
14588
+ if (url.pathname === "/mcp")
14589
+ return handleMcpRequest(req, buildServer);
14590
+ return new Response("Not Found", { status: 404 });
14591
+ }
14592
+ });
14593
+ console.error(`${name}-mcp HTTP listening on http://${MCP_HTTP_HOST}:${port}/mcp`);
14594
+ return server;
13665
14595
  }
13666
14596
 
13667
- // src/lib/doctor.ts
13668
- import { existsSync as existsSync4 } from "fs";
13669
- import { join as join6 } from "path";
13670
- function runDoctor() {
13671
- const checks = [];
13672
- const configDir = getConfigDir();
13673
- checks.push({
13674
- name: "Config directory",
13675
- status: existsSync4(configDir) ? "ok" : "warn",
13676
- message: existsSync4(configDir) ? `Found at ${configDir}` : `Not found at ${configDir}. Run 'wallets provider add' to create it.`
14597
+ // src/mcp/index.ts
14598
+ function buildServer() {
14599
+ const server = new McpServer({
14600
+ name: "wallets",
14601
+ version: "0.1.0"
13677
14602
  });
13678
- const configPath = getConfigPath();
13679
- if (existsSync4(configPath)) {
14603
+ function resolveId(partialId, table = "cards") {
14604
+ const db = getDatabase();
14605
+ const id = resolvePartialId(db, table, partialId);
14606
+ if (!id)
14607
+ throw new Error(`Could not resolve ID: ${partialId}`);
14608
+ return id;
14609
+ }
14610
+ function getDefaultProvider() {
14611
+ const config = loadConfig();
14612
+ if (!config.default_provider)
14613
+ return null;
14614
+ const record = getProviderByName(config.default_provider);
14615
+ if (!record)
14616
+ return null;
14617
+ return { name: config.default_provider, record };
14618
+ }
14619
+ server.tool("create_card", "Create a new funded virtual card", {
14620
+ amount: exports_external.number().describe("Funding amount in dollars"),
14621
+ name: exports_external.string().optional().describe("Card display name"),
14622
+ provider: exports_external.string().optional().describe("Provider name (uses default if omitted)"),
14623
+ currency: exports_external.string().optional().describe("Currency code (default: USD)"),
14624
+ agent_id: exports_external.string().optional().describe("Agent name to assign the card to"),
14625
+ idempotency_key: exports_external.string().optional().describe("Unique key to prevent duplicate card creation on retries")
14626
+ }, async (params) => {
13680
14627
  try {
13681
- const config = loadConfig();
13682
- checks.push({
13683
- name: "Config file",
13684
- status: "ok",
13685
- message: `Valid config at ${configPath}`
13686
- });
13687
- if (config.default_provider) {
13688
- checks.push({
13689
- name: "Default provider",
13690
- status: "ok",
13691
- message: `Set to '${config.default_provider}'`
13692
- });
13693
- } else {
13694
- checks.push({
13695
- name: "Default provider",
13696
- status: "warn",
13697
- message: "No default provider set. Use 'wallets provider add' to register one."
13698
- });
14628
+ if (params.idempotency_key) {
14629
+ const existing = getCardByIdempotencyKey(params.idempotency_key);
14630
+ if (existing) {
14631
+ return {
14632
+ content: [
14633
+ {
14634
+ type: "text",
14635
+ text: `existing: ${formatCard(existing)}`
14636
+ }
14637
+ ]
14638
+ };
14639
+ }
13699
14640
  }
13700
- } catch {
13701
- checks.push({
13702
- name: "Config file",
13703
- status: "error",
13704
- message: `Invalid JSON at ${configPath}`
13705
- });
13706
- }
13707
- } else {
13708
- checks.push({
13709
- name: "Config file",
13710
- status: "warn",
13711
- message: `Not found at ${configPath}. Run 'wallets provider add' to create it.`
13712
- });
13713
- }
13714
- try {
13715
- const db = getDatabase();
13716
- const providers2 = listProviders(db);
13717
- checks.push({
13718
- name: "Database",
13719
- status: "ok",
13720
- message: `Connected. ${providers2.length} provider(s) registered.`
13721
- });
13722
- for (const provider of providers2) {
13723
- const hasConfig = provider.config && Object.keys(provider.config).length > 0;
13724
- if (provider.type === "agentcard") {
13725
- const hasJwt = provider.config && "jwt" in provider.config;
13726
- checks.push({
13727
- name: `Provider: ${provider.name}`,
13728
- status: hasJwt ? "ok" : "error",
13729
- message: hasJwt ? `AgentCard configured (status: ${provider.status})` : "Missing JWT token. Run 'wallets provider add agentcard' to configure."
13730
- });
13731
- } else {
13732
- checks.push({
13733
- name: `Provider: ${provider.name}`,
13734
- status: hasConfig ? "ok" : "warn",
13735
- message: `Type: ${provider.type}, Status: ${provider.status}${hasConfig ? "" : " (no config)"}`
13736
- });
14641
+ const providerName = params.provider || getDefaultProvider()?.name;
14642
+ if (!providerName) {
14643
+ return {
14644
+ content: [
14645
+ {
14646
+ type: "text",
14647
+ text: formatError(new Error("No provider specified and no default set. Use register_provider first."))
14648
+ }
14649
+ ],
14650
+ isError: true
14651
+ };
14652
+ }
14653
+ const providerRecord = getProviderByName(providerName);
14654
+ if (!providerRecord) {
14655
+ return {
14656
+ content: [
14657
+ {
14658
+ type: "text",
14659
+ text: formatError(new Error(`Provider not found: ${providerName}`))
14660
+ }
14661
+ ],
14662
+ isError: true
14663
+ };
14664
+ }
14665
+ const providerConfig = {
14666
+ ...providerRecord.config,
14667
+ ...getProviderConfig(providerName) || {}
14668
+ };
14669
+ const instance = getProviderInstance(providerRecord.type, providerConfig);
14670
+ let agentId = null;
14671
+ if (params.agent_id) {
14672
+ const agent = registerAgent({ name: params.agent_id });
14673
+ agentId = agent.id;
14674
+ }
14675
+ const result = await instance.createCard({
14676
+ amount: params.amount,
14677
+ name: params.name,
14678
+ currency: params.currency || "USD",
14679
+ agent_id: agentId ?? undefined
14680
+ });
14681
+ const card = createCardRecord({
14682
+ provider_id: providerRecord.id,
14683
+ external_id: result.external_id || result.id,
14684
+ name: result.name,
14685
+ last_four: result.last_four,
14686
+ brand: result.brand,
14687
+ status: result.status,
14688
+ currency: result.currency,
14689
+ balance: result.balance,
14690
+ funded_amount: result.funded_amount,
14691
+ spending_limit: result.spending_limit,
14692
+ agent_id: agentId,
14693
+ metadata: result.funding_url ? { funding_url: result.funding_url } : {},
14694
+ idempotency_key: params.idempotency_key ?? null
14695
+ });
14696
+ let text = `created: ${formatCard(card)}`;
14697
+ if (result.funding_url) {
14698
+ text += `
14699
+ Funding URL: ${result.funding_url}`;
13737
14700
  }
14701
+ return { content: [{ type: "text", text }] };
14702
+ } catch (e) {
14703
+ return {
14704
+ content: [{ type: "text", text: formatError(e) }],
14705
+ isError: true
14706
+ };
13738
14707
  }
13739
- if (providers2.length === 0) {
13740
- checks.push({
13741
- name: "Providers",
13742
- status: "warn",
13743
- message: "No providers registered. Use 'wallets provider add' to add one."
14708
+ });
14709
+ server.tool("list_cards", "List all virtual cards", {
14710
+ status: exports_external.enum(["active", "frozen", "closed", "pending"]).optional().describe("Filter by status"),
14711
+ provider: exports_external.string().optional().describe("Filter by provider name"),
14712
+ agent_id: exports_external.string().optional().describe("Filter by agent")
14713
+ }, async (params) => {
14714
+ try {
14715
+ let providerId;
14716
+ if (params.provider) {
14717
+ const p = getProviderByName(params.provider);
14718
+ if (p)
14719
+ providerId = p.id;
14720
+ }
14721
+ const cards = listCards({
14722
+ status: params.status,
14723
+ provider_id: providerId
13744
14724
  });
14725
+ if (cards.length === 0) {
14726
+ return {
14727
+ content: [{ type: "text", text: "No cards found." }]
14728
+ };
14729
+ }
14730
+ const lines = cards.map(formatCard);
14731
+ return { content: [{ type: "text", text: lines.join(`
14732
+ `) }] };
14733
+ } catch (e) {
14734
+ return {
14735
+ content: [{ type: "text", text: formatError(e) }],
14736
+ isError: true
14737
+ };
13745
14738
  }
13746
- } catch (e) {
13747
- checks.push({
13748
- name: "Database",
13749
- status: "error",
13750
- message: `Failed to connect: ${e instanceof Error ? e.message : String(e)}`
13751
- });
13752
- }
13753
- try {
13754
- const binPath = join6(import.meta.dir, "../../dist/mcp/index.js");
13755
- const srcPath = join6(import.meta.dir, "../mcp/index.ts");
13756
- const mcpAvailable = existsSync4(binPath) || existsSync4(srcPath);
13757
- checks.push({
13758
- name: "MCP server",
13759
- status: mcpAvailable ? "ok" : "warn",
13760
- message: mcpAvailable ? "MCP server binary available" : "MCP server not found. Run 'bun run build' to compile."
13761
- });
13762
- } catch {
13763
- checks.push({
13764
- name: "MCP server",
13765
- status: "warn",
13766
- message: "Could not check MCP server status."
13767
- });
13768
- }
13769
- return {
13770
- checks,
13771
- healthy: checks.every((c) => c.status !== "error")
13772
- };
13773
- }
13774
-
13775
- // src/lib/format.ts
13776
- function formatCard(card) {
13777
- const id = card.id.slice(0, 8);
13778
- const last4 = card.last_four ? `*${card.last_four}` : "----";
13779
- return `${id} ${card.status.padEnd(8)} ${last4.padEnd(6)} $${card.balance.toFixed(2).padStart(10)} ${card.name}`;
13780
- }
13781
- function formatProvider(provider) {
13782
- return `${provider.id.slice(0, 8)} ${provider.status.padEnd(8)} ${provider.type.padEnd(12)} ${provider.name}`;
13783
- }
13784
- function formatTransaction(tx) {
13785
- const id = tx.id.slice(0, 8);
13786
- const sign = tx.type === "refund" || tx.type === "load" ? "+" : "-";
13787
- return `${id} ${tx.type.padEnd(10)} ${tx.status.padEnd(10)} ${sign}$${tx.amount.toFixed(2).padStart(9)} ${tx.merchant || tx.description || ""}`;
13788
- }
13789
- function formatDoctorCheck(check) {
13790
- const icon = check.status === "ok" ? "[ok]" : check.status === "warn" ? "[!!]" : "[ERR]";
13791
- return `${icon} ${check.name}: ${check.message}`;
13792
- }
13793
- function formatError(error) {
13794
- if (error instanceof ProviderNotFoundError) {
13795
- return JSON.stringify({ code: ProviderNotFoundError.code, message: error.message, suggestion: ProviderNotFoundError.suggestion });
13796
- }
13797
- if (error instanceof CardNotFoundError) {
13798
- return JSON.stringify({ code: CardNotFoundError.code, message: error.message, suggestion: CardNotFoundError.suggestion });
13799
- }
13800
- if (error instanceof ProviderError) {
13801
- return JSON.stringify({ code: ProviderError.code, message: error.message, suggestion: ProviderError.suggestion });
13802
- }
13803
- if (error instanceof InsufficientFundsError) {
13804
- return JSON.stringify({ code: InsufficientFundsError.code, message: error.message, suggestion: InsufficientFundsError.suggestion });
13805
- }
13806
- if (error instanceof ConfigError) {
13807
- return JSON.stringify({ code: ConfigError.code, message: error.message, suggestion: ConfigError.suggestion });
13808
- }
13809
- if (error instanceof AgentNotFoundError) {
13810
- return JSON.stringify({ code: AgentNotFoundError.code, message: error.message, suggestion: AgentNotFoundError.suggestion });
13811
- }
13812
- if (error instanceof WalletError) {
13813
- return JSON.stringify({ code: WalletError.code, message: error.message, suggestion: WalletError.suggestion });
13814
- }
13815
- if (error instanceof Error) {
13816
- return JSON.stringify({ code: "UNKNOWN_ERROR", message: error.message });
13817
- }
13818
- return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
13819
- }
13820
-
13821
- // src/mcp/index.ts
13822
- var server = new McpServer({
13823
- name: "wallets",
13824
- version: "0.1.0"
13825
- });
13826
- function resolveId(partialId, table = "cards") {
13827
- const db = getDatabase();
13828
- const id = resolvePartialId(db, table, partialId);
13829
- if (!id)
13830
- throw new Error(`Could not resolve ID: ${partialId}`);
13831
- return id;
13832
- }
13833
- function getDefaultProvider() {
13834
- const config = loadConfig();
13835
- if (!config.default_provider)
13836
- return null;
13837
- const record = getProviderByName(config.default_provider);
13838
- if (!record)
13839
- return null;
13840
- return { name: config.default_provider, record };
13841
- }
13842
- server.tool("create_card", "Create a new funded virtual card", {
13843
- amount: exports_external.number().describe("Funding amount in dollars"),
13844
- name: exports_external.string().optional().describe("Card display name"),
13845
- provider: exports_external.string().optional().describe("Provider name (uses default if omitted)"),
13846
- currency: exports_external.string().optional().describe("Currency code (default: USD)"),
13847
- agent_id: exports_external.string().optional().describe("Agent name to assign the card to")
13848
- }, async (params) => {
13849
- try {
13850
- const providerName = params.provider || getDefaultProvider()?.name;
13851
- if (!providerName) {
13852
- return { content: [{ type: "text", text: formatError(new Error("No provider specified and no default set. Use register_provider first.")) }], isError: true };
13853
- }
13854
- const providerRecord = getProviderByName(providerName);
13855
- if (!providerRecord) {
13856
- return { content: [{ type: "text", text: formatError(new Error(`Provider not found: ${providerName}`)) }], isError: true };
13857
- }
13858
- const providerConfig = { ...providerRecord.config, ...getProviderConfig(providerName) || {} };
13859
- const instance = getProviderInstance(providerRecord.type, providerConfig);
13860
- let agentId = null;
13861
- if (params.agent_id) {
13862
- const agent = registerAgent({ name: params.agent_id });
13863
- agentId = agent.id;
13864
- }
13865
- const result = await instance.createCard({
13866
- amount: params.amount,
13867
- name: params.name,
13868
- currency: params.currency || "USD",
13869
- agent_id: agentId ?? undefined
13870
- });
13871
- const card = createCardRecord({
13872
- provider_id: providerRecord.id,
13873
- external_id: result.external_id || result.id,
13874
- name: result.name,
13875
- last_four: result.last_four,
13876
- brand: result.brand,
13877
- status: result.status,
13878
- currency: result.currency,
13879
- balance: result.balance,
13880
- funded_amount: result.funded_amount,
13881
- spending_limit: result.spending_limit,
13882
- agent_id: agentId,
13883
- metadata: result.funding_url ? { funding_url: result.funding_url } : {}
13884
- });
13885
- let text = `created: ${formatCard(card)}`;
13886
- if (result.funding_url) {
13887
- text += `
13888
- Funding URL: ${result.funding_url}`;
14739
+ });
14740
+ server.tool("get_card_details", "Get full card details (PAN, CVV, expiry)", {
14741
+ card_id: exports_external.string().describe("Card ID (supports partial matching)")
14742
+ }, async (params) => {
14743
+ try {
14744
+ const cardId = resolveId(params.card_id, "cards");
14745
+ const card = getCard(cardId);
14746
+ if (!card)
14747
+ throw new Error(`Card not found: ${params.card_id}`);
14748
+ const provider = getProvider(card.provider_id);
14749
+ if (!provider)
14750
+ throw new Error("Provider not found for card");
14751
+ const providerConfig = {
14752
+ ...provider.config,
14753
+ ...getProviderConfig(provider.name) || {}
14754
+ };
14755
+ const instance = getProviderInstance(provider.type, providerConfig);
14756
+ const details = await instance.getCardDetails(card.external_id);
14757
+ return {
14758
+ content: [
14759
+ {
14760
+ type: "text",
14761
+ text: [
14762
+ `ID: ${card.id}`,
14763
+ `Name: ${card.name}`,
14764
+ `PAN: ${details.pan}`,
14765
+ `CVV: ${details.cvv}`,
14766
+ `Exp: ${details.exp_month}/${details.exp_year}`,
14767
+ `Status: ${card.status}`,
14768
+ `Balance: $${details.balance.toFixed(2)}`
14769
+ ].join(`
14770
+ `)
14771
+ }
14772
+ ]
14773
+ };
14774
+ } catch (e) {
14775
+ return {
14776
+ content: [{ type: "text", text: formatError(e) }],
14777
+ isError: true
14778
+ };
13889
14779
  }
13890
- return { content: [{ type: "text", text }] };
13891
- } catch (e) {
13892
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
13893
- }
13894
- });
13895
- server.tool("list_cards", "List all virtual cards", {
13896
- status: exports_external.enum(["active", "frozen", "closed", "pending"]).optional().describe("Filter by status"),
13897
- provider: exports_external.string().optional().describe("Filter by provider name"),
13898
- agent_id: exports_external.string().optional().describe("Filter by agent")
13899
- }, async (params) => {
13900
- try {
13901
- let providerId;
13902
- if (params.provider) {
13903
- const p = getProviderByName(params.provider);
13904
- if (p)
13905
- providerId = p.id;
14780
+ });
14781
+ server.tool("get_balance", "Check card balance", {
14782
+ card_id: exports_external.string().describe("Card ID")
14783
+ }, async (params) => {
14784
+ try {
14785
+ const cardId = resolveId(params.card_id, "cards");
14786
+ const card = getCard(cardId);
14787
+ if (!card)
14788
+ throw new Error(`Card not found: ${params.card_id}`);
14789
+ const provider = getProvider(card.provider_id);
14790
+ if (!provider)
14791
+ throw new Error("Provider not found for card");
14792
+ const providerConfig = {
14793
+ ...provider.config,
14794
+ ...getProviderConfig(provider.name) || {}
14795
+ };
14796
+ const instance = getProviderInstance(provider.type, providerConfig);
14797
+ const { balance, currency } = await instance.getBalance(card.external_id);
14798
+ updateCardBalance(cardId, balance);
14799
+ return {
14800
+ content: [
14801
+ {
14802
+ type: "text",
14803
+ text: `${card.name}: $${balance.toFixed(2)} ${currency}`
14804
+ }
14805
+ ]
14806
+ };
14807
+ } catch (e) {
14808
+ return {
14809
+ content: [{ type: "text", text: formatError(e) }],
14810
+ isError: true
14811
+ };
13906
14812
  }
13907
- const cards = listCards({
13908
- status: params.status,
13909
- provider_id: providerId
13910
- });
13911
- if (cards.length === 0) {
13912
- return { content: [{ type: "text", text: "No cards found." }] };
14813
+ });
14814
+ server.tool("close_card", "Close a card permanently", {
14815
+ card_id: exports_external.string().describe("Card ID")
14816
+ }, async (params) => {
14817
+ try {
14818
+ const cardId = resolveId(params.card_id, "cards");
14819
+ const card = getCard(cardId);
14820
+ if (!card)
14821
+ throw new Error(`Card not found: ${params.card_id}`);
14822
+ const provider = getProvider(card.provider_id);
14823
+ if (!provider)
14824
+ throw new Error("Provider not found for card");
14825
+ const providerConfig = {
14826
+ ...provider.config,
14827
+ ...getProviderConfig(provider.name) || {}
14828
+ };
14829
+ const instance = getProviderInstance(provider.type, providerConfig);
14830
+ await instance.closeCard(card.external_id);
14831
+ updateCard(cardId, { status: "closed" });
14832
+ return {
14833
+ content: [
14834
+ {
14835
+ type: "text",
14836
+ text: `Card closed: ${card.id.slice(0, 8)} ${card.name}`
14837
+ }
14838
+ ]
14839
+ };
14840
+ } catch (e) {
14841
+ return {
14842
+ content: [{ type: "text", text: formatError(e) }],
14843
+ isError: true
14844
+ };
14845
+ }
14846
+ });
14847
+ server.tool("freeze_card", "Freeze a card temporarily", {
14848
+ card_id: exports_external.string().describe("Card ID")
14849
+ }, async (params) => {
14850
+ try {
14851
+ const cardId = resolveId(params.card_id, "cards");
14852
+ const card = getCard(cardId);
14853
+ if (!card)
14854
+ throw new Error(`Card not found: ${params.card_id}`);
14855
+ if (card.status !== "active")
14856
+ throw new Error(`Card is not active: ${card.status}`);
14857
+ const provider = getProvider(card.provider_id);
14858
+ if (!provider)
14859
+ throw new Error("Provider not found for card");
14860
+ const providerConfig = {
14861
+ ...provider.config,
14862
+ ...getProviderConfig(provider.name) || {}
14863
+ };
14864
+ const instance = getProviderInstance(provider.type, providerConfig);
14865
+ if (instance.freezeCard) {
14866
+ await instance.freezeCard(card.external_id);
14867
+ }
14868
+ updateCard(cardId, { status: "frozen" });
14869
+ return {
14870
+ content: [
14871
+ {
14872
+ type: "text",
14873
+ text: `Card frozen: ${card.id.slice(0, 8)} ${card.name}`
14874
+ }
14875
+ ]
14876
+ };
14877
+ } catch (e) {
14878
+ return {
14879
+ content: [{ type: "text", text: formatError(e) }],
14880
+ isError: true
14881
+ };
14882
+ }
14883
+ });
14884
+ server.tool("unfreeze_card", "Unfreeze a frozen card", {
14885
+ card_id: exports_external.string().describe("Card ID")
14886
+ }, async (params) => {
14887
+ try {
14888
+ const cardId = resolveId(params.card_id, "cards");
14889
+ const card = getCard(cardId);
14890
+ if (!card)
14891
+ throw new Error(`Card not found: ${params.card_id}`);
14892
+ if (card.status !== "frozen")
14893
+ throw new Error(`Card is not frozen: ${card.status}`);
14894
+ const provider = getProvider(card.provider_id);
14895
+ if (!provider)
14896
+ throw new Error("Provider not found for card");
14897
+ const providerConfig = {
14898
+ ...provider.config,
14899
+ ...getProviderConfig(provider.name) || {}
14900
+ };
14901
+ const instance = getProviderInstance(provider.type, providerConfig);
14902
+ if (instance.unfreezeCard) {
14903
+ await instance.unfreezeCard(card.external_id);
14904
+ }
14905
+ updateCard(cardId, { status: "active" });
14906
+ return {
14907
+ content: [
14908
+ {
14909
+ type: "text",
14910
+ text: `Card unfrozen: ${card.id.slice(0, 8)} ${card.name}`
14911
+ }
14912
+ ]
14913
+ };
14914
+ } catch (e) {
14915
+ return {
14916
+ content: [{ type: "text", text: formatError(e) }],
14917
+ isError: true
14918
+ };
14919
+ }
14920
+ });
14921
+ server.tool("list_providers", "List registered wallet providers", {}, async () => {
14922
+ try {
14923
+ const providers2 = listProviders();
14924
+ if (providers2.length === 0) {
14925
+ return {
14926
+ content: [
14927
+ {
14928
+ type: "text",
14929
+ text: "No providers registered. Use register_provider to add one."
14930
+ }
14931
+ ]
14932
+ };
14933
+ }
14934
+ const lines = providers2.map(formatProvider);
14935
+ return { content: [{ type: "text", text: lines.join(`
14936
+ `) }] };
14937
+ } catch (e) {
14938
+ return {
14939
+ content: [{ type: "text", text: formatError(e) }],
14940
+ isError: true
14941
+ };
14942
+ }
14943
+ });
14944
+ server.tool("register_provider", "Register a wallet provider", {
14945
+ type: exports_external.string().describe("Provider type (e.g., agentcard)"),
14946
+ name: exports_external.string().optional().describe("Display name"),
14947
+ jwt: exports_external.string().optional().describe("JWT/API token"),
14948
+ api_key: exports_external.string().optional().describe("API key"),
14949
+ base_url: exports_external.string().optional().describe("Custom API base URL"),
14950
+ set_default: exports_external.boolean().optional().describe("Set as default provider")
14951
+ }, async (params) => {
14952
+ try {
14953
+ const name = params.name || params.type;
14954
+ const config = {};
14955
+ if (params.jwt)
14956
+ config.jwt = params.jwt;
14957
+ if (params.api_key)
14958
+ config.api_key = params.api_key;
14959
+ if (params.base_url)
14960
+ config.baseUrl = params.base_url;
14961
+ const provider = ensureProvider(name, params.type, config);
14962
+ setProviderConfig(name, config);
14963
+ if (params.set_default) {
14964
+ const cfg = loadConfig();
14965
+ cfg.default_provider = name;
14966
+ saveConfig(cfg);
14967
+ }
14968
+ return {
14969
+ content: [
14970
+ {
14971
+ type: "text",
14972
+ text: `registered: ${formatProvider(provider)}`
14973
+ }
14974
+ ]
14975
+ };
14976
+ } catch (e) {
14977
+ return {
14978
+ content: [{ type: "text", text: formatError(e) }],
14979
+ isError: true
14980
+ };
14981
+ }
14982
+ });
14983
+ server.tool("list_transactions", "List card transactions", {
14984
+ card_id: exports_external.string().optional().describe("Card ID to filter"),
14985
+ limit: exports_external.number().optional().describe("Max results (default: 20)"),
14986
+ offset: exports_external.number().optional().describe("Offset for pagination (default: 0)")
14987
+ }, async (params) => {
14988
+ try {
14989
+ let cardId;
14990
+ if (params.card_id) {
14991
+ cardId = resolveId(params.card_id, "cards");
14992
+ }
14993
+ const txns = listTransactions({
14994
+ card_id: cardId,
14995
+ limit: params.limit || 20,
14996
+ offset: params.offset || 0
14997
+ });
14998
+ if (txns.length === 0) {
14999
+ return {
15000
+ content: [{ type: "text", text: "No transactions found." }]
15001
+ };
15002
+ }
15003
+ const lines = txns.map(formatTransaction);
15004
+ return { content: [{ type: "text", text: lines.join(`
15005
+ `) }] };
15006
+ } catch (e) {
15007
+ return {
15008
+ content: [{ type: "text", text: formatError(e) }],
15009
+ isError: true
15010
+ };
15011
+ }
15012
+ });
15013
+ server.tool("doctor", "Run wallet diagnostics", {}, async () => {
15014
+ try {
15015
+ const result = runDoctor();
15016
+ const lines = result.checks.map(formatDoctorCheck);
15017
+ lines.push("");
15018
+ lines.push(result.healthy ? "All checks passed." : "Some checks failed.");
15019
+ return { content: [{ type: "text", text: lines.join(`
15020
+ `) }] };
15021
+ } catch (e) {
15022
+ return {
15023
+ content: [{ type: "text", text: formatError(e) }],
15024
+ isError: true
15025
+ };
15026
+ }
15027
+ });
15028
+ server.tool("describe_tools", "Get full schema for wallet tools", {
15029
+ name: exports_external.string().optional().describe("Tool name to describe (omit for all)")
15030
+ }, async (params) => {
15031
+ const tools = {
15032
+ create_card: "Create a funded virtual card. Params: amount (required, number), name (string), provider (string), currency (string, default USD), agent_id (string).",
15033
+ list_cards: "List cards. Params: status (active|frozen|closed|pending), provider (string), agent_id (string).",
15034
+ get_card_details: "Get PAN, CVV, expiry. Params: card_id (required, string).",
15035
+ get_balance: "Check balance. Params: card_id (required, string).",
15036
+ close_card: "Close permanently. Params: card_id (required, string).",
15037
+ list_providers: "List all providers. No params.",
15038
+ register_provider: "Register provider. Params: type (required), name, jwt, api_key, base_url, set_default.",
15039
+ list_transactions: "List transactions. Params: card_id, limit.",
15040
+ doctor: "Run diagnostics. No params.",
15041
+ register_agent: "Register agent session (idempotent). Params: name, description?",
15042
+ list_agents: "List all registered agents.",
15043
+ heartbeat: "Update last_seen_at to signal agent is active. Params: agent_id",
15044
+ set_focus: "Set active project context. Params: agent_id, project_id?"
15045
+ };
15046
+ if (params.name) {
15047
+ const desc = tools[params.name];
15048
+ if (!desc)
15049
+ return {
15050
+ content: [
15051
+ { type: "text", text: `Unknown tool: ${params.name}` }
15052
+ ]
15053
+ };
15054
+ return {
15055
+ content: [{ type: "text", text: `${params.name}: ${desc}` }]
15056
+ };
13913
15057
  }
13914
- const lines = cards.map(formatCard);
15058
+ const lines = Object.entries(tools).map(([k, v]) => `${k}: ${v}`);
13915
15059
  return { content: [{ type: "text", text: lines.join(`
13916
15060
  `) }] };
13917
- } catch (e) {
13918
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
13919
- }
13920
- });
13921
- server.tool("get_card_details", "Get full card details (PAN, CVV, expiry)", {
13922
- card_id: exports_external.string().describe("Card ID (supports partial matching)")
13923
- }, async (params) => {
13924
- try {
13925
- const cardId = resolveId(params.card_id, "cards");
13926
- const card = getCard(cardId);
13927
- if (!card)
13928
- throw new Error(`Card not found: ${params.card_id}`);
13929
- const provider = getProvider(card.provider_id);
13930
- if (!provider)
13931
- throw new Error("Provider not found for card");
13932
- const providerConfig = { ...provider.config, ...getProviderConfig(provider.name) || {} };
13933
- const instance = getProviderInstance(provider.type, providerConfig);
13934
- const details = await instance.getCardDetails(card.external_id);
15061
+ });
15062
+ server.resource("wallets://cards", "wallets://cards", async () => {
15063
+ const cards = listCards();
13935
15064
  return {
13936
- content: [{
13937
- type: "text",
13938
- text: [
13939
- `ID: ${card.id}`,
13940
- `Name: ${card.name}`,
13941
- `PAN: ${details.pan}`,
13942
- `CVV: ${details.cvv}`,
13943
- `Exp: ${details.exp_month}/${details.exp_year}`,
13944
- `Status: ${card.status}`,
13945
- `Balance: $${details.balance.toFixed(2)}`
13946
- ].join(`
13947
- `)
13948
- }]
15065
+ contents: [
15066
+ {
15067
+ uri: "wallets://cards",
15068
+ text: JSON.stringify(cards, null, 2),
15069
+ mimeType: "application/json"
15070
+ }
15071
+ ]
13949
15072
  };
13950
- } catch (e) {
13951
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
13952
- }
13953
- });
13954
- server.tool("get_balance", "Check card balance", {
13955
- card_id: exports_external.string().describe("Card ID")
13956
- }, async (params) => {
13957
- try {
13958
- const cardId = resolveId(params.card_id, "cards");
13959
- const card = getCard(cardId);
13960
- if (!card)
13961
- throw new Error(`Card not found: ${params.card_id}`);
13962
- const provider = getProvider(card.provider_id);
13963
- if (!provider)
13964
- throw new Error("Provider not found for card");
13965
- const providerConfig = { ...provider.config, ...getProviderConfig(provider.name) || {} };
13966
- const instance = getProviderInstance(provider.type, providerConfig);
13967
- const { balance, currency } = await instance.getBalance(card.external_id);
13968
- updateCardBalance(cardId, balance);
15073
+ });
15074
+ server.resource("wallets://providers", "wallets://providers", async () => {
15075
+ const providers2 = listProviders();
13969
15076
  return {
13970
- content: [{
13971
- type: "text",
13972
- text: `${card.name}: $${balance.toFixed(2)} ${currency}`
13973
- }]
15077
+ contents: [
15078
+ {
15079
+ uri: "wallets://providers",
15080
+ text: JSON.stringify(providers2, null, 2),
15081
+ mimeType: "application/json"
15082
+ }
15083
+ ]
13974
15084
  };
13975
- } catch (e) {
13976
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
13977
- }
13978
- });
13979
- server.tool("close_card", "Close a card permanently", {
13980
- card_id: exports_external.string().describe("Card ID")
13981
- }, async (params) => {
13982
- try {
13983
- const cardId = resolveId(params.card_id, "cards");
13984
- const card = getCard(cardId);
13985
- if (!card)
13986
- throw new Error(`Card not found: ${params.card_id}`);
13987
- const provider = getProvider(card.provider_id);
13988
- if (!provider)
13989
- throw new Error("Provider not found for card");
13990
- const providerConfig = { ...provider.config, ...getProviderConfig(provider.name) || {} };
13991
- const instance = getProviderInstance(provider.type, providerConfig);
13992
- await instance.closeCard(card.external_id);
13993
- updateCard(cardId, { status: "closed" });
13994
- return { content: [{ type: "text", text: `Card closed: ${card.id.slice(0, 8)} ${card.name}` }] };
13995
- } catch (e) {
13996
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
13997
- }
13998
- });
13999
- server.tool("list_providers", "List registered wallet providers", {}, async () => {
14000
- try {
14001
- const providers2 = listProviders();
14002
- if (providers2.length === 0) {
14003
- return { content: [{ type: "text", text: "No providers registered. Use register_provider to add one." }] };
15085
+ });
15086
+ server.resource("wallets://agents", "wallets://agents", async () => {
15087
+ const agents = listAgents();
15088
+ return {
15089
+ contents: [
15090
+ {
15091
+ uri: "wallets://agents",
15092
+ text: JSON.stringify(agents, null, 2),
15093
+ mimeType: "application/json"
15094
+ }
15095
+ ]
15096
+ };
15097
+ });
15098
+ server.tool("register_agent", "Register an agent session (idempotent). Auto-updates last_seen_at on re-register.", {
15099
+ name: exports_external.string().describe("Agent name"),
15100
+ description: exports_external.string().optional().describe("Agent description")
15101
+ }, async (params) => {
15102
+ try {
15103
+ const agent = registerAgent({
15104
+ name: params.name,
15105
+ description: params.description
15106
+ });
15107
+ return {
15108
+ content: [
15109
+ { type: "text", text: JSON.stringify(agent, null, 2) }
15110
+ ]
15111
+ };
15112
+ } catch (e) {
15113
+ return {
15114
+ content: [{ type: "text", text: formatError(e) }],
15115
+ isError: true
15116
+ };
14004
15117
  }
14005
- const lines = providers2.map(formatProvider);
14006
- return { content: [{ type: "text", text: lines.join(`
14007
- `) }] };
14008
- } catch (e) {
14009
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14010
- }
14011
- });
14012
- server.tool("register_provider", "Register a wallet provider", {
14013
- type: exports_external.string().describe("Provider type (e.g., agentcard)"),
14014
- name: exports_external.string().optional().describe("Display name"),
14015
- jwt: exports_external.string().optional().describe("JWT/API token"),
14016
- api_key: exports_external.string().optional().describe("API key"),
14017
- base_url: exports_external.string().optional().describe("Custom API base URL"),
14018
- set_default: exports_external.boolean().optional().describe("Set as default provider")
14019
- }, async (params) => {
14020
- try {
14021
- const name = params.name || params.type;
14022
- const config = {};
14023
- if (params.jwt)
14024
- config["jwt"] = params.jwt;
14025
- if (params.api_key)
14026
- config["api_key"] = params.api_key;
14027
- if (params.base_url)
14028
- config["baseUrl"] = params.base_url;
14029
- const provider = ensureProvider(name, params.type, config);
14030
- setProviderConfig(name, config);
14031
- if (params.set_default) {
14032
- const cfg = loadConfig();
14033
- cfg.default_provider = name;
14034
- saveConfig(cfg);
14035
- }
14036
- return { content: [{ type: "text", text: `registered: ${formatProvider(provider)}` }] };
14037
- } catch (e) {
14038
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14039
- }
14040
- });
14041
- server.tool("list_transactions", "List card transactions", {
14042
- card_id: exports_external.string().optional().describe("Card ID to filter"),
14043
- limit: exports_external.number().optional().describe("Max results (default: 20)")
14044
- }, async (params) => {
14045
- try {
14046
- let cardId;
14047
- if (params.card_id) {
14048
- cardId = resolveId(params.card_id, "cards");
15118
+ });
15119
+ server.tool("list_agents", "List all registered agents.", {}, async () => {
15120
+ try {
15121
+ const agents = listAgents();
15122
+ return {
15123
+ content: [
15124
+ { type: "text", text: JSON.stringify(agents, null, 2) }
15125
+ ]
15126
+ };
15127
+ } catch (e) {
15128
+ return {
15129
+ content: [{ type: "text", text: formatError(e) }],
15130
+ isError: true
15131
+ };
14049
15132
  }
14050
- const txns = listTransactions({
14051
- card_id: cardId,
14052
- limit: params.limit || 20
14053
- });
14054
- if (txns.length === 0) {
14055
- return { content: [{ type: "text", text: "No transactions found." }] };
15133
+ });
15134
+ server.tool("heartbeat", "Update last_seen_at to signal agent is active. Call periodically during long tasks.", { agent_id: exports_external.string().describe("Agent ID or name") }, async (params) => {
15135
+ try {
15136
+ const agent = heartbeatAgent(params.agent_id);
15137
+ if (!agent)
15138
+ return {
15139
+ content: [
15140
+ {
15141
+ type: "text",
15142
+ text: `Agent not found: ${params.agent_id}`
15143
+ }
15144
+ ],
15145
+ isError: true
15146
+ };
15147
+ return {
15148
+ content: [
15149
+ { type: "text", text: JSON.stringify(agent, null, 2) }
15150
+ ]
15151
+ };
15152
+ } catch (e) {
15153
+ return {
15154
+ content: [{ type: "text", text: formatError(e) }],
15155
+ isError: true
15156
+ };
14056
15157
  }
14057
- const lines = txns.map(formatTransaction);
14058
- return { content: [{ type: "text", text: lines.join(`
14059
- `) }] };
14060
- } catch (e) {
14061
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14062
- }
14063
- });
14064
- server.tool("doctor", "Run wallet diagnostics", {}, async () => {
14065
- try {
14066
- const result = runDoctor();
14067
- const lines = result.checks.map(formatDoctorCheck);
14068
- lines.push("");
14069
- lines.push(result.healthy ? "All checks passed." : "Some checks failed.");
14070
- return { content: [{ type: "text", text: lines.join(`
14071
- `) }] };
14072
- } catch (e) {
14073
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14074
- }
14075
- });
14076
- server.tool("describe_tools", "Get full schema for wallet tools", {
14077
- name: exports_external.string().optional().describe("Tool name to describe (omit for all)")
14078
- }, async (params) => {
14079
- const tools = {
14080
- create_card: "Create a funded virtual card. Params: amount (required, number), name (string), provider (string), currency (string, default USD), agent_id (string).",
14081
- list_cards: "List cards. Params: status (active|frozen|closed|pending), provider (string), agent_id (string).",
14082
- get_card_details: "Get PAN, CVV, expiry. Params: card_id (required, string).",
14083
- get_balance: "Check balance. Params: card_id (required, string).",
14084
- close_card: "Close permanently. Params: card_id (required, string).",
14085
- list_providers: "List all providers. No params.",
14086
- register_provider: "Register provider. Params: type (required), name, jwt, api_key, base_url, set_default.",
14087
- list_transactions: "List transactions. Params: card_id, limit.",
14088
- doctor: "Run diagnostics. No params.",
14089
- register_agent: "Register agent session (idempotent). Params: name, description?",
14090
- list_agents: "List all registered agents.",
14091
- heartbeat: "Update last_seen_at to signal agent is active. Params: agent_id",
14092
- set_focus: "Set active project context. Params: agent_id, project_id?"
14093
- };
14094
- if (params.name) {
14095
- const desc = tools[params.name];
14096
- if (!desc)
14097
- return { content: [{ type: "text", text: `Unknown tool: ${params.name}` }] };
14098
- return { content: [{ type: "text", text: `${params.name}: ${desc}` }] };
14099
- }
14100
- const lines = Object.entries(tools).map(([k, v]) => `${k}: ${v}`);
14101
- return { content: [{ type: "text", text: lines.join(`
14102
- `) }] };
14103
- });
14104
- server.resource("wallets://cards", "wallets://cards", async () => {
14105
- const cards = listCards();
14106
- return { contents: [{ uri: "wallets://cards", text: JSON.stringify(cards, null, 2), mimeType: "application/json" }] };
14107
- });
14108
- server.resource("wallets://providers", "wallets://providers", async () => {
14109
- const providers2 = listProviders();
14110
- return { contents: [{ uri: "wallets://providers", text: JSON.stringify(providers2, null, 2), mimeType: "application/json" }] };
14111
- });
14112
- server.resource("wallets://agents", "wallets://agents", async () => {
14113
- const agents = listAgents();
14114
- return { contents: [{ uri: "wallets://agents", text: JSON.stringify(agents, null, 2), mimeType: "application/json" }] };
14115
- });
14116
- server.tool("register_agent", "Register an agent session (idempotent). Auto-updates last_seen_at on re-register.", {
14117
- name: exports_external.string().describe("Agent name"),
14118
- description: exports_external.string().optional().describe("Agent description")
14119
- }, async (params) => {
14120
- try {
14121
- const agent = registerAgent({ name: params.name, description: params.description });
14122
- return { content: [{ type: "text", text: JSON.stringify(agent, null, 2) }] };
14123
- } catch (e) {
14124
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14125
- }
14126
- });
14127
- server.tool("list_agents", "List all registered agents.", {}, async () => {
14128
- try {
14129
- const agents = listAgents();
14130
- return { content: [{ type: "text", text: JSON.stringify(agents, null, 2) }] };
14131
- } catch (e) {
14132
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14133
- }
14134
- });
14135
- server.tool("heartbeat", "Update last_seen_at to signal agent is active. Call periodically during long tasks.", { agent_id: exports_external.string().describe("Agent ID or name") }, async (params) => {
14136
- try {
14137
- const agent = heartbeatAgent(params.agent_id);
14138
- if (!agent)
14139
- return { content: [{ type: "text", text: `Agent not found: ${params.agent_id}` }], isError: true };
14140
- return { content: [{ type: "text", text: JSON.stringify(agent, null, 2) }] };
14141
- } catch (e) {
14142
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14143
- }
14144
- });
14145
- server.tool("set_focus", "Set active project context for this agent session.", {
14146
- agent_id: exports_external.string().describe("Agent ID or name"),
14147
- project_id: exports_external.string().nullable().optional().describe("Project ID to focus on, or null to clear")
14148
- }, async (params) => {
14149
- try {
14150
- const agent = setAgentFocus(params.agent_id, params.project_id ?? null);
14151
- if (!agent)
14152
- return { content: [{ type: "text", text: `Agent not found: ${params.agent_id}` }], isError: true };
14153
- return { content: [{ type: "text", text: params.project_id ? `Focus: ${params.project_id}` : "Focus cleared" }] };
14154
- } catch (e) {
14155
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14156
- }
14157
- });
14158
- server.tool("send_feedback", "Send feedback about this service", {
14159
- message: exports_external.string().describe("Feedback message"),
14160
- email: exports_external.string().optional().describe("Contact email (optional)"),
14161
- category: exports_external.enum(["bug", "feature", "general"]).optional().describe("Feedback category")
14162
- }, async (params) => {
14163
- try {
14164
- const db = getDatabase();
14165
- const pkg = require_package();
14166
- db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [params.message, params.email || null, params.category || "general", pkg.version]);
14167
- return { content: [{ type: "text", text: "Feedback saved. Thank you!" }] };
14168
- } catch (e) {
14169
- return { content: [{ type: "text", text: formatError(e) }], isError: true };
14170
- }
14171
- });
15158
+ });
15159
+ server.tool("set_focus", "Set active project context for this agent session.", {
15160
+ agent_id: exports_external.string().describe("Agent ID or name"),
15161
+ project_id: exports_external.string().nullable().optional().describe("Project ID to focus on, or null to clear")
15162
+ }, async (params) => {
15163
+ try {
15164
+ const agent = setAgentFocus(params.agent_id, params.project_id ?? null);
15165
+ if (!agent)
15166
+ return {
15167
+ content: [
15168
+ {
15169
+ type: "text",
15170
+ text: `Agent not found: ${params.agent_id}`
15171
+ }
15172
+ ],
15173
+ isError: true
15174
+ };
15175
+ return {
15176
+ content: [
15177
+ {
15178
+ type: "text",
15179
+ text: params.project_id ? `Focus: ${params.project_id}` : "Focus cleared"
15180
+ }
15181
+ ]
15182
+ };
15183
+ } catch (e) {
15184
+ return {
15185
+ content: [{ type: "text", text: formatError(e) }],
15186
+ isError: true
15187
+ };
15188
+ }
15189
+ });
15190
+ server.tool("send_feedback", "Send feedback about this service", {
15191
+ message: exports_external.string().describe("Feedback message"),
15192
+ email: exports_external.string().optional().describe("Contact email (optional)"),
15193
+ category: exports_external.enum(["bug", "feature", "general"]).optional().describe("Feedback category")
15194
+ }, async (params) => {
15195
+ try {
15196
+ const db = getDatabase();
15197
+ const pkg = require_package();
15198
+ db.run("INSERT INTO feedback (message, email, category, version) VALUES (?, ?, ?, ?)", [
15199
+ params.message,
15200
+ params.email || null,
15201
+ params.category || "general",
15202
+ pkg.version
15203
+ ]);
15204
+ return {
15205
+ content: [
15206
+ { type: "text", text: "Feedback saved. Thank you!" }
15207
+ ]
15208
+ };
15209
+ } catch (e) {
15210
+ return {
15211
+ content: [{ type: "text", text: formatError(e) }],
15212
+ isError: true
15213
+ };
15214
+ }
15215
+ });
15216
+ return server;
15217
+ }
14172
15218
  async function main() {
14173
- const transport = new StdioServerTransport;
14174
- await server.connect(transport);
15219
+ const args = process.argv.slice(2);
15220
+ if (isStdioMode(args)) {
15221
+ const transport = new StdioServerTransport;
15222
+ await buildServer().connect(transport);
15223
+ return;
15224
+ }
15225
+ startMcpHttpServer({
15226
+ name: "wallets",
15227
+ port: resolveMcpHttpPort(args),
15228
+ buildServer
15229
+ });
14175
15230
  }
14176
15231
  main().catch((e) => {
14177
15232
  console.error("Fatal:", e);
14178
15233
  process.exit(1);
14179
15234
  });
15235
+ export {
15236
+ buildServer
15237
+ };