@layr-labs/ecloud-cli 1.0.0-dev.3 → 1.0.0-devep1

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 (50) hide show
  1. package/VERSION +2 -2
  2. package/dist/commands/billing/__tests__/status.test.js +23 -4
  3. package/dist/commands/billing/__tests__/status.test.js.map +1 -1
  4. package/dist/commands/billing/__tests__/top-up.test.js +89 -273
  5. package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
  6. package/dist/commands/billing/status.js +23 -4
  7. package/dist/commands/billing/status.js.map +1 -1
  8. package/dist/commands/billing/top-up.js +79 -151
  9. package/dist/commands/billing/top-up.js.map +1 -1
  10. package/dist/commands/compute/app/deploy.js +1 -1
  11. package/dist/commands/compute/app/deploy.js.map +1 -1
  12. package/dist/commands/compute/app/info.js +1 -1
  13. package/dist/commands/compute/app/info.js.map +1 -1
  14. package/dist/commands/compute/app/list.js +1 -1
  15. package/dist/commands/compute/app/list.js.map +1 -1
  16. package/dist/commands/compute/app/logs.js +1 -1
  17. package/dist/commands/compute/app/logs.js.map +1 -1
  18. package/dist/commands/compute/app/profile/set.js +1 -1
  19. package/dist/commands/compute/app/profile/set.js.map +1 -1
  20. package/dist/commands/compute/app/releases.js +1 -1
  21. package/dist/commands/compute/app/releases.js.map +1 -1
  22. package/dist/commands/compute/app/start.js +1 -1
  23. package/dist/commands/compute/app/start.js.map +1 -1
  24. package/dist/commands/compute/app/stop.js +1 -1
  25. package/dist/commands/compute/app/stop.js.map +1 -1
  26. package/dist/commands/compute/app/terminate.js +1 -1
  27. package/dist/commands/compute/app/terminate.js.map +1 -1
  28. package/dist/commands/compute/app/upgrade.js +1 -1
  29. package/dist/commands/compute/app/upgrade.js.map +1 -1
  30. package/dist/commands/compute/build/info.js +1 -1
  31. package/dist/commands/compute/build/info.js.map +1 -1
  32. package/dist/commands/compute/build/list.js +1 -1
  33. package/dist/commands/compute/build/list.js.map +1 -1
  34. package/dist/commands/compute/build/logs.js +1 -1
  35. package/dist/commands/compute/build/logs.js.map +1 -1
  36. package/dist/commands/compute/build/status.js +1 -1
  37. package/dist/commands/compute/build/status.js.map +1 -1
  38. package/dist/commands/compute/build/submit.js +1 -1
  39. package/dist/commands/compute/build/submit.js.map +1 -1
  40. package/dist/commands/compute/build/verify.js +1 -1
  41. package/dist/commands/compute/build/verify.js.map +1 -1
  42. package/dist/commands/compute/undelegate.js +1 -1
  43. package/dist/commands/compute/undelegate.js.map +1 -1
  44. package/dist/hooks/init/__tests__/version-check.test.js +1 -1
  45. package/dist/hooks/init/__tests__/version-check.test.js.map +1 -1
  46. package/dist/hooks/init/version-check.js +1 -1
  47. package/dist/hooks/init/version-check.js.map +1 -1
  48. package/package.json +2 -2
  49. package/dist/commands/billing/list-cards.js +0 -409
  50. package/dist/commands/billing/list-cards.js.map +0 -1
@@ -319,8 +319,7 @@ async function createBillingClient(flags) {
319
319
  // src/commands/billing/top-up.ts
320
320
  import { formatUnits } from "viem";
321
321
  import chalk2 from "chalk";
322
- import { input as input2, select as select2 } from "@inquirer/prompts";
323
- import open from "open";
322
+ import { input as input2 } from "@inquirer/prompts";
324
323
 
325
324
  // src/telemetry.ts
326
325
  import {
@@ -384,22 +383,12 @@ async function withTelemetry(command, action) {
384
383
  var POLL_INTERVAL_MS = 5e3;
385
384
  var POLL_TIMEOUT_MS = 3 * 60 * 1e3;
386
385
  var BillingTopUp = class _BillingTopUp extends Command {
387
- static description = "Purchase EigenCompute credits with USDC or credit card";
388
- static examples = [
389
- "<%= config.bin %> billing top-up",
390
- "<%= config.bin %> billing top-up --method usdc --amount 50",
391
- "<%= config.bin %> billing top-up --method card --amount 25"
392
- ];
386
+ static description = "Purchase EigenCompute credits with USDC";
393
387
  static flags = {
394
388
  ...commonFlags,
395
- method: Flags2.string({
396
- required: false,
397
- description: "Payment method: usdc (on-chain) or card (credit card)",
398
- options: ["usdc", "card"]
399
- }),
400
389
  amount: Flags2.string({
401
390
  required: false,
402
- description: "Amount to spend (USDC for on-chain, whole dollars for card)"
391
+ description: "Amount of USDC to spend (e.g., '50')"
403
392
  }),
404
393
  account: Flags2.string({
405
394
  required: false,
@@ -435,162 +424,101 @@ ${chalk2.bold("Purchase EigenCompute credits")}`);
435
424
  const remaining = status.remainingCredits ?? 0;
436
425
  const applied = status.creditsApplied ?? 0;
437
426
  baselineTotal = remaining + applied;
438
- this.log(` ${chalk2.bold("Credits:")} ${chalk2.cyan(`$${remaining.toFixed(2)}`)}`);
427
+ if (status.remainingCredits !== void 0) {
428
+ this.log(` ${chalk2.bold("Credits:")} ${chalk2.cyan(`$${status.remainingCredits.toFixed(2)}`)}`);
429
+ }
439
430
  } catch {
440
431
  this.debug("Could not fetch current credit balance");
441
432
  }
442
- const method = flags.method ?? await select2({
443
- message: "How would you like to pay?",
444
- choices: [
445
- { value: "card", name: "Credit card" },
446
- { value: "usdc", name: "USDC (on-chain)" }
447
- ]
448
- });
449
- if (method === "usdc") {
450
- await this.handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal);
451
- } else {
452
- await this.handleCard(billing, flags, baselineTotal);
453
- }
454
- });
455
- }
456
- async handleUsdc(billing, flags, walletAddress, targetAccount, baselineTotal) {
457
- const onChainState = await billing.getTopUpInfo();
458
- const { usdcBalance, minimumPurchase } = onChainState;
459
- const balanceFormatted = formatUnits(usdcBalance, 6);
460
- this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
461
- if (usdcBalance === BigInt(0)) {
462
- this.log(`
433
+ const onChainState = await billing.getTopUpInfo();
434
+ const { usdcBalance, minimumPurchase } = onChainState;
435
+ const balanceFormatted = formatUnits(usdcBalance, 6);
436
+ this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
437
+ if (usdcBalance === BigInt(0)) {
438
+ this.log(`
463
439
  ${chalk2.yellow(" No USDC in wallet.")}`);
464
- this.log(` Send USDC on Sepolia to: ${chalk2.cyan(walletAddress)}`);
465
- this.log(` Then re-run: ${chalk2.cyan("ecloud billing top-up")}
440
+ this.log(` Send USDC on Sepolia to: ${chalk2.cyan(walletAddress)}`);
441
+ this.log(` Then re-run: ${chalk2.cyan("ecloud billing top-up")}
466
442
  `);
467
- return;
468
- }
469
- const minimumFormatted = formatUnits(minimumPurchase, 6);
470
- const amountStr = flags.amount ?? await input2({
471
- message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
472
- validate: (val) => {
473
- const n = parseFloat(val);
474
- if (isNaN(n) || n <= 0) return "Enter a positive number";
475
- const raw = BigInt(Math.round(n * 1e6));
476
- if (raw < minimumPurchase)
477
- return `Minimum purchase is ${minimumFormatted} USDC`;
478
- if (raw > usdcBalance)
479
- return `Insufficient balance. You have ${balanceFormatted} USDC`;
480
- return true;
481
- }
482
- });
483
- const amountFloat = parseFloat(amountStr);
484
- const amountRaw = BigInt(Math.round(amountFloat * 1e6));
485
- if (amountRaw < minimumPurchase) {
486
- this.error(`Minimum purchase is ${minimumFormatted} USDC`);
487
- }
488
- if (amountRaw > usdcBalance) {
489
- this.error(
490
- `Insufficient USDC balance. You have ${balanceFormatted} USDC but requested ${amountFloat.toFixed(2)}`
491
- );
492
- }
493
- this.log(`
494
- Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
495
- const { txHash } = await billing.topUp({
496
- amount: amountRaw,
497
- account: targetAccount
498
- });
499
- this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
500
- await this.pollForCredits(billing, flags, baselineTotal, amountFloat);
501
- }
502
- async handleCard(billing, flags, baselineTotal) {
503
- const MINIMUM_DOLLARS = 5;
504
- const amountStr = flags.amount ?? await input2({
505
- message: `How many dollars of credits to purchase? (minimum: $${MINIMUM_DOLLARS})`,
506
- validate: (val) => {
507
- const n = parseInt(val, 10);
508
- if (isNaN(n) || n <= 0) return "Enter a positive whole number";
509
- if (n.toString() !== val.trim()) return "Enter a whole dollar amount (no cents)";
510
- if (n < MINIMUM_DOLLARS) return `Minimum purchase is $${MINIMUM_DOLLARS}`;
511
- return true;
443
+ return;
512
444
  }
513
- });
514
- const dollars = parseInt(amountStr, 10);
515
- if (isNaN(dollars) || dollars < MINIMUM_DOLLARS) {
516
- this.error(`Minimum purchase is $${MINIMUM_DOLLARS}`);
517
- }
518
- const amountCents = dollars * 100;
519
- const { paymentMethods } = await billing.getPaymentMethods();
520
- let paymentMethodId;
521
- if (paymentMethods.length > 0) {
522
- const choices = paymentMethods.map((card) => ({
523
- value: card.id,
524
- name: `${card.brand.charAt(0).toUpperCase() + card.brand.slice(1)} ending in ${card.last4}`
525
- }));
526
- choices.push({ value: "new", name: "Add a new card" });
527
- const selection = await select2({
528
- message: "Which card would you like to use?",
529
- choices
445
+ const minimumFormatted = formatUnits(minimumPurchase, 6);
446
+ const amountStr = flags.amount ?? await input2({
447
+ message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
448
+ validate: (val) => {
449
+ const n = parseFloat(val);
450
+ if (isNaN(n) || n <= 0) return "Enter a positive number";
451
+ const raw = BigInt(Math.round(n * 1e6));
452
+ if (raw < minimumPurchase)
453
+ return `Minimum purchase is ${minimumFormatted} USDC`;
454
+ if (raw > usdcBalance)
455
+ return `Insufficient balance. You have ${balanceFormatted} USDC`;
456
+ return true;
457
+ }
530
458
  });
531
- if (selection !== "new") {
532
- paymentMethodId = selection;
459
+ const amountFloat = parseFloat(amountStr);
460
+ const amountRaw = BigInt(Math.round(amountFloat * 1e6));
461
+ if (amountRaw < minimumPurchase) {
462
+ this.error(`Minimum purchase is ${minimumFormatted} USDC`);
533
463
  }
534
- }
535
- this.log(`
536
- Purchasing ${chalk2.bold(`$${dollars}`)} in credits...`);
537
- const result = await billing.purchaseCredits(amountCents, paymentMethodId);
538
- if (result.checkoutUrl) {
539
- this.log(`
540
- ${chalk2.cyan(result.checkoutUrl)}`);
541
- this.log(chalk2.gray(" Opening checkout in browser..."));
542
- await open(result.checkoutUrl);
543
- } else if (result.checkoutSessionId) {
544
- this.error(
545
- "Checkout session created but no URL was returned. Please contact support."
546
- );
547
- } else {
548
- this.log(` ${chalk2.green("\u2713")} Payment submitted`);
549
- }
550
- await this.pollForCredits(billing, flags, baselineTotal, dollars);
551
- }
552
- async pollForCredits(billing, flags, baselineTotal, amountPurchased) {
553
- this.log(chalk2.gray("\n Waiting for credits to appear..."));
554
- const startTime = Date.now();
555
- while (Date.now() - startTime < POLL_TIMEOUT_MS) {
556
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
557
- try {
558
- const status = await billing.getStatus({
559
- productId: flags.product
560
- });
561
- const remaining = status.remainingCredits ?? 0;
562
- const applied = status.creditsApplied ?? 0;
563
- const currentTotal = remaining + applied;
564
- this.debug(
565
- `Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`
464
+ if (amountRaw > usdcBalance) {
465
+ this.error(
466
+ `Insufficient USDC balance. You have ${balanceFormatted} USDC but requested ${amountFloat.toFixed(2)}`
566
467
  );
567
- if (baselineTotal === void 0 || currentTotal > baselineTotal) {
568
- const creditsAdded = baselineTotal !== void 0 ? currentTotal - baselineTotal : void 0;
569
- this.log(
570
- `
571
- ${chalk2.green("\u2713")} Credits received: ${chalk2.cyan(`$${(creditsAdded ?? amountPurchased).toFixed(2)}`)}`
572
- );
573
- if (remaining > 0) {
574
- this.log(` Remaining balance: ${chalk2.cyan(`$${remaining.toFixed(2)}`)}`);
468
+ }
469
+ this.log(`
470
+ Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
471
+ const { txHash } = await billing.topUp({
472
+ amount: amountRaw,
473
+ account: targetAccount
474
+ });
475
+ this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${txHash}`);
476
+ this.log(chalk2.gray("\n Waiting for credits to appear..."));
477
+ const startTime = Date.now();
478
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
479
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
480
+ try {
481
+ const status = await billing.getStatus({
482
+ productId: flags.product
483
+ });
484
+ const remaining = status.remainingCredits ?? 0;
485
+ const applied = status.creditsApplied ?? 0;
486
+ const currentTotal = remaining + applied;
487
+ this.debug(`Poll: remaining=${remaining}, applied=${applied}, total=${currentTotal}, baseline=${baselineTotal}`);
488
+ if (baselineTotal === void 0 || currentTotal > baselineTotal) {
489
+ const creditsAdded = baselineTotal !== void 0 ? currentTotal - baselineTotal : void 0;
490
+ const isMatched = creditsAdded !== void 0 && Math.abs(creditsAdded - amountFloat * 2) < 0.01;
491
+ const appliedFromTopUp = creditsAdded !== void 0 ? creditsAdded - remaining : 0;
492
+ this.log(`
493
+ ${chalk2.green("\u2713")} Credits received: ${chalk2.cyan(`$${(creditsAdded ?? amountFloat).toFixed(2)}`)}`);
494
+ if (isMatched) {
495
+ this.log(` ${chalk2.green("\u2713")} Includes $${amountFloat.toFixed(2)} match bonus!`);
496
+ }
497
+ if (remaining > 0) {
498
+ this.log(` Remaining balance: ${chalk2.cyan(`$${remaining.toFixed(2)}`)}`);
499
+ }
500
+ if (appliedFromTopUp > 0) {
501
+ this.log(` ${chalk2.gray(`$${appliedFromTopUp.toFixed(2)} applied to current bill`)}`);
502
+ }
503
+ this.log();
504
+ return;
575
505
  }
576
- this.log();
577
- return;
506
+ } catch {
507
+ this.debug("Error polling for credit balance");
578
508
  }
579
- } catch {
580
- this.debug("Error polling for credit balance");
581
509
  }
582
- }
583
- this.log(
584
- `
510
+ this.log(
511
+ `
585
512
  ${chalk2.yellow("\u26A0")} Credits haven't appeared yet. This can take a few minutes.`
586
- );
587
- this.log(` ${chalk2.gray("Check your balance:")} ecloud billing status
513
+ );
514
+ this.log(` ${chalk2.gray("Check your balance:")} ecloud billing status
588
515
  `);
516
+ });
589
517
  }
590
518
  };
591
519
 
592
520
  // src/commands/billing/__tests__/top-up.test.ts
593
- import { input as input3, select as select3 } from "@inquirer/prompts";
521
+ import { input as input3 } from "@inquirer/prompts";
594
522
  vi.mock("../../../client", () => ({
595
523
  createBillingClient: vi.fn()
596
524
  }));
@@ -598,11 +526,7 @@ vi.mock("../../../telemetry", () => ({
598
526
  withTelemetry: vi.fn((_cmd, fn) => fn())
599
527
  }));
600
528
  vi.mock("@inquirer/prompts", () => ({
601
- input: vi.fn(),
602
- select: vi.fn()
603
- }));
604
- vi.mock("open", () => ({
605
- default: vi.fn()
529
+ input: vi.fn()
606
530
  }));
607
531
  var WALLET_ADDRESS = "0x1234567890abcdef1234567890abcdef12345678";
608
532
  var TX_HASH = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
@@ -617,9 +541,7 @@ describe("ecloud billing top-up", () => {
617
541
  address: WALLET_ADDRESS,
618
542
  getStatus: vi.fn(),
619
543
  getTopUpInfo: vi.fn(),
620
- topUp: vi.fn(),
621
- getPaymentMethods: vi.fn(),
622
- purchaseCredits: vi.fn()
544
+ topUp: vi.fn()
623
545
  };
624
546
  createBillingClient.mockResolvedValue(mockBilling);
625
547
  input3.mockResolvedValue("50");
@@ -664,7 +586,7 @@ describe("ecloud billing top-up", () => {
664
586
  setupOnChainState();
665
587
  mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
666
588
  mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
667
- const cmd = createCommand({ amount: "50", method: "usdc" });
589
+ const cmd = createCommand({ amount: "50" });
668
590
  const promise = cmd.run();
669
591
  for (let i = 0; i < 10; i++) {
670
592
  await vi.advanceTimersByTimeAsync(5e3);
@@ -686,7 +608,7 @@ describe("ecloud billing top-up", () => {
686
608
  it("zero USDC balance: exits with fund wallet message", async () => {
687
609
  setupOnChainState({ usdcBalance: BigInt(0) });
688
610
  mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
689
- const cmd = createCommand({ amount: "50", method: "usdc" });
611
+ const cmd = createCommand({ amount: "50" });
690
612
  await cmd.run();
691
613
  const fullOutput = logOutput.join("\n");
692
614
  expect(fullOutput).toContain("No USDC in wallet");
@@ -697,7 +619,7 @@ describe("ecloud billing top-up", () => {
697
619
  it("below minimum purchase: shows error", async () => {
698
620
  setupOnChainState({ minimumPurchase: BigInt(1e7) });
699
621
  mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "inactive" });
700
- const cmd = createCommand({ amount: "5", method: "usdc" });
622
+ const cmd = createCommand({ amount: "5" });
701
623
  await expect(cmd.run()).rejects.toThrow("Minimum purchase is 10 USDC");
702
624
  });
703
625
  it("--account flag: passes different address to topUp", async () => {
@@ -705,7 +627,7 @@ describe("ecloud billing top-up", () => {
705
627
  setupOnChainState();
706
628
  mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
707
629
  mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
708
- const cmd = createCommand({ amount: "50", method: "usdc", account: targetAccount });
630
+ const cmd = createCommand({ amount: "50", account: targetAccount });
709
631
  const promise = cmd.run();
710
632
  for (let i = 0; i < 10; i++) {
711
633
  await vi.advanceTimersByTimeAsync(5e3);
@@ -725,7 +647,7 @@ describe("ecloud billing top-up", () => {
725
647
  subscriptionStatus: "active",
726
648
  remainingCredits: 10
727
649
  });
728
- const cmd = createCommand({ amount: "50", method: "usdc" });
650
+ const cmd = createCommand({ amount: "50" });
729
651
  const promise = cmd.run();
730
652
  await vi.advanceTimersByTimeAsync(2e5);
731
653
  await promise;
@@ -737,7 +659,7 @@ describe("ecloud billing top-up", () => {
737
659
  setupOnChainState();
738
660
  mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
739
661
  mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "inactive" }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 100 });
740
- const cmd = createCommand({ amount: "100", method: "usdc" });
662
+ const cmd = createCommand({ amount: "100" });
741
663
  const promise = cmd.run();
742
664
  for (let i = 0; i < 10; i++) {
743
665
  await vi.advanceTimersByTimeAsync(5e3);
@@ -749,7 +671,7 @@ describe("ecloud billing top-up", () => {
749
671
  setupOnChainState();
750
672
  mockBilling.topUp.mockResolvedValue({ txHash: TX_HASH, walletAddress: WALLET_ADDRESS });
751
673
  mockBilling.getStatus.mockRejectedValue(new Error("API unavailable"));
752
- const cmd = createCommand({ amount: "50", method: "usdc" });
674
+ const cmd = createCommand({ amount: "50" });
753
675
  const promise = cmd.run();
754
676
  await vi.advanceTimersByTimeAsync(2e5);
755
677
  await promise;
@@ -758,111 +680,5 @@ describe("ecloud billing top-up", () => {
758
680
  expect(fullOutput).toContain("Transaction confirmed");
759
681
  expect(fullOutput).toContain("Credits haven't appeared yet");
760
682
  });
761
- it("credit card: charges selected card on file", async () => {
762
- mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 35 });
763
- mockBilling.getPaymentMethods.mockResolvedValue({
764
- paymentMethods: [
765
- {
766
- id: "029641fc-3e5c-11f1-986c-5601121cbf6d",
767
- stripePaymentMethodId: "pm_1ABC1234",
768
- brand: "visa",
769
- last4: "1234",
770
- createdAt: "2026-04-20T15:00:00Z"
771
- },
772
- {
773
- id: "139752fd-4e6d-22f2-a97d-6712232dcg7e",
774
- stripePaymentMethodId: "pm_2DEF5678",
775
- brand: "mastercard",
776
- last4: "5678",
777
- createdAt: "2026-04-21T10:00:00Z"
778
- }
779
- ]
780
- });
781
- mockBilling.purchaseCredits.mockResolvedValue({
782
- purchaseId: "a1b2c3d4",
783
- amountCents: "2500"
784
- });
785
- select3.mockResolvedValue("029641fc-3e5c-11f1-986c-5601121cbf6d");
786
- const cmd = createCommand({ amount: "25", method: "card" });
787
- const promise = cmd.run();
788
- for (let i = 0; i < 10; i++) {
789
- await vi.advanceTimersByTimeAsync(5e3);
790
- }
791
- await promise;
792
- const fullOutput = logOutput.join("\n");
793
- expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(2500, "029641fc-3e5c-11f1-986c-5601121cbf6d");
794
- expect(fullOutput).toContain("Payment submitted");
795
- expect(fullOutput).toContain("Credits received");
796
- });
797
- it("credit card: opens checkout when user selects add new card", async () => {
798
- const openMock = (await import("open")).default;
799
- mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
800
- mockBilling.getPaymentMethods.mockResolvedValue({
801
- paymentMethods: [
802
- {
803
- id: "029641fc-3e5c-11f1-986c-5601121cbf6d",
804
- stripePaymentMethodId: "pm_1ABC1234",
805
- brand: "visa",
806
- last4: "1234",
807
- createdAt: "2026-04-20T15:00:00Z"
808
- }
809
- ]
810
- });
811
- mockBilling.purchaseCredits.mockResolvedValue({
812
- checkoutSessionId: "cs_test_abc123",
813
- checkoutUrl: "https://checkout.stripe.com/test",
814
- amountCents: "2500"
815
- });
816
- select3.mockResolvedValue("new");
817
- const cmd = createCommand({ amount: "25", method: "card" });
818
- const promise = cmd.run();
819
- await vi.advanceTimersByTimeAsync(2e5);
820
- await promise;
821
- const fullOutput = logOutput.join("\n");
822
- expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(2500, void 0);
823
- expect(openMock).toHaveBeenCalledWith("https://checkout.stripe.com/test");
824
- expect(fullOutput).toContain("https://checkout.stripe.com/test");
825
- });
826
- it("credit card: opens checkout when no card on file", async () => {
827
- const openMock = (await import("open")).default;
828
- mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
829
- mockBilling.getPaymentMethods.mockResolvedValue({ paymentMethods: [] });
830
- mockBilling.purchaseCredits.mockResolvedValue({
831
- checkoutSessionId: "cs_test_abc123",
832
- checkoutUrl: "https://checkout.stripe.com/test",
833
- amountCents: "5000"
834
- });
835
- const cmd = createCommand({ amount: "50", method: "card" });
836
- const promise = cmd.run();
837
- await vi.advanceTimersByTimeAsync(2e5);
838
- await promise;
839
- const fullOutput = logOutput.join("\n");
840
- expect(select3).not.toHaveBeenCalled();
841
- expect(mockBilling.purchaseCredits).toHaveBeenCalledWith(5e3, void 0);
842
- expect(openMock).toHaveBeenCalledWith("https://checkout.stripe.com/test");
843
- expect(fullOutput).toContain("https://checkout.stripe.com/test");
844
- });
845
- it("credit card: rejects amount below $5 minimum", async () => {
846
- mockBilling.getStatus.mockResolvedValue({ subscriptionStatus: "active", remainingCredits: 10 });
847
- const cmd = createCommand({ amount: "3", method: "card" });
848
- await expect(cmd.run()).rejects.toThrow("Minimum purchase is $5");
849
- });
850
- it("credit card: --method and --amount flags skip prompts", async () => {
851
- mockBilling.getStatus.mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 10 }).mockResolvedValueOnce({ subscriptionStatus: "active", remainingCredits: 60 });
852
- mockBilling.getPaymentMethods.mockResolvedValue({ paymentMethods: [] });
853
- mockBilling.purchaseCredits.mockResolvedValue({
854
- checkoutSessionId: "cs_test_abc123",
855
- checkoutUrl: "https://checkout.stripe.com/test",
856
- amountCents: "5000"
857
- });
858
- const cmd = createCommand({ amount: "50", method: "card" });
859
- const promise = cmd.run();
860
- for (let i = 0; i < 10; i++) {
861
- await vi.advanceTimersByTimeAsync(5e3);
862
- }
863
- await promise;
864
- expect(select3).not.toHaveBeenCalled();
865
- expect(input3).not.toHaveBeenCalled();
866
- });
867
683
  });
868
684
  //# sourceMappingURL=top-up.test.js.map