globodai-mcp-payment-manager 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.env.example +23 -0
  2. package/.github/workflows/ci.yml +26 -0
  3. package/.github/workflows/release.yml +82 -0
  4. package/LICENSE +21 -0
  5. package/README.md +362 -0
  6. package/dist/index.d.ts +31 -0
  7. package/dist/index.js +122 -0
  8. package/dist/lib/blockchain.d.ts +50 -0
  9. package/dist/lib/blockchain.js +287 -0
  10. package/dist/lib/cards.d.ts +83 -0
  11. package/dist/lib/cards.js +276 -0
  12. package/dist/lib/cli-runner.d.ts +31 -0
  13. package/dist/lib/cli-runner.js +77 -0
  14. package/dist/lib/crypto.d.ts +39 -0
  15. package/dist/lib/crypto.js +228 -0
  16. package/dist/lib/cvv-crypto.d.ts +23 -0
  17. package/dist/lib/cvv-crypto.js +67 -0
  18. package/dist/lib/mcp-core.d.ts +46 -0
  19. package/dist/lib/mcp-core.js +86 -0
  20. package/dist/lib/pin-manager.d.ts +69 -0
  21. package/dist/lib/pin-manager.js +199 -0
  22. package/dist/lib/wallets.d.ts +91 -0
  23. package/dist/lib/wallets.js +227 -0
  24. package/dist/tools/add-card.d.ts +65 -0
  25. package/dist/tools/add-card.js +97 -0
  26. package/dist/tools/add-wallet.d.ts +65 -0
  27. package/dist/tools/add-wallet.js +104 -0
  28. package/dist/tools/card-status.d.ts +20 -0
  29. package/dist/tools/card-status.js +26 -0
  30. package/dist/tools/confirm-payment.d.ts +44 -0
  31. package/dist/tools/confirm-payment.js +88 -0
  32. package/dist/tools/get-total-balance.d.ts +41 -0
  33. package/dist/tools/get-total-balance.js +98 -0
  34. package/dist/tools/get-transactions.d.ts +39 -0
  35. package/dist/tools/get-transactions.js +40 -0
  36. package/dist/tools/get-wallet-balance.d.ts +43 -0
  37. package/dist/tools/get-wallet-balance.js +69 -0
  38. package/dist/tools/list-cards.d.ts +36 -0
  39. package/dist/tools/list-cards.js +39 -0
  40. package/dist/tools/list-wallet-transactions.d.ts +63 -0
  41. package/dist/tools/list-wallet-transactions.js +76 -0
  42. package/dist/tools/list-wallets.d.ts +41 -0
  43. package/dist/tools/list-wallets.js +50 -0
  44. package/dist/tools/lock-cards.d.ts +16 -0
  45. package/dist/tools/lock-cards.js +23 -0
  46. package/dist/tools/prepare-crypto-tx.d.ts +69 -0
  47. package/dist/tools/prepare-crypto-tx.js +93 -0
  48. package/dist/tools/prepare-payment.d.ts +57 -0
  49. package/dist/tools/prepare-payment.js +93 -0
  50. package/dist/tools/remove-card.d.ts +25 -0
  51. package/dist/tools/remove-card.js +39 -0
  52. package/dist/tools/remove-wallet.d.ts +27 -0
  53. package/dist/tools/remove-wallet.js +40 -0
  54. package/dist/tools/setup-pin.d.ts +26 -0
  55. package/dist/tools/setup-pin.js +33 -0
  56. package/dist/tools/sign-crypto-tx.d.ts +42 -0
  57. package/dist/tools/sign-crypto-tx.js +75 -0
  58. package/dist/tools/unlock-cards.d.ts +35 -0
  59. package/dist/tools/unlock-cards.js +41 -0
  60. package/package.json +50 -0
  61. package/src/index.ts +139 -0
  62. package/src/lib/blockchain.ts +375 -0
  63. package/src/lib/cards.ts +372 -0
  64. package/src/lib/cli-runner.ts +113 -0
  65. package/src/lib/crypto.ts +284 -0
  66. package/src/lib/cvv-crypto.ts +81 -0
  67. package/src/lib/mcp-core.ts +127 -0
  68. package/src/lib/pin-manager.ts +252 -0
  69. package/src/lib/wallets.ts +331 -0
  70. package/src/tools/add-card.ts +108 -0
  71. package/src/tools/add-wallet.ts +114 -0
  72. package/src/tools/card-status.ts +32 -0
  73. package/src/tools/confirm-payment.ts +103 -0
  74. package/src/tools/get-total-balance.ts +123 -0
  75. package/src/tools/get-transactions.ts +49 -0
  76. package/src/tools/get-wallet-balance.ts +75 -0
  77. package/src/tools/list-cards.ts +52 -0
  78. package/src/tools/list-wallet-transactions.ts +83 -0
  79. package/src/tools/list-wallets.ts +63 -0
  80. package/src/tools/lock-cards.ts +31 -0
  81. package/src/tools/prepare-crypto-tx.ts +108 -0
  82. package/src/tools/prepare-payment.ts +108 -0
  83. package/src/tools/remove-card.ts +46 -0
  84. package/src/tools/remove-wallet.ts +47 -0
  85. package/src/tools/setup-pin.ts +39 -0
  86. package/src/tools/sign-crypto-tx.ts +90 -0
  87. package/src/tools/unlock-cards.ts +48 -0
  88. package/tsconfig.json +19 -0
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Payment Card Management
3
+ *
4
+ * Secure storage for payment cards with encryption at rest.
5
+ * Uses the same crypto system as mail-manager for consistency.
6
+ *
7
+ * Security model:
8
+ * - Card numbers stored encrypted (only last 4 digits visible in plaintext)
9
+ * - CVV encrypted with PIN-derived key (requires unlock to use)
10
+ * - Expiration dates encrypted
11
+ * - All sensitive operations require explicit confirmation
12
+ */
13
+
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+ import { encrypt, decrypt, isEncrypted } from "./crypto";
18
+ import { encryptCvv, decryptCvv, isCvvEncrypted } from "./cvv-crypto";
19
+ import { isUnlocked } from "./pin-manager";
20
+
21
+ // Fields that must be encrypted with system key
22
+ const SENSITIVE_FIELDS = ["cardNumber", "expirationDate"];
23
+ // CVV is encrypted with PIN-derived key (separate)
24
+
25
+ export type CardType = "visa" | "mastercard" | "amex" | "discover" | "other";
26
+ export type CardUsage = "flight" | "train" | "hotel" | "general" | "all";
27
+
28
+ export interface PaymentCard {
29
+ id: string;
30
+ nickname: string; // e.g., "Visa perso", "Amex pro"
31
+ cardType: CardType;
32
+ lastFourDigits: string; // Always stored in plaintext for identification
33
+ cardNumber: string; // Encrypted - full card number
34
+ expirationDate: string; // Encrypted - MM/YY format
35
+ cvv?: string; // Encrypted with PIN-derived key
36
+ cardholderName: string;
37
+ billingAddress?: {
38
+ street: string;
39
+ city: string;
40
+ postalCode: string;
41
+ country: string;
42
+ };
43
+ // Usage restrictions
44
+ allowedUsage: CardUsage[];
45
+ enabled: boolean;
46
+ // Spending limits (optional)
47
+ limits?: {
48
+ perTransaction?: number;
49
+ daily?: number;
50
+ monthly?: number;
51
+ currency: string;
52
+ };
53
+ // Metadata
54
+ addedAt: string;
55
+ lastUsedAt?: string;
56
+ }
57
+
58
+ // Transaction for audit log
59
+ export interface Transaction {
60
+ id: string;
61
+ cardId: string;
62
+ type: "flight" | "train" | "hotel" | "general";
63
+ amount: number;
64
+ currency: string;
65
+ description: string;
66
+ provider: string;
67
+ status: "pending" | "confirmed" | "completed" | "failed" | "refunded";
68
+ createdAt: string;
69
+ confirmedAt?: string;
70
+ completedAt?: string;
71
+ reference?: string; // Booking reference
72
+ details?: Record<string, unknown>;
73
+ }
74
+
75
+ const CONFIG_DIR = join(homedir(), ".mcp-ecosystem");
76
+ const CARDS_FILE = join(CONFIG_DIR, "payment-cards.json");
77
+ const TRANSACTIONS_FILE = join(CONFIG_DIR, "payment-transactions.json");
78
+
79
+ /**
80
+ * Detect card type from number
81
+ */
82
+ export function detectCardType(cardNumber: string): CardType {
83
+ const cleaned = cardNumber.replace(/\D/g, "");
84
+
85
+ if (/^4/.test(cleaned)) return "visa";
86
+ if (/^5[1-5]/.test(cleaned) || /^2[2-7]/.test(cleaned)) return "mastercard";
87
+ if (/^3[47]/.test(cleaned)) return "amex";
88
+ if (/^6(?:011|5)/.test(cleaned)) return "discover";
89
+
90
+ return "other";
91
+ }
92
+
93
+ /**
94
+ * Validate card number using Luhn algorithm
95
+ */
96
+ export function validateCardNumber(cardNumber: string): boolean {
97
+ const cleaned = cardNumber.replace(/\D/g, "");
98
+
99
+ if (cleaned.length < 13 || cleaned.length > 19) return false;
100
+
101
+ let sum = 0;
102
+ let isEven = false;
103
+
104
+ for (let i = cleaned.length - 1; i >= 0; i--) {
105
+ let digit = parseInt(cleaned[i]!, 10);
106
+
107
+ if (isEven) {
108
+ digit *= 2;
109
+ if (digit > 9) digit -= 9;
110
+ }
111
+
112
+ sum += digit;
113
+ isEven = !isEven;
114
+ }
115
+
116
+ return sum % 10 === 0;
117
+ }
118
+
119
+ /**
120
+ * Validate expiration date
121
+ */
122
+ export function validateExpiration(expiration: string): boolean {
123
+ const match = expiration.match(/^(\d{2})\/(\d{2})$/);
124
+ if (!match) return false;
125
+
126
+ const month = parseInt(match[1]!, 10);
127
+ const year = parseInt(match[2]!, 10) + 2000;
128
+
129
+ if (month < 1 || month > 12) return false;
130
+
131
+ const now = new Date();
132
+ const expDate = new Date(year, month, 0); // Last day of expiration month
133
+
134
+ return expDate > now;
135
+ }
136
+
137
+ function ensureConfigDir(): void {
138
+ if (!existsSync(CONFIG_DIR)) {
139
+ mkdirSync(CONFIG_DIR, { recursive: true });
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Decrypt sensitive fields in a card
145
+ */
146
+ async function decryptCard(card: PaymentCard): Promise<PaymentCard> {
147
+ const decrypted = { ...card };
148
+
149
+ for (const field of SENSITIVE_FIELDS) {
150
+ const value = (decrypted as any)[field];
151
+ if (value && typeof value === "string" && isEncrypted(value)) {
152
+ try {
153
+ (decrypted as any)[field] = await decrypt(value);
154
+ } catch (err) {
155
+ console.error(`[cards] Failed to decrypt ${field} for card ${card.nickname}`);
156
+ }
157
+ }
158
+ }
159
+
160
+ return decrypted;
161
+ }
162
+
163
+ /**
164
+ * Encrypt sensitive fields in a card
165
+ */
166
+ async function encryptCard(card: PaymentCard): Promise<PaymentCard> {
167
+ const encrypted = { ...card };
168
+
169
+ for (const field of SENSITIVE_FIELDS) {
170
+ const value = (encrypted as any)[field];
171
+ if (value && typeof value === "string" && !isEncrypted(value)) {
172
+ (encrypted as any)[field] = await encrypt(value);
173
+ }
174
+ }
175
+
176
+ return encrypted;
177
+ }
178
+
179
+ // ============================================================================
180
+ // Card CRUD
181
+ // ============================================================================
182
+
183
+ export async function getCards(): Promise<PaymentCard[]> {
184
+ ensureConfigDir();
185
+
186
+ if (!existsSync(CARDS_FILE)) {
187
+ return [];
188
+ }
189
+
190
+ try {
191
+ const data = readFileSync(CARDS_FILE, "utf-8");
192
+ const cards: PaymentCard[] = JSON.parse(data);
193
+ return Promise.all(cards.map(decryptCard));
194
+ } catch {
195
+ return [];
196
+ }
197
+ }
198
+
199
+ export async function getCard(id: string): Promise<PaymentCard | null> {
200
+ const cards = await getCards();
201
+ return cards.find((c) => c.id === id) ?? null;
202
+ }
203
+
204
+ /**
205
+ * Get cards without sensitive data (for listing)
206
+ */
207
+ export async function getCardsSafe(): Promise<Omit<PaymentCard, "cardNumber" | "expirationDate" | "cvv">[]> {
208
+ const cards = await getCards();
209
+ return cards.map(({ cardNumber, expirationDate, cvv, ...safe }) => safe);
210
+ }
211
+
212
+ /**
213
+ * Get decrypted CVV for a card (requires PIN unlock)
214
+ */
215
+ export async function getCardCvv(cardId: string): Promise<string | null> {
216
+ if (!isUnlocked()) {
217
+ return null; // Must be unlocked
218
+ }
219
+
220
+ // Read raw card to get encrypted CVV
221
+ if (!existsSync(CARDS_FILE)) {
222
+ return null;
223
+ }
224
+
225
+ try {
226
+ const rawCards: PaymentCard[] = JSON.parse(readFileSync(CARDS_FILE, "utf-8"));
227
+ const card = rawCards.find((c) => c.id === cardId);
228
+
229
+ if (!card?.cvv) {
230
+ return null;
231
+ }
232
+
233
+ return decryptCvv(card.cvv);
234
+ } catch {
235
+ return null;
236
+ }
237
+ }
238
+
239
+ export async function addCard(card: PaymentCard): Promise<void> {
240
+ ensureConfigDir();
241
+
242
+ // Validate card
243
+ if (!validateCardNumber(card.cardNumber)) {
244
+ throw new Error("Invalid card number");
245
+ }
246
+ if (!validateExpiration(card.expirationDate)) {
247
+ throw new Error("Card is expired or invalid expiration date");
248
+ }
249
+
250
+ // Set derived fields
251
+ card.cardType = detectCardType(card.cardNumber);
252
+ card.lastFourDigits = card.cardNumber.replace(/\D/g, "").slice(-4);
253
+ card.addedAt = new Date().toISOString();
254
+
255
+ // Load existing (raw/encrypted)
256
+ let rawCards: PaymentCard[] = [];
257
+ if (existsSync(CARDS_FILE)) {
258
+ try {
259
+ rawCards = JSON.parse(readFileSync(CARDS_FILE, "utf-8"));
260
+ } catch {
261
+ rawCards = [];
262
+ }
263
+ }
264
+
265
+ // Encrypt and save
266
+ const encryptedCard = await encryptCard(card);
267
+
268
+ const existingIndex = rawCards.findIndex((c) => c.id === card.id);
269
+ if (existingIndex >= 0) {
270
+ rawCards[existingIndex] = encryptedCard;
271
+ } else {
272
+ rawCards.push(encryptedCard);
273
+ }
274
+
275
+ writeFileSync(CARDS_FILE, JSON.stringify(rawCards, null, 2), { mode: 0o600 });
276
+ console.error(`[cards] Saved card ${card.nickname} (****${card.lastFourDigits}) - encrypted`);
277
+ }
278
+
279
+ export async function removeCard(id: string): Promise<boolean> {
280
+ if (!existsSync(CARDS_FILE)) {
281
+ return false;
282
+ }
283
+
284
+ let rawCards: PaymentCard[] = [];
285
+ try {
286
+ rawCards = JSON.parse(readFileSync(CARDS_FILE, "utf-8"));
287
+ } catch {
288
+ return false;
289
+ }
290
+
291
+ const filtered = rawCards.filter((c) => c.id !== id);
292
+
293
+ if (filtered.length === rawCards.length) {
294
+ return false;
295
+ }
296
+
297
+ writeFileSync(CARDS_FILE, JSON.stringify(filtered, null, 2), { mode: 0o600 });
298
+ console.error(`[cards] Removed card ${id}`);
299
+ return true;
300
+ }
301
+
302
+ export async function updateCardUsage(id: string): Promise<void> {
303
+ const cards = await getCards();
304
+ const card = cards.find((c) => c.id === id);
305
+
306
+ if (card) {
307
+ card.lastUsedAt = new Date().toISOString();
308
+
309
+ // Re-encrypt and save all
310
+ const encrypted = await Promise.all(cards.map(encryptCard));
311
+ writeFileSync(CARDS_FILE, JSON.stringify(encrypted, null, 2), { mode: 0o600 });
312
+ }
313
+ }
314
+
315
+ // ============================================================================
316
+ // Transaction Log
317
+ // ============================================================================
318
+
319
+ export async function getTransactions(limit = 50): Promise<Transaction[]> {
320
+ ensureConfigDir();
321
+
322
+ if (!existsSync(TRANSACTIONS_FILE)) {
323
+ return [];
324
+ }
325
+
326
+ try {
327
+ const data = readFileSync(TRANSACTIONS_FILE, "utf-8");
328
+ const transactions: Transaction[] = JSON.parse(data);
329
+ return transactions.slice(-limit);
330
+ } catch {
331
+ return [];
332
+ }
333
+ }
334
+
335
+ export async function logTransaction(tx: Transaction): Promise<void> {
336
+ ensureConfigDir();
337
+
338
+ let transactions: Transaction[] = [];
339
+ if (existsSync(TRANSACTIONS_FILE)) {
340
+ try {
341
+ transactions = JSON.parse(readFileSync(TRANSACTIONS_FILE, "utf-8"));
342
+ } catch {
343
+ transactions = [];
344
+ }
345
+ }
346
+
347
+ transactions.push(tx);
348
+
349
+ // Keep last 1000 transactions
350
+ if (transactions.length > 1000) {
351
+ transactions = transactions.slice(-1000);
352
+ }
353
+
354
+ writeFileSync(TRANSACTIONS_FILE, JSON.stringify(transactions, null, 2), { mode: 0o600 });
355
+ }
356
+
357
+ export async function updateTransaction(id: string, updates: Partial<Transaction>): Promise<void> {
358
+ if (!existsSync(TRANSACTIONS_FILE)) return;
359
+
360
+ let transactions: Transaction[] = [];
361
+ try {
362
+ transactions = JSON.parse(readFileSync(TRANSACTIONS_FILE, "utf-8"));
363
+ } catch {
364
+ return;
365
+ }
366
+
367
+ const idx = transactions.findIndex((t) => t.id === id);
368
+ if (idx >= 0) {
369
+ transactions[idx] = { ...transactions[idx]!, ...updates };
370
+ writeFileSync(TRANSACTIONS_FILE, JSON.stringify(transactions, null, 2), { mode: 0o600 });
371
+ }
372
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * CLI Runner - Execute shell commands with secure credential injection
3
+ */
4
+
5
+ import { spawn } from "child_process";
6
+
7
+ export interface ExecResult {
8
+ stdout: string;
9
+ stderr: string;
10
+ exitCode: number;
11
+ success: boolean;
12
+ }
13
+
14
+ export interface ExecOptions {
15
+ cwd?: string;
16
+ env?: Record<string, string>;
17
+ timeout?: number;
18
+ stdin?: string;
19
+ }
20
+
21
+ /**
22
+ * Execute a CLI command and return the result
23
+ */
24
+ export async function execCommand(
25
+ command: string,
26
+ args: string[],
27
+ options: ExecOptions = {}
28
+ ): Promise<ExecResult> {
29
+ return new Promise((resolve) => {
30
+ const env = {
31
+ ...process.env,
32
+ ...options.env,
33
+ };
34
+
35
+ const proc = spawn(command, args, {
36
+ cwd: options.cwd,
37
+ env,
38
+ timeout: options.timeout ?? 60000,
39
+ });
40
+
41
+ let stdout = "";
42
+ let stderr = "";
43
+
44
+ proc.stdout.on("data", (data) => {
45
+ stdout += data.toString();
46
+ });
47
+
48
+ proc.stderr.on("data", (data) => {
49
+ stderr += data.toString();
50
+ });
51
+
52
+ if (options.stdin) {
53
+ proc.stdin.write(options.stdin);
54
+ proc.stdin.end();
55
+ }
56
+
57
+ proc.on("close", (code) => {
58
+ resolve({
59
+ stdout: stdout.trim(),
60
+ stderr: stderr.trim(),
61
+ exitCode: code ?? 0,
62
+ success: code === 0,
63
+ });
64
+ });
65
+
66
+ proc.on("error", (err) => {
67
+ resolve({
68
+ stdout: "",
69
+ stderr: err.message,
70
+ exitCode: 1,
71
+ success: false,
72
+ });
73
+ });
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Execute a git command
79
+ */
80
+ export async function execGit(
81
+ args: string[],
82
+ options: ExecOptions = {}
83
+ ): Promise<ExecResult> {
84
+ return execCommand("git", args, options);
85
+ }
86
+
87
+ /**
88
+ * Execute a heroku command with API key
89
+ */
90
+ export async function execHeroku(
91
+ args: string[],
92
+ apiKey: string,
93
+ options: ExecOptions = {}
94
+ ): Promise<ExecResult> {
95
+ return execCommand("heroku", args, {
96
+ ...options,
97
+ env: {
98
+ ...options.env,
99
+ HEROKU_API_KEY: apiKey,
100
+ },
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Parse JSON output safely
106
+ */
107
+ export function parseJsonOutput<T>(output: string): T | null {
108
+ try {
109
+ return JSON.parse(output);
110
+ } catch {
111
+ return null;
112
+ }
113
+ }