vibecash 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +642 -0
  2. package/dist/index.js +1012 -0
  3. package/package.json +45 -0
package/dist/index.js ADDED
@@ -0,0 +1,1012 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command as Command9 } from "commander";
5
+
6
+ // src/commands/wallet.ts
7
+ import { Command } from "commander";
8
+
9
+ // src/config.ts
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
11
+ import { homedir } from "os";
12
+ import { join } from "path";
13
+
14
+ // ../shared/src/constants.ts
15
+ var PRODUCT_NAME = "vibecash";
16
+ var DOMAINS = {
17
+ api: `api.${PRODUCT_NAME}.dev`,
18
+ app: `${PRODUCT_NAME}.dev`,
19
+ pay: `pay.${PRODUCT_NAME}.dev`,
20
+ js: `js.${PRODUCT_NAME}.dev`
21
+ };
22
+ var API_BASE_URL = `https://${DOMAINS.api}/v1`;
23
+ var EXPIRATION = {
24
+ checkoutSession: 24 * 60 * 60 * 1e3,
25
+ // 24 hours
26
+ claimToken: 7 * 24 * 60 * 60 * 1e3,
27
+ // 7 days
28
+ portalSession: 60 * 60 * 1e3
29
+ // 1 hour
30
+ };
31
+
32
+ // ../shared/src/utils-browser.ts
33
+ function formatAmount(amountCents, currency) {
34
+ const amount = amountCents / 100;
35
+ return new Intl.NumberFormat("en-US", {
36
+ style: "currency",
37
+ currency
38
+ }).format(amount);
39
+ }
40
+ function parseAmountToCents(amount) {
41
+ const parsed = typeof amount === "string" ? parseFloat(amount) : amount;
42
+ return Math.round(parsed * 100);
43
+ }
44
+
45
+ // ../shared/src/utils-node.ts
46
+ import { createHash, randomBytes, timingSafeEqual } from "crypto";
47
+
48
+ // src/config.ts
49
+ var CONFIG_DIR = join(homedir(), `.${PRODUCT_NAME}`);
50
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
51
+ function getConfig() {
52
+ if (!existsSync(CONFIG_FILE)) {
53
+ return {};
54
+ }
55
+ try {
56
+ const content = readFileSync(CONFIG_FILE, "utf-8");
57
+ return JSON.parse(content);
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+ function saveConfig(config) {
63
+ if (!existsSync(CONFIG_DIR)) {
64
+ mkdirSync(CONFIG_DIR, { recursive: true });
65
+ }
66
+ const existing = getConfig();
67
+ const merged = { ...existing, ...config };
68
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
69
+ }
70
+ function getSecret() {
71
+ const envSecret = process.env.AIPAY_SECRET;
72
+ if (envSecret) {
73
+ return envSecret;
74
+ }
75
+ const config = getConfig();
76
+ return config.secret;
77
+ }
78
+ function getApiUrl() {
79
+ const envUrl = process.env.AIPAY_API_URL;
80
+ if (envUrl) {
81
+ return envUrl;
82
+ }
83
+ const config = getConfig();
84
+ return config.apiUrl || "https://api.vibecash.dev";
85
+ }
86
+ function getOutputFormat() {
87
+ const envFormat = process.env.AIPAY_OUTPUT;
88
+ if (envFormat === "human" || envFormat === "json") {
89
+ return envFormat;
90
+ }
91
+ const config = getConfig();
92
+ return config.outputFormat || "json";
93
+ }
94
+
95
+ // src/api-client.ts
96
+ var ApiClient = class {
97
+ baseUrl;
98
+ secret;
99
+ constructor(options) {
100
+ this.baseUrl = options?.baseUrl || getApiUrl();
101
+ this.secret = options?.secret || getSecret();
102
+ }
103
+ async request(method, path, body, requiresAuth = true) {
104
+ const url = `${this.baseUrl}${path}`;
105
+ const headers = {
106
+ "Content-Type": "application/json"
107
+ };
108
+ if (requiresAuth) {
109
+ if (!this.secret) {
110
+ throw new Error(
111
+ "No API secret found. Set AIPAY_SECRET environment variable or run `aipay wallet create`"
112
+ );
113
+ }
114
+ headers["Authorization"] = `Bearer ${this.secret}`;
115
+ }
116
+ const response = await fetch(url, {
117
+ method,
118
+ headers,
119
+ body: body ? JSON.stringify(body) : void 0
120
+ });
121
+ const json = await response.json();
122
+ if (!response.ok) {
123
+ const errorMessage = json.error?.message || `API error: ${response.status}`;
124
+ throw new Error(errorMessage);
125
+ }
126
+ return json;
127
+ }
128
+ // Wallets
129
+ async createWallet() {
130
+ return this.request("POST", "/v1/wallets", void 0, false);
131
+ }
132
+ async getWallet() {
133
+ return this.request("GET", "/v1/wallets/current");
134
+ }
135
+ async createClaimLink() {
136
+ return this.request("POST", "/v1/wallets/current/claim_link");
137
+ }
138
+ // Products
139
+ async createProduct(data) {
140
+ return this.request("POST", "/v1/products", data);
141
+ }
142
+ async listProducts() {
143
+ return this.request("GET", "/v1/products");
144
+ }
145
+ async getProduct(id) {
146
+ return this.request("GET", `/v1/products/${id}`);
147
+ }
148
+ // Prices
149
+ async createPrice(data) {
150
+ return this.request("POST", "/v1/prices", {
151
+ productId: data.productId,
152
+ amount: data.amount,
153
+ currency: data.currency,
154
+ type: data.type,
155
+ interval: data.interval,
156
+ intervalCount: data.intervalCount,
157
+ trialPeriodDays: data.trialPeriodDays
158
+ });
159
+ }
160
+ async listPrices(productId) {
161
+ const query = productId ? `?product_id=${productId}` : "";
162
+ return this.request("GET", `/v1/prices${query}`);
163
+ }
164
+ // Checkout
165
+ async createCheckoutSession(data) {
166
+ return this.request("POST", "/v1/checkout/sessions", data);
167
+ }
168
+ async getCheckoutSession(id) {
169
+ return this.request("GET", `/v1/checkout/sessions/${id}`, void 0, false);
170
+ }
171
+ // Subscriptions
172
+ async listSubscriptions(params) {
173
+ const query = new URLSearchParams();
174
+ if (params?.customerId) query.set("customer_id", params.customerId);
175
+ if (params?.status) query.set("status", params.status);
176
+ const queryStr = query.toString() ? `?${query.toString()}` : "";
177
+ return this.request("GET", `/v1/subscriptions${queryStr}`);
178
+ }
179
+ async getSubscription(id) {
180
+ return this.request("GET", `/v1/subscriptions/${id}`);
181
+ }
182
+ async cancelSubscription(id, immediately = false) {
183
+ return this.request("POST", `/v1/subscriptions/${id}/cancel`, {
184
+ cancel_at_period_end: !immediately
185
+ });
186
+ }
187
+ async resumeSubscription(id) {
188
+ return this.request("POST", `/v1/subscriptions/${id}/resume`);
189
+ }
190
+ // Customers
191
+ async createCustomer(data) {
192
+ return this.request("POST", "/v1/customers", data);
193
+ }
194
+ async listCustomers() {
195
+ return this.request("GET", "/v1/customers");
196
+ }
197
+ async getCustomer(id) {
198
+ return this.request("GET", `/v1/customers/${id}`);
199
+ }
200
+ async createPortalSession(customerId, returnUrl) {
201
+ return this.request("POST", `/v1/customers/${customerId}/portal_sessions`, {
202
+ return_url: returnUrl
203
+ });
204
+ }
205
+ // Payment Links
206
+ async createPaymentLink(data) {
207
+ return this.request("POST", "/v1/payment_links", data);
208
+ }
209
+ async listPaymentLinks() {
210
+ return this.request("GET", "/v1/payment_links");
211
+ }
212
+ async getPaymentLink(id) {
213
+ return this.request("GET", `/v1/payment_links/${id}`, void 0, false);
214
+ }
215
+ async updatePaymentLink(id, data) {
216
+ return this.request("PATCH", `/v1/payment_links/${id}`, data);
217
+ }
218
+ };
219
+
220
+ // src/utils/output.ts
221
+ function formatAmount2(amountCents, currency = "USD") {
222
+ return formatAmount(amountCents, currency);
223
+ }
224
+ function output(data, humanFormatter) {
225
+ const format = getOutputFormat();
226
+ if (format === "human" && humanFormatter) {
227
+ console.log(humanFormatter(data));
228
+ } else {
229
+ console.log(JSON.stringify(data, null, 2));
230
+ }
231
+ }
232
+ function formatDate(timestamp) {
233
+ return new Date(timestamp).toLocaleString();
234
+ }
235
+ function formatCurrency(amountCents, currency = "USD") {
236
+ return formatAmount(amountCents, currency);
237
+ }
238
+ function formatStatus(status) {
239
+ const colors = {
240
+ active: "\x1B[32m",
241
+ // green
242
+ trialing: "\x1B[33m",
243
+ // yellow
244
+ past_due: "\x1B[31m",
245
+ // red
246
+ canceled: "\x1B[90m",
247
+ // gray
248
+ succeeded: "\x1B[32m",
249
+ failed: "\x1B[31m",
250
+ pending: "\x1B[33m",
251
+ open: "\x1B[33m",
252
+ complete: "\x1B[32m",
253
+ expired: "\x1B[90m",
254
+ created: "\x1B[36m",
255
+ // cyan
256
+ claimed: "\x1B[32m",
257
+ verified: "\x1B[32m"
258
+ };
259
+ const reset = "\x1B[0m";
260
+ const color = colors[status] || "";
261
+ return `${color}${status}${reset}`;
262
+ }
263
+ function formatTable(rows) {
264
+ if (rows.length === 0) return "";
265
+ const colWidths = [];
266
+ for (const row of rows) {
267
+ row.forEach((cell, i) => {
268
+ const cellLen = stripAnsi(cell).length;
269
+ colWidths[i] = Math.max(colWidths[i] || 0, cellLen);
270
+ });
271
+ }
272
+ return rows.map(
273
+ (row) => row.map((cell, i) => cell.padEnd(colWidths[i] + (cell.length - stripAnsi(cell).length))).join(" ")
274
+ ).join("\n");
275
+ }
276
+ function stripAnsi(str) {
277
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
278
+ }
279
+ function error(message) {
280
+ console.error(`\x1B[31mError:\x1B[0m ${message}`);
281
+ process.exit(1);
282
+ }
283
+ function info(message) {
284
+ console.log(`\x1B[36m\u2139\x1B[0m ${message}`);
285
+ }
286
+
287
+ // src/commands/wallet.ts
288
+ function createWalletCommand() {
289
+ const wallet = new Command("wallet").description("Manage wallets");
290
+ wallet.command("create").description("Create a new wallet").action(async () => {
291
+ try {
292
+ const client = new ApiClient();
293
+ const result = await client.createWallet();
294
+ saveConfig({ secret: result.secret });
295
+ output(result, (data) => {
296
+ const d = data;
297
+ return [
298
+ `Wallet created successfully!`,
299
+ ``,
300
+ ` ID: ${d.id}`,
301
+ ` Secret: ${d.secret}`,
302
+ ` Status: ${formatStatus(d.status)}`,
303
+ ``,
304
+ `Your secret has been saved to ~/.aipay/config.json`,
305
+ `You can also set it as an environment variable:`,
306
+ ` export AIPAY_SECRET=${d.secret}`
307
+ ].join("\n");
308
+ });
309
+ } catch (err) {
310
+ error(err instanceof Error ? err.message : "Failed to create wallet");
311
+ }
312
+ });
313
+ wallet.command("status").description("Get current wallet status").action(async () => {
314
+ try {
315
+ const client = new ApiClient();
316
+ const result = await client.getWallet();
317
+ output(result, (data) => {
318
+ const d = data;
319
+ return [
320
+ `Wallet: ${d.id}`,
321
+ ``,
322
+ ` Status: ${formatStatus(d.status)}`,
323
+ ` Balance: ${formatCurrency(d.balance, d.currency)}`,
324
+ ` Pending Balance: ${formatCurrency(d.pendingBalance, d.currency)}`,
325
+ ` Created: ${formatDate(d.createdAt)}`
326
+ ].join("\n");
327
+ });
328
+ } catch (err) {
329
+ error(err instanceof Error ? err.message : "Failed to get wallet status");
330
+ }
331
+ });
332
+ wallet.command("claim").description("Generate a claim link for this wallet").action(async () => {
333
+ try {
334
+ const client = new ApiClient();
335
+ const wallet2 = await client.getWallet();
336
+ if (wallet2.status === "claimed" || wallet2.status === "verified") {
337
+ error("This wallet has already been claimed and cannot be claimed again.");
338
+ return;
339
+ }
340
+ const result = await client.createClaimLink();
341
+ output(result, (data) => {
342
+ const d = data;
343
+ return [
344
+ `Claim link generated!`,
345
+ ``,
346
+ ` URL: ${d.claimUrl}`,
347
+ ` Expires: ${formatDate(d.expiresAt)}`,
348
+ ``,
349
+ `Share this link to allow someone to claim ownership of this wallet.`
350
+ ].join("\n");
351
+ });
352
+ } catch (err) {
353
+ error(err instanceof Error ? err.message : "Failed to generate claim link");
354
+ }
355
+ });
356
+ return wallet;
357
+ }
358
+
359
+ // src/commands/product.ts
360
+ import { Command as Command2 } from "commander";
361
+ function createProductCommand() {
362
+ const product = new Command2("product").description("Manage products");
363
+ product.command("create").description("Create a new product").argument("<name>", "Product name").option("-d, --description <description>", "Product description").action(async (name, options) => {
364
+ try {
365
+ const client = new ApiClient();
366
+ const result = await client.createProduct({
367
+ name,
368
+ description: options.description
369
+ });
370
+ output(result, (data) => {
371
+ const d = data;
372
+ return [
373
+ `Product created!`,
374
+ ``,
375
+ ` ID: ${d.id}`,
376
+ ` Name: ${d.name}`,
377
+ d.description ? ` Description: ${d.description}` : null,
378
+ ` Active: ${d.active}`,
379
+ ` Created: ${formatDate(d.createdAt)}`
380
+ ].filter(Boolean).join("\n");
381
+ });
382
+ } catch (err) {
383
+ error(err instanceof Error ? err.message : "Failed to create product");
384
+ }
385
+ });
386
+ product.command("list").description("List all products").action(async () => {
387
+ try {
388
+ const client = new ApiClient();
389
+ const result = await client.listProducts();
390
+ output(result, (data) => {
391
+ const d = data;
392
+ if (d.data.length === 0) {
393
+ return "No products found.";
394
+ }
395
+ const rows = [
396
+ ["ID", "NAME", "ACTIVE", "CREATED"],
397
+ ...d.data.map((p) => [p.id, p.name, p.active ? "yes" : "no", formatDate(p.createdAt)])
398
+ ];
399
+ return formatTable(rows);
400
+ });
401
+ } catch (err) {
402
+ error(err instanceof Error ? err.message : "Failed to list products");
403
+ }
404
+ });
405
+ product.command("get").description("Get product details").argument("<id>", "Product ID").action(async (id) => {
406
+ try {
407
+ const client = new ApiClient();
408
+ const result = await client.getProduct(id);
409
+ output(result, (data) => {
410
+ const d = data;
411
+ return [
412
+ `Product: ${d.id}`,
413
+ ``,
414
+ ` Name: ${d.name}`,
415
+ d.description ? ` Description: ${d.description}` : null,
416
+ ` Active: ${d.active}`,
417
+ ` Created: ${formatDate(d.createdAt)}`
418
+ ].filter(Boolean).join("\n");
419
+ });
420
+ } catch (err) {
421
+ error(err instanceof Error ? err.message : "Failed to get product");
422
+ }
423
+ });
424
+ return product;
425
+ }
426
+
427
+ // src/commands/price.ts
428
+ import { Command as Command3 } from "commander";
429
+ function createPriceCommand() {
430
+ const price = new Command3("price").description("Manage prices");
431
+ price.command("create").description("Create a new price").argument("<product_id>", "Product ID").requiredOption("-a, --amount <amount>", "Price amount (e.g., 9.99)").requiredOption("-t, --type <type>", "Price type (one_time or recurring)").option("-c, --currency <currency>", "Currency code", "USD").option("-i, --interval <interval>", "Billing interval (day, week, month, year)").option("--interval-count <count>", "Number of intervals between billings", "1").option("--trial-days <days>", "Trial period in days").action(
432
+ async (productId, options) => {
433
+ try {
434
+ const type = options.type;
435
+ if (!["one_time", "recurring"].includes(type)) {
436
+ error("Type must be one_time or recurring");
437
+ return;
438
+ }
439
+ if (type === "recurring" && !options.interval) {
440
+ error("Interval is required for recurring prices");
441
+ return;
442
+ }
443
+ const client = new ApiClient();
444
+ const result = await client.createPrice({
445
+ productId,
446
+ amount: parseAmountToCents(options.amount),
447
+ currency: options.currency,
448
+ type,
449
+ interval: options.interval,
450
+ intervalCount: parseInt(options.intervalCount, 10),
451
+ trialPeriodDays: options.trialDays ? parseInt(options.trialDays, 10) : void 0
452
+ });
453
+ output(result, (data) => {
454
+ const d = data;
455
+ const interval = d.type === "recurring" && d.interval ? d.intervalCount === 1 ? `/${d.interval}` : `/${d.intervalCount} ${d.interval}s` : "";
456
+ return [
457
+ `Price created!`,
458
+ ``,
459
+ ` ID: ${d.id}`,
460
+ ` Amount: ${formatCurrency(d.amount, d.currency)}${interval}`,
461
+ ` Type: ${d.type}`,
462
+ d.interval ? ` Interval: ${d.interval}` : null,
463
+ d.trialPeriodDays ? ` Trial: ${d.trialPeriodDays} days` : null,
464
+ ` Created: ${formatDate(d.createdAt)}`
465
+ ].filter(Boolean).join("\n");
466
+ });
467
+ } catch (err) {
468
+ error(err instanceof Error ? err.message : "Failed to create price");
469
+ }
470
+ }
471
+ );
472
+ price.command("list").description("List prices").option("-p, --product <id>", "Filter by product ID").action(async (options) => {
473
+ try {
474
+ const client = new ApiClient();
475
+ const result = await client.listPrices(options.product);
476
+ output(result, (data) => {
477
+ const d = data;
478
+ if (d.data.length === 0) {
479
+ return "No prices found.";
480
+ }
481
+ const rows = [
482
+ ["ID", "PRODUCT", "AMOUNT", "TYPE", "INTERVAL"],
483
+ ...d.data.map((p) => [
484
+ p.id,
485
+ p.productId,
486
+ formatCurrency(p.amount, p.currency),
487
+ p.type,
488
+ p.interval || "-"
489
+ ])
490
+ ];
491
+ return formatTable(rows);
492
+ });
493
+ } catch (err) {
494
+ error(err instanceof Error ? err.message : "Failed to list prices");
495
+ }
496
+ });
497
+ return price;
498
+ }
499
+
500
+ // src/commands/checkout.ts
501
+ import { Command as Command4 } from "commander";
502
+ function createCheckoutCommand() {
503
+ const checkout = new Command4("checkout").description("Manage checkout sessions");
504
+ checkout.command("create").description("Create a checkout session").option("-p, --price <id>", "Price ID").option("-a, --amount <amount>", "Amount for inline price (e.g., 9.99)").option("-d, --description <desc>", "Description for inline price").option("-m, --mode <mode>", "Checkout mode (payment, subscription)", "payment").option("--success-url <url>", "Success redirect URL").option("--cancel-url <url>", "Cancel redirect URL").option("--email <email>", "Customer email").option("-c, --currency <currency>", "Currency code (default: USD)", "USD").option("--trial-days <days>", "Trial period days (for subscriptions)").action(
505
+ async (options) => {
506
+ try {
507
+ if (!options.price && !options.amount) {
508
+ error("Either --price or --amount is required");
509
+ return;
510
+ }
511
+ const mode = options.mode;
512
+ if (!["payment", "subscription", "setup"].includes(mode)) {
513
+ error("Mode must be payment, subscription, or setup");
514
+ return;
515
+ }
516
+ const client = new ApiClient();
517
+ const params = {
518
+ mode,
519
+ successUrl: options.successUrl,
520
+ cancelUrl: options.cancelUrl,
521
+ customerEmail: options.email,
522
+ trialPeriodDays: options.trialDays ? parseInt(options.trialDays, 10) : void 0
523
+ };
524
+ if (options.price) {
525
+ params.priceId = options.price;
526
+ } else if (options.amount) {
527
+ params.lineItems = [
528
+ {
529
+ description: options.description,
530
+ amount: parseAmountToCents(options.amount),
531
+ currency: options.currency.toUpperCase(),
532
+ quantity: 1
533
+ }
534
+ ];
535
+ }
536
+ const result = await client.createCheckoutSession(params);
537
+ output(result, (data) => {
538
+ const d = data;
539
+ return [
540
+ `Checkout session created!`,
541
+ ``,
542
+ ` ID: ${d.id}`,
543
+ ` Mode: ${d.mode}`,
544
+ ` Status: ${formatStatus(d.status)}`,
545
+ d.amountTotal ? ` Amount: ${formatCurrency(d.amountTotal, d.currency || "USD")}` : null,
546
+ ` URL: ${d.url}`,
547
+ ` Expires: ${formatDate(d.expiresAt)}`
548
+ ].filter(Boolean).join("\n");
549
+ });
550
+ } catch (err) {
551
+ error(err instanceof Error ? err.message : "Failed to create checkout session");
552
+ }
553
+ }
554
+ );
555
+ checkout.command("status").description("Get checkout session status").argument("<id>", "Checkout session ID").option("-w, --wait", "Wait for session to complete").option("--timeout <ms>", "Timeout in milliseconds when waiting", "300000").action(async (id, options) => {
556
+ try {
557
+ const client = new ApiClient();
558
+ if (options.wait) {
559
+ const timeout = parseInt(options.timeout, 10);
560
+ const startTime = Date.now();
561
+ info("Waiting for checkout to complete...");
562
+ while (Date.now() - startTime < timeout) {
563
+ const result = await client.getCheckoutSession(id);
564
+ if (result.session.status === "complete") {
565
+ output(result, formatCheckoutSession);
566
+ return;
567
+ }
568
+ if (result.session.status === "expired") {
569
+ error("Checkout session expired");
570
+ return;
571
+ }
572
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
573
+ }
574
+ error("Timeout waiting for checkout to complete");
575
+ } else {
576
+ const result = await client.getCheckoutSession(id);
577
+ output(result, formatCheckoutSession);
578
+ }
579
+ } catch (err) {
580
+ error(err instanceof Error ? err.message : "Failed to get checkout session");
581
+ }
582
+ });
583
+ return checkout;
584
+ }
585
+ function formatCheckoutSession(data) {
586
+ const d = data;
587
+ const lines = [
588
+ `Checkout Session: ${d.session.id}`,
589
+ ``,
590
+ ` Status: ${formatStatus(d.session.status)}`,
591
+ ` Mode: ${d.session.mode}`
592
+ ];
593
+ if (d.product) {
594
+ lines.push(` Product: ${d.product.name} (${d.product.id})`);
595
+ }
596
+ if (d.price) {
597
+ lines.push(` Price: ${formatCurrency(d.price.amount, d.price.currency)} (${d.price.id})`);
598
+ } else if (d.session.amountTotal) {
599
+ lines.push(` Amount: ${formatCurrency(d.session.amountTotal, d.session.currency || "USD")}`);
600
+ }
601
+ lines.push(` URL: ${d.session.url}`);
602
+ lines.push(` Created: ${formatDate(d.session.createdAt)}`);
603
+ if (d.session.completedAt) {
604
+ lines.push(` Completed: ${formatDate(d.session.completedAt)}`);
605
+ }
606
+ return lines.join("\n");
607
+ }
608
+
609
+ // src/commands/subscription.ts
610
+ import { Command as Command5 } from "commander";
611
+ function createSubscriptionCommand() {
612
+ const subscription = new Command5("subscription").description("Manage subscriptions");
613
+ subscription.command("list").description("List subscriptions").option("-c, --customer <id>", "Filter by customer ID").option("-s, --status <status>", "Filter by status").action(async (options) => {
614
+ try {
615
+ const client = new ApiClient();
616
+ const result = await client.listSubscriptions({
617
+ customerId: options.customer,
618
+ status: options.status
619
+ });
620
+ output(result, (data) => {
621
+ const d = data;
622
+ if (d.data.length === 0) {
623
+ return "No subscriptions found.";
624
+ }
625
+ const rows = [
626
+ ["ID", "CUSTOMER", "STATUS", "PERIOD END", "CANCEL"],
627
+ ...d.data.map((s) => [
628
+ s.id,
629
+ s.customerId,
630
+ formatStatus(s.status),
631
+ formatDate(s.currentPeriodEnd),
632
+ s.cancelAtPeriodEnd ? "yes" : "no"
633
+ ])
634
+ ];
635
+ return formatTable(rows);
636
+ });
637
+ } catch (err) {
638
+ error(err instanceof Error ? err.message : "Failed to list subscriptions");
639
+ }
640
+ });
641
+ subscription.command("get").description("Get subscription details").argument("<id>", "Subscription ID").action(async (id) => {
642
+ try {
643
+ const client = new ApiClient();
644
+ const result = await client.getSubscription(id);
645
+ output(result, (data) => {
646
+ const d = data;
647
+ return [
648
+ `Subscription: ${d.id}`,
649
+ ``,
650
+ ` Customer: ${d.customerId}`,
651
+ ` Price: ${d.priceId}`,
652
+ ` Status: ${formatStatus(d.status)}`,
653
+ ` Period Start: ${formatDate(d.currentPeriodStart)}`,
654
+ ` Period End: ${formatDate(d.currentPeriodEnd)}`,
655
+ d.trialEnd ? ` Trial End: ${formatDate(d.trialEnd)}` : null,
656
+ ` Cancel: ${d.cancelAtPeriodEnd ? "at period end" : "no"}`,
657
+ ` Created: ${formatDate(d.createdAt)}`
658
+ ].filter(Boolean).join("\n");
659
+ });
660
+ } catch (err) {
661
+ error(err instanceof Error ? err.message : "Failed to get subscription");
662
+ }
663
+ });
664
+ subscription.command("cancel").description("Cancel a subscription").argument("<id>", "Subscription ID").option("--immediately", "Cancel immediately instead of at period end").action(async (id, options) => {
665
+ try {
666
+ const client = new ApiClient();
667
+ const result = await client.cancelSubscription(id, options.immediately);
668
+ output(result, (data) => {
669
+ const d = data;
670
+ const when = options.immediately ? "immediately" : "at period end";
671
+ return [
672
+ `Subscription ${d.id} will be canceled ${when}.`,
673
+ ``,
674
+ ` Status: ${formatStatus(d.status)}`,
675
+ ` Cancel: ${d.cancelAtPeriodEnd ? "at period end" : "immediately"}`,
676
+ d.canceledAt ? ` Canceled At: ${formatDate(d.canceledAt)}` : null
677
+ ].filter(Boolean).join("\n");
678
+ });
679
+ } catch (err) {
680
+ error(err instanceof Error ? err.message : "Failed to cancel subscription");
681
+ }
682
+ });
683
+ subscription.command("resume").description("Resume a canceled subscription").argument("<id>", "Subscription ID").action(async (id) => {
684
+ try {
685
+ const client = new ApiClient();
686
+ const result = await client.resumeSubscription(id);
687
+ output(result, (data) => {
688
+ const d = data;
689
+ return [
690
+ `Subscription ${d.id} has been resumed.`,
691
+ ``,
692
+ ` Status: ${formatStatus(d.status)}`,
693
+ ` Cancel: ${d.cancelAtPeriodEnd ? "at period end" : "no"}`
694
+ ].join("\n");
695
+ });
696
+ } catch (err) {
697
+ error(err instanceof Error ? err.message : "Failed to resume subscription");
698
+ }
699
+ });
700
+ return subscription;
701
+ }
702
+
703
+ // src/commands/customer.ts
704
+ import { Command as Command6 } from "commander";
705
+ function createCustomerCommand() {
706
+ const customer = new Command6("customer").description("Manage customers");
707
+ customer.command("create").description("Create a new customer").requiredOption("-e, --email <email>", "Customer email").option("-n, --name <name>", "Customer name").action(async (options) => {
708
+ try {
709
+ const client = new ApiClient();
710
+ const result = await client.createCustomer({
711
+ email: options.email,
712
+ name: options.name
713
+ });
714
+ output(result, (data) => {
715
+ const d = data;
716
+ return [
717
+ `Customer created!`,
718
+ ``,
719
+ ` ID: ${d.id}`,
720
+ ` Email: ${d.email}`,
721
+ d.name ? ` Name: ${d.name}` : null,
722
+ ` Created: ${formatDate(d.createdAt)}`
723
+ ].filter(Boolean).join("\n");
724
+ });
725
+ } catch (err) {
726
+ error(err instanceof Error ? err.message : "Failed to create customer");
727
+ }
728
+ });
729
+ customer.command("list").description("List all customers").action(async () => {
730
+ try {
731
+ const client = new ApiClient();
732
+ const result = await client.listCustomers();
733
+ output(result, (data) => {
734
+ const d = data;
735
+ if (d.data.length === 0) {
736
+ return "No customers found.";
737
+ }
738
+ const rows = [
739
+ ["ID", "EMAIL", "NAME", "CREATED"],
740
+ ...d.data.map((c) => [c.id, c.email, c.name || "-", formatDate(c.createdAt)])
741
+ ];
742
+ return formatTable(rows);
743
+ });
744
+ } catch (err) {
745
+ error(err instanceof Error ? err.message : "Failed to list customers");
746
+ }
747
+ });
748
+ customer.command("get").description("Get customer details").argument("<id>", "Customer ID").action(async (id) => {
749
+ try {
750
+ const client = new ApiClient();
751
+ const result = await client.getCustomer(id);
752
+ output(result, (data) => {
753
+ const d = data;
754
+ return [
755
+ `Customer: ${d.id}`,
756
+ ``,
757
+ ` Email: ${d.email}`,
758
+ d.name ? ` Name: ${d.name}` : null,
759
+ ` Created: ${formatDate(d.createdAt)}`
760
+ ].filter(Boolean).join("\n");
761
+ });
762
+ } catch (err) {
763
+ error(err instanceof Error ? err.message : "Failed to get customer");
764
+ }
765
+ });
766
+ customer.command("portal").description("Create a customer portal session").argument("<id>", "Customer ID").option("-r, --return-url <url>", "Return URL after portal session").action(async (id, options) => {
767
+ try {
768
+ const client = new ApiClient();
769
+ const result = await client.createPortalSession(id, options.returnUrl);
770
+ output(result, (data) => {
771
+ const d = data;
772
+ return [
773
+ `Portal session created!`,
774
+ ``,
775
+ ` ID: ${d.id}`,
776
+ ` URL: ${d.url}`,
777
+ ` Expires: ${formatDate(d.expiresAt)}`
778
+ ].join("\n");
779
+ });
780
+ } catch (err) {
781
+ error(err instanceof Error ? err.message : "Failed to create portal session");
782
+ }
783
+ });
784
+ return customer;
785
+ }
786
+
787
+ // src/commands/create.ts
788
+ import { Command as Command7 } from "commander";
789
+ function createQuickCreateCommand() {
790
+ const create = new Command7("create").description("Quick command to create a checkout session").argument("<amount>", "Amount to charge (e.g., 9.99)").option("-d, --description <desc>", "Product/payment description").option("--monthly", "Create a monthly subscription").option("--yearly", "Create a yearly subscription").option("--weekly", "Create a weekly subscription").option("--trial-days <days>", "Trial period in days (for subscriptions)").option("--success-url <url>", "Success redirect URL").option("--cancel-url <url>", "Cancel redirect URL").option("--email <email>", "Customer email").action(
791
+ async (amountStr, options) => {
792
+ try {
793
+ const client = new ApiClient();
794
+ const amount = parseAmountToCents(amountStr);
795
+ let mode = "payment";
796
+ let interval;
797
+ if (options.monthly || options.yearly || options.weekly) {
798
+ mode = "subscription";
799
+ if (options.monthly) interval = "month";
800
+ else if (options.yearly) interval = "year";
801
+ else if (options.weekly) interval = "week";
802
+ }
803
+ if (mode === "subscription") {
804
+ const product = await client.createProduct({
805
+ name: options.description || "Subscription"
806
+ });
807
+ const price = await client.createPrice({
808
+ productId: product.id,
809
+ amount,
810
+ currency: "USD",
811
+ type: "recurring",
812
+ interval,
813
+ trialPeriodDays: options.trialDays ? parseInt(options.trialDays, 10) : void 0
814
+ });
815
+ const session = await client.createCheckoutSession({
816
+ mode: "subscription",
817
+ priceId: price.id,
818
+ successUrl: options.successUrl,
819
+ cancelUrl: options.cancelUrl,
820
+ customerEmail: options.email
821
+ });
822
+ output(
823
+ {
824
+ product,
825
+ price,
826
+ session
827
+ },
828
+ (data) => {
829
+ const d = data;
830
+ return [
831
+ `Subscription checkout created!`,
832
+ ``,
833
+ ` Product: ${d.product.name} (${d.product.id})`,
834
+ ` Price: ${formatCurrency(d.price.amount, d.price.currency)}/${d.price.interval} (${d.price.id})`,
835
+ ``,
836
+ ` Session: ${d.session.id}`,
837
+ ` Status: ${formatStatus(d.session.status)}`,
838
+ ` URL: ${d.session.url}`,
839
+ ` Expires: ${formatDate(d.session.expiresAt)}`
840
+ ].join("\n");
841
+ }
842
+ );
843
+ } else {
844
+ const session = await client.createCheckoutSession({
845
+ mode: "payment",
846
+ lineItems: [
847
+ {
848
+ description: options.description,
849
+ amount,
850
+ currency: "USD",
851
+ quantity: 1
852
+ }
853
+ ],
854
+ successUrl: options.successUrl,
855
+ cancelUrl: options.cancelUrl,
856
+ customerEmail: options.email
857
+ });
858
+ output(session, (data) => {
859
+ const d = data;
860
+ return [
861
+ `Payment checkout created!`,
862
+ ``,
863
+ ` Session: ${d.id}`,
864
+ ` Amount: ${formatCurrency(d.amountTotal || 0, d.currency || "USD")}`,
865
+ ` Status: ${formatStatus(d.status)}`,
866
+ ` URL: ${d.url}`,
867
+ ` Expires: ${formatDate(d.expiresAt)}`
868
+ ].join("\n");
869
+ });
870
+ }
871
+ } catch (err) {
872
+ error(err instanceof Error ? err.message : "Failed to create checkout");
873
+ }
874
+ }
875
+ );
876
+ return create;
877
+ }
878
+
879
+ // src/commands/link.ts
880
+ import { Command as Command8 } from "commander";
881
+ function createLinkCommand() {
882
+ const link = new Command8("link").description("Manage payment links (reusable payment URLs)");
883
+ link.command("create").description("Create a new payment link").argument("<amount>", "Amount in dollars (e.g., 9.99)").option("-c, --currency <currency>", "Currency code (default: USD)", "USD").option("-n, --name <name>", "Product/payment name").option("-d, --description <description>", "Description").option("--success-url <url>", "Redirect URL after successful payment").option("--cancel-url <url>", "Redirect URL if customer cancels").action(
884
+ async (amountStr, options) => {
885
+ try {
886
+ const amount = parseAmountToCents(amountStr);
887
+ const client = new ApiClient();
888
+ const result = await client.createPaymentLink({
889
+ amount,
890
+ currency: options.currency,
891
+ name: options.name,
892
+ description: options.description,
893
+ successUrl: options.successUrl,
894
+ cancelUrl: options.cancelUrl
895
+ });
896
+ output(result, (data) => {
897
+ const d = data;
898
+ return [
899
+ `Payment Link created!`,
900
+ ``,
901
+ ` ID: ${d.id}`,
902
+ d.name ? ` Name: ${d.name}` : null,
903
+ d.description ? ` Description: ${d.description}` : null,
904
+ ` Amount: ${formatAmount2(d.amount, d.currency)}`,
905
+ ` URL: ${d.url}`,
906
+ ` Active: ${d.active}`,
907
+ ` Created: ${formatDate(d.createdAt)}`,
908
+ ``,
909
+ `Share this URL with customers - each visitor gets their own checkout session.`
910
+ ].filter(Boolean).join("\n");
911
+ });
912
+ } catch (err) {
913
+ error(err instanceof Error ? err.message : "Failed to create payment link");
914
+ }
915
+ }
916
+ );
917
+ link.command("list").description("List all payment links").action(async () => {
918
+ try {
919
+ const client = new ApiClient();
920
+ const result = await client.listPaymentLinks();
921
+ output(result, (data) => {
922
+ const d = data;
923
+ if (d.data.length === 0) {
924
+ return "No payment links found.";
925
+ }
926
+ const rows = [
927
+ ["ID", "NAME", "AMOUNT", "ACTIVE", "URL"],
928
+ ...d.data.map((l) => [
929
+ l.id,
930
+ l.name || "-",
931
+ formatAmount2(l.amount, l.currency),
932
+ l.active ? "yes" : "no",
933
+ l.url
934
+ ])
935
+ ];
936
+ return formatTable(rows);
937
+ });
938
+ } catch (err) {
939
+ error(err instanceof Error ? err.message : "Failed to list payment links");
940
+ }
941
+ });
942
+ link.command("get").description("Get payment link details").argument("<id>", "Payment Link ID").action(async (id) => {
943
+ try {
944
+ const client = new ApiClient();
945
+ const result = await client.getPaymentLink(id);
946
+ output(result, (data) => {
947
+ const d = data;
948
+ return [
949
+ `Payment Link: ${d.id}`,
950
+ ``,
951
+ d.name ? ` Name: ${d.name}` : null,
952
+ d.description ? ` Description: ${d.description}` : null,
953
+ ` Amount: ${formatAmount2(d.amount, d.currency)}`,
954
+ ` URL: ${d.url}`,
955
+ ` Active: ${d.active}`,
956
+ d.successUrl ? ` Success URL: ${d.successUrl}` : null,
957
+ ` Created: ${formatDate(d.createdAt)}`
958
+ ].filter(Boolean).join("\n");
959
+ });
960
+ } catch (err) {
961
+ error(err instanceof Error ? err.message : "Failed to get payment link");
962
+ }
963
+ });
964
+ link.command("deactivate").description("Deactivate a payment link").argument("<id>", "Payment Link ID").action(async (id) => {
965
+ try {
966
+ const client = new ApiClient();
967
+ const result = await client.updatePaymentLink(id, { active: false });
968
+ output(result, (data) => {
969
+ const d = data;
970
+ return `Payment link ${d.id} has been deactivated.`;
971
+ });
972
+ } catch (err) {
973
+ error(err instanceof Error ? err.message : "Failed to deactivate payment link");
974
+ }
975
+ });
976
+ link.command("activate").description("Activate a payment link").argument("<id>", "Payment Link ID").action(async (id) => {
977
+ try {
978
+ const client = new ApiClient();
979
+ const result = await client.updatePaymentLink(id, { active: true });
980
+ output(result, (data) => {
981
+ const d = data;
982
+ return `Payment link ${d.id} has been activated.`;
983
+ });
984
+ } catch (err) {
985
+ error(err instanceof Error ? err.message : "Failed to activate payment link");
986
+ }
987
+ });
988
+ return link;
989
+ }
990
+
991
+ // src/index.ts
992
+ var program = new Command9();
993
+ program.name("vibecash").description("AI Guardian CLI - Payment infrastructure for AI agents").version("0.1.0");
994
+ program.addCommand(createWalletCommand());
995
+ program.addCommand(createProductCommand());
996
+ program.addCommand(createPriceCommand());
997
+ program.addCommand(createCheckoutCommand());
998
+ program.addCommand(createSubscriptionCommand());
999
+ program.addCommand(createCustomerCommand());
1000
+ program.addCommand(createQuickCreateCommand());
1001
+ program.addCommand(createLinkCommand());
1002
+ program.option("--human", "Output in human-readable format");
1003
+ program.option("--json", "Output in JSON format (default)");
1004
+ program.hook("preAction", (thisCommand) => {
1005
+ const opts = thisCommand.opts();
1006
+ if (opts.human) {
1007
+ process.env.AIPAY_OUTPUT = "human";
1008
+ } else if (opts.json) {
1009
+ process.env.AIPAY_OUTPUT = "json";
1010
+ }
1011
+ });
1012
+ program.parse();