@stripe/link-cli 0.4.2 → 0.5.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 +86 -39
  2. package/dist/cli.js +1723 -851
  3. package/package.json +8 -6
package/dist/cli.js CHANGED
@@ -3218,8 +3218,8 @@ var require_utils = __commonJS({
3218
3218
  }
3219
3219
  return ind;
3220
3220
  }
3221
- function removeDotSegments(path5) {
3222
- let input = path5;
3221
+ function removeDotSegments(path7) {
3222
+ let input = path7;
3223
3223
  const output = [];
3224
3224
  let nextSlash = -1;
3225
3225
  let len = 0;
@@ -3418,8 +3418,8 @@ var require_schemes = __commonJS({
3418
3418
  wsComponent.secure = void 0;
3419
3419
  }
3420
3420
  if (wsComponent.resourceName) {
3421
- const [path5, query] = wsComponent.resourceName.split("?");
3422
- wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
3421
+ const [path7, query] = wsComponent.resourceName.split("?");
3422
+ wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
3423
3423
  wsComponent.query = query;
3424
3424
  wsComponent.resourceName = void 0;
3425
3425
  }
@@ -7605,12 +7605,12 @@ var require_dist = __commonJS({
7605
7605
  throw new Error(`Unknown format "${name}"`);
7606
7606
  return f;
7607
7607
  };
7608
- function addFormats(ajv, list, fs4, exportName) {
7608
+ function addFormats(ajv, list, fs5, exportName) {
7609
7609
  var _a;
7610
7610
  var _b;
7611
7611
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
7612
7612
  for (const f of list)
7613
- ajv.addFormat(f, fs4[f]);
7613
+ ajv.addFormat(f, fs5[f]);
7614
7614
  }
7615
7615
  module.exports = exports = formatsPlugin;
7616
7616
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -9548,12 +9548,9 @@ var require_semver2 = __commonJS({
9548
9548
  }
9549
9549
  });
9550
9550
 
9551
- // src/cli.tsx
9552
- import { Cli as Cli7 } from "incur";
9553
- import updateNotifier from "update-notifier";
9554
-
9555
9551
  // ../sdk/dist/index.js
9556
9552
  import fs3 from "fs";
9553
+ import path5 from "path";
9557
9554
 
9558
9555
  // ../../node_modules/.pnpm/conf@13.1.0/node_modules/conf/dist/source/index.js
9559
9556
  import { isDeepStrictEqual } from "util";
@@ -9574,12 +9571,12 @@ var disallowedKeys = /* @__PURE__ */ new Set([
9574
9571
  "constructor"
9575
9572
  ]);
9576
9573
  var digits = new Set("0123456789");
9577
- function getPathSegments(path5) {
9574
+ function getPathSegments(path7) {
9578
9575
  const parts = [];
9579
9576
  let currentSegment = "";
9580
9577
  let currentPart = "start";
9581
9578
  let isIgnoring = false;
9582
- for (const character of path5) {
9579
+ for (const character of path7) {
9583
9580
  switch (character) {
9584
9581
  case "\\": {
9585
9582
  if (currentPart === "index") {
@@ -9701,11 +9698,11 @@ function assertNotStringIndex(object, key) {
9701
9698
  throw new Error("Cannot use string index");
9702
9699
  }
9703
9700
  }
9704
- function getProperty(object, path5, value) {
9705
- if (!isObject(object) || typeof path5 !== "string") {
9701
+ function getProperty(object, path7, value) {
9702
+ if (!isObject(object) || typeof path7 !== "string") {
9706
9703
  return value === void 0 ? object : value;
9707
9704
  }
9708
- const pathArray = getPathSegments(path5);
9705
+ const pathArray = getPathSegments(path7);
9709
9706
  if (pathArray.length === 0) {
9710
9707
  return value;
9711
9708
  }
@@ -9725,12 +9722,12 @@ function getProperty(object, path5, value) {
9725
9722
  }
9726
9723
  return object === void 0 ? value : object;
9727
9724
  }
9728
- function setProperty(object, path5, value) {
9729
- if (!isObject(object) || typeof path5 !== "string") {
9725
+ function setProperty(object, path7, value) {
9726
+ if (!isObject(object) || typeof path7 !== "string") {
9730
9727
  return object;
9731
9728
  }
9732
9729
  const root = object;
9733
- const pathArray = getPathSegments(path5);
9730
+ const pathArray = getPathSegments(path7);
9734
9731
  for (let index = 0; index < pathArray.length; index++) {
9735
9732
  const key = pathArray[index];
9736
9733
  assertNotStringIndex(object, key);
@@ -9743,11 +9740,11 @@ function setProperty(object, path5, value) {
9743
9740
  }
9744
9741
  return root;
9745
9742
  }
9746
- function deleteProperty(object, path5) {
9747
- if (!isObject(object) || typeof path5 !== "string") {
9743
+ function deleteProperty(object, path7) {
9744
+ if (!isObject(object) || typeof path7 !== "string") {
9748
9745
  return false;
9749
9746
  }
9750
- const pathArray = getPathSegments(path5);
9747
+ const pathArray = getPathSegments(path7);
9751
9748
  for (let index = 0; index < pathArray.length; index++) {
9752
9749
  const key = pathArray[index];
9753
9750
  assertNotStringIndex(object, key);
@@ -9761,11 +9758,11 @@ function deleteProperty(object, path5) {
9761
9758
  }
9762
9759
  }
9763
9760
  }
9764
- function hasProperty(object, path5) {
9765
- if (!isObject(object) || typeof path5 !== "string") {
9761
+ function hasProperty(object, path7) {
9762
+ if (!isObject(object) || typeof path7 !== "string") {
9766
9763
  return false;
9767
9764
  }
9768
- const pathArray = getPathSegments(path5);
9765
+ const pathArray = getPathSegments(path7);
9769
9766
  if (pathArray.length === 0) {
9770
9767
  return false;
9771
9768
  }
@@ -10861,12 +10858,27 @@ function withComputedExpiry(auth) {
10861
10858
  expires_at: auth.expires_at ?? Date.now() + auth.expires_in * 1e3
10862
10859
  };
10863
10860
  }
10861
+ var CONFIG_FILE_MODE = 384;
10864
10862
  var Storage = class {
10865
10863
  config;
10864
+ options;
10865
+ constructor(options = {}) {
10866
+ this.options = options;
10867
+ }
10866
10868
  getConfig() {
10867
10869
  if (!this.config) {
10870
+ let locationOverride;
10871
+ if (this.options.configPath) {
10872
+ const parsed = path5.parse(path5.resolve(this.options.configPath));
10873
+ const configName = parsed.ext === ".json" ? parsed.name : parsed.base;
10874
+ locationOverride = { cwd: parsed.dir, configName };
10875
+ } else if (this.options.cwd) {
10876
+ locationOverride = { cwd: this.options.cwd };
10877
+ }
10868
10878
  this.config = new Conf({
10869
10879
  projectName: "link-cli",
10880
+ configFileMode: CONFIG_FILE_MODE,
10881
+ ...locationOverride,
10870
10882
  defaults: {
10871
10883
  auth: null,
10872
10884
  pendingDeviceAuth: null
@@ -11076,6 +11088,91 @@ var PaymentMethodsResource = class {
11076
11088
  return body?.payment_details ?? [];
11077
11089
  }
11078
11090
  };
11091
+ var ShippingAddressResource = class {
11092
+ verbose;
11093
+ getAccessToken;
11094
+ fetchImpl;
11095
+ shippingAddressesEndpoint;
11096
+ logger;
11097
+ constructor(options) {
11098
+ const config = resolveLinkSdkConfig(options);
11099
+ this.verbose = config.verbose;
11100
+ this.getAccessToken = config.getAccessToken;
11101
+ this.fetchImpl = requireFetchImplementation(config);
11102
+ this.shippingAddressesEndpoint = `${config.apiBaseUrl}/shipping_addresses`;
11103
+ this.logger = config.logger;
11104
+ }
11105
+ async rawFetch(opts) {
11106
+ if (this.verbose) {
11107
+ const redactedHeaders = { ...opts.headers };
11108
+ if (redactedHeaders.Authorization)
11109
+ redactedHeaders.Authorization = "Bearer <redacted>";
11110
+ this.logger.debug(`> ${opts.method} ${opts.url}`);
11111
+ this.logger.debug(` Headers: ${JSON.stringify(redactedHeaders)}`);
11112
+ }
11113
+ let response;
11114
+ try {
11115
+ response = await this.fetchImpl(opts.url, {
11116
+ method: opts.method,
11117
+ headers: opts.headers
11118
+ });
11119
+ } catch (error) {
11120
+ throw new LinkTransportError(
11121
+ `Request failed: ${opts.method} ${opts.url}`,
11122
+ {
11123
+ cause: error
11124
+ }
11125
+ );
11126
+ }
11127
+ const rawBody = await response.text();
11128
+ let data = null;
11129
+ try {
11130
+ data = JSON.parse(rawBody);
11131
+ } catch {
11132
+ }
11133
+ if (this.verbose) {
11134
+ this.logger.debug(`< ${response.status} ${response.statusText}`);
11135
+ response.headers.forEach((value, key) => {
11136
+ this.logger.debug(` ${key}: ${value}`);
11137
+ });
11138
+ this.logger.debug(rawBody);
11139
+ }
11140
+ return { status: response.status, data, rawBody };
11141
+ }
11142
+ async apiFetch(opts) {
11143
+ const token = await this.getAccessToken();
11144
+ const authedOpts = {
11145
+ ...opts,
11146
+ headers: { ...opts.headers, Authorization: `Bearer ${token}` }
11147
+ };
11148
+ const res = await this.rawFetch(authedOpts);
11149
+ if (res.status === 401) {
11150
+ const refreshedToken = await this.getAccessToken({ forceRefresh: true });
11151
+ authedOpts.headers.Authorization = `Bearer ${refreshedToken}`;
11152
+ return this.rawFetch(authedOpts);
11153
+ }
11154
+ return res;
11155
+ }
11156
+ list() {
11157
+ return this.listShippingAddresses();
11158
+ }
11159
+ async listShippingAddresses() {
11160
+ const { status, data, rawBody } = await this.apiFetch({
11161
+ method: "GET",
11162
+ url: this.shippingAddressesEndpoint
11163
+ });
11164
+ if (status < 200 || status >= 300) {
11165
+ const body2 = data;
11166
+ const msg = body2?.error ?? body2?.message ?? (rawBody || "unknown error");
11167
+ throw new LinkApiError(
11168
+ `Failed to list shipping addresses (${status}): ${msg}`,
11169
+ { status, rawBody, details: data }
11170
+ );
11171
+ }
11172
+ const body = data;
11173
+ return body?.shipping_addresses ?? [];
11174
+ }
11175
+ };
11079
11176
  function normalizeSpendRequest(data) {
11080
11177
  const sr = data;
11081
11178
  if (typeof sr.shared_payment_token === "string") {
@@ -11208,6 +11305,22 @@ var SpendRequestResource = class {
11208
11305
  }
11209
11306
  return normalizeSpendRequest(data);
11210
11307
  }
11308
+ cancel(id) {
11309
+ return this.cancelSpendRequest(id);
11310
+ }
11311
+ async cancelSpendRequest(id) {
11312
+ const { status, data, rawBody } = await this.apiFetch({
11313
+ method: "POST",
11314
+ url: `${this.spendRequestsEndpoint}/${id}/cancel`
11315
+ });
11316
+ if (status < 200 || status >= 300) {
11317
+ throw new LinkApiError(
11318
+ `Failed to cancel spend request (${status}): ${extractApiError(data, rawBody)}`,
11319
+ { status, rawBody, details: data }
11320
+ );
11321
+ }
11322
+ return normalizeSpendRequest(data);
11323
+ }
11211
11324
  async requestApproval(id) {
11212
11325
  const { status, data, rawBody } = await this.apiFetch({
11213
11326
  method: "POST",
@@ -11245,16 +11358,155 @@ var SpendRequestResource = class {
11245
11358
  return normalizeSpendRequest(data);
11246
11359
  }
11247
11360
  };
11361
+ var UserInfoResource = class {
11362
+ verbose;
11363
+ getAccessToken;
11364
+ fetchImpl;
11365
+ userInfoEndpoint;
11366
+ logger;
11367
+ constructor(options) {
11368
+ const config = resolveLinkSdkConfig(options);
11369
+ this.verbose = config.verbose;
11370
+ this.getAccessToken = config.getAccessToken;
11371
+ this.fetchImpl = requireFetchImplementation(config);
11372
+ this.userInfoEndpoint = `${config.apiBaseUrl}/userinfo`;
11373
+ this.logger = config.logger;
11374
+ }
11375
+ async rawFetch(opts) {
11376
+ if (this.verbose) {
11377
+ const redactedHeaders = { ...opts.headers };
11378
+ if (redactedHeaders.Authorization)
11379
+ redactedHeaders.Authorization = "Bearer <redacted>";
11380
+ this.logger.debug(`> ${opts.method} ${opts.url}`);
11381
+ this.logger.debug(` Headers: ${JSON.stringify(redactedHeaders)}`);
11382
+ }
11383
+ let response;
11384
+ try {
11385
+ response = await this.fetchImpl(opts.url, {
11386
+ method: opts.method,
11387
+ headers: opts.headers
11388
+ });
11389
+ } catch (error) {
11390
+ throw new LinkTransportError(
11391
+ `Request failed: ${opts.method} ${opts.url}`,
11392
+ {
11393
+ cause: error
11394
+ }
11395
+ );
11396
+ }
11397
+ const rawBody = await response.text();
11398
+ let data = null;
11399
+ try {
11400
+ data = JSON.parse(rawBody);
11401
+ } catch {
11402
+ }
11403
+ if (this.verbose) {
11404
+ this.logger.debug(`< ${response.status} ${response.statusText}`);
11405
+ response.headers.forEach((value, key) => {
11406
+ this.logger.debug(` ${key}: ${value}`);
11407
+ });
11408
+ this.logger.debug(rawBody);
11409
+ }
11410
+ return { status: response.status, data, rawBody };
11411
+ }
11412
+ async apiFetch(opts) {
11413
+ const token = await this.getAccessToken();
11414
+ const authedOpts = {
11415
+ ...opts,
11416
+ headers: { ...opts.headers, Authorization: `Bearer ${token}` }
11417
+ };
11418
+ const res = await this.rawFetch(authedOpts);
11419
+ if (res.status === 401) {
11420
+ const refreshedToken = await this.getAccessToken({ forceRefresh: true });
11421
+ authedOpts.headers.Authorization = `Bearer ${refreshedToken}`;
11422
+ return this.rawFetch(authedOpts);
11423
+ }
11424
+ return res;
11425
+ }
11426
+ async retrieve() {
11427
+ const { status, data, rawBody } = await this.apiFetch({
11428
+ method: "GET",
11429
+ url: this.userInfoEndpoint
11430
+ });
11431
+ if (status < 200 || status >= 300) {
11432
+ const body2 = data;
11433
+ const msg = body2?.error ?? body2?.message ?? (rawBody || "unknown error");
11434
+ throw new LinkApiError(
11435
+ `Failed to retrieve user info (${status}): ${msg}`,
11436
+ {
11437
+ status,
11438
+ rawBody,
11439
+ details: data
11440
+ }
11441
+ );
11442
+ }
11443
+ const body = data;
11444
+ return {
11445
+ email: body?.email ?? null,
11446
+ name: body?.name ?? null,
11447
+ first_name: body?.first_name ?? null,
11448
+ last_name: body?.last_name ?? null,
11449
+ phone: body?.phone ?? null
11450
+ };
11451
+ }
11452
+ };
11453
+
11454
+ // src/cli.tsx
11455
+ import { Cli as Cli9 } from "incur";
11248
11456
 
11249
11457
  // src/commands/auth/index.tsx
11250
11458
  import { Cli } from "incur";
11459
+
11460
+ // src/utils/poll-until.ts
11461
+ async function* pollUntil(options) {
11462
+ const { fn, isTerminal, interval, timeout, maxAttempts } = options;
11463
+ const deadline = timeout !== void 0 && timeout >= 0 ? Date.now() + timeout * 1e3 : void 0;
11464
+ let attempts = 0;
11465
+ let previousSnapshot;
11466
+ while (true) {
11467
+ const value = await fn();
11468
+ if (isTerminal(value) || interval <= 0) {
11469
+ yield { value, terminal: true };
11470
+ return;
11471
+ }
11472
+ attempts++;
11473
+ const maxAttemptsExhausted = maxAttempts > 0 && attempts >= maxAttempts;
11474
+ const timeoutReached = deadline !== void 0 && Date.now() >= deadline;
11475
+ if (maxAttemptsExhausted) {
11476
+ yield { value, terminal: true, reason: "max_attempts" };
11477
+ return;
11478
+ }
11479
+ if (timeoutReached) {
11480
+ yield { value, terminal: true, reason: "timeout" };
11481
+ return;
11482
+ }
11483
+ const snapshot = JSON.stringify(value);
11484
+ if (snapshot !== previousSnapshot) {
11485
+ previousSnapshot = snapshot;
11486
+ yield { value, terminal: false };
11487
+ }
11488
+ await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
11489
+ }
11490
+ }
11491
+
11492
+ // src/utils/render-interactive.tsx
11251
11493
  import { render } from "ink";
11494
+ async function renderInteractive(element, getResult) {
11495
+ const { waitUntilExit } = render(element);
11496
+ await waitUntilExit();
11497
+ if (getResult) {
11498
+ return getResult();
11499
+ }
11500
+ }
11252
11501
 
11253
11502
  // src/commands/auth/login.tsx
11254
11503
  import { Box, Text, useInput } from "ink";
11255
11504
  import Spinner from "ink-spinner";
11256
11505
  import { useEffect, useState } from "react";
11257
11506
 
11507
+ // src/utils/constants.ts
11508
+ var DISPLAY_DELAY_MS = 1500;
11509
+
11258
11510
  // src/utils/open-url.ts
11259
11511
  import { spawn } from "child_process";
11260
11512
  function openUrl(url) {
@@ -11286,8 +11538,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
11286
11538
  var Login = ({
11287
11539
  authResource,
11288
11540
  clientName,
11541
+ authStorage: authStorage2 = storage,
11289
11542
  onComplete
11290
11543
  }) => {
11544
+ const storage2 = authStorage2;
11291
11545
  const [status, setStatus] = useState("initiating");
11292
11546
  const [userCode, setUserCode] = useState("");
11293
11547
  const [verificationUrl, setVerificationUrl] = useState("");
@@ -11324,9 +11578,9 @@ var Login = ({
11324
11578
  const tokens = await authResource.pollDeviceAuth(deviceCode);
11325
11579
  if (tokens) {
11326
11580
  clearInterval(pollInterval);
11327
- storage.setAuth(tokens);
11581
+ storage2.setAuth(tokens);
11328
11582
  setStatus("success");
11329
- setTimeout(onComplete, 1e3);
11583
+ setTimeout(onComplete, DISPLAY_DELAY_MS);
11330
11584
  }
11331
11585
  } catch (err) {
11332
11586
  clearInterval(pollInterval);
@@ -11338,7 +11592,7 @@ var Login = ({
11338
11592
  };
11339
11593
  const timeout = setTimeout(startPolling, 1e3);
11340
11594
  return () => clearTimeout(timeout);
11341
- }, [status, deviceCode, authResource, onComplete]);
11595
+ }, [status, deviceCode, authResource, onComplete, storage2]);
11342
11596
  if (status === "initiating") {
11343
11597
  return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
11344
11598
  /* @__PURE__ */ jsx(Spinner, { type: "dots" }),
@@ -11391,28 +11645,75 @@ var Login = ({
11391
11645
 
11392
11646
  // src/commands/auth/logout.tsx
11393
11647
  import { Box as Box2, Text as Text2 } from "ink";
11394
- import { useEffect as useEffect2, useState as useState2 } from "react";
11395
- import { jsx as jsx2 } from "react/jsx-runtime";
11396
- var Logout = ({ authResource, onComplete }) => {
11397
- const [done, setDone] = useState2(false);
11648
+ import Spinner2 from "ink-spinner";
11649
+ import { useCallback } from "react";
11650
+
11651
+ // src/hooks/use-async-action.ts
11652
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
11653
+ function useAsyncAction(action, onComplete) {
11654
+ const [status, setStatus] = useState2("loading");
11655
+ const [data, setData] = useState2(null);
11656
+ const [error, setError] = useState2("");
11657
+ const onCompleteRef = useRef(onComplete);
11658
+ onCompleteRef.current = onComplete;
11398
11659
  useEffect2(() => {
11660
+ let timeoutId = null;
11661
+ let cancelled = false;
11399
11662
  const run = async () => {
11400
- const auth = storage.getAuth();
11401
- if (auth?.refresh_token) {
11402
- try {
11403
- await authResource.revokeToken(auth.refresh_token);
11404
- } catch {
11405
- }
11663
+ try {
11664
+ const result = await action();
11665
+ if (cancelled) return;
11666
+ setData(result);
11667
+ setStatus("success");
11668
+ timeoutId = setTimeout(
11669
+ () => onCompleteRef.current(result),
11670
+ DISPLAY_DELAY_MS
11671
+ );
11672
+ } catch (err) {
11673
+ if (cancelled) return;
11674
+ const message = err instanceof Error ? err.message : String(err);
11675
+ setError(message);
11676
+ setStatus("error");
11677
+ timeoutId = setTimeout(
11678
+ () => onCompleteRef.current(null),
11679
+ DISPLAY_DELAY_MS
11680
+ );
11406
11681
  }
11407
- storage.clearAuth();
11408
- storage.deleteConfig();
11409
- setDone(true);
11410
- setTimeout(onComplete, 1e3);
11411
11682
  };
11412
11683
  run();
11413
- }, [authResource, onComplete]);
11414
- if (!done) {
11415
- return null;
11684
+ return () => {
11685
+ cancelled = true;
11686
+ if (timeoutId) clearTimeout(timeoutId);
11687
+ };
11688
+ }, [action]);
11689
+ return { status, data, error };
11690
+ }
11691
+
11692
+ // src/commands/auth/logout.tsx
11693
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
11694
+ var Logout = ({
11695
+ authResource,
11696
+ authStorage: authStorage2 = storage,
11697
+ onComplete
11698
+ }) => {
11699
+ const storage2 = authStorage2;
11700
+ const action = useCallback(async () => {
11701
+ const auth = storage2.getAuth();
11702
+ if (auth?.refresh_token) {
11703
+ try {
11704
+ await authResource.revokeToken(auth.refresh_token);
11705
+ } catch {
11706
+ }
11707
+ }
11708
+ storage2.clearAuth();
11709
+ storage2.deleteConfig();
11710
+ }, [authResource, storage2]);
11711
+ const { status } = useAsyncAction(action, onComplete);
11712
+ if (status === "loading") {
11713
+ return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
11714
+ /* @__PURE__ */ jsx2(Spinner2, { type: "dots" }),
11715
+ " Logging out..."
11716
+ ] }) });
11416
11717
  }
11417
11718
  return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713 Logged out successfully" }) });
11418
11719
  };
@@ -11435,16 +11736,20 @@ var statusOptions = z.object({
11435
11736
  // src/commands/auth/status.tsx
11436
11737
  import { Box as Box3, Text as Text3 } from "ink";
11437
11738
  import { useEffect as useEffect3, useState as useState3 } from "react";
11438
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
11439
- var AuthStatus = ({ onComplete }) => {
11739
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
11740
+ var AuthStatus = ({
11741
+ authStorage: authStorage2 = storage,
11742
+ onComplete
11743
+ }) => {
11744
+ const storage2 = authStorage2;
11440
11745
  const [checked, setChecked] = useState3(false);
11441
11746
  const [authenticated, setAuthenticated] = useState3(false);
11442
11747
  const [tokenPreview, setTokenPreview] = useState3("");
11443
11748
  const [tokenType, setTokenType] = useState3("");
11444
11749
  const [credentialsPath, setCredentialsPath] = useState3("");
11445
11750
  useEffect3(() => {
11446
- const auth = storage.getAuth();
11447
- const credentialsPath2 = storage.getPath();
11751
+ const auth = storage2.getAuth();
11752
+ const credentialsPath2 = storage2.getPath();
11448
11753
  if (auth) {
11449
11754
  setAuthenticated(true);
11450
11755
  setTokenPreview(`${auth.access_token.substring(0, 20)}...`);
@@ -11452,34 +11757,34 @@ var AuthStatus = ({ onComplete }) => {
11452
11757
  }
11453
11758
  setCredentialsPath(credentialsPath2);
11454
11759
  setChecked(true);
11455
- setTimeout(onComplete, 1e3);
11456
- }, [onComplete]);
11760
+ setTimeout(onComplete, DISPLAY_DELAY_MS);
11761
+ }, [onComplete, storage2]);
11457
11762
  if (!checked) {
11458
11763
  return null;
11459
11764
  }
11460
11765
  if (authenticated) {
11461
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
11766
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
11462
11767
  /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Authenticated" }),
11463
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
11464
- /* @__PURE__ */ jsxs2(Text3, { children: [
11768
+ /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
11769
+ /* @__PURE__ */ jsxs3(Text3, { children: [
11465
11770
  "Access token: ",
11466
11771
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: tokenPreview })
11467
11772
  ] }),
11468
- /* @__PURE__ */ jsxs2(Text3, { children: [
11773
+ /* @__PURE__ */ jsxs3(Text3, { children: [
11469
11774
  "Token type: ",
11470
11775
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: tokenType })
11471
11776
  ] }),
11472
- /* @__PURE__ */ jsxs2(Text3, { children: [
11777
+ /* @__PURE__ */ jsxs3(Text3, { children: [
11473
11778
  "Credentials: ",
11474
11779
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: credentialsPath })
11475
11780
  ] })
11476
11781
  ] })
11477
11782
  ] });
11478
11783
  }
11479
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
11784
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
11480
11785
  /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u2717 Not authenticated" }),
11481
11786
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: 'Run "link-cli auth login" to authenticate' }),
11482
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs2(Text3, { children: [
11787
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs3(Text3, { children: [
11483
11788
  "Credentials: ",
11484
11789
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: credentialsPath })
11485
11790
  ] }) })
@@ -11488,7 +11793,8 @@ var AuthStatus = ({ onComplete }) => {
11488
11793
 
11489
11794
  // src/commands/auth/index.tsx
11490
11795
  import { jsx as jsx4 } from "react/jsx-runtime";
11491
- function createAuthCli(authResource, updateInfo) {
11796
+ function createAuthCli(authResource, getUpdateInfo2, authStorage2) {
11797
+ const storage2 = authStorage2 ?? storage;
11492
11798
  const cli2 = Cli.create("auth", {
11493
11799
  description: "Authentication commands"
11494
11800
  });
@@ -11505,25 +11811,22 @@ function createAuthCli(authResource, updateInfo) {
11505
11811
  });
11506
11812
  }
11507
11813
  if (!c.agent && !c.formatExplicit) {
11508
- return new Promise((resolve) => {
11509
- const { waitUntilExit } = render(
11510
- /* @__PURE__ */ jsx4(
11511
- Login,
11512
- {
11513
- authResource,
11514
- clientName,
11515
- onComplete: () => {
11516
- }
11814
+ return renderInteractive(
11815
+ /* @__PURE__ */ jsx4(
11816
+ Login,
11817
+ {
11818
+ authResource,
11819
+ clientName,
11820
+ authStorage: storage2,
11821
+ onComplete: () => {
11517
11822
  }
11518
- )
11519
- );
11520
- waitUntilExit().then(
11521
- () => resolve({ authenticated: true, token_type: "Bearer" })
11522
- );
11523
- });
11823
+ }
11824
+ ),
11825
+ () => ({ authenticated: true, token_type: "Bearer" })
11826
+ );
11524
11827
  }
11525
11828
  const authRequest = await authResource.initiateDeviceAuth(clientName);
11526
- storage.setPendingDeviceAuth({
11829
+ storage2.setPendingDeviceAuth({
11527
11830
  device_code: authRequest.device_code,
11528
11831
  interval: authRequest.interval,
11529
11832
  expires_at: Date.now() + authRequest.expires_in * 1e3,
@@ -11546,25 +11849,30 @@ function createAuthCli(authResource, updateInfo) {
11546
11849
  description: "Log out from Link",
11547
11850
  outputPolicy: "agent-only",
11548
11851
  async run(c) {
11549
- const auth = storage.getAuth();
11852
+ const auth = storage2.getAuth();
11550
11853
  if (auth?.refresh_token) {
11551
11854
  try {
11552
11855
  await authResource.revokeToken(auth.refresh_token);
11553
11856
  } catch {
11554
11857
  }
11555
11858
  }
11556
- storage.clearAuth();
11557
- storage.clearPendingDeviceAuth();
11558
- storage.deleteConfig();
11859
+ storage2.clearAuth();
11860
+ storage2.clearPendingDeviceAuth();
11861
+ storage2.deleteConfig();
11559
11862
  const result = { authenticated: false };
11560
11863
  if (!c.agent && !c.formatExplicit) {
11561
- return new Promise((resolve) => {
11562
- const { waitUntilExit } = render(
11563
- /* @__PURE__ */ jsx4(Logout, { authResource, onComplete: () => {
11564
- } })
11565
- );
11566
- waitUntilExit().then(() => resolve(result));
11567
- });
11864
+ return renderInteractive(
11865
+ /* @__PURE__ */ jsx4(
11866
+ Logout,
11867
+ {
11868
+ authResource,
11869
+ authStorage: storage2,
11870
+ onComplete: () => {
11871
+ }
11872
+ }
11873
+ ),
11874
+ () => result
11875
+ );
11568
11876
  }
11569
11877
  return result;
11570
11878
  }
@@ -11574,79 +11882,75 @@ function createAuthCli(authResource, updateInfo) {
11574
11882
  options: statusOptions,
11575
11883
  outputPolicy: "agent-only",
11576
11884
  async *run(c) {
11885
+ const opts = c.options;
11886
+ const interval = opts.interval;
11887
+ const maxAttempts = opts.maxAttempts;
11888
+ const updateInfo = await getUpdateInfo2?.({
11889
+ polling: interval > 0
11890
+ });
11891
+ const update = updateInfo ? {
11892
+ current_version: updateInfo.current,
11893
+ latest_version: updateInfo.latest,
11894
+ update_command: "npm install -g @stripe/link-cli"
11895
+ } : void 0;
11577
11896
  if (!c.agent && !c.formatExplicit) {
11578
- return new Promise((resolve) => {
11579
- const { waitUntilExit } = render(
11580
- /* @__PURE__ */ jsx4(AuthStatus, { onComplete: () => {
11581
- } })
11582
- );
11583
- waitUntilExit().then(() => {
11584
- const auth = storage.getAuth();
11585
- resolve({
11897
+ return renderInteractive(
11898
+ /* @__PURE__ */ jsx4(AuthStatus, { authStorage: storage2, onComplete: () => {
11899
+ } }),
11900
+ () => {
11901
+ const auth = storage2.getAuth();
11902
+ return {
11586
11903
  authenticated: !!auth,
11587
11904
  ...auth ? {
11588
11905
  access_token: `${auth.access_token.substring(0, 20)}...`,
11589
11906
  token_type: auth.token_type
11590
11907
  } : {},
11591
- credentials_path: storage.getPath()
11592
- });
11593
- });
11594
- });
11908
+ credentials_path: storage2.getPath(),
11909
+ ...update && { update }
11910
+ };
11911
+ }
11912
+ );
11595
11913
  }
11596
- const opts = c.options;
11597
- const interval = opts.interval;
11598
- const maxAttempts = opts.maxAttempts;
11599
- const deadline = Date.now() + opts.timeout * 1e3;
11600
- let attempts = 0;
11601
- let previousAttemptData;
11602
- while (true) {
11603
- const pending = storage.getPendingDeviceAuth();
11604
- if (pending && !storage.isAuthenticated()) {
11605
- const tokens = await authResource.pollDeviceAuth(pending.device_code);
11606
- if (tokens) {
11607
- storage.setAuth(tokens);
11608
- storage.clearPendingDeviceAuth();
11609
- }
11610
- }
11611
- const auth = storage.getAuth();
11612
- const update = updateInfo ? {
11613
- current_version: updateInfo.current,
11614
- latest_version: updateInfo.latest,
11615
- update_command: "npm install -g @stripe/link-cli"
11616
- } : void 0;
11617
- if (auth) {
11618
- yield {
11619
- authenticated: true,
11620
- access_token: `${auth.access_token.substring(0, 20)}...`,
11621
- token_type: auth.token_type,
11622
- credentials_path: storage.getPath(),
11623
- ...update && { update }
11914
+ for await (const result of pollUntil({
11915
+ fn: async () => {
11916
+ const pending = storage2.getPendingDeviceAuth();
11917
+ if (pending && !storage2.isAuthenticated()) {
11918
+ const tokens = await authResource.pollDeviceAuth(
11919
+ pending.device_code
11920
+ );
11921
+ if (tokens) {
11922
+ storage2.setAuth(tokens);
11923
+ storage2.clearPendingDeviceAuth();
11924
+ }
11925
+ }
11926
+ const auth = storage2.getAuth();
11927
+ if (auth) {
11928
+ return {
11929
+ authenticated: true,
11930
+ access_token: `${auth.access_token.substring(0, 20)}...`,
11931
+ token_type: auth.token_type,
11932
+ credentials_path: storage2.getPath(),
11933
+ ...update && { update }
11934
+ };
11935
+ }
11936
+ const currentPending = storage2.getPendingDeviceAuth();
11937
+ return {
11938
+ authenticated: false,
11939
+ credentials_path: storage2.getPath(),
11940
+ ...update && { update },
11941
+ ...currentPending ? {
11942
+ pending: true,
11943
+ verification_url: currentPending.verification_url,
11944
+ phrase: currentPending.phrase
11945
+ } : {}
11624
11946
  };
11625
- return;
11626
- }
11627
- const currentPending = storage.getPendingDeviceAuth();
11628
- const status = {
11629
- authenticated: false,
11630
- credentials_path: storage.getPath(),
11631
- ...update && { update },
11632
- ...currentPending ? {
11633
- pending: true,
11634
- verification_url: currentPending.verification_url,
11635
- phrase: currentPending.phrase
11636
- } : {}
11637
- };
11638
- attempts++;
11639
- const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
11640
- if (shouldStop) {
11641
- yield status;
11642
- return;
11643
- }
11644
- const snapshot = JSON.stringify(status);
11645
- if (snapshot !== previousAttemptData) {
11646
- previousAttemptData = snapshot;
11647
- yield status;
11648
- }
11649
- await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
11947
+ },
11948
+ isTerminal: (status) => status.authenticated,
11949
+ interval,
11950
+ maxAttempts,
11951
+ timeout: opts.timeout
11952
+ })) {
11953
+ yield result.value;
11650
11954
  }
11651
11955
  }
11652
11956
  });
@@ -11655,11 +11959,10 @@ function createAuthCli(authResource, updateInfo) {
11655
11959
 
11656
11960
  // src/commands/demo/index.tsx
11657
11961
  import { Cli as Cli2, z as z2 } from "incur";
11658
- import { render as render2 } from "ink";
11659
11962
 
11660
11963
  // src/commands/demo/demo-runner.tsx
11661
- import { Box as Box8, Text as Text9, useInput as useInput4 } from "ink";
11662
- import { useCallback, useState as useState7 } from "react";
11964
+ import { Box as Box8, Text as Text9, useApp, useInput as useInput4 } from "ink";
11965
+ import { useCallback as useCallback2, useState as useState7 } from "react";
11663
11966
 
11664
11967
  // src/utils/markdown-text.tsx
11665
11968
  import { Text as Text4 } from "ink";
@@ -11748,13 +12051,13 @@ function renderQrMatrix(url) {
11748
12051
  }
11749
12052
 
11750
12053
  // src/commands/spend-request/app-download-qr-codes.tsx
11751
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
12054
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
11752
12055
  var DOWNLOAD_URL = "https://link.com/download";
11753
12056
  var AppDownloadQrCodes = () => {
11754
12057
  const qrLines = useMemo(() => renderQrMatrix(DOWNLOAD_URL), []);
11755
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginTop: 1, children: [
12058
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
11756
12059
  /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "Get the Link app to approve spend requests from your phone" }),
11757
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
12060
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
11758
12061
  qrLines.map((line, i) => (
11759
12062
  // biome-ignore lint/suspicious/noArrayIndexKey: stable static array
11760
12063
  /* @__PURE__ */ jsx6(Text5, { children: line }, i)
@@ -11766,7 +12069,7 @@ var AppDownloadQrCodes = () => {
11766
12069
 
11767
12070
  // src/commands/demo/card-flow.tsx
11768
12071
  import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
11769
- import { useEffect as useEffect4, useRef, useState as useState4 } from "react";
12072
+ import { useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
11770
12073
 
11771
12074
  // src/utils/poll-until-approved.ts
11772
12075
  function pollUntilApproved(repository, id, options = {}) {
@@ -11930,7 +12233,7 @@ var ONBOARD = {
11930
12233
  };
11931
12234
 
11932
12235
  // src/commands/demo/card-flow.tsx
11933
- import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
12236
+ import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
11934
12237
  function formatPmLabel(pm) {
11935
12238
  return `${pm.card_details?.brand ?? pm.type} ****${pm.card_details?.last4 ?? ""}`;
11936
12239
  }
@@ -11955,9 +12258,9 @@ var CardFlow = ({
11955
12258
  const [spendRequest, setSpendRequest] = useState4(null);
11956
12259
  const [error, setError] = useState4("");
11957
12260
  const approvalUrl = spendRequest?.approval_url ?? "";
11958
- const enterResolver = useRef(null);
11959
- const pmResolver = useRef(null);
11960
- const retryChoiceResolver = useRef(null);
12261
+ const enterResolver = useRef2(null);
12262
+ const pmResolver = useRef2(null);
12263
+ const retryChoiceResolver = useRef2(null);
11961
12264
  function waitForEnter() {
11962
12265
  return new Promise((resolve) => {
11963
12266
  enterResolver.current = resolve;
@@ -12005,7 +12308,7 @@ var CardFlow = ({
12005
12308
  }
12006
12309
  }
12007
12310
  });
12008
- const started = useRef(false);
12311
+ const started = useRef2(false);
12009
12312
  useEffect4(() => {
12010
12313
  if (started.current) return;
12011
12314
  started.current = true;
@@ -12056,7 +12359,12 @@ var CardFlow = ({
12056
12359
  setStep("await-approval");
12057
12360
  for (; ; ) {
12058
12361
  try {
12059
- await pollUntilApproved(spendRequestRepo2, result.id);
12362
+ const final = await pollUntilApproved(spendRequestRepo2, result.id);
12363
+ if (final.status !== "approved") {
12364
+ throw new Error(
12365
+ `Spend request did not reach approved (status: ${final.status})`
12366
+ );
12367
+ }
12060
12368
  break;
12061
12369
  } catch (err) {
12062
12370
  if (err.message === "Approval polling timed out") {
@@ -12109,21 +12417,21 @@ var CardFlow = ({
12109
12417
  ];
12110
12418
  return order.indexOf(step) > order.indexOf(target);
12111
12419
  };
12112
- const prompt = (label = "Press [Enter] to continue") => /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
12420
+ const prompt = (label = "Press [Enter] to continue") => /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
12113
12421
  "\n",
12114
12422
  ">",
12115
12423
  " ",
12116
12424
  label
12117
12425
  ] });
12118
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", gap: 1, children: [
12426
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
12119
12427
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "cyan", children: CARD_FLOW.title }),
12120
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12121
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "row", gap: 1, children: [
12428
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12429
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", gap: 1, children: [
12122
12430
  /* @__PURE__ */ jsx7(Text6, { color: "yellow", children: "[testmode]" }),
12123
12431
  /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: DEMO_MERCHANT_URL })
12124
12432
  ] }),
12125
12433
  /* @__PURE__ */ jsx7(MarkdownText, { children: CARD_FLOW.intro.description }),
12126
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
12434
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
12127
12435
  /* @__PURE__ */ jsx7(Text6, { children: "What happens:" }),
12128
12436
  CARD_FLOW.intro.steps.map((s, i) => {
12129
12437
  const doneAfter = [
@@ -12140,17 +12448,17 @@ var CardFlow = ({
12140
12448
  ];
12141
12449
  const done = pastStep(doneAfter[i]);
12142
12450
  const active = !done && (step === activeFrom[i] || pastStep(activeFrom[i]));
12143
- return done ? /* @__PURE__ */ jsxs4(Text6, { dimColor: true, strikethrough: true, children: [
12451
+ return done ? /* @__PURE__ */ jsxs5(Text6, { dimColor: true, strikethrough: true, children: [
12144
12452
  " ",
12145
12453
  i + 1,
12146
12454
  ". ",
12147
12455
  s
12148
- ] }, s) : active ? /* @__PURE__ */ jsxs4(Text6, { bold: true, color: "cyan", children: [
12456
+ ] }, s) : active ? /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "cyan", children: [
12149
12457
  " ",
12150
12458
  i + 1,
12151
12459
  ". ",
12152
12460
  s
12153
- ] }, s) : /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
12461
+ ] }, s) : /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
12154
12462
  " ",
12155
12463
  i + 1,
12156
12464
  ". ",
@@ -12161,23 +12469,23 @@ var CardFlow = ({
12161
12469
  step === "intro" && prompt(CARD_FLOW.intro.prompt)
12162
12470
  ] }),
12163
12471
  step === "fetch-pm" && /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "Fetching payment methods from your Link wallet..." }) }),
12164
- (step === "pick-pm" || step === "explain-pm") && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12165
- step === "pick-pm" && paymentMethods.length > 1 && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12472
+ (step === "pick-pm" || step === "explain-pm") && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12473
+ step === "pick-pm" && paymentMethods.length > 1 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12166
12474
  /* @__PURE__ */ jsx7(Text6, { children: "Which payment method should we use for the demo?" }),
12167
- /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", marginTop: 1, children: paymentMethods.map((pm, i) => /* @__PURE__ */ jsx7(Text6, { children: i === selectedPmIndex ? /* @__PURE__ */ jsxs4(Text6, { color: "cyan", bold: true, children: [
12475
+ /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", marginTop: 1, children: paymentMethods.map((pm, i) => /* @__PURE__ */ jsx7(Text6, { children: i === selectedPmIndex ? /* @__PURE__ */ jsxs5(Text6, { color: "cyan", bold: true, children: [
12168
12476
  ">",
12169
12477
  " ",
12170
12478
  formatPmLabel(pm),
12171
12479
  pm.is_default ? " (default)" : ""
12172
- ] }) : /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
12480
+ ] }) : /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
12173
12481
  " ",
12174
12482
  formatPmLabel(pm),
12175
12483
  pm.is_default ? " (default)" : ""
12176
12484
  ] }) }, pm.id)) }),
12177
12485
  /* @__PURE__ */ jsx7(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "Use \u2191\u2193 to select, [Enter] to confirm" }) })
12178
12486
  ] }),
12179
- paymentMethod && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12180
- /* @__PURE__ */ jsxs4(Text6, { color: "green", children: [
12487
+ paymentMethod && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12488
+ /* @__PURE__ */ jsxs5(Text6, { color: "green", children: [
12181
12489
  "\u2713 Using ",
12182
12490
  /* @__PURE__ */ jsx7(Text6, { bold: true, children: pmLabel }),
12183
12491
  paymentMethod.is_default ? " (default)" : ""
@@ -12185,19 +12493,19 @@ var CardFlow = ({
12185
12493
  step === "explain-pm" && prompt(CARD_FLOW.explainPm.prompt)
12186
12494
  ] })
12187
12495
  ] }),
12188
- step === "create-spend" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12496
+ step === "create-spend" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12189
12497
  /* @__PURE__ */ jsx7(MarkdownText, { children: CARD_FLOW.createSpend.description }),
12190
12498
  /* @__PURE__ */ jsx7(Box5, { marginY: 1, children: /* @__PURE__ */ jsx7(Text6, { color: "cyan", children: CARD_FLOW.createSpend.loading }) })
12191
12499
  ] }),
12192
- (step === "await-approval" || step === "approval-timeout") && spendRequest && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12193
- /* @__PURE__ */ jsxs4(Text6, { color: "green", children: [
12500
+ (step === "await-approval" || step === "approval-timeout") && spendRequest && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12501
+ /* @__PURE__ */ jsxs5(Text6, { color: "green", children: [
12194
12502
  "\u2713 Spend request created (",
12195
12503
  spendRequest.id,
12196
12504
  ")"
12197
12505
  ] }),
12198
- step === "await-approval" && /* @__PURE__ */ jsxs4(Fragment, { children: [
12506
+ step === "await-approval" && /* @__PURE__ */ jsxs5(Fragment, { children: [
12199
12507
  /* @__PURE__ */ jsx7(Text6, { children: CARD_FLOW.approval.description }),
12200
- /* @__PURE__ */ jsxs4(
12508
+ /* @__PURE__ */ jsxs5(
12201
12509
  Box5,
12202
12510
  {
12203
12511
  flexDirection: "column",
@@ -12207,7 +12515,7 @@ var CardFlow = ({
12207
12515
  paddingY: 1,
12208
12516
  marginTop: 1,
12209
12517
  children: [
12210
- /* @__PURE__ */ jsxs4(Text6, { children: [
12518
+ /* @__PURE__ */ jsxs5(Text6, { children: [
12211
12519
  "Approve at:",
12212
12520
  " ",
12213
12521
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "cyan", children: approvalUrl })
@@ -12218,9 +12526,9 @@ var CardFlow = ({
12218
12526
  ),
12219
12527
  /* @__PURE__ */ jsx7(Box5, { marginY: 1, children: /* @__PURE__ */ jsx7(Text6, { color: "cyan", children: CARD_FLOW.approval.loading }) })
12220
12528
  ] }),
12221
- step === "approval-timeout" && /* @__PURE__ */ jsxs4(Fragment, { children: [
12529
+ step === "approval-timeout" && /* @__PURE__ */ jsxs5(Fragment, { children: [
12222
12530
  /* @__PURE__ */ jsx7(Text6, { color: "yellow", children: "\u26A0 Approval timed out (5 min). Still pending \u2014 you can still approve." }),
12223
- /* @__PURE__ */ jsxs4(
12531
+ /* @__PURE__ */ jsxs5(
12224
12532
  Box5,
12225
12533
  {
12226
12534
  flexDirection: "column",
@@ -12230,7 +12538,7 @@ var CardFlow = ({
12230
12538
  paddingY: 1,
12231
12539
  marginTop: 1,
12232
12540
  children: [
12233
- /* @__PURE__ */ jsxs4(Text6, { children: [
12541
+ /* @__PURE__ */ jsxs5(Text6, { children: [
12234
12542
  "Approve at:",
12235
12543
  " ",
12236
12544
  /* @__PURE__ */ jsx7(Text6, { bold: true, color: "cyan", children: approvalUrl })
@@ -12242,10 +12550,10 @@ var CardFlow = ({
12242
12550
  /* @__PURE__ */ jsx7(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "r Retry polling q Quit demo" }) })
12243
12551
  ] })
12244
12552
  ] }),
12245
- (step === "show-card" || step === "open-url" || step === "done") && card && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12553
+ (step === "show-card" || step === "open-url" || step === "done") && card && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12246
12554
  /* @__PURE__ */ jsx7(Text6, { color: "green", children: "\u2713 Approved!" }),
12247
12555
  /* @__PURE__ */ jsx7(MarkdownText, { children: CARD_FLOW.showCard.description }),
12248
- /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", paddingX: 2, marginTop: 1, children: /* @__PURE__ */ jsxs4(Text6, { children: [
12556
+ /* @__PURE__ */ jsx7(Box5, { flexDirection: "column", paddingX: 2, marginTop: 1, children: /* @__PURE__ */ jsxs5(Text6, { children: [
12249
12557
  /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "Number " }),
12250
12558
  /* @__PURE__ */ jsx7(Text6, { bold: true, children: formatCardNumber(card.number) }),
12251
12559
  " ",
@@ -12254,14 +12562,14 @@ var CardFlow = ({
12254
12562
  " ",
12255
12563
  /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "CVC " }),
12256
12564
  /* @__PURE__ */ jsx7(Text6, { bold: true, children: card.cvc }),
12257
- card.billing_address?.postal_code && /* @__PURE__ */ jsxs4(Fragment, { children: [
12565
+ card.billing_address?.postal_code && /* @__PURE__ */ jsxs5(Fragment, { children: [
12258
12566
  " ",
12259
12567
  /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "Zip " }),
12260
12568
  /* @__PURE__ */ jsx7(Text6, { bold: true, children: card.billing_address.postal_code })
12261
12569
  ] }),
12262
- card.valid_until && /* @__PURE__ */ jsxs4(Fragment, { children: [
12570
+ card.valid_until && /* @__PURE__ */ jsxs5(Fragment, { children: [
12263
12571
  " ",
12264
- /* @__PURE__ */ jsxs4(Text6, { dimColor: true, children: [
12572
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
12265
12573
  "expires",
12266
12574
  " ",
12267
12575
  new Date(card.valid_until).toLocaleTimeString([], {
@@ -12273,14 +12581,14 @@ var CardFlow = ({
12273
12581
  ] }) }),
12274
12582
  step === "show-card" && prompt(CARD_FLOW.showCard.prompt)
12275
12583
  ] }),
12276
- step === "done" && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12277
- /* @__PURE__ */ jsxs4(Text6, { color: "green", children: [
12584
+ step === "done" && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
12585
+ /* @__PURE__ */ jsxs5(Text6, { color: "green", children: [
12278
12586
  "\u2713 ",
12279
12587
  CARD_FLOW.done.success
12280
12588
  ] }),
12281
12589
  /* @__PURE__ */ jsx7(Text6, { children: CARD_FLOW.done.detail })
12282
12590
  ] }),
12283
- step === "error" && /* @__PURE__ */ jsxs4(Text6, { color: "red", children: [
12591
+ step === "error" && /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
12284
12592
  "Error: ",
12285
12593
  error
12286
12594
  ] })
@@ -12289,20 +12597,20 @@ var CardFlow = ({
12289
12597
 
12290
12598
  // src/commands/demo/spt-flow.tsx
12291
12599
  import { Box as Box7, Text as Text8, useInput as useInput3 } from "ink";
12292
- import { useEffect as useEffect6, useRef as useRef2, useState as useState6 } from "react";
12600
+ import { useEffect as useEffect6, useRef as useRef3, useState as useState6 } from "react";
12293
12601
 
12294
12602
  // src/commands/mpp/decode.ts
12295
12603
  import { Challenge } from "mppx";
12296
- function getString(value, path5, required = true) {
12604
+ function getString(value, path7, required = true) {
12297
12605
  if (value == null) {
12298
12606
  if (required) {
12299
- throw new Error(`Invalid stripe challenge request: ${path5}: missing`);
12607
+ throw new Error(`Invalid stripe challenge request: ${path7}: missing`);
12300
12608
  }
12301
12609
  return void 0;
12302
12610
  }
12303
12611
  if (typeof value !== "string") {
12304
12612
  throw new Error(
12305
- `Invalid stripe challenge request: ${path5}: expected string, received ${typeof value}`
12613
+ `Invalid stripe challenge request: ${path7}: expected string, received ${typeof value}`
12306
12614
  );
12307
12615
  }
12308
12616
  return value;
@@ -12371,12 +12679,12 @@ function decodeStripeChallenge(challengeHeader) {
12371
12679
 
12372
12680
  // src/commands/mpp/pay.tsx
12373
12681
  import { Box as Box6, Text as Text7 } from "ink";
12374
- import Spinner2 from "ink-spinner";
12682
+ import Spinner3 from "ink-spinner";
12375
12683
  import { Credential, Method } from "mppx";
12376
12684
  import { Mppx, Transport } from "mppx/client";
12377
12685
  import { Methods as StripeMethods } from "mppx/stripe";
12378
12686
  import { useEffect as useEffect5, useState as useState5 } from "react";
12379
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
12687
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
12380
12688
  function buildHeaders(data, headers) {
12381
12689
  const result = {};
12382
12690
  if (data !== void 0) {
@@ -12391,14 +12699,9 @@ function buildHeaders(data, headers) {
12391
12699
  }
12392
12700
  return result;
12393
12701
  }
12394
- async function readPayResult(response, options) {
12702
+ async function readPayResult(response) {
12395
12703
  const responseHeaders = Object.fromEntries(response.headers.entries());
12396
12704
  const body = await response.text();
12397
- if (options?.failOnError && !response.ok) {
12398
- throw new Error(
12399
- `Payment submission failed with status ${response.status}: ${body}`
12400
- );
12401
- }
12402
12705
  return { status: response.status, headers: responseHeaders, body };
12403
12706
  }
12404
12707
  function createStripePaymentClient(spt) {
@@ -12470,7 +12773,7 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
12470
12773
  Authorization: authHeader
12471
12774
  }
12472
12775
  });
12473
- return readPayResult(retryResponse, { failOnError: true });
12776
+ return readPayResult(retryResponse);
12474
12777
  }
12475
12778
  function MppPay({
12476
12779
  url,
@@ -12518,9 +12821,10 @@ function MppPay({
12518
12821
  headers: requestHeaders
12519
12822
  });
12520
12823
  if (initialResponse.status !== 402) {
12521
- setResult(await readPayResult(initialResponse));
12824
+ const payResult2 = await readPayResult(initialResponse);
12825
+ setResult(payResult2);
12522
12826
  setStep("done");
12523
- onComplete();
12827
+ onComplete(payResult2);
12524
12828
  return;
12525
12829
  }
12526
12830
  setStep("signing");
@@ -12536,12 +12840,13 @@ function MppPay({
12536
12840
  Authorization: authHeader
12537
12841
  }
12538
12842
  });
12539
- setResult(await readPayResult(retryResponse, { failOnError: true }));
12843
+ const payResult = await readPayResult(retryResponse);
12844
+ setResult(payResult);
12540
12845
  setStep("done");
12541
- onComplete();
12846
+ onComplete(payResult);
12542
12847
  } catch (err) {
12543
12848
  setError(err.message);
12544
- onComplete();
12849
+ onComplete(null);
12545
12850
  }
12546
12851
  })();
12547
12852
  }, [url, spendRequestId, method, data, headers, repository, onComplete]);
@@ -12553,30 +12858,36 @@ function MppPay({
12553
12858
  done: "Done"
12554
12859
  };
12555
12860
  if (error) {
12556
- return /* @__PURE__ */ jsxs5(Text7, { color: "red", children: [
12861
+ return /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
12557
12862
  "Error: ",
12558
12863
  error
12559
12864
  ] });
12560
12865
  }
12561
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12562
- step !== "done" && /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsxs5(Text7, { color: "cyan", children: [
12563
- /* @__PURE__ */ jsx8(Spinner2, { type: "dots" }),
12866
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
12867
+ step !== "done" && /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsxs6(Text7, { color: "cyan", children: [
12868
+ /* @__PURE__ */ jsx8(Spinner3, { type: "dots" }),
12564
12869
  " ",
12565
12870
  stepLabels[step],
12566
12871
  "..."
12567
12872
  ] }) }),
12568
- result && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12569
- /* @__PURE__ */ jsxs5(Text7, { color: "green", children: [
12570
- "HTTP ",
12571
- result.status
12572
- ] }),
12873
+ result && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
12874
+ /* @__PURE__ */ jsxs6(
12875
+ Text7,
12876
+ {
12877
+ color: result.status >= 400 ? "red" : result.status >= 300 ? "yellow" : "green",
12878
+ children: [
12879
+ "HTTP ",
12880
+ result.status
12881
+ ]
12882
+ }
12883
+ ),
12573
12884
  /* @__PURE__ */ jsx8(Text7, { children: result.body })
12574
12885
  ] })
12575
12886
  ] });
12576
12887
  }
12577
12888
 
12578
12889
  // src/commands/demo/spt-flow.tsx
12579
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
12890
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
12580
12891
  var SptFlow = ({
12581
12892
  spendRequestRepo: spendRequestRepo2,
12582
12893
  paymentMethodsResource,
@@ -12591,9 +12902,9 @@ var SptFlow = ({
12591
12902
  const [payResult, setPayResult] = useState6(null);
12592
12903
  const [error, setError] = useState6("");
12593
12904
  const approvalUrl = spendRequest?.approval_url ?? "";
12594
- const enterResolver = useRef2(null);
12595
- const pmResolver = useRef2(null);
12596
- const retryChoiceResolver = useRef2(null);
12905
+ const enterResolver = useRef3(null);
12906
+ const pmResolver = useRef3(null);
12907
+ const retryChoiceResolver = useRef3(null);
12597
12908
  function waitForEnter() {
12598
12909
  return new Promise((resolve) => {
12599
12910
  enterResolver.current = resolve;
@@ -12641,7 +12952,7 @@ var SptFlow = ({
12641
12952
  }
12642
12953
  }
12643
12954
  });
12644
- const started = useRef2(false);
12955
+ const started = useRef3(false);
12645
12956
  useEffect6(() => {
12646
12957
  if (started.current) return;
12647
12958
  started.current = true;
@@ -12701,6 +13012,11 @@ var SptFlow = ({
12701
13012
  spendRequestRepo2,
12702
13013
  result.id
12703
13014
  );
13015
+ if (approved.status !== "approved") {
13016
+ throw new Error(
13017
+ `Spend request did not reach approved (status: ${approved.status})`
13018
+ );
13019
+ }
12704
13020
  setSpendRequest(approved);
12705
13021
  break;
12706
13022
  } catch (err) {
@@ -12754,25 +13070,25 @@ var SptFlow = ({
12754
13070
  ];
12755
13071
  return order.indexOf(step) > order.indexOf(target);
12756
13072
  };
12757
- const prompt = (label = "Press [Enter] to continue") => /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
13073
+ const prompt = (label = "Press [Enter] to continue") => /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
12758
13074
  "\n",
12759
13075
  ">",
12760
13076
  " ",
12761
13077
  label
12762
13078
  ] });
12763
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", gap: 1, children: [
13079
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", gap: 1, children: [
12764
13080
  /* @__PURE__ */ jsx9(Text8, { bold: true, color: "cyan", children: SPT_FLOW.title }),
12765
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
12766
- /* @__PURE__ */ jsxs6(Box7, { flexDirection: "row", gap: 1, children: [
13081
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
13082
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", gap: 1, children: [
12767
13083
  /* @__PURE__ */ jsx9(Text8, { color: "yellow", children: "[testmode]" }),
12768
- /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
13084
+ /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
12769
13085
  DEMO_CLIMATE_API_URL,
12770
13086
  " ",
12771
13087
  DEMO_MPP_DEV_URL
12772
13088
  ] })
12773
13089
  ] }),
12774
13090
  /* @__PURE__ */ jsx9(MarkdownText, { children: SPT_FLOW.intro.description }),
12775
- /* @__PURE__ */ jsxs6(Box7, { marginTop: 1, flexDirection: "column", children: [
13091
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
12776
13092
  /* @__PURE__ */ jsx9(Text8, { children: SPT_FLOW.intro.preamble }),
12777
13093
  SPT_FLOW.intro.steps.map((s, i) => {
12778
13094
  const doneAfter = [
@@ -12792,17 +13108,17 @@ var SptFlow = ({
12792
13108
  const done = pastStep(doneAfter[i]);
12793
13109
  const active = !done && (step === activeFrom[i] || pastStep(activeFrom[i]));
12794
13110
  const label = s.replace(/`/g, "");
12795
- return done ? /* @__PURE__ */ jsxs6(Text8, { dimColor: true, strikethrough: true, children: [
13111
+ return done ? /* @__PURE__ */ jsxs7(Text8, { dimColor: true, strikethrough: true, children: [
12796
13112
  " ",
12797
13113
  i + 1,
12798
13114
  ". ",
12799
13115
  label
12800
- ] }, s) : active ? /* @__PURE__ */ jsxs6(Text8, { bold: true, color: "cyan", children: [
13116
+ ] }, s) : active ? /* @__PURE__ */ jsxs7(Text8, { bold: true, color: "cyan", children: [
12801
13117
  " ",
12802
13118
  i + 1,
12803
13119
  ". ",
12804
13120
  label
12805
- ] }, s) : /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
13121
+ ] }, s) : /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
12806
13122
  " ",
12807
13123
  i + 1,
12808
13124
  ". ",
@@ -12813,45 +13129,45 @@ var SptFlow = ({
12813
13129
  step === "intro" && prompt(SPT_FLOW.intro.prompt)
12814
13130
  ] }),
12815
13131
  step === "fetch-pm" && /* @__PURE__ */ jsx9(Box7, { marginY: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "cyan", children: "Fetching payment methods..." }) }),
12816
- step === "pick-pm" && paymentMethods.length > 1 && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
13132
+ step === "pick-pm" && paymentMethods.length > 1 && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
12817
13133
  /* @__PURE__ */ jsx9(Text8, { children: "Which payment method should we use for the demo?" }),
12818
- /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginTop: 1, children: paymentMethods.map((pm, i) => /* @__PURE__ */ jsx9(Text8, { children: i === selectedPmIndex ? /* @__PURE__ */ jsxs6(Text8, { color: "cyan", bold: true, children: [
13134
+ /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginTop: 1, children: paymentMethods.map((pm, i) => /* @__PURE__ */ jsx9(Text8, { children: i === selectedPmIndex ? /* @__PURE__ */ jsxs7(Text8, { color: "cyan", bold: true, children: [
12819
13135
  ">",
12820
13136
  " ",
12821
13137
  pm.card_details ? `${pm.card_details.brand} ****${pm.card_details.last4}` : pm.type,
12822
13138
  pm.is_default ? " (default)" : ""
12823
- ] }) : /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
13139
+ ] }) : /* @__PURE__ */ jsxs7(Text8, { dimColor: true, children: [
12824
13140
  " ",
12825
13141
  pm.card_details ? `${pm.card_details.brand} ****${pm.card_details.last4}` : pm.type,
12826
13142
  pm.is_default ? " (default)" : ""
12827
13143
  ] }) }, pm.id)) }),
12828
13144
  /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "Use \u2191\u2193 to select, [Enter] to confirm" }) })
12829
13145
  ] }),
12830
- (step === "probe" || step === "explain-402") && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
13146
+ (step === "probe" || step === "explain-402") && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
12831
13147
  /* @__PURE__ */ jsx9(MarkdownText, { children: SPT_FLOW.probe.description }),
12832
13148
  step === "probe" && /* @__PURE__ */ jsx9(Box7, { marginY: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "cyan", children: SPT_FLOW.probe.loading }) }),
12833
- step === "explain-402" && networkId && /* @__PURE__ */ jsxs6(Fragment2, { children: [
13149
+ step === "explain-402" && networkId && /* @__PURE__ */ jsxs7(Fragment2, { children: [
12834
13150
  /* @__PURE__ */ jsx9(MarkdownText, { children: SPT_FLOW.probe.detail }),
12835
- /* @__PURE__ */ jsxs6(Text8, { color: "green", children: [
13151
+ /* @__PURE__ */ jsxs7(Text8, { color: "green", children: [
12836
13152
  "\u2713 Got HTTP 402 \u2014 network_id: ",
12837
13153
  /* @__PURE__ */ jsx9(Text8, { bold: true, children: networkId })
12838
13154
  ] }),
12839
13155
  prompt(SPT_FLOW.probe.prompt)
12840
13156
  ] })
12841
13157
  ] }),
12842
- step === "create-spend" && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
13158
+ step === "create-spend" && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
12843
13159
  /* @__PURE__ */ jsx9(MarkdownText, { children: SPT_FLOW.createSpend.description }),
12844
13160
  /* @__PURE__ */ jsx9(Box7, { marginY: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "cyan", children: SPT_FLOW.createSpend.loading }) })
12845
13161
  ] }),
12846
- (step === "await-approval" || step === "approval-timeout") && spendRequest && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
12847
- /* @__PURE__ */ jsxs6(Text8, { color: "green", children: [
13162
+ (step === "await-approval" || step === "approval-timeout") && spendRequest && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
13163
+ /* @__PURE__ */ jsxs7(Text8, { color: "green", children: [
12848
13164
  "\u2713 Spend request created (",
12849
13165
  spendRequest.id,
12850
13166
  ")"
12851
13167
  ] }),
12852
- step === "await-approval" && /* @__PURE__ */ jsxs6(Fragment2, { children: [
13168
+ step === "await-approval" && /* @__PURE__ */ jsxs7(Fragment2, { children: [
12853
13169
  /* @__PURE__ */ jsx9(Text8, { children: SPT_FLOW.approval.description }),
12854
- /* @__PURE__ */ jsxs6(
13170
+ /* @__PURE__ */ jsxs7(
12855
13171
  Box7,
12856
13172
  {
12857
13173
  flexDirection: "column",
@@ -12861,7 +13177,7 @@ var SptFlow = ({
12861
13177
  paddingY: 1,
12862
13178
  marginTop: 1,
12863
13179
  children: [
12864
- /* @__PURE__ */ jsxs6(Text8, { children: [
13180
+ /* @__PURE__ */ jsxs7(Text8, { children: [
12865
13181
  "Approve at:",
12866
13182
  " ",
12867
13183
  /* @__PURE__ */ jsx9(Text8, { bold: true, color: "cyan", children: approvalUrl })
@@ -12872,9 +13188,9 @@ var SptFlow = ({
12872
13188
  ),
12873
13189
  /* @__PURE__ */ jsx9(Box7, { marginY: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "cyan", children: SPT_FLOW.approval.loading }) })
12874
13190
  ] }),
12875
- step === "approval-timeout" && /* @__PURE__ */ jsxs6(Fragment2, { children: [
13191
+ step === "approval-timeout" && /* @__PURE__ */ jsxs7(Fragment2, { children: [
12876
13192
  /* @__PURE__ */ jsx9(Text8, { color: "yellow", children: "\u26A0 Approval timed out (5 min). Still pending \u2014 you can still approve." }),
12877
- /* @__PURE__ */ jsxs6(
13193
+ /* @__PURE__ */ jsxs7(
12878
13194
  Box7,
12879
13195
  {
12880
13196
  flexDirection: "column",
@@ -12884,7 +13200,7 @@ var SptFlow = ({
12884
13200
  paddingY: 1,
12885
13201
  marginTop: 1,
12886
13202
  children: [
12887
- /* @__PURE__ */ jsxs6(Text8, { children: [
13203
+ /* @__PURE__ */ jsxs7(Text8, { children: [
12888
13204
  "Approve at:",
12889
13205
  " ",
12890
13206
  /* @__PURE__ */ jsx9(Text8, { bold: true, color: "cyan", children: approvalUrl })
@@ -12896,14 +13212,14 @@ var SptFlow = ({
12896
13212
  /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: "r Retry polling q Quit demo" }) })
12897
13213
  ] })
12898
13214
  ] }),
12899
- (step === "mpp-pay-gate" || step === "mpp-pay") && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
13215
+ (step === "mpp-pay-gate" || step === "mpp-pay") && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
12900
13216
  /* @__PURE__ */ jsx9(Text8, { color: "green", children: "\u2713 Approved!" }),
12901
13217
  /* @__PURE__ */ jsx9(MarkdownText, { children: SPT_FLOW.mppPay.description }),
12902
13218
  step === "mpp-pay-gate" && prompt(SPT_FLOW.mppPay.prompt),
12903
13219
  step === "mpp-pay" && /* @__PURE__ */ jsx9(Box7, { marginY: 1, children: /* @__PURE__ */ jsx9(Text8, { color: "cyan", children: SPT_FLOW.mppPay.loading }) })
12904
13220
  ] }),
12905
- step === "done" && payResult && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
12906
- /* @__PURE__ */ jsxs6(Text8, { bold: true, color: "green", children: [
13221
+ step === "done" && payResult && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
13222
+ /* @__PURE__ */ jsxs7(Text8, { bold: true, color: "green", children: [
12907
13223
  "\u2713 ",
12908
13224
  SPT_FLOW.done.success,
12909
13225
  " (HTTP ",
@@ -12912,7 +13228,7 @@ var SptFlow = ({
12912
13228
  ] }),
12913
13229
  /* @__PURE__ */ jsx9(MarkdownText, { children: SPT_FLOW.done.detail })
12914
13230
  ] }),
12915
- step === "error" && /* @__PURE__ */ jsxs6(Text8, { color: "red", children: [
13231
+ step === "error" && /* @__PURE__ */ jsxs7(Text8, { color: "red", children: [
12916
13232
  "Error: ",
12917
13233
  error
12918
13234
  ] })
@@ -12920,22 +13236,25 @@ var SptFlow = ({
12920
13236
  };
12921
13237
 
12922
13238
  // src/commands/demo/demo-runner.tsx
12923
- import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
13239
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
12924
13240
  var DemoRunner = ({
12925
13241
  authRepo: authRepo2,
12926
13242
  spendRequestRepo: spendRequestRepo2,
12927
13243
  paymentMethodsResource,
13244
+ authStorage: authStorage2 = storage,
12928
13245
  paymentMethodId: preselectedPmId,
12929
13246
  onlyCard,
12930
13247
  onlySpt,
12931
13248
  onComplete
12932
13249
  }) => {
13250
+ const { exit } = useApp();
13251
+ const storage2 = authStorage2;
12933
13252
  const preselected = onlyCard ? "card" : onlySpt ? "spt" : null;
12934
13253
  const [choice, setChoice] = useState7(preselected);
12935
13254
  const [menuIndex, setMenuIndex] = useState7(0);
12936
13255
  const postAuthPhase = preselected === "spt" ? "spt-flow" : preselected ? "card-flow" : "menu";
12937
13256
  const [phase, setPhase] = useState7(
12938
- storage.isAuthenticated() ? postAuthPhase : "auth"
13257
+ storage2.isAuthenticated() ? postAuthPhase : "auth"
12939
13258
  );
12940
13259
  const [paymentMethodId, setPaymentMethodId] = useState7(
12941
13260
  preselectedPmId ?? ""
@@ -12959,29 +13278,35 @@ var DemoRunner = ({
12959
13278
  setPhase("spt-flow");
12960
13279
  }
12961
13280
  });
12962
- const onCardComplete = useCallback(
13281
+ const onCardComplete = useCallback2(
12963
13282
  (result) => {
12964
13283
  setPaymentMethodId(result.paymentMethodId);
12965
13284
  setCardSuccess(result.success);
12966
13285
  if (!runSpt) {
12967
13286
  setPhase("summary");
12968
- setTimeout(onComplete, 1500);
13287
+ setTimeout(() => {
13288
+ onComplete();
13289
+ exit();
13290
+ }, DISPLAY_DELAY_MS);
12969
13291
  } else {
12970
13292
  setPhase("card-done");
12971
13293
  }
12972
13294
  },
12973
- [runSpt, onComplete]
13295
+ [runSpt, onComplete, exit]
12974
13296
  );
12975
- const onSptComplete = useCallback(
13297
+ const onSptComplete = useCallback2(
12976
13298
  (success) => {
12977
13299
  setSptSuccess(success);
12978
13300
  setPhase("summary");
12979
- setTimeout(onComplete, 1500);
13301
+ setTimeout(() => {
13302
+ onComplete();
13303
+ exit();
13304
+ }, DISPLAY_DELAY_MS);
12980
13305
  },
12981
- [onComplete]
13306
+ [onComplete, exit]
12982
13307
  );
12983
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", gap: 1, children: [
12984
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
13308
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
13309
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
12985
13310
  /* @__PURE__ */ jsx10(Text9, { bold: true, children: DEMO_MENU.title }),
12986
13311
  /* @__PURE__ */ jsx10(Text9, { children: DEMO_MENU.subtitle })
12987
13312
  ] }),
@@ -12990,22 +13315,23 @@ var DemoRunner = ({
12990
13315
  {
12991
13316
  authResource: authRepo2,
12992
13317
  clientName: ONBOARD.auth.clientName,
13318
+ authStorage: storage2,
12993
13319
  onComplete: () => setPhase(postAuthPhase)
12994
13320
  }
12995
13321
  ),
12996
- phase === "menu" && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
13322
+ phase === "menu" && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
12997
13323
  /* @__PURE__ */ jsx10(Text9, { children: DEMO_MENU.question }),
12998
- /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", marginTop: 1, gap: 1, children: DEMO_MENU.options.map((opt, i) => /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: i === menuIndex ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
12999
- /* @__PURE__ */ jsxs7(Text9, { color: "cyan", bold: true, children: [
13324
+ /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", marginTop: 1, gap: 1, children: DEMO_MENU.options.map((opt, i) => /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", children: i === menuIndex ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
13325
+ /* @__PURE__ */ jsxs8(Text9, { color: "cyan", bold: true, children: [
13000
13326
  ">",
13001
13327
  " ",
13002
13328
  opt.label
13003
13329
  ] }),
13004
- /* @__PURE__ */ jsxs7(Text9, { color: "cyan", children: [
13330
+ /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
13005
13331
  " ",
13006
13332
  opt.description
13007
13333
  ] })
13008
- ] }) : /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
13334
+ ] }) : /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
13009
13335
  " ",
13010
13336
  opt.label
13011
13337
  ] }) }, opt.key)) }),
@@ -13020,17 +13346,17 @@ var DemoRunner = ({
13020
13346
  onComplete: onCardComplete
13021
13347
  }
13022
13348
  ),
13023
- phase === "card-done" && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
13349
+ phase === "card-done" && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
13024
13350
  /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u2500\u2500\u2500" }),
13025
13351
  /* @__PURE__ */ jsx10(MarkdownText, { children: DEMO_MENU.transition }),
13026
- /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
13352
+ /* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
13027
13353
  "\n",
13028
13354
  ">",
13029
13355
  " ",
13030
13356
  DEMO_MENU.transitionPrompt
13031
13357
  ] })
13032
13358
  ] }),
13033
- runSpt && (phase === "spt-flow" || phase === "summary") && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
13359
+ runSpt && (phase === "spt-flow" || phase === "summary") && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
13034
13360
  runCard && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u2500\u2500\u2500" }),
13035
13361
  /* @__PURE__ */ jsx10(
13036
13362
  SptFlow,
@@ -13042,14 +13368,14 @@ var DemoRunner = ({
13042
13368
  }
13043
13369
  )
13044
13370
  ] }),
13045
- phase === "summary" && /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
13371
+ phase === "summary" && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
13046
13372
  /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "\u2500\u2500\u2500" }),
13047
13373
  /* @__PURE__ */ jsx10(Text9, { bold: true, children: "Done!" }),
13048
- cardSuccess !== null && /* @__PURE__ */ jsxs7(Text9, { color: cardSuccess ? "green" : "red", children: [
13374
+ cardSuccess !== null && /* @__PURE__ */ jsxs8(Text9, { color: cardSuccess ? "green" : "red", children: [
13049
13375
  cardSuccess ? "\u2713" : "\u2717",
13050
13376
  " Virtual card flow"
13051
13377
  ] }),
13052
- sptSuccess !== null && /* @__PURE__ */ jsxs7(Text9, { color: sptSuccess ? "green" : "red", children: [
13378
+ sptSuccess !== null && /* @__PURE__ */ jsxs8(Text9, { color: sptSuccess ? "green" : "red", children: [
13053
13379
  sptSuccess ? "\u2713" : "\u2717",
13054
13380
  " Machine payment flow"
13055
13381
  ] }),
@@ -13064,7 +13390,7 @@ var demoOptions = z2.object({
13064
13390
  onlyCard: z2.boolean().default(false).describe("Run only the virtual card flow"),
13065
13391
  onlySpt: z2.boolean().default(false).describe("Run only the machine payment (SPT) flow")
13066
13392
  });
13067
- function createDemoCli(authRepo2, spendRequestRepo2, createPaymentMethodsResource) {
13393
+ function createDemoCli(authRepo2, spendRequestRepo2, createPaymentMethodsResource, authStorage2) {
13068
13394
  return Cli2.create("demo", {
13069
13395
  description: "Run an interactive demo of both Link payment flows (virtual card + machine payment)",
13070
13396
  options: demoOptions,
@@ -13077,48 +13403,71 @@ function createDemoCli(authRepo2, spendRequestRepo2, createPaymentMethodsResourc
13077
13403
  });
13078
13404
  }
13079
13405
  const paymentMethodsResource = createPaymentMethodsResource();
13080
- return new Promise((resolve) => {
13081
- const { waitUntilExit, unmount } = render2(
13082
- /* @__PURE__ */ jsx11(
13083
- DemoRunner,
13084
- {
13085
- authRepo: authRepo2,
13086
- spendRequestRepo: spendRequestRepo2,
13087
- paymentMethodsResource,
13088
- onlyCard: c.options.onlyCard,
13089
- onlySpt: c.options.onlySpt,
13090
- onComplete: () => unmount()
13406
+ return renderInteractive(
13407
+ /* @__PURE__ */ jsx11(
13408
+ DemoRunner,
13409
+ {
13410
+ authRepo: authRepo2,
13411
+ spendRequestRepo: spendRequestRepo2,
13412
+ paymentMethodsResource,
13413
+ authStorage: authStorage2,
13414
+ onlyCard: c.options.onlyCard,
13415
+ onlySpt: c.options.onlySpt,
13416
+ onComplete: () => {
13091
13417
  }
13092
- )
13093
- );
13094
- waitUntilExit().then(() => resolve({}));
13095
- });
13418
+ }
13419
+ ),
13420
+ () => ({})
13421
+ );
13096
13422
  }
13097
13423
  });
13098
13424
  }
13099
13425
 
13100
13426
  // src/commands/mpp/index.tsx
13101
13427
  import { Cli as Cli3, z as z4 } from "incur";
13102
- import { render as render3 } from "ink";
13428
+
13429
+ // src/utils/require-auth.ts
13430
+ var NOT_AUTHENTICATED_ERROR = {
13431
+ code: "NOT_AUTHENTICATED",
13432
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13433
+ cta: {
13434
+ commands: [{ command: "auth login", description: "Log in to Link" }]
13435
+ }
13436
+ };
13437
+ function requireAuth(authStorage2) {
13438
+ const store = authStorage2 ?? storage;
13439
+ return (c, next) => {
13440
+ if (!store.isAuthenticated()) {
13441
+ return c.error(NOT_AUTHENTICATED_ERROR);
13442
+ }
13443
+ return next();
13444
+ };
13445
+ }
13446
+ function requireAuthGuard(c, authStorage2) {
13447
+ const store = authStorage2 ?? storage;
13448
+ if (!store.isAuthenticated()) {
13449
+ c.error(NOT_AUTHENTICATED_ERROR);
13450
+ }
13451
+ }
13103
13452
 
13104
13453
  // src/commands/mpp/decode-view.tsx
13105
13454
  import { Box as Box9, Text as Text10 } from "ink";
13106
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
13455
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
13107
13456
  function DecodeChallengeView({
13108
13457
  decoded
13109
13458
  }) {
13110
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
13459
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
13111
13460
  /* @__PURE__ */ jsx12(Text10, { color: "green", children: "\u2713 Stripe challenge decoded" }),
13112
- /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13113
- /* @__PURE__ */ jsxs8(Text10, { children: [
13461
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13462
+ /* @__PURE__ */ jsxs9(Text10, { children: [
13114
13463
  "ID: ",
13115
13464
  /* @__PURE__ */ jsx12(Text10, { bold: true, children: decoded.id })
13116
13465
  ] }),
13117
- /* @__PURE__ */ jsxs8(Text10, { children: [
13466
+ /* @__PURE__ */ jsxs9(Text10, { children: [
13118
13467
  "Realm: ",
13119
13468
  /* @__PURE__ */ jsx12(Text10, { bold: true, children: decoded.realm })
13120
13469
  ] }),
13121
- /* @__PURE__ */ jsxs8(Text10, { children: [
13470
+ /* @__PURE__ */ jsxs9(Text10, { children: [
13122
13471
  "Network ID: ",
13123
13472
  /* @__PURE__ */ jsx12(Text10, { bold: true, children: decoded.network_id })
13124
13473
  ] }),
@@ -13146,7 +13495,7 @@ var decodeOptions = z3.object({
13146
13495
 
13147
13496
  // src/commands/mpp/index.tsx
13148
13497
  import { jsx as jsx13 } from "react/jsx-runtime";
13149
- function createMppCli(repository) {
13498
+ function createMppCli(repository, authStorage2) {
13150
13499
  const cli2 = Cli3.create("mpp", {
13151
13500
  description: "Machine payment protocol (MPP) commands"
13152
13501
  });
@@ -13158,53 +13507,36 @@ function createMppCli(repository) {
13158
13507
  options: payOptions,
13159
13508
  alias: { method: "X", data: "d", header: "H" },
13160
13509
  outputPolicy: "agent-only",
13510
+ middleware: [requireAuth(authStorage2)],
13161
13511
  async run(c) {
13162
- if (!storage.isAuthenticated()) {
13163
- return c.error({
13164
- code: "NOT_AUTHENTICATED",
13165
- message: 'Not authenticated. Run "link-cli auth login" first.',
13166
- cta: {
13167
- commands: [
13168
- { command: "auth login", description: "Log in to Link" }
13169
- ]
13170
- }
13171
- });
13172
- }
13173
13512
  const url = c.args.url;
13174
13513
  const opts = c.options;
13175
13514
  const method = opts.method;
13176
13515
  const data = opts.data;
13177
13516
  const headers = opts.header?.length ? opts.header : void 0;
13178
13517
  if (!c.agent && !c.formatExplicit) {
13179
- return new Promise((resolve) => {
13180
- const { waitUntilExit } = render3(
13181
- /* @__PURE__ */ jsx13(
13182
- MppPay,
13183
- {
13184
- url,
13185
- spendRequestId: opts.spendRequestId,
13186
- method,
13187
- data,
13188
- headers,
13189
- repository,
13190
- onComplete: () => {
13191
- }
13518
+ let capturedResult = null;
13519
+ return renderInteractive(
13520
+ /* @__PURE__ */ jsx13(
13521
+ MppPay,
13522
+ {
13523
+ url,
13524
+ spendRequestId: opts.spendRequestId,
13525
+ method,
13526
+ data,
13527
+ headers,
13528
+ repository,
13529
+ onComplete: (result) => {
13530
+ capturedResult = result;
13192
13531
  }
13193
- )
13194
- );
13195
- waitUntilExit().then(async () => {
13196
- resolve(
13197
- await runMppPay(
13198
- url,
13199
- opts.spendRequestId,
13200
- method,
13201
- data,
13202
- headers,
13203
- repository
13204
- )
13205
- );
13206
- });
13207
- });
13532
+ }
13533
+ ),
13534
+ () => {
13535
+ if (!capturedResult)
13536
+ throw new Error("Component exited without producing a result");
13537
+ return capturedResult;
13538
+ }
13539
+ );
13208
13540
  }
13209
13541
  return runMppPay(
13210
13542
  url,
@@ -13223,12 +13555,10 @@ function createMppCli(repository) {
13223
13555
  async run(c) {
13224
13556
  const decoded = decodeStripeChallenge(c.options.challenge);
13225
13557
  if (!c.agent && !c.formatExplicit) {
13226
- return new Promise((resolve) => {
13227
- const { waitUntilExit } = render3(
13228
- /* @__PURE__ */ jsx13(DecodeChallengeView, { decoded })
13229
- );
13230
- waitUntilExit().then(() => resolve(decoded));
13231
- });
13558
+ return renderInteractive(
13559
+ /* @__PURE__ */ jsx13(DecodeChallengeView, { decoded }),
13560
+ () => decoded
13561
+ );
13232
13562
  }
13233
13563
  return decoded;
13234
13564
  }
@@ -13238,29 +13568,31 @@ function createMppCli(repository) {
13238
13568
 
13239
13569
  // src/commands/onboard/index.tsx
13240
13570
  import { Cli as Cli4 } from "incur";
13241
- import { render as render4 } from "ink";
13242
13571
 
13243
13572
  // src/commands/onboard/onboard-runner.tsx
13244
- import { Box as Box10, Text as Text11, useInput as useInput5 } from "ink";
13245
- import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
13246
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
13573
+ import { Box as Box10, Text as Text11, useApp as useApp2, useInput as useInput5 } from "ink";
13574
+ import { useEffect as useEffect7, useRef as useRef4, useState as useState8 } from "react";
13575
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
13247
13576
  var OnboardRunner = ({
13248
13577
  authRepo: authRepo2,
13249
13578
  spendRequestRepo: spendRequestRepo2,
13250
13579
  paymentMethodsResource,
13580
+ authStorage: authStorage2 = storage,
13251
13581
  onComplete
13252
13582
  }) => {
13583
+ const { exit } = useApp2();
13584
+ const storage2 = authStorage2;
13253
13585
  const [phase, setPhase] = useState8("welcome");
13254
13586
  const [authSkipped, setAuthSkipped] = useState8(false);
13255
13587
  const [pmMissing, setPmMissing] = useState8(false);
13256
13588
  const [error, setError] = useState8("");
13257
- const enterResolver = useRef3(null);
13589
+ const enterResolver = useRef4(null);
13258
13590
  function waitForEnter() {
13259
13591
  return new Promise((resolve) => {
13260
13592
  enterResolver.current = resolve;
13261
13593
  });
13262
13594
  }
13263
- const authResolver = useRef3(null);
13595
+ const authResolver = useRef4(null);
13264
13596
  function waitForAuth() {
13265
13597
  return new Promise((resolve) => {
13266
13598
  authResolver.current = resolve;
@@ -13273,14 +13605,14 @@ var OnboardRunner = ({
13273
13605
  resolve();
13274
13606
  }
13275
13607
  });
13276
- const started = useRef3(false);
13608
+ const started = useRef4(false);
13277
13609
  useEffect7(() => {
13278
13610
  if (started.current) return;
13279
13611
  started.current = true;
13280
13612
  const run = async () => {
13281
13613
  try {
13282
13614
  setPhase("auth");
13283
- if (storage.isAuthenticated()) {
13615
+ if (storage2.isAuthenticated()) {
13284
13616
  setAuthSkipped(true);
13285
13617
  } else {
13286
13618
  setAuthSkipped(false);
@@ -13297,6 +13629,7 @@ var OnboardRunner = ({
13297
13629
  setPhase("demo");
13298
13630
  } catch (err) {
13299
13631
  setError(err.message);
13632
+ setTimeout(exit, 2e3);
13300
13633
  }
13301
13634
  };
13302
13635
  run();
@@ -13305,33 +13638,34 @@ var OnboardRunner = ({
13305
13638
  const order = ["welcome", "auth", "payment-methods", "demo"];
13306
13639
  return order.indexOf(phase) > order.indexOf(target);
13307
13640
  };
13308
- const prompt = (label = "Press [Enter] to continue") => /* @__PURE__ */ jsxs9(Text11, { dimColor: true, children: [
13641
+ const prompt = (label = "Press [Enter] to continue") => /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
13309
13642
  "\n",
13310
13643
  ">",
13311
13644
  " ",
13312
13645
  label
13313
13646
  ] });
13314
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", gap: 1, children: [
13315
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
13647
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
13648
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
13316
13649
  /* @__PURE__ */ jsx14(Text11, { bold: true, children: ONBOARD.title }),
13317
13650
  /* @__PURE__ */ jsx14(Text11, { children: ONBOARD.subtitle })
13318
13651
  ] }),
13319
- /* @__PURE__ */ jsx14(Box10, { flexDirection: "column", children: authSkipped || pastPhase("auth") ? /* @__PURE__ */ jsxs9(Text11, { color: "green", children: [
13652
+ /* @__PURE__ */ jsx14(Box10, { flexDirection: "column", children: authSkipped || pastPhase("auth") ? /* @__PURE__ */ jsxs10(Text11, { color: "green", children: [
13320
13653
  "\u2713 ",
13321
13654
  authSkipped ? ONBOARD.auth.alreadyLoggedIn : ONBOARD.auth.authenticated
13322
- ] }) : phase === "auth" && !storage.isAuthenticated() ? /* @__PURE__ */ jsx14(
13655
+ ] }) : phase === "auth" && !storage2.isAuthenticated() ? /* @__PURE__ */ jsx14(
13323
13656
  Login,
13324
13657
  {
13325
13658
  authResource: authRepo2,
13326
13659
  clientName: ONBOARD.auth.clientName,
13660
+ authStorage: storage2,
13327
13661
  onComplete: () => authResolver.current?.()
13328
13662
  }
13329
13663
  ) : null }),
13330
- pastPhase("auth") && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
13664
+ pastPhase("auth") && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
13331
13665
  phase === "payment-methods" && !pmMissing && /* @__PURE__ */ jsx14(Text11, { color: "cyan", children: ONBOARD.paymentMethods.loading }),
13332
- pmMissing && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
13666
+ pmMissing && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
13333
13667
  /* @__PURE__ */ jsx14(Text11, { color: "yellow", children: ONBOARD.paymentMethods.missing }),
13334
- /* @__PURE__ */ jsx14(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text11, { children: [
13668
+ /* @__PURE__ */ jsx14(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { children: [
13335
13669
  "Visit",
13336
13670
  " ",
13337
13671
  /* @__PURE__ */ jsx14(Text11, { bold: true, color: "cyan", children: "app.link.com/wallet" }),
@@ -13342,7 +13676,7 @@ var OnboardRunner = ({
13342
13676
  ] }),
13343
13677
  pastPhase("payment-methods") && /* @__PURE__ */ jsx14(Text11, { color: "green", children: "\u2713 Payment method found" })
13344
13678
  ] }),
13345
- phase === "demo" && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
13679
+ phase === "demo" && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
13346
13680
  /* @__PURE__ */ jsx14(Text11, { dimColor: true, children: "\u2500\u2500\u2500" }),
13347
13681
  /* @__PURE__ */ jsx14(
13348
13682
  DemoRunner,
@@ -13350,11 +13684,12 @@ var OnboardRunner = ({
13350
13684
  authRepo: authRepo2,
13351
13685
  spendRequestRepo: spendRequestRepo2,
13352
13686
  paymentMethodsResource,
13687
+ authStorage: storage2,
13353
13688
  onComplete
13354
13689
  }
13355
13690
  )
13356
13691
  ] }),
13357
- error && /* @__PURE__ */ jsxs9(Text11, { color: "red", children: [
13692
+ error && /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
13358
13693
  "Error: ",
13359
13694
  error
13360
13695
  ] })
@@ -13363,7 +13698,7 @@ var OnboardRunner = ({
13363
13698
 
13364
13699
  // src/commands/onboard/index.tsx
13365
13700
  import { jsx as jsx15 } from "react/jsx-runtime";
13366
- function createOnboardCli(authRepo2, spendRequestRepo2, createPaymentMethodsResource) {
13701
+ function createOnboardCli(authRepo2, spendRequestRepo2, createPaymentMethodsResource, authStorage2) {
13367
13702
  return Cli4.create("onboard", {
13368
13703
  description: "Guided setup: authenticate, verify payment methods, and demo both payment flows",
13369
13704
  outputPolicy: "agent-only",
@@ -13375,43 +13710,42 @@ function createOnboardCli(authRepo2, spendRequestRepo2, createPaymentMethodsReso
13375
13710
  });
13376
13711
  }
13377
13712
  const paymentMethodsResource = createPaymentMethodsResource();
13378
- return new Promise((resolve) => {
13379
- const { waitUntilExit, unmount } = render4(
13380
- /* @__PURE__ */ jsx15(
13381
- OnboardRunner,
13382
- {
13383
- authRepo: authRepo2,
13384
- spendRequestRepo: spendRequestRepo2,
13385
- paymentMethodsResource,
13386
- onComplete: () => unmount()
13713
+ return renderInteractive(
13714
+ /* @__PURE__ */ jsx15(
13715
+ OnboardRunner,
13716
+ {
13717
+ authRepo: authRepo2,
13718
+ spendRequestRepo: spendRequestRepo2,
13719
+ paymentMethodsResource,
13720
+ authStorage: authStorage2,
13721
+ onComplete: () => {
13387
13722
  }
13388
- )
13389
- );
13390
- waitUntilExit().then(() => resolve({}));
13391
- });
13723
+ }
13724
+ ),
13725
+ () => ({})
13726
+ );
13392
13727
  }
13393
13728
  });
13394
13729
  }
13395
13730
 
13396
13731
  // src/commands/payment-methods/index.tsx
13397
13732
  import { Cli as Cli5 } from "incur";
13398
- import { render as render5 } from "ink";
13399
13733
 
13400
13734
  // src/commands/payment-methods/add.tsx
13401
- import { Box as Box11, Text as Text12, useApp, useInput as useInput6 } from "ink";
13402
- import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
13735
+ import { Box as Box11, Text as Text12, useApp as useApp3, useInput as useInput6 } from "ink";
13736
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
13403
13737
  var WALLET_URL = "https://app.link.com/wallet";
13404
13738
  var AddPaymentMethod = () => {
13405
- const { exit } = useApp();
13739
+ const { exit } = useApp3();
13406
13740
  useInput6((_input, key) => {
13407
13741
  if (key.return) {
13408
13742
  openUrl(WALLET_URL);
13409
13743
  exit();
13410
13744
  }
13411
13745
  });
13412
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", paddingY: 1, children: [
13746
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingY: 1, children: [
13413
13747
  /* @__PURE__ */ jsx16(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx16(Text12, { bold: true, children: "Add Payment Method" }) }),
13414
- /* @__PURE__ */ jsxs10(
13748
+ /* @__PURE__ */ jsxs11(
13415
13749
  Box11,
13416
13750
  {
13417
13751
  flexDirection: "column",
@@ -13420,7 +13754,7 @@ var AddPaymentMethod = () => {
13420
13754
  paddingX: 2,
13421
13755
  paddingY: 1,
13422
13756
  children: [
13423
- /* @__PURE__ */ jsxs10(Text12, { children: [
13757
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13424
13758
  "Open:",
13425
13759
  " ",
13426
13760
  /* @__PURE__ */ jsx16(Text12, { bold: true, color: "cyan", children: WALLET_URL })
@@ -13434,55 +13768,37 @@ var AddPaymentMethod = () => {
13434
13768
 
13435
13769
  // src/commands/payment-methods/list.tsx
13436
13770
  import { Box as Box12, Text as Text13 } from "ink";
13437
- import Spinner3 from "ink-spinner";
13438
- import { useEffect as useEffect8, useState as useState9 } from "react";
13439
- import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
13771
+ import Spinner4 from "ink-spinner";
13772
+ import { useCallback as useCallback3 } from "react";
13773
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
13440
13774
  var PaymentMethodsList = ({
13441
13775
  resource,
13442
13776
  onComplete
13443
13777
  }) => {
13444
- const [status, setStatus] = useState9(
13445
- "loading"
13446
- );
13447
- const [methods, setMethods] = useState9([]);
13448
- const [error, setError] = useState9("");
13449
- useEffect8(() => {
13450
- const fetch2 = async () => {
13451
- try {
13452
- const result = await resource.listPaymentMethods();
13453
- setMethods(result);
13454
- setStatus("success");
13455
- setTimeout(onComplete, 1500);
13456
- } catch (err) {
13457
- setError(err.message);
13458
- setStatus("error");
13459
- setTimeout(onComplete, 1500);
13460
- }
13461
- };
13462
- fetch2();
13463
- }, [resource, onComplete]);
13778
+ const action = useCallback3(() => resource.listPaymentMethods(), [resource]);
13779
+ const { status, data: methods, error } = useAsyncAction(action, onComplete);
13464
13780
  if (status === "loading") {
13465
- return /* @__PURE__ */ jsx17(Box12, { children: /* @__PURE__ */ jsxs11(Text13, { color: "cyan", children: [
13466
- /* @__PURE__ */ jsx17(Spinner3, { type: "dots" }),
13781
+ return /* @__PURE__ */ jsx17(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
13782
+ /* @__PURE__ */ jsx17(Spinner4, { type: "dots" }),
13467
13783
  " Loading payment methods..."
13468
13784
  ] }) });
13469
13785
  }
13470
13786
  if (status === "error") {
13471
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13787
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
13472
13788
  /* @__PURE__ */ jsx17(Text13, { color: "red", children: "\u2717 Failed to load payment methods" }),
13473
13789
  /* @__PURE__ */ jsx17(Text13, { color: "red", children: error })
13474
13790
  ] });
13475
13791
  }
13476
- if (methods.length === 0) {
13792
+ if (!methods || methods.length === 0) {
13477
13793
  return /* @__PURE__ */ jsx17(Box12, { children: /* @__PURE__ */ jsx17(Text13, { dimColor: true, children: "No payment methods found" }) });
13478
13794
  }
13479
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13795
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
13480
13796
  /* @__PURE__ */ jsx17(Text13, { bold: true, children: "Payment Methods" }),
13481
13797
  /* @__PURE__ */ jsx17(Box12, { flexDirection: "column", marginTop: 1, children: methods.map((pm) => {
13482
13798
  const label = pm.card_details?.brand ?? pm.bank_account_details?.bank_name ?? "Bank account";
13483
13799
  const last4 = pm.card_details?.last4 ?? pm.bank_account_details?.last4;
13484
- const suffix = [pm.nickname ? `(${pm.nickname})` : ""].filter(Boolean).join(" ");
13485
- return /* @__PURE__ */ jsx17(Box12, { paddingX: 2, children: /* @__PURE__ */ jsxs11(Text13, { children: [
13800
+ const suffix = pm.nickname ? `(${pm.nickname})` : "";
13801
+ return /* @__PURE__ */ jsx17(Box12, { paddingX: 2, children: /* @__PURE__ */ jsxs12(Text13, { children: [
13486
13802
  /* @__PURE__ */ jsx17(Text13, { dimColor: true, children: pm.id }),
13487
13803
  " ",
13488
13804
  label,
@@ -13497,36 +13813,22 @@ var PaymentMethodsList = ({
13497
13813
 
13498
13814
  // src/commands/payment-methods/index.tsx
13499
13815
  import { jsx as jsx18 } from "react/jsx-runtime";
13500
- function createPaymentMethodsCli(createResource) {
13816
+ function createPaymentMethodsCli(createResource, authStorage2) {
13501
13817
  const cli2 = Cli5.create("payment-methods", {
13502
13818
  description: "Payment methods management commands"
13503
13819
  });
13504
13820
  cli2.command("list", {
13505
13821
  description: "List all payment methods on your account",
13506
13822
  outputPolicy: "agent-only",
13823
+ middleware: [requireAuth(authStorage2)],
13507
13824
  async run(c) {
13508
- if (!storage.isAuthenticated()) {
13509
- return c.error({
13510
- code: "NOT_AUTHENTICATED",
13511
- message: 'Not authenticated. Run "link-cli auth login" first.',
13512
- cta: {
13513
- commands: [
13514
- { command: "auth login", description: "Log in to Link" }
13515
- ]
13516
- }
13517
- });
13518
- }
13519
13825
  const resource = createResource();
13520
13826
  if (!c.agent && !c.formatExplicit) {
13521
- return new Promise((resolve) => {
13522
- const { waitUntilExit } = render5(
13523
- /* @__PURE__ */ jsx18(PaymentMethodsList, { resource, onComplete: () => {
13524
- } })
13525
- );
13526
- waitUntilExit().then(async () => {
13527
- resolve(await resource.listPaymentMethods());
13528
- });
13529
- });
13827
+ return renderInteractive(
13828
+ /* @__PURE__ */ jsx18(PaymentMethodsList, { resource, onComplete: () => {
13829
+ } }),
13830
+ () => resource.listPaymentMethods()
13831
+ );
13530
13832
  }
13531
13833
  return resource.listPaymentMethods();
13532
13834
  }
@@ -13534,23 +13836,12 @@ function createPaymentMethodsCli(createResource) {
13534
13836
  cli2.command("add", {
13535
13837
  description: "Open the Link wallet to add a new payment method",
13536
13838
  outputPolicy: "agent-only",
13839
+ middleware: [requireAuth(authStorage2)],
13537
13840
  async run(c) {
13538
- if (!storage.isAuthenticated()) {
13539
- return c.error({
13540
- code: "NOT_AUTHENTICATED",
13541
- message: 'Not authenticated. Run "link-cli auth login" first.',
13542
- cta: {
13543
- commands: [
13544
- { command: "auth login", description: "Log in to Link" }
13545
- ]
13546
- }
13547
- });
13548
- }
13549
13841
  if (!c.agent && !c.formatExplicit) {
13550
- return new Promise((resolve) => {
13551
- const { waitUntilExit } = render5(/* @__PURE__ */ jsx18(AddPaymentMethod, {}));
13552
- waitUntilExit().then(() => resolve({ url: WALLET_URL }));
13553
- });
13842
+ return renderInteractive(/* @__PURE__ */ jsx18(AddPaymentMethod, {}), () => ({
13843
+ url: WALLET_URL
13844
+ }));
13554
13845
  }
13555
13846
  return { url: WALLET_URL };
13556
13847
  }
@@ -13558,9 +13849,147 @@ function createPaymentMethodsCli(createResource) {
13558
13849
  return cli2;
13559
13850
  }
13560
13851
 
13852
+ // src/commands/shipping-address/index.tsx
13853
+ import { Cli as Cli6 } from "incur";
13854
+
13855
+ // src/commands/shipping-address/list.tsx
13856
+ import { Box as Box13, Text as Text14 } from "ink";
13857
+ import Spinner5 from "ink-spinner";
13858
+ import { useCallback as useCallback4 } from "react";
13859
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
13860
+ function formatStreetLine(address) {
13861
+ const parts = [address.line_1, address.line_2].filter(Boolean);
13862
+ return parts.length > 0 ? parts.join(", ") : null;
13863
+ }
13864
+ function formatLocalityLine(address) {
13865
+ const locality = [
13866
+ address.dependent_locality,
13867
+ address.locality,
13868
+ address.administrative_area
13869
+ ].filter(Boolean).join(", ");
13870
+ const postal = [address.postal_code, address.sorting_code].filter(Boolean).join(" ");
13871
+ const localityPostal = locality && postal ? `${locality} ${postal}` : locality || postal || null;
13872
+ if (localityPostal && address.country_code) {
13873
+ return `${localityPostal}, ${address.country_code}`;
13874
+ }
13875
+ return localityPostal || address.country_code || null;
13876
+ }
13877
+ function formatAddressLines(addressRecord) {
13878
+ if (!addressRecord.address) {
13879
+ return ["Address details unavailable"];
13880
+ }
13881
+ const address = addressRecord.address;
13882
+ const lines = [formatStreetLine(address), formatLocalityLine(address)].filter(
13883
+ (line) => Boolean(line)
13884
+ );
13885
+ return lines.length > 0 ? lines : ["Address details unavailable"];
13886
+ }
13887
+ var ShippingAddressList = ({
13888
+ resource,
13889
+ onComplete
13890
+ }) => {
13891
+ const action = useCallback4(
13892
+ () => resource.listShippingAddresses(),
13893
+ [resource]
13894
+ );
13895
+ const {
13896
+ status,
13897
+ data: shippingAddresses,
13898
+ error
13899
+ } = useAsyncAction(action, onComplete);
13900
+ if (status === "loading") {
13901
+ return /* @__PURE__ */ jsx19(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
13902
+ /* @__PURE__ */ jsx19(Spinner5, { type: "dots" }),
13903
+ " Loading shipping addresses..."
13904
+ ] }) });
13905
+ }
13906
+ if (status === "error") {
13907
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
13908
+ /* @__PURE__ */ jsx19(Text14, { color: "red", children: "\u2717 Failed to load shipping addresses" }),
13909
+ /* @__PURE__ */ jsx19(Text14, { color: "red", children: error })
13910
+ ] });
13911
+ }
13912
+ if (!shippingAddresses || shippingAddresses.length === 0) {
13913
+ return /* @__PURE__ */ jsx19(Box13, { children: /* @__PURE__ */ jsx19(Text14, { dimColor: true, children: "No shipping addresses found" }) });
13914
+ }
13915
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
13916
+ /* @__PURE__ */ jsx19(Text14, { bold: true, children: "Shipping Addresses" }),
13917
+ /* @__PURE__ */ jsx19(Box13, { flexDirection: "column", marginTop: 1, children: shippingAddresses.map((shippingAddress) => {
13918
+ const addressName = shippingAddress.address?.name;
13919
+ const nickname = shippingAddress.nickname ? ` (${shippingAddress.nickname})` : "";
13920
+ return /* @__PURE__ */ jsxs13(
13921
+ Box13,
13922
+ {
13923
+ flexDirection: "column",
13924
+ paddingX: 2,
13925
+ marginBottom: 1,
13926
+ children: [
13927
+ /* @__PURE__ */ jsxs13(Text14, { children: [
13928
+ /* @__PURE__ */ jsx19(Text14, { dimColor: true, children: shippingAddress.id }),
13929
+ nickname,
13930
+ shippingAddress.is_default ? /* @__PURE__ */ jsx19(Text14, { color: "green", children: " (default)" }) : null
13931
+ ] }),
13932
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
13933
+ addressName ? /* @__PURE__ */ jsx19(Text14, { bold: true, children: addressName }) : null,
13934
+ formatAddressLines(shippingAddress).map((line) => /* @__PURE__ */ jsx19(Text14, { children: line }, `${shippingAddress.id}:${line}`))
13935
+ ] })
13936
+ ]
13937
+ },
13938
+ shippingAddress.id
13939
+ );
13940
+ }) })
13941
+ ] });
13942
+ };
13943
+
13944
+ // src/commands/shipping-address/index.tsx
13945
+ import { jsx as jsx20 } from "react/jsx-runtime";
13946
+ function createShippingAddressCli(createResource, authStorage2) {
13947
+ const cli2 = Cli6.create("shipping-address", {
13948
+ description: "Shipping address management commands"
13949
+ });
13950
+ cli2.command("list", {
13951
+ description: "List all shipping addresses on your account",
13952
+ outputPolicy: "agent-only",
13953
+ middleware: [requireAuth(authStorage2)],
13954
+ async run(c) {
13955
+ const resource = createResource();
13956
+ if (!c.agent && !c.formatExplicit) {
13957
+ return renderInteractive(
13958
+ /* @__PURE__ */ jsx20(ShippingAddressList, { resource, onComplete: () => {
13959
+ } }),
13960
+ () => resource.listShippingAddresses()
13961
+ );
13962
+ }
13963
+ return resource.listShippingAddresses();
13964
+ }
13965
+ });
13966
+ return cli2;
13967
+ }
13968
+
13561
13969
  // src/commands/spend-request/index.tsx
13562
- import { Cli as Cli6, z as z7 } from "incur";
13563
- import { render as render6 } from "ink";
13970
+ import { Cli as Cli7, z as z7 } from "incur";
13971
+
13972
+ // src/utils/credential-output.ts
13973
+ import fs4 from "fs/promises";
13974
+ import path6 from "path";
13975
+ async function writeCredentialFile(filePath, data, force) {
13976
+ const resolved = path6.resolve(filePath);
13977
+ if (!force) {
13978
+ try {
13979
+ await fs4.access(resolved);
13980
+ throw new Error(
13981
+ `OUTPUT_FILE_EXISTS: ${resolved} already exists. Use --force to overwrite.`
13982
+ );
13983
+ } catch (err) {
13984
+ if (err.code !== "ENOENT") throw err;
13985
+ }
13986
+ }
13987
+ await fs4.writeFile(resolved, JSON.stringify(data, null, 2), {
13988
+ mode: 384
13989
+ });
13990
+ await fs4.chmod(resolved, 384);
13991
+ return resolved;
13992
+ }
13564
13993
 
13565
13994
  // src/utils/line-item-parser.ts
13566
13995
  import { z as z5 } from "zod";
@@ -13623,21 +14052,59 @@ function parseTotalFlag(raw) {
13623
14052
  }
13624
14053
  }
13625
14054
 
13626
- // src/commands/spend-request/create.tsx
14055
+ // src/commands/spend-request/cancel.tsx
13627
14056
  import { Box as Box14, Text as Text15 } from "ink";
13628
- import Spinner5 from "ink-spinner";
13629
- import { useCallback as useCallback2, useEffect as useEffect10, useState as useState10 } from "react";
14057
+ import Spinner6 from "ink-spinner";
14058
+ import { useCallback as useCallback5 } from "react";
14059
+ import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
14060
+ var CancelSpendRequest = ({
14061
+ repository,
14062
+ id,
14063
+ onComplete
14064
+ }) => {
14065
+ const action = useCallback5(
14066
+ () => repository.cancelSpendRequest(id),
14067
+ [repository, id]
14068
+ );
14069
+ const { status, data: request, error } = useAsyncAction(action, onComplete);
14070
+ if (status === "loading") {
14071
+ return /* @__PURE__ */ jsx21(Box14, { children: /* @__PURE__ */ jsxs14(Text15, { color: "cyan", children: [
14072
+ /* @__PURE__ */ jsx21(Spinner6, { type: "dots" }),
14073
+ " Canceling spend request ",
14074
+ id,
14075
+ "..."
14076
+ ] }) });
14077
+ }
14078
+ if (status === "error") {
14079
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
14080
+ /* @__PURE__ */ jsx21(Text15, { color: "red", children: "\u2717 Failed to cancel spend request" }),
14081
+ /* @__PURE__ */ jsx21(Text15, { color: "red", children: error })
14082
+ ] });
14083
+ }
14084
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
14085
+ /* @__PURE__ */ jsx21(Text15, { color: "green", children: "\u2713 Spend request canceled" }),
14086
+ /* @__PURE__ */ jsx21(Box14, { flexDirection: "column", marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs14(Text15, { children: [
14087
+ "ID: ",
14088
+ /* @__PURE__ */ jsx21(Text15, { bold: true, children: request?.id })
14089
+ ] }) })
14090
+ ] });
14091
+ };
14092
+
14093
+ // src/commands/spend-request/create.tsx
14094
+ import { Box as Box16, Text as Text17 } from "ink";
14095
+ import Spinner8 from "ink-spinner";
14096
+ import { useCallback as useCallback6, useEffect as useEffect9, useState as useState9 } from "react";
13630
14097
 
13631
14098
  // src/commands/spend-request/approval-waiting-view.tsx
13632
- import { Box as Box13, Text as Text14 } from "ink";
13633
- import Spinner4 from "ink-spinner";
13634
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
14099
+ import { Box as Box15, Text as Text16 } from "ink";
14100
+ import Spinner7 from "ink-spinner";
14101
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
13635
14102
  var ApprovalWaitingView = ({
13636
14103
  status,
13637
14104
  approvalUrl
13638
- }) => /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingY: 1, children: [
13639
- /* @__PURE__ */ jsxs12(
13640
- Box13,
14105
+ }) => /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", paddingY: 1, children: [
14106
+ /* @__PURE__ */ jsxs15(
14107
+ Box15,
13641
14108
  {
13642
14109
  flexDirection: "column",
13643
14110
  borderStyle: "round",
@@ -13645,25 +14112,25 @@ var ApprovalWaitingView = ({
13645
14112
  paddingX: 2,
13646
14113
  paddingY: 1,
13647
14114
  children: [
13648
- /* @__PURE__ */ jsxs12(Text14, { children: [
14115
+ /* @__PURE__ */ jsxs15(Text16, { children: [
13649
14116
  "Approve at:",
13650
14117
  " ",
13651
- /* @__PURE__ */ jsx19(Text14, { bold: true, color: "cyan", children: approvalUrl })
14118
+ /* @__PURE__ */ jsx22(Text16, { bold: true, color: "cyan", children: approvalUrl })
13652
14119
  ] }),
13653
- /* @__PURE__ */ jsx19(Text14, { dimColor: true, children: "Press Enter to open in browser" })
14120
+ /* @__PURE__ */ jsx22(Text16, { dimColor: true, children: "Press Enter to open in browser" })
13654
14121
  ]
13655
14122
  }
13656
14123
  ),
13657
- /* @__PURE__ */ jsx19(AppDownloadQrCodes, {}),
13658
- /* @__PURE__ */ jsx19(Box13, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs12(Text14, { color: "cyan", children: [
13659
- /* @__PURE__ */ jsx19(Spinner4, { type: "dots" }),
14124
+ /* @__PURE__ */ jsx22(AppDownloadQrCodes, {}),
14125
+ /* @__PURE__ */ jsx22(Box15, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs15(Text16, { color: "cyan", children: [
14126
+ /* @__PURE__ */ jsx22(Spinner7, { type: "dots" }),
13660
14127
  " Waiting for approval..."
13661
- ] }) : /* @__PURE__ */ jsx19(Text14, { dimColor: true, children: "Waiting..." }) })
14128
+ ] }) : /* @__PURE__ */ jsx22(Text16, { dimColor: true, children: "Waiting..." }) })
13662
14129
  ] });
13663
14130
 
13664
14131
  // src/commands/spend-request/use-approval-polling.ts
13665
14132
  import { useInput as useInput7 } from "ink";
13666
- import { useEffect as useEffect9 } from "react";
14133
+ import { useEffect as useEffect8 } from "react";
13667
14134
  function useApprovalPolling({
13668
14135
  status,
13669
14136
  setStatus,
@@ -13681,22 +14148,29 @@ function useApprovalPolling({
13681
14148
  },
13682
14149
  { isActive: isWaiting }
13683
14150
  );
13684
- useEffect9(() => {
14151
+ useEffect8(() => {
13685
14152
  if (status !== "waiting") return;
13686
14153
  const timeout = setTimeout(() => setStatus("polling"), 1e3);
13687
14154
  return () => clearTimeout(timeout);
13688
14155
  }, [status, setStatus]);
13689
- useEffect9(() => {
14156
+ useEffect8(() => {
13690
14157
  if (status !== "polling" || !requestId) return;
13691
14158
  let cancelled = false;
13692
14159
  const poll = async () => {
13693
14160
  try {
13694
14161
  const final = await pollUntilApproved(repository, requestId);
13695
- if (!cancelled) {
13696
- onSuccess(final);
13697
- setStatus("success");
13698
- setTimeout(() => onComplete(final), 1e3);
14162
+ if (cancelled) return;
14163
+ if (final.status !== "approved") {
14164
+ onError(
14165
+ `Spend request did not reach approved (status: ${final.status})`
14166
+ );
14167
+ setStatus("error");
14168
+ setTimeout(() => onComplete(final), DISPLAY_DELAY_MS);
14169
+ return;
13699
14170
  }
14171
+ onSuccess(final);
14172
+ setStatus("success");
14173
+ setTimeout(() => onComplete(final), DISPLAY_DELAY_MS);
13700
14174
  } catch (err) {
13701
14175
  if (!cancelled) {
13702
14176
  onError(err.message);
@@ -13720,22 +14194,26 @@ function useApprovalPolling({
13720
14194
  }
13721
14195
 
13722
14196
  // src/commands/spend-request/create.tsx
13723
- import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
14197
+ import { Fragment as Fragment4, jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
13724
14198
  var CreateSpendRequest = ({
13725
14199
  repository,
13726
14200
  params,
13727
14201
  requestApproval = false,
14202
+ outputFile,
14203
+ force,
13728
14204
  onComplete
13729
14205
  }) => {
13730
- const [status, setStatus] = useState10("creating");
13731
- const [request, setRequest] = useState10(null);
13732
- const [error, setError] = useState10("");
14206
+ const [status, setStatus] = useState9("creating");
14207
+ const [request, setRequest] = useState9(null);
14208
+ const [error, setError] = useState9("");
14209
+ const [outputFilePath, setOutputFilePath] = useState9(null);
14210
+ const [fileError, setFileError] = useState9("");
13733
14211
  const approvalUrl = request?.approval_url ?? "";
13734
- const onSuccess = useCallback2(
14212
+ const onSuccess = useCallback6(
13735
14213
  (result) => setRequest(result),
13736
14214
  []
13737
14215
  );
13738
- const onError = useCallback2((msg) => setError(msg), []);
14216
+ const onError = useCallback6((msg) => setError(msg), []);
13739
14217
  useApprovalPolling({
13740
14218
  status,
13741
14219
  setStatus,
@@ -13746,7 +14224,7 @@ var CreateSpendRequest = ({
13746
14224
  onSuccess,
13747
14225
  onError
13748
14226
  });
13749
- useEffect10(() => {
14227
+ useEffect9(() => {
13750
14228
  const create = async () => {
13751
14229
  try {
13752
14230
  const result = await repository.createSpendRequest(params);
@@ -13755,69 +14233,124 @@ var CreateSpendRequest = ({
13755
14233
  setStatus("waiting");
13756
14234
  } else {
13757
14235
  setStatus("success");
13758
- setTimeout(() => onComplete(result), 1500);
14236
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
13759
14237
  }
13760
14238
  } catch (err) {
13761
14239
  setError(err.message);
13762
14240
  setStatus("error");
13763
- setTimeout(() => onComplete(null), 1500);
14241
+ setTimeout(() => onComplete(null), DISPLAY_DELAY_MS);
13764
14242
  }
13765
14243
  };
13766
14244
  create();
13767
14245
  }, [repository, params, requestApproval, onComplete]);
14246
+ useEffect9(() => {
14247
+ if (status !== "success" || !outputFile || !request?.card) return;
14248
+ const fileData = {
14249
+ spend_request_id: request.id,
14250
+ merchant_name: request.merchant_name,
14251
+ merchant_url: request.merchant_url,
14252
+ context: request.context,
14253
+ created_at: request.created_at,
14254
+ card: request.card
14255
+ };
14256
+ writeCredentialFile(outputFile, fileData, force ?? false).then((path7) => setOutputFilePath(path7)).catch((err) => setFileError(err.message));
14257
+ }, [status, outputFile, force, request]);
13768
14258
  if (status === "creating") {
13769
- return /* @__PURE__ */ jsx20(Box14, { children: /* @__PURE__ */ jsxs13(Text15, { color: "cyan", children: [
13770
- /* @__PURE__ */ jsx20(Spinner5, { type: "dots" }),
14259
+ return /* @__PURE__ */ jsx23(Box16, { children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
14260
+ /* @__PURE__ */ jsx23(Spinner8, { type: "dots" }),
13771
14261
  " Creating spend request..."
13772
14262
  ] }) });
13773
14263
  }
13774
14264
  if (status === "error") {
13775
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
13776
- /* @__PURE__ */ jsx20(Text15, { color: "red", children: "\u2717 Failed to create spend request" }),
13777
- /* @__PURE__ */ jsx20(Text15, { color: "red", children: error })
14265
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
14266
+ /* @__PURE__ */ jsx23(Text17, { color: "red", children: "\u2717 Failed to create spend request" }),
14267
+ /* @__PURE__ */ jsx23(Text17, { color: "red", children: error })
13778
14268
  ] });
13779
14269
  }
13780
14270
  if (status === "success") {
13781
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
13782
- /* @__PURE__ */ jsx20(Text15, { color: "green", children: "\u2713 Spend request created" }),
13783
- /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13784
- /* @__PURE__ */ jsxs13(Text15, { children: [
14271
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
14272
+ /* @__PURE__ */ jsx23(Text17, { color: "green", children: "\u2713 Spend request created" }),
14273
+ /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14274
+ /* @__PURE__ */ jsxs16(Text17, { children: [
13785
14275
  "ID: ",
13786
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request?.id })
14276
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request?.id })
13787
14277
  ] }),
13788
- /* @__PURE__ */ jsxs13(Text15, { children: [
14278
+ /* @__PURE__ */ jsxs16(Text17, { children: [
13789
14279
  "Status: ",
13790
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request?.status })
14280
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request?.status })
13791
14281
  ] }),
13792
- /* @__PURE__ */ jsxs13(Text15, { children: [
14282
+ /* @__PURE__ */ jsxs16(Text17, { children: [
13793
14283
  "Amount:",
13794
14284
  " ",
13795
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request?.amount != null ? `${request.amount} ${request.currency?.toUpperCase() ?? ""}`.trim() : "N/A" })
14285
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request?.amount != null ? `${request.amount} ${request.currency?.toUpperCase() ?? ""}`.trim() : "N/A" })
13796
14286
  ] }),
13797
- /* @__PURE__ */ jsxs13(Text15, { children: [
14287
+ /* @__PURE__ */ jsxs16(Text17, { children: [
13798
14288
  "Merchant: ",
13799
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request?.merchant_name })
14289
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request?.merchant_name })
13800
14290
  ] }),
13801
- /* @__PURE__ */ jsxs13(Text15, { children: [
14291
+ /* @__PURE__ */ jsxs16(Text17, { children: [
13802
14292
  "Line Items:",
13803
14293
  " ",
13804
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
14294
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
13805
14295
  ] }),
13806
- request?.credential_type === "shared_payment_token" && request.shared_payment_token && /* @__PURE__ */ jsxs13(Text15, { children: [
14296
+ request?.credential_type === "shared_payment_token" && request.shared_payment_token && /* @__PURE__ */ jsxs16(Text17, { children: [
13807
14297
  "Token: ",
13808
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request.shared_payment_token.id })
14298
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request.shared_payment_token.id })
14299
+ ] })
14300
+ ] }),
14301
+ request?.card && !outputFile && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginTop: 1, children: [
14302
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: "Card Details:" }),
14303
+ /* @__PURE__ */ jsxs16(Text17, { children: [
14304
+ " ",
14305
+ "Number: ",
14306
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request.card.number })
14307
+ ] }),
14308
+ /* @__PURE__ */ jsxs16(Text17, { children: [
14309
+ " ",
14310
+ "Brand: ",
14311
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request.card.brand })
14312
+ ] }),
14313
+ /* @__PURE__ */ jsxs16(Text17, { children: [
14314
+ " ",
14315
+ "Expiry:",
14316
+ " ",
14317
+ /* @__PURE__ */ jsxs16(Text17, { bold: true, children: [
14318
+ String(request.card.exp_month).padStart(2, "0"),
14319
+ "/",
14320
+ request.card.exp_year
14321
+ ] })
14322
+ ] }),
14323
+ request.card.cvc && /* @__PURE__ */ jsxs16(Text17, { children: [
14324
+ " ",
14325
+ "CVC: ",
14326
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request.card.cvc })
14327
+ ] }),
14328
+ request.card.valid_until && /* @__PURE__ */ jsxs16(Text17, { children: [
14329
+ " ",
14330
+ "Valid Until: ",
14331
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request.card.valid_until })
14332
+ ] })
14333
+ ] }),
14334
+ request?.card && outputFile && /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", marginTop: 1, children: [
14335
+ outputFilePath && /* @__PURE__ */ jsxs16(Text17, { color: "green", children: [
14336
+ "Card credentials written to ",
14337
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: outputFilePath })
14338
+ ] }),
14339
+ fileError && /* @__PURE__ */ jsxs16(Text17, { color: "red", children: [
14340
+ "Failed to write card file: ",
14341
+ fileError
13809
14342
  ] })
13810
14343
  ] }),
13811
- /* @__PURE__ */ jsx20(AppDownloadQrCodes, {})
14344
+ /* @__PURE__ */ jsx23(AppDownloadQrCodes, {})
13812
14345
  ] });
13813
14346
  }
13814
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
13815
- /* @__PURE__ */ jsx20(Box14, { children: /* @__PURE__ */ jsxs13(Text15, { color: "green", children: [
14347
+ return /* @__PURE__ */ jsxs16(Fragment4, { children: [
14348
+ /* @__PURE__ */ jsx23(Box16, { children: /* @__PURE__ */ jsxs16(Text17, { color: "green", children: [
13816
14349
  "\u2713 Spend request created (ID: ",
13817
- /* @__PURE__ */ jsx20(Text15, { bold: true, children: request?.id }),
14350
+ /* @__PURE__ */ jsx23(Text17, { bold: true, children: request?.id }),
13818
14351
  ")"
13819
14352
  ] }) }),
13820
- /* @__PURE__ */ jsx20(
14353
+ /* @__PURE__ */ jsx23(
13821
14354
  ApprovalWaitingView,
13822
14355
  {
13823
14356
  status,
@@ -13828,21 +14361,21 @@ var CreateSpendRequest = ({
13828
14361
  };
13829
14362
 
13830
14363
  // src/commands/spend-request/request-approval.tsx
13831
- import { Box as Box15, Text as Text16 } from "ink";
13832
- import Spinner6 from "ink-spinner";
13833
- import { useCallback as useCallback3, useEffect as useEffect11, useState as useState11 } from "react";
13834
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
14364
+ import { Box as Box17, Text as Text18 } from "ink";
14365
+ import Spinner9 from "ink-spinner";
14366
+ import { useCallback as useCallback7, useEffect as useEffect10, useState as useState10 } from "react";
14367
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
13835
14368
  var RequestApproval = ({
13836
14369
  repository,
13837
14370
  id,
13838
14371
  onComplete
13839
14372
  }) => {
13840
- const [status, setStatus] = useState11("requesting");
13841
- const [approvalUrl, setApprovalUrl] = useState11("");
13842
- const [result, setResult] = useState11(null);
13843
- const [error, setError] = useState11("");
13844
- const onSuccess = useCallback3((r) => setResult(r), []);
13845
- const onError = useCallback3((msg) => setError(msg), []);
14373
+ const [status, setStatus] = useState10("requesting");
14374
+ const [approvalUrl, setApprovalUrl] = useState10("");
14375
+ const [result, setResult] = useState10(null);
14376
+ const [error, setError] = useState10("");
14377
+ const onSuccess = useCallback7((r) => setResult(r), []);
14378
+ const onError = useCallback7((msg) => setError(msg), []);
13846
14379
  useApprovalPolling({
13847
14380
  status,
13848
14381
  setStatus,
@@ -13853,7 +14386,7 @@ var RequestApproval = ({
13853
14386
  onSuccess,
13854
14387
  onError
13855
14388
  });
13856
- useEffect11(() => {
14389
+ useEffect10(() => {
13857
14390
  const request = async () => {
13858
14391
  try {
13859
14392
  const res = await repository.requestApproval(id);
@@ -13867,46 +14400,46 @@ var RequestApproval = ({
13867
14400
  request();
13868
14401
  }, [repository, id]);
13869
14402
  if (status === "requesting") {
13870
- return /* @__PURE__ */ jsx21(Box15, { children: /* @__PURE__ */ jsxs14(Text16, { color: "cyan", children: [
13871
- /* @__PURE__ */ jsx21(Spinner6, { type: "dots" }),
14403
+ return /* @__PURE__ */ jsx24(Box17, { children: /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
14404
+ /* @__PURE__ */ jsx24(Spinner9, { type: "dots" }),
13872
14405
  " Requesting approval..."
13873
14406
  ] }) });
13874
14407
  }
13875
14408
  if (status === "error") {
13876
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
13877
- /* @__PURE__ */ jsx21(Text16, { color: "red", children: "\u2717 Failed to request approval" }),
13878
- /* @__PURE__ */ jsx21(Text16, { color: "red", children: error })
14409
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
14410
+ /* @__PURE__ */ jsx24(Text18, { color: "red", children: "\u2717 Failed to request approval" }),
14411
+ /* @__PURE__ */ jsx24(Text18, { color: "red", children: error })
13879
14412
  ] });
13880
14413
  }
13881
14414
  if (status === "success") {
13882
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
13883
- /* @__PURE__ */ jsx21(Text16, { color: "green", children: "\u2713 Approval completed" }),
13884
- /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13885
- /* @__PURE__ */ jsxs14(Text16, { children: [
14415
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
14416
+ /* @__PURE__ */ jsx24(Text18, { color: "green", children: "\u2713 Approval completed" }),
14417
+ /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14418
+ /* @__PURE__ */ jsxs17(Text18, { children: [
13886
14419
  "ID: ",
13887
- /* @__PURE__ */ jsx21(Text16, { bold: true, children: result?.id })
14420
+ /* @__PURE__ */ jsx24(Text18, { bold: true, children: result?.id })
13888
14421
  ] }),
13889
- /* @__PURE__ */ jsxs14(Text16, { children: [
14422
+ /* @__PURE__ */ jsxs17(Text18, { children: [
13890
14423
  "Status: ",
13891
- /* @__PURE__ */ jsx21(Text16, { bold: true, children: result?.status })
14424
+ /* @__PURE__ */ jsx24(Text18, { bold: true, children: result?.status })
13892
14425
  ] }),
13893
- /* @__PURE__ */ jsxs14(Text16, { children: [
14426
+ /* @__PURE__ */ jsxs17(Text18, { children: [
13894
14427
  "Amount:",
13895
14428
  " ",
13896
- /* @__PURE__ */ jsx21(Text16, { bold: true, children: result?.amount != null ? `${result.amount} ${result.currency?.toUpperCase() ?? ""}`.trim() : "N/A" })
14429
+ /* @__PURE__ */ jsx24(Text18, { bold: true, children: result?.amount != null ? `${result.amount} ${result.currency?.toUpperCase() ?? ""}`.trim() : "N/A" })
13897
14430
  ] }),
13898
- /* @__PURE__ */ jsxs14(Text16, { children: [
14431
+ /* @__PURE__ */ jsxs17(Text18, { children: [
13899
14432
  "Merchant: ",
13900
- /* @__PURE__ */ jsx21(Text16, { bold: true, children: result?.merchant_name })
14433
+ /* @__PURE__ */ jsx24(Text18, { bold: true, children: result?.merchant_name })
13901
14434
  ] }),
13902
- result?.credential_type === "shared_payment_token" && result.shared_payment_token && /* @__PURE__ */ jsxs14(Text16, { children: [
14435
+ result?.credential_type === "shared_payment_token" && result.shared_payment_token && /* @__PURE__ */ jsxs17(Text18, { children: [
13903
14436
  "Token: ",
13904
- /* @__PURE__ */ jsx21(Text16, { bold: true, children: result.shared_payment_token.id })
14437
+ /* @__PURE__ */ jsx24(Text18, { bold: true, children: result.shared_payment_token.id })
13905
14438
  ] })
13906
14439
  ] })
13907
14440
  ] });
13908
14441
  }
13909
- return /* @__PURE__ */ jsx21(
14442
+ return /* @__PURE__ */ jsx24(
13910
14443
  ApprovalWaitingView,
13911
14444
  {
13912
14445
  status,
@@ -13916,49 +14449,76 @@ var RequestApproval = ({
13916
14449
  };
13917
14450
 
13918
14451
  // src/commands/spend-request/retrieve.tsx
13919
- import { Box as Box16, Text as Text17 } from "ink";
13920
- import Spinner7 from "ink-spinner";
13921
- import { useEffect as useEffect12, useRef as useRef4, useState as useState12 } from "react";
13922
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
14452
+ import { Box as Box18, Text as Text19 } from "ink";
14453
+ import Spinner10 from "ink-spinner";
14454
+ import { useEffect as useEffect11, useRef as useRef5, useState as useState11 } from "react";
14455
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
14456
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
14457
+ "approved",
14458
+ "denied",
14459
+ "expired",
14460
+ "succeeded",
14461
+ "failed",
14462
+ "canceled"
14463
+ ]);
13923
14464
  var RetrieveSpendRequest = ({
13924
14465
  repository,
13925
14466
  id,
13926
- timeout = 300,
14467
+ timeout = 600,
13927
14468
  include,
14469
+ outputFile,
14470
+ force,
13928
14471
  onComplete
13929
14472
  }) => {
13930
- const [phase, setPhase] = useState12("fetching");
13931
- const [request, setRequest] = useState12(null);
13932
- const [error, setError] = useState12("");
13933
- const [elapsed, setElapsed] = useState12(0);
13934
- const startTimeRef = useRef4(Date.now());
13935
- const pollRef = useRef4(null);
13936
- const timerRef = useRef4(null);
13937
- const requestRef = useRef4(null);
13938
- useEffect12(() => {
14473
+ const [phase, setPhase] = useState11("fetching");
14474
+ const [request, setRequest] = useState11(null);
14475
+ const [error, setError] = useState11("");
14476
+ const [elapsed, setElapsed] = useState11(0);
14477
+ const [outputFilePath, setOutputFilePath] = useState11(null);
14478
+ const [fileError, setFileError] = useState11("");
14479
+ const startTimeRef = useRef5(Date.now());
14480
+ const pollRef = useRef5(null);
14481
+ const timerRef = useRef5(null);
14482
+ const requestRef = useRef5(null);
14483
+ useEffect11(() => {
13939
14484
  return () => {
13940
14485
  if (pollRef.current) clearInterval(pollRef.current);
13941
14486
  if (timerRef.current) clearInterval(timerRef.current);
13942
14487
  };
13943
14488
  }, []);
13944
- useEffect12(() => {
14489
+ useEffect11(() => {
14490
+ if (phase !== "success" || !outputFile || !request?.card) return;
14491
+ const fileData = {
14492
+ spend_request_id: request.id,
14493
+ merchant_name: request.merchant_name,
14494
+ merchant_url: request.merchant_url,
14495
+ context: request.context,
14496
+ created_at: request.created_at,
14497
+ card: request.card
14498
+ };
14499
+ writeCredentialFile(outputFile, fileData, force ?? false).then((path7) => setOutputFilePath(path7)).catch((err) => setFileError(err.message));
14500
+ }, [phase, outputFile, force, request]);
14501
+ useEffect11(() => {
13945
14502
  const fetch2 = async () => {
13946
14503
  try {
13947
14504
  const result = await repository.getSpendRequest(id, { include });
13948
14505
  if (!result) {
13949
14506
  setError(`Spend request ${id} not found`);
13950
14507
  setPhase("error");
13951
- setTimeout(() => onComplete(null), 1500);
14508
+ setTimeout(() => onComplete(null), DISPLAY_DELAY_MS);
13952
14509
  return;
13953
14510
  }
13954
14511
  requestRef.current = result;
13955
14512
  setRequest(result);
13956
14513
  if (result.status === "approved") {
13957
14514
  setPhase("success");
13958
- setTimeout(() => onComplete(result), 1500);
14515
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
13959
14516
  } else if (result.status === "denied") {
13960
14517
  setPhase("declined");
13961
- setTimeout(() => onComplete(result), 1500);
14518
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
14519
+ } else if (TERMINAL_STATUSES.has(result.status)) {
14520
+ setPhase("finalized");
14521
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
13962
14522
  } else {
13963
14523
  startTimeRef.current = Date.now();
13964
14524
  setPhase("polling");
@@ -13966,12 +14526,12 @@ var RetrieveSpendRequest = ({
13966
14526
  } catch (err) {
13967
14527
  setError(err.message);
13968
14528
  setPhase("error");
13969
- setTimeout(() => onComplete(null), 1500);
14529
+ setTimeout(() => onComplete(null), DISPLAY_DELAY_MS);
13970
14530
  }
13971
14531
  };
13972
14532
  fetch2();
13973
14533
  }, [repository, id, include, onComplete]);
13974
- useEffect12(() => {
14534
+ useEffect11(() => {
13975
14535
  if (phase !== "polling") return;
13976
14536
  timerRef.current = setInterval(() => {
13977
14537
  const secs = Math.floor((Date.now() - startTimeRef.current) / 1e3);
@@ -13983,7 +14543,7 @@ var RetrieveSpendRequest = ({
13983
14543
  if (pollRef.current) clearInterval(pollRef.current);
13984
14544
  if (timerRef.current) clearInterval(timerRef.current);
13985
14545
  setPhase("timeout");
13986
- setTimeout(() => onComplete(requestRef.current), 1500);
14546
+ setTimeout(() => onComplete(requestRef.current), DISPLAY_DELAY_MS);
13987
14547
  return;
13988
14548
  }
13989
14549
  try {
@@ -13995,12 +14555,17 @@ var RetrieveSpendRequest = ({
13995
14555
  if (pollRef.current) clearInterval(pollRef.current);
13996
14556
  if (timerRef.current) clearInterval(timerRef.current);
13997
14557
  setPhase("success");
13998
- setTimeout(() => onComplete(result), 1500);
14558
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
13999
14559
  } else if (result.status === "denied") {
14000
14560
  if (pollRef.current) clearInterval(pollRef.current);
14001
14561
  if (timerRef.current) clearInterval(timerRef.current);
14002
14562
  setPhase("declined");
14003
- setTimeout(() => onComplete(result), 1500);
14563
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
14564
+ } else if (TERMINAL_STATUSES.has(result.status)) {
14565
+ if (pollRef.current) clearInterval(pollRef.current);
14566
+ if (timerRef.current) clearInterval(timerRef.current);
14567
+ setPhase("finalized");
14568
+ setTimeout(() => onComplete(result), DISPLAY_DELAY_MS);
14004
14569
  }
14005
14570
  } catch {
14006
14571
  }
@@ -14011,163 +14576,181 @@ var RetrieveSpendRequest = ({
14011
14576
  };
14012
14577
  }, [phase, repository, id, include, timeout, onComplete]);
14013
14578
  if (phase === "fetching") {
14014
- return /* @__PURE__ */ jsx22(Box16, { children: /* @__PURE__ */ jsxs15(Text17, { color: "cyan", children: [
14015
- /* @__PURE__ */ jsx22(Spinner7, { type: "dots" }),
14579
+ return /* @__PURE__ */ jsx25(Box18, { children: /* @__PURE__ */ jsxs18(Text19, { color: "cyan", children: [
14580
+ /* @__PURE__ */ jsx25(Spinner10, { type: "dots" }),
14016
14581
  " Retrieving spend request ",
14017
14582
  id,
14018
14583
  "..."
14019
14584
  ] }) });
14020
14585
  }
14021
14586
  if (phase === "error") {
14022
- return /* @__PURE__ */ jsx22(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsxs15(Text17, { color: "red", children: [
14587
+ return /* @__PURE__ */ jsx25(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsxs18(Text19, { color: "red", children: [
14023
14588
  "\u2717 ",
14024
14589
  error
14025
14590
  ] }) });
14026
14591
  }
14027
14592
  if (phase === "timeout") {
14028
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
14029
- /* @__PURE__ */ jsxs15(Text17, { color: "yellow", children: [
14593
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
14594
+ /* @__PURE__ */ jsxs18(Text19, { color: "yellow", children: [
14030
14595
  "\u2717 Timed out waiting for approval after ",
14031
14596
  timeout,
14032
14597
  "s"
14033
14598
  ] }),
14034
- request && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14035
- /* @__PURE__ */ jsxs15(Text17, { children: [
14599
+ request && /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14600
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14036
14601
  "ID: ",
14037
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request.id })
14602
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request.id })
14038
14603
  ] }),
14039
- /* @__PURE__ */ jsxs15(Text17, { children: [
14604
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14040
14605
  "Status: ",
14041
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request.status })
14606
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request.status })
14042
14607
  ] })
14043
14608
  ] })
14044
14609
  ] });
14045
14610
  }
14046
14611
  if (phase === "polling") {
14047
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
14048
- /* @__PURE__ */ jsx22(Box16, { children: /* @__PURE__ */ jsxs15(Text17, { color: "cyan", children: [
14049
- /* @__PURE__ */ jsx22(Spinner7, { type: "dots" }),
14612
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
14613
+ /* @__PURE__ */ jsx25(Box18, { children: /* @__PURE__ */ jsxs18(Text19, { color: "cyan", children: [
14614
+ /* @__PURE__ */ jsx25(Spinner10, { type: "dots" }),
14050
14615
  " Awaiting approval... (",
14051
14616
  elapsed,
14052
14617
  "s elapsed)"
14053
14618
  ] }) }),
14054
- request?.approval_url && /* @__PURE__ */ jsx22(Box16, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs15(Text17, { dimColor: true, children: [
14619
+ request?.approval_url && /* @__PURE__ */ jsx25(Box18, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs18(Text19, { dimColor: true, children: [
14055
14620
  "Approval URL: ",
14056
- /* @__PURE__ */ jsx22(Text17, { color: "cyan", children: request.approval_url })
14621
+ /* @__PURE__ */ jsx25(Text19, { color: "cyan", children: request.approval_url })
14057
14622
  ] }) })
14058
14623
  ] });
14059
14624
  }
14625
+ if (phase === "finalized") {
14626
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
14627
+ /* @__PURE__ */ jsxs18(Text19, { color: "yellow", children: [
14628
+ "Spend request reached terminal status: ",
14629
+ request?.status
14630
+ ] }),
14631
+ /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14632
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14633
+ "ID: ",
14634
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.id })
14635
+ ] }),
14636
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14637
+ "Status: ",
14638
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.status })
14639
+ ] })
14640
+ ] })
14641
+ ] });
14642
+ }
14060
14643
  if (phase === "declined") {
14061
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
14062
- /* @__PURE__ */ jsx22(Text17, { color: "red", children: "\u2717 Spend request declined" }),
14063
- /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14064
- /* @__PURE__ */ jsxs15(Text17, { children: [
14644
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
14645
+ /* @__PURE__ */ jsx25(Text19, { color: "red", children: "\u2717 Spend request declined" }),
14646
+ /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14647
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14065
14648
  "ID: ",
14066
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.id })
14649
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.id })
14067
14650
  ] }),
14068
- /* @__PURE__ */ jsxs15(Text17, { children: [
14651
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14069
14652
  "Status:",
14070
14653
  " ",
14071
- /* @__PURE__ */ jsx22(Text17, { bold: true, color: "red", children: request?.status })
14654
+ /* @__PURE__ */ jsx25(Text19, { bold: true, color: "red", children: request?.status })
14072
14655
  ] }),
14073
- /* @__PURE__ */ jsxs15(Text17, { children: [
14656
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14074
14657
  "Amount:",
14075
14658
  " ",
14076
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.amount != null ? String(request.amount) : "N/A" })
14659
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.amount != null ? String(request.amount) : "N/A" })
14077
14660
  ] }),
14078
- /* @__PURE__ */ jsxs15(Text17, { children: [
14661
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14079
14662
  "Merchant: ",
14080
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.merchant_name })
14663
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.merchant_name })
14081
14664
  ] })
14082
14665
  ] })
14083
14666
  ] });
14084
14667
  }
14085
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
14086
- /* @__PURE__ */ jsx22(Text17, { color: "green", children: "\u2713 Spend request approved" }),
14087
- /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14088
- /* @__PURE__ */ jsxs15(Text17, { children: [
14668
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
14669
+ /* @__PURE__ */ jsx25(Text19, { color: "green", children: "\u2713 Spend request approved" }),
14670
+ /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14671
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14089
14672
  "ID: ",
14090
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.id })
14673
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.id })
14091
14674
  ] }),
14092
- /* @__PURE__ */ jsxs15(Text17, { children: [
14675
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14093
14676
  "Status:",
14094
14677
  " ",
14095
- /* @__PURE__ */ jsx22(Text17, { bold: true, color: "green", children: request?.status })
14678
+ /* @__PURE__ */ jsx25(Text19, { bold: true, color: "green", children: request?.status })
14096
14679
  ] }),
14097
- /* @__PURE__ */ jsxs15(Text17, { children: [
14680
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14098
14681
  "Amount:",
14099
14682
  " ",
14100
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.amount != null ? String(request.amount) : "N/A" })
14683
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.amount != null ? String(request.amount) : "N/A" })
14101
14684
  ] }),
14102
- /* @__PURE__ */ jsxs15(Text17, { children: [
14685
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14103
14686
  "Merchant: ",
14104
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.merchant_name })
14687
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.merchant_name })
14105
14688
  ] }),
14106
- /* @__PURE__ */ jsxs15(Text17, { children: [
14689
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14107
14690
  "Line Items:",
14108
14691
  " ",
14109
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
14692
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
14110
14693
  ] }),
14111
- request?.shared_payment_token && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
14112
- /* @__PURE__ */ jsxs15(Text17, { bold: true, children: [
14694
+ request?.shared_payment_token && /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, children: [
14695
+ /* @__PURE__ */ jsxs18(Text19, { bold: true, children: [
14113
14696
  "\x1B]8;;https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens\x07",
14114
14697
  "Shared Payment Token",
14115
14698
  "\x1B]8;;\x07",
14116
14699
  ":"
14117
14700
  ] }),
14118
- /* @__PURE__ */ jsxs15(Text17, { children: [
14701
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14119
14702
  " ",
14120
14703
  "Token: ",
14121
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request.shared_payment_token.id })
14704
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request.shared_payment_token.id })
14122
14705
  ] })
14123
14706
  ] }),
14124
- request?.card && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
14125
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: "Card Details:" }),
14126
- /* @__PURE__ */ jsxs15(Text17, { children: [
14707
+ request?.card && !outputFile && /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, children: [
14708
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: "Card Details:" }),
14709
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14127
14710
  " ",
14128
14711
  "Number: ",
14129
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.card.number })
14712
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.card.number })
14130
14713
  ] }),
14131
- /* @__PURE__ */ jsxs15(Text17, { children: [
14714
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14132
14715
  " ",
14133
14716
  "Brand: ",
14134
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request?.card.brand })
14717
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request?.card.brand })
14135
14718
  ] }),
14136
- /* @__PURE__ */ jsxs15(Text17, { children: [
14719
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14137
14720
  " ",
14138
14721
  "Expiry:",
14139
14722
  " ",
14140
- /* @__PURE__ */ jsxs15(Text17, { bold: true, children: [
14723
+ /* @__PURE__ */ jsxs18(Text19, { bold: true, children: [
14141
14724
  String(request?.card.exp_month).padStart(2, "0"),
14142
14725
  "/",
14143
14726
  request?.card.exp_year
14144
14727
  ] })
14145
14728
  ] }),
14146
- request?.card.cvc && /* @__PURE__ */ jsxs15(Text17, { children: [
14729
+ request?.card.cvc && /* @__PURE__ */ jsxs18(Text19, { children: [
14147
14730
  " ",
14148
14731
  "CVC: ",
14149
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request.card.cvc })
14732
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request.card.cvc })
14150
14733
  ] }),
14151
- request?.card.valid_until && /* @__PURE__ */ jsxs15(Text17, { children: [
14734
+ request?.card.valid_until && /* @__PURE__ */ jsxs18(Text19, { children: [
14152
14735
  " ",
14153
14736
  "Valid Until: ",
14154
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: request.card.valid_until })
14737
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: request.card.valid_until })
14155
14738
  ] }),
14156
- request?.card.billing_address && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
14157
- /* @__PURE__ */ jsx22(Text17, { bold: true, children: " Billing Address:" }),
14158
- /* @__PURE__ */ jsxs15(Text17, { children: [
14739
+ request?.card.billing_address && /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, children: [
14740
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: " Billing Address:" }),
14741
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14159
14742
  " ",
14160
14743
  request.card.billing_address.name
14161
14744
  ] }),
14162
- /* @__PURE__ */ jsxs15(Text17, { children: [
14745
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14163
14746
  " ",
14164
14747
  request.card.billing_address.line1
14165
14748
  ] }),
14166
- request.card.billing_address.line2 && /* @__PURE__ */ jsxs15(Text17, { children: [
14749
+ request.card.billing_address.line2 && /* @__PURE__ */ jsxs18(Text19, { children: [
14167
14750
  " ",
14168
14751
  request.card.billing_address.line2
14169
14752
  ] }),
14170
- /* @__PURE__ */ jsxs15(Text17, { children: [
14753
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14171
14754
  " ",
14172
14755
  [
14173
14756
  request.card.billing_address.city,
@@ -14175,11 +14758,21 @@ var RetrieveSpendRequest = ({
14175
14758
  request.card.billing_address.postal_code
14176
14759
  ].filter(Boolean).join(", ")
14177
14760
  ] }),
14178
- /* @__PURE__ */ jsxs15(Text17, { children: [
14761
+ /* @__PURE__ */ jsxs18(Text19, { children: [
14179
14762
  " ",
14180
14763
  request.card.billing_address.country
14181
14764
  ] })
14182
14765
  ] })
14766
+ ] }),
14767
+ request?.card && outputFile && /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", marginTop: 1, children: [
14768
+ outputFilePath && /* @__PURE__ */ jsxs18(Text19, { color: "green", children: [
14769
+ "Card credentials written to ",
14770
+ /* @__PURE__ */ jsx25(Text19, { bold: true, children: outputFilePath })
14771
+ ] }),
14772
+ fileError && /* @__PURE__ */ jsxs18(Text19, { color: "red", children: [
14773
+ "Failed to write card file: ",
14774
+ fileError
14775
+ ] })
14183
14776
  ] })
14184
14777
  ] })
14185
14778
  ] });
@@ -14210,16 +14803,20 @@ var createOptions = z6.object({
14210
14803
  'Line item (repeatable, key:value format). Keys: name (required), quantity, unit_amount, description, sku, url, image_url, product_url. Example: "name:Shoes,unit_amount:5000,quantity:2"'
14211
14804
  ),
14212
14805
  total: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
14213
- 'Total (repeatable, key:value format). Keys: type (required), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
14806
+ 'Total (repeatable, key:value format). Keys: type (required; one of: subtotal, tax, total, items_base_amount, items_discount, discount, fulfillment, shipping, fee, gift_wrap, tip, store_credit), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
14214
14807
  ),
14215
14808
  requestApproval: z6.boolean().default(true).describe("Request approval and poll until approved/denied/expired"),
14216
14809
  test: z6.boolean().default(false).describe(
14217
14810
  "Use test mode (creates testmode credentials from test card data)"
14218
- )
14811
+ ),
14812
+ outputFile: z6.string().optional().describe(
14813
+ "Write full card credentials to this file path; stdout shows redacted card data only"
14814
+ ),
14815
+ force: z6.boolean().default(false).describe("Overwrite output file if it already exists")
14219
14816
  });
14220
14817
  var retrieveOptions = z6.object({
14221
- timeout: z6.coerce.number().default(300).describe(
14222
- "Polling timeout in seconds. When reached during active polling, exits non-zero with POLLING_TIMEOUT."
14818
+ timeout: z6.coerce.number().default(600).describe(
14819
+ "Polling timeout in seconds. When reached during active polling, exits non-zero with POLLING_TIMEOUT. Default exceeds the server-side spend-request expiry so polling outlives the request itself."
14223
14820
  ),
14224
14821
  interval: z6.coerce.number().default(0).describe(
14225
14822
  "Poll interval in seconds. When > 0, polls until status is terminal, timeout is reached, or max attempts are exhausted."
@@ -14227,7 +14824,11 @@ var retrieveOptions = z6.object({
14227
14824
  maxAttempts: z6.coerce.number().default(0).describe(
14228
14825
  "Max poll attempts. 0 = unlimited. Exhaustion during active polling exits non-zero with POLLING_TIMEOUT."
14229
14826
  ),
14230
- include: z6.array(z6.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)")
14827
+ include: z6.array(z6.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)"),
14828
+ outputFile: z6.string().optional().describe(
14829
+ "Write full card credentials to this file path; stdout shows redacted card data only"
14830
+ ),
14831
+ force: z6.boolean().default(false).describe("Overwrite output file if it already exists")
14231
14832
  });
14232
14833
  var updateOptions = z6.object({
14233
14834
  paymentMethodId: z6.string().optional().describe("Payment method ID"),
@@ -14240,91 +14841,93 @@ var updateOptions = z6.object({
14240
14841
  'Line item (repeatable, key:value format). Keys: name (required), quantity, unit_amount, description, sku, url, image_url, product_url. Example: "name:Shoes,unit_amount:5000,quantity:2"'
14241
14842
  ),
14242
14843
  total: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
14243
- 'Total (repeatable, key:value format). Keys: type (required), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
14844
+ 'Total (repeatable, key:value format). Keys: type (required; one of: subtotal, tax, total, items_base_amount, items_discount, discount, fulfillment, shipping, fee, gift_wrap, tip, store_credit), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
14244
14845
  )
14245
14846
  });
14246
14847
 
14247
14848
  // src/commands/spend-request/update.tsx
14248
- import { Box as Box17, Text as Text18 } from "ink";
14249
- import Spinner8 from "ink-spinner";
14250
- import { useEffect as useEffect13, useState as useState13 } from "react";
14251
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
14849
+ import { Box as Box19, Text as Text20 } from "ink";
14850
+ import Spinner11 from "ink-spinner";
14851
+ import { useCallback as useCallback8 } from "react";
14852
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
14252
14853
  var UpdateSpendRequest = ({
14253
14854
  repository,
14254
14855
  id,
14255
14856
  params,
14256
14857
  onComplete
14257
14858
  }) => {
14258
- const [status, setStatus] = useState13(
14259
- "loading"
14859
+ const action = useCallback8(
14860
+ () => repository.updateSpendRequest(id, params),
14861
+ [repository, id, params]
14260
14862
  );
14261
- const [request, setRequest] = useState13(null);
14262
- const [error, setError] = useState13("");
14263
- useEffect13(() => {
14264
- const update = async () => {
14265
- try {
14266
- const result = await repository.updateSpendRequest(id, params);
14267
- setRequest(result);
14268
- setStatus("success");
14269
- setTimeout(() => onComplete(result), 1500);
14270
- } catch (err) {
14271
- setError(err.message);
14272
- setStatus("error");
14273
- setTimeout(() => onComplete(null), 1500);
14274
- }
14275
- };
14276
- update();
14277
- }, [repository, id, params, onComplete]);
14863
+ const { status, data: request, error } = useAsyncAction(action, onComplete);
14278
14864
  if (status === "loading") {
14279
- return /* @__PURE__ */ jsx23(Box17, { children: /* @__PURE__ */ jsxs16(Text18, { color: "cyan", children: [
14280
- /* @__PURE__ */ jsx23(Spinner8, { type: "dots" }),
14865
+ return /* @__PURE__ */ jsx26(Box19, { children: /* @__PURE__ */ jsxs19(Text20, { color: "cyan", children: [
14866
+ /* @__PURE__ */ jsx26(Spinner11, { type: "dots" }),
14281
14867
  " Updating spend request ",
14282
14868
  id,
14283
14869
  "..."
14284
14870
  ] }) });
14285
14871
  }
14286
14872
  if (status === "error") {
14287
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
14288
- /* @__PURE__ */ jsx23(Text18, { color: "red", children: "\u2717 Failed to update spend request" }),
14289
- /* @__PURE__ */ jsx23(Text18, { color: "red", children: error })
14873
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
14874
+ /* @__PURE__ */ jsx26(Text20, { color: "red", children: "\u2717 Failed to update spend request" }),
14875
+ /* @__PURE__ */ jsx26(Text20, { color: "red", children: error })
14290
14876
  ] });
14291
14877
  }
14292
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
14293
- /* @__PURE__ */ jsx23(Text18, { color: "green", children: "\u2713 Spend request updated" }),
14294
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14295
- /* @__PURE__ */ jsxs16(Text18, { children: [
14878
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
14879
+ /* @__PURE__ */ jsx26(Text20, { color: "green", children: "\u2713 Spend request updated" }),
14880
+ /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
14881
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14296
14882
  "ID: ",
14297
- /* @__PURE__ */ jsx23(Text18, { bold: true, children: request?.id })
14883
+ /* @__PURE__ */ jsx26(Text20, { bold: true, children: request?.id })
14298
14884
  ] }),
14299
- /* @__PURE__ */ jsxs16(Text18, { children: [
14885
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14300
14886
  "Status: ",
14301
- /* @__PURE__ */ jsx23(Text18, { bold: true, children: request?.status })
14887
+ /* @__PURE__ */ jsx26(Text20, { bold: true, children: request?.status })
14302
14888
  ] }),
14303
- /* @__PURE__ */ jsxs16(Text18, { children: [
14889
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14304
14890
  "Amount:",
14305
14891
  " ",
14306
- /* @__PURE__ */ jsx23(Text18, { bold: true, children: (() => {
14892
+ /* @__PURE__ */ jsx26(Text20, { bold: true, children: (() => {
14307
14893
  const t = request?.totals.find((t2) => t2.type === "total");
14308
14894
  return t ? String(t.amount) : "N/A";
14309
14895
  })() })
14310
14896
  ] }),
14311
- /* @__PURE__ */ jsxs16(Text18, { children: [
14897
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14312
14898
  "Merchant: ",
14313
- /* @__PURE__ */ jsx23(Text18, { bold: true, children: request?.merchant_name })
14899
+ /* @__PURE__ */ jsx26(Text20, { bold: true, children: request?.merchant_name })
14314
14900
  ] }),
14315
- /* @__PURE__ */ jsxs16(Text18, { children: [
14901
+ /* @__PURE__ */ jsxs19(Text20, { children: [
14316
14902
  "Line Items:",
14317
14903
  " ",
14318
- /* @__PURE__ */ jsx23(Text18, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
14904
+ /* @__PURE__ */ jsx26(Text20, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
14319
14905
  ] })
14320
14906
  ] })
14321
14907
  ] });
14322
14908
  };
14323
14909
 
14324
14910
  // src/commands/spend-request/index.tsx
14325
- import { jsx as jsx24 } from "react/jsx-runtime";
14326
- function createSpendRequestCli(repository) {
14327
- const cli2 = Cli6.create("spend-request", {
14911
+ import { jsx as jsx27 } from "react/jsx-runtime";
14912
+ async function applyOutputFile(request, outputFile, force) {
14913
+ if (!outputFile || !request.card) return request;
14914
+ const fileData = {
14915
+ spend_request_id: request.id,
14916
+ merchant_name: request.merchant_name,
14917
+ merchant_url: request.merchant_url,
14918
+ context: request.context,
14919
+ created_at: request.created_at,
14920
+ card: request.card
14921
+ };
14922
+ const resolvedPath = await writeCredentialFile(outputFile, fileData, force);
14923
+ const { card: _, ...withoutCard } = request;
14924
+ return {
14925
+ ...withoutCard,
14926
+ card_output_file: resolvedPath
14927
+ };
14928
+ }
14929
+ function createSpendRequestCli(repository, authStorage2) {
14930
+ const cli2 = Cli7.create("spend-request", {
14328
14931
  description: "Spend request management commands"
14329
14932
  });
14330
14933
  cli2.command("create", {
@@ -14333,17 +14936,7 @@ function createSpendRequestCli(repository) {
14333
14936
  alias: { merchantName: "m" },
14334
14937
  outputPolicy: "agent-only",
14335
14938
  async *run(c) {
14336
- if (!storage.isAuthenticated()) {
14337
- return c.error({
14338
- code: "NOT_AUTHENTICATED",
14339
- message: 'Not authenticated. Run "link-cli auth login" first.',
14340
- cta: {
14341
- commands: [
14342
- { command: "auth login", description: "Log in to Link" }
14343
- ]
14344
- }
14345
- });
14346
- }
14939
+ requireAuthGuard(c, authStorage2);
14347
14940
  const opts = c.options;
14348
14941
  const requestApproval = !!opts.requestApproval;
14349
14942
  const credentialType = opts.credentialType;
@@ -14400,37 +14993,49 @@ function createSpendRequestCli(repository) {
14400
14993
  request_approval: requestApproval || void 0,
14401
14994
  test: opts.test ? true : void 0
14402
14995
  };
14996
+ const outputFile = opts.outputFile;
14997
+ const forceOverwrite = opts.force;
14403
14998
  if (!c.agent && !c.formatExplicit) {
14404
- return new Promise((resolve) => {
14405
- let capturedResult = null;
14406
- const { waitUntilExit } = render6(
14407
- /* @__PURE__ */ jsx24(
14408
- CreateSpendRequest,
14409
- {
14410
- repository,
14411
- params: createParams,
14412
- requestApproval,
14413
- onComplete: (result) => {
14414
- capturedResult = result;
14415
- }
14999
+ let capturedResult = null;
15000
+ return renderInteractive(
15001
+ /* @__PURE__ */ jsx27(
15002
+ CreateSpendRequest,
15003
+ {
15004
+ repository,
15005
+ params: createParams,
15006
+ requestApproval,
15007
+ outputFile,
15008
+ force: forceOverwrite,
15009
+ onComplete: (result) => {
15010
+ capturedResult = result;
14416
15011
  }
14417
- )
14418
- );
14419
- waitUntilExit().then(() => {
14420
- resolve(capturedResult);
14421
- });
14422
- });
15012
+ }
15013
+ ),
15014
+ () => {
15015
+ if (!capturedResult)
15016
+ throw new Error("Component exited without producing a result");
15017
+ return capturedResult;
15018
+ }
15019
+ );
14423
15020
  }
14424
15021
  const created = await repository.createSpendRequest(createParams);
14425
15022
  if (!requestApproval) {
14426
- yield created;
15023
+ try {
15024
+ yield await applyOutputFile(created, outputFile, forceOverwrite);
15025
+ } catch (err) {
15026
+ const message = err.message;
15027
+ if (message.startsWith("OUTPUT_FILE_EXISTS")) {
15028
+ return c.error({ code: "OUTPUT_FILE_EXISTS", message });
15029
+ }
15030
+ return c.error({ code: "OUTPUT_FILE_WRITE_ERROR", message });
15031
+ }
14427
15032
  return;
14428
15033
  }
14429
15034
  yield {
14430
15035
  ...created,
14431
- instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${created.id} --interval 2 --max-attempts 150\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
15036
+ instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${created.id} --interval 2 --max-attempts 300\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
14432
15037
  _next: {
14433
- command: `spend-request retrieve ${created.id} --interval 2 --max-attempts 150`,
15038
+ command: `spend-request retrieve ${created.id} --interval 2 --max-attempts 300`,
14434
15039
  until: "status changes from pending_approval"
14435
15040
  }
14436
15041
  };
@@ -14443,18 +15048,8 @@ function createSpendRequestCli(repository) {
14443
15048
  }),
14444
15049
  options: updateOptions,
14445
15050
  outputPolicy: "agent-only",
15051
+ middleware: [requireAuth(authStorage2)],
14446
15052
  async run(c) {
14447
- if (!storage.isAuthenticated()) {
14448
- return c.error({
14449
- code: "NOT_AUTHENTICATED",
14450
- message: 'Not authenticated. Run "link-cli auth login" first.',
14451
- cta: {
14452
- commands: [
14453
- { command: "auth login", description: "Log in to Link" }
14454
- ]
14455
- }
14456
- });
14457
- }
14458
15053
  const id = c.args.id;
14459
15054
  const opts = c.options;
14460
15055
  const params = {};
@@ -14475,25 +15070,25 @@ function createSpendRequestCli(repository) {
14475
15070
  (item) => typeof item === "string" ? parseTotalFlag(item) : item
14476
15071
  );
14477
15072
  if (!c.agent && !c.formatExplicit) {
14478
- return new Promise((resolve) => {
14479
- let capturedResult = null;
14480
- const { waitUntilExit } = render6(
14481
- /* @__PURE__ */ jsx24(
14482
- UpdateSpendRequest,
14483
- {
14484
- repository,
14485
- id,
14486
- params,
14487
- onComplete: (result) => {
14488
- capturedResult = result;
14489
- }
15073
+ let capturedResult = null;
15074
+ return renderInteractive(
15075
+ /* @__PURE__ */ jsx27(
15076
+ UpdateSpendRequest,
15077
+ {
15078
+ repository,
15079
+ id,
15080
+ params,
15081
+ onComplete: (result) => {
15082
+ capturedResult = result;
14490
15083
  }
14491
- )
14492
- );
14493
- waitUntilExit().then(() => {
14494
- resolve(capturedResult);
14495
- });
14496
- });
15084
+ }
15085
+ ),
15086
+ () => {
15087
+ if (!capturedResult)
15088
+ throw new Error("Component exited without producing a result");
15089
+ return capturedResult;
15090
+ }
15091
+ );
14497
15092
  }
14498
15093
  return repository.updateSpendRequest(id, params);
14499
15094
  }
@@ -14505,44 +15100,34 @@ function createSpendRequestCli(repository) {
14505
15100
  }),
14506
15101
  outputPolicy: "agent-only",
14507
15102
  async *run(c) {
14508
- if (!storage.isAuthenticated()) {
14509
- return c.error({
14510
- code: "NOT_AUTHENTICATED",
14511
- message: 'Not authenticated. Run "link-cli auth login" first.',
14512
- cta: {
14513
- commands: [
14514
- { command: "auth login", description: "Log in to Link" }
14515
- ]
14516
- }
14517
- });
14518
- }
15103
+ requireAuthGuard(c, authStorage2);
14519
15104
  const id = c.args.id;
14520
15105
  if (!c.agent && !c.formatExplicit) {
14521
- return new Promise((resolve) => {
14522
- let capturedResult = null;
14523
- const { waitUntilExit } = render6(
14524
- /* @__PURE__ */ jsx24(
14525
- RequestApproval,
14526
- {
14527
- repository,
14528
- id,
14529
- onComplete: (result) => {
14530
- capturedResult = result;
14531
- }
15106
+ let capturedResult = null;
15107
+ return renderInteractive(
15108
+ /* @__PURE__ */ jsx27(
15109
+ RequestApproval,
15110
+ {
15111
+ repository,
15112
+ id,
15113
+ onComplete: (result) => {
15114
+ capturedResult = result;
14532
15115
  }
14533
- )
14534
- );
14535
- waitUntilExit().then(() => {
14536
- resolve(capturedResult);
14537
- });
14538
- });
15116
+ }
15117
+ ),
15118
+ () => {
15119
+ if (!capturedResult)
15120
+ throw new Error("Component exited without producing a result");
15121
+ return capturedResult;
15122
+ }
15123
+ );
14539
15124
  }
14540
15125
  const approval = await repository.requestApproval(id);
14541
15126
  yield {
14542
15127
  ...approval,
14543
- instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${id} --interval 2 --max-attempts 150\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
15128
+ instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${id} --interval 2 --max-attempts 300\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
14544
15129
  _next: {
14545
- command: `spend-request retrieve ${id} --interval 2 --max-attempts 150`,
15130
+ command: `spend-request retrieve ${id} --interval 2 --max-attempts 300`,
14546
15131
  until: "status changes from pending_approval"
14547
15132
  }
14548
15133
  };
@@ -14556,17 +15141,7 @@ function createSpendRequestCli(repository) {
14556
15141
  options: retrieveOptions,
14557
15142
  outputPolicy: "agent-only",
14558
15143
  async *run(c) {
14559
- if (!storage.isAuthenticated()) {
14560
- return c.error({
14561
- code: "NOT_AUTHENTICATED",
14562
- message: 'Not authenticated. Run "link-cli auth login" first.',
14563
- cta: {
14564
- commands: [
14565
- { command: "auth login", description: "Log in to Link" }
14566
- ]
14567
- }
14568
- });
14569
- }
15144
+ requireAuthGuard(c, authStorage2);
14570
15145
  const id = c.args.id;
14571
15146
  const opts = c.options;
14572
15147
  const timeout = opts.timeout;
@@ -14574,74 +15149,183 @@ function createSpendRequestCli(repository) {
14574
15149
  const maxAttempts = opts.maxAttempts;
14575
15150
  const includeArr = opts.include;
14576
15151
  const include = includeArr?.length ? includeArr : void 0;
15152
+ const outputFile = opts.outputFile;
15153
+ const forceOverwrite = opts.force;
14577
15154
  if (!c.agent && !c.formatExplicit) {
14578
- return new Promise((resolve) => {
14579
- let capturedResult = null;
14580
- const { waitUntilExit } = render6(
14581
- /* @__PURE__ */ jsx24(
14582
- RetrieveSpendRequest,
14583
- {
14584
- repository,
14585
- id,
14586
- timeout,
14587
- include,
14588
- onComplete: (result) => {
14589
- capturedResult = result;
14590
- }
15155
+ let capturedResult = null;
15156
+ return renderInteractive(
15157
+ /* @__PURE__ */ jsx27(
15158
+ RetrieveSpendRequest,
15159
+ {
15160
+ repository,
15161
+ id,
15162
+ timeout,
15163
+ include,
15164
+ outputFile,
15165
+ force: forceOverwrite,
15166
+ onComplete: (result) => {
15167
+ capturedResult = result;
14591
15168
  }
14592
- )
14593
- );
14594
- waitUntilExit().then(() => {
14595
- resolve(capturedResult);
14596
- });
14597
- });
15169
+ }
15170
+ ),
15171
+ () => {
15172
+ if (!capturedResult)
15173
+ throw new Error("Component exited without producing a result");
15174
+ return capturedResult;
15175
+ }
15176
+ );
14598
15177
  }
14599
15178
  const terminalStatuses = /* @__PURE__ */ new Set([
14600
15179
  "approved",
14601
15180
  "denied",
14602
15181
  "expired",
14603
15182
  "succeeded",
14604
- "failed"
15183
+ "failed",
15184
+ "canceled"
14605
15185
  ]);
14606
- const deadline = Date.now() + timeout * 1e3;
14607
- let attempts = 0;
14608
- let previousAttemptData;
14609
- while (true) {
14610
- const request = await repository.getSpendRequest(id, { include });
14611
- if (!request) {
15186
+ for await (const result of pollUntil({
15187
+ fn: () => repository.getSpendRequest(id, { include }),
15188
+ isTerminal: (req) => req === null || terminalStatuses.has(req.status),
15189
+ interval,
15190
+ maxAttempts,
15191
+ timeout
15192
+ })) {
15193
+ if (result.value === null) {
14612
15194
  return c.error({
14613
15195
  code: "NOT_FOUND",
14614
15196
  message: `Spend request ${id} not found`
14615
15197
  });
14616
15198
  }
14617
- if (terminalStatuses.has(request.status)) {
14618
- yield request;
14619
- return;
14620
- }
14621
- attempts++;
14622
- if (interval <= 0) {
14623
- yield request;
14624
- return;
14625
- }
14626
- const maxAttemptsExhausted = maxAttempts > 0 && attempts >= maxAttempts;
14627
- const timeoutReached = Date.now() >= deadline;
14628
- if (maxAttemptsExhausted || timeoutReached) {
14629
- const reason = maxAttemptsExhausted ? `max attempts (${maxAttempts}) exhausted` : `timeout (${timeout}s) reached`;
15199
+ if (result.terminal) {
15200
+ if (terminalStatuses.has(result.value.status) || !result.reason) {
15201
+ try {
15202
+ yield await applyOutputFile(
15203
+ result.value,
15204
+ outputFile,
15205
+ forceOverwrite
15206
+ );
15207
+ } catch (err) {
15208
+ const message = err.message;
15209
+ if (message.startsWith("OUTPUT_FILE_EXISTS")) {
15210
+ return c.error({ code: "OUTPUT_FILE_EXISTS", message });
15211
+ }
15212
+ return c.error({ code: "OUTPUT_FILE_WRITE_ERROR", message });
15213
+ }
15214
+ return;
15215
+ }
15216
+ const reason = result.reason === "max_attempts" ? `max attempts (${maxAttempts}) exhausted` : `timeout (${timeout}s) reached`;
14630
15217
  return c.error({
14631
15218
  code: "POLLING_TIMEOUT",
14632
- message: `Polling stopped before spend request ${id} reached a terminal status: ${reason}; current status is ${request.status}.`,
15219
+ message: `Polling stopped before spend request ${id} reached a terminal status: ${reason}; current status is ${result.value.status}.`,
14633
15220
  retryable: true
14634
15221
  });
14635
15222
  }
14636
- const snapshot = JSON.stringify(request);
14637
- if (snapshot !== previousAttemptData) {
14638
- previousAttemptData = snapshot;
14639
- yield request;
14640
- }
14641
- await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
15223
+ yield result.value;
14642
15224
  }
14643
15225
  }
14644
15226
  });
15227
+ cli2.command("cancel", {
15228
+ description: "Cancel a spend request",
15229
+ args: z7.object({
15230
+ id: z7.string().describe("Spend request ID")
15231
+ }),
15232
+ outputPolicy: "agent-only",
15233
+ middleware: [requireAuth(authStorage2)],
15234
+ async run(c) {
15235
+ const id = c.args.id;
15236
+ if (!c.agent && !c.formatExplicit) {
15237
+ let capturedResult = null;
15238
+ return renderInteractive(
15239
+ /* @__PURE__ */ jsx27(
15240
+ CancelSpendRequest,
15241
+ {
15242
+ repository,
15243
+ id,
15244
+ onComplete: (result) => {
15245
+ capturedResult = result;
15246
+ }
15247
+ }
15248
+ ),
15249
+ () => {
15250
+ if (!capturedResult)
15251
+ throw new Error("Component exited without producing a result");
15252
+ return capturedResult;
15253
+ }
15254
+ );
15255
+ }
15256
+ return repository.cancelSpendRequest(id);
15257
+ }
15258
+ });
15259
+ return cli2;
15260
+ }
15261
+
15262
+ // src/commands/user-info/index.tsx
15263
+ import { Cli as Cli8 } from "incur";
15264
+
15265
+ // src/commands/user-info/retrieve.tsx
15266
+ import { Box as Box20, Text as Text21 } from "ink";
15267
+ import Spinner12 from "ink-spinner";
15268
+ import { useCallback as useCallback9 } from "react";
15269
+ import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
15270
+ var UserInfoRetrieve = ({
15271
+ resource,
15272
+ onComplete
15273
+ }) => {
15274
+ const action = useCallback9(() => resource.retrieve(), [resource]);
15275
+ const { status, data: userInfo, error } = useAsyncAction(action, onComplete);
15276
+ if (status === "loading") {
15277
+ return /* @__PURE__ */ jsx28(Box20, { children: /* @__PURE__ */ jsxs20(Text21, { color: "cyan", children: [
15278
+ /* @__PURE__ */ jsx28(Spinner12, { type: "dots" }),
15279
+ " Loading user info..."
15280
+ ] }) });
15281
+ }
15282
+ if (status === "error") {
15283
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
15284
+ /* @__PURE__ */ jsx28(Text21, { color: "red", children: "\u2717 Failed to load user info" }),
15285
+ /* @__PURE__ */ jsx28(Text21, { color: "red", children: error })
15286
+ ] });
15287
+ }
15288
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
15289
+ /* @__PURE__ */ jsx28(Text21, { bold: true, children: "User Info" }),
15290
+ /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
15291
+ /* @__PURE__ */ jsxs20(Text21, { children: [
15292
+ /* @__PURE__ */ jsx28(Text21, { dimColor: true, children: "Email: " }),
15293
+ userInfo?.email ?? /* @__PURE__ */ jsx28(Text21, { dimColor: true, children: "Not set" })
15294
+ ] }),
15295
+ /* @__PURE__ */ jsxs20(Text21, { children: [
15296
+ /* @__PURE__ */ jsx28(Text21, { dimColor: true, children: "Name: " }),
15297
+ userInfo?.name ?? /* @__PURE__ */ jsx28(Text21, { dimColor: true, children: "Not set" })
15298
+ ] }),
15299
+ /* @__PURE__ */ jsxs20(Text21, { children: [
15300
+ /* @__PURE__ */ jsx28(Text21, { dimColor: true, children: "Phone: " }),
15301
+ userInfo?.phone ?? /* @__PURE__ */ jsx28(Text21, { dimColor: true, children: "Not set" })
15302
+ ] })
15303
+ ] })
15304
+ ] });
15305
+ };
15306
+
15307
+ // src/commands/user-info/index.tsx
15308
+ import { jsx as jsx29 } from "react/jsx-runtime";
15309
+ function createUserInfoCli(createResource, authStorage2) {
15310
+ const cli2 = Cli8.create("user-info", {
15311
+ description: "User information commands"
15312
+ });
15313
+ cli2.command("retrieve", {
15314
+ description: "Retrieve user info (email, name, phone)",
15315
+ outputPolicy: "agent-only",
15316
+ middleware: [requireAuth(authStorage2)],
15317
+ async run(c) {
15318
+ const resource = createResource();
15319
+ if (!c.agent && !c.formatExplicit) {
15320
+ return renderInteractive(
15321
+ /* @__PURE__ */ jsx29(UserInfoRetrieve, { resource, onComplete: () => {
15322
+ } }),
15323
+ () => resource.retrieve()
15324
+ );
15325
+ }
15326
+ return resource.retrieve();
15327
+ }
15328
+ });
14645
15329
  return cli2;
14646
15330
  }
14647
15331
 
@@ -14869,9 +15553,9 @@ ${JSON.stringify(redacted, null, 2)}`
14869
15553
 
14870
15554
  // src/auth/session.ts
14871
15555
  var EXPIRY_BUFFER_MS = 6e4;
14872
- function createAccessTokenProvider(authResource, authStorage = storage) {
15556
+ function createAccessTokenProvider(authResource, authStorage2 = storage) {
14873
15557
  return async ({ forceRefresh } = {}) => {
14874
- const auth = authStorage.getAuth();
15558
+ const auth = authStorage2.getAuth();
14875
15559
  if (!auth) {
14876
15560
  throw new LinkAuthenticationError(
14877
15561
  'Not authenticated. Run "link-cli auth login" first.'
@@ -14882,22 +15566,78 @@ function createAccessTokenProvider(authResource, authStorage = storage) {
14882
15566
  return auth.access_token;
14883
15567
  }
14884
15568
  const refreshed = await authResource.refreshToken(auth.refresh_token);
14885
- authStorage.setAuth(refreshed);
15569
+ authStorage2.setAuth(refreshed);
14886
15570
  return refreshed.access_token;
14887
15571
  };
14888
15572
  }
14889
15573
 
15574
+ // src/utils/sanitize-text.ts
15575
+ import stripAnsi from "strip-ansi";
15576
+ var CONTROL_CHAR_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\r]/g;
15577
+ var NEEDS_SANITIZE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\r\x1b]/;
15578
+ function sanitizeText(input) {
15579
+ if (!input) {
15580
+ return "";
15581
+ }
15582
+ if (!NEEDS_SANITIZE_RE.test(input)) {
15583
+ return input;
15584
+ }
15585
+ return stripAnsi(input).replace(CONTROL_CHAR_RE, "");
15586
+ }
15587
+ function sanitizeDeep(value) {
15588
+ if (value === null || value === void 0) {
15589
+ return value;
15590
+ }
15591
+ if (typeof value === "string") {
15592
+ return sanitizeText(value);
15593
+ }
15594
+ if (Array.isArray(value)) {
15595
+ return value.map(sanitizeDeep);
15596
+ }
15597
+ if (typeof value === "object") {
15598
+ const result = {};
15599
+ const keys = Object.keys(value);
15600
+ for (let i = 0; i < keys.length; i++) {
15601
+ const k = keys[i];
15602
+ result[k] = sanitizeDeep(value[k]);
15603
+ }
15604
+ return result;
15605
+ }
15606
+ return value;
15607
+ }
15608
+
14890
15609
  // src/utils/resource-factory.ts
15610
+ function sanitizeResource(resource) {
15611
+ return new Proxy(resource, {
15612
+ get(target, prop, receiver) {
15613
+ const value = Reflect.get(target, prop, receiver);
15614
+ if (typeof value !== "function") {
15615
+ return value;
15616
+ }
15617
+ return (...args) => {
15618
+ const result = value.apply(target, args);
15619
+ if (result && typeof result.then === "function") {
15620
+ return result.then(sanitizeDeep);
15621
+ }
15622
+ return result;
15623
+ };
15624
+ }
15625
+ });
15626
+ }
14891
15627
  var ResourceFactory = class {
14892
15628
  verbose;
14893
15629
  defaultHeaders;
15630
+ authStorage;
14894
15631
  authResource;
14895
15632
  accessTokenProvider;
14896
15633
  spendRequestResource;
14897
15634
  paymentMethodsResource;
15635
+ shippingAddressResource;
15636
+ userInfoResource;
14898
15637
  constructor(options = {}) {
14899
15638
  this.verbose = options.verbose ?? false;
14900
15639
  this.defaultHeaders = options.defaultHeaders;
15640
+ this.authStorage = options.authStorage;
14901
15641
  }
14902
15642
  createAuthResource() {
14903
15643
  if (this.authResource) {
@@ -14909,12 +15649,16 @@ var ResourceFactory = class {
14909
15649
  });
14910
15650
  return this.authResource;
14911
15651
  }
15652
+ getAuthStorage() {
15653
+ return this.authStorage;
15654
+ }
14912
15655
  createSdkAccessTokenProvider() {
14913
15656
  if (this.accessTokenProvider) {
14914
15657
  return this.accessTokenProvider;
14915
15658
  }
14916
15659
  this.accessTokenProvider = createAccessTokenProvider(
14917
- this.createAuthResource()
15660
+ this.createAuthResource(),
15661
+ this.authStorage
14918
15662
  );
14919
15663
  return this.accessTokenProvider;
14920
15664
  }
@@ -14923,11 +15667,13 @@ var ResourceFactory = class {
14923
15667
  return this.spendRequestResource;
14924
15668
  }
14925
15669
  const getAccessToken = this.createSdkAccessTokenProvider();
14926
- this.spendRequestResource = new SpendRequestResource({
14927
- verbose: this.verbose,
14928
- defaultHeaders: this.defaultHeaders,
14929
- getAccessToken
14930
- });
15670
+ this.spendRequestResource = sanitizeResource(
15671
+ new SpendRequestResource({
15672
+ verbose: this.verbose,
15673
+ defaultHeaders: this.defaultHeaders,
15674
+ getAccessToken
15675
+ })
15676
+ );
14931
15677
  return this.spendRequestResource;
14932
15678
  }
14933
15679
  createPaymentMethodsResource() {
@@ -14935,17 +15681,117 @@ var ResourceFactory = class {
14935
15681
  return this.paymentMethodsResource;
14936
15682
  }
14937
15683
  const getAccessToken = this.createSdkAccessTokenProvider();
14938
- this.paymentMethodsResource = new PaymentMethodsResource({
14939
- verbose: this.verbose,
14940
- defaultHeaders: this.defaultHeaders,
14941
- getAccessToken
14942
- });
15684
+ this.paymentMethodsResource = sanitizeResource(
15685
+ new PaymentMethodsResource({
15686
+ verbose: this.verbose,
15687
+ defaultHeaders: this.defaultHeaders,
15688
+ getAccessToken
15689
+ })
15690
+ );
14943
15691
  return this.paymentMethodsResource;
14944
15692
  }
15693
+ createShippingAddressResource() {
15694
+ if (this.shippingAddressResource) {
15695
+ return this.shippingAddressResource;
15696
+ }
15697
+ const getAccessToken = this.createSdkAccessTokenProvider();
15698
+ this.shippingAddressResource = sanitizeResource(
15699
+ new ShippingAddressResource({
15700
+ verbose: this.verbose,
15701
+ defaultHeaders: this.defaultHeaders,
15702
+ getAccessToken
15703
+ })
15704
+ );
15705
+ return this.shippingAddressResource;
15706
+ }
15707
+ createUserInfoResource() {
15708
+ if (this.userInfoResource) {
15709
+ return this.userInfoResource;
15710
+ }
15711
+ const getAccessToken = this.createSdkAccessTokenProvider();
15712
+ this.userInfoResource = sanitizeResource(
15713
+ new UserInfoResource({
15714
+ verbose: this.verbose,
15715
+ defaultHeaders: this.defaultHeaders,
15716
+ getAccessToken
15717
+ })
15718
+ );
15719
+ return this.userInfoResource;
15720
+ }
14945
15721
  };
14946
15722
 
15723
+ // src/utils/update-info.ts
15724
+ import process8 from "process";
15725
+ import updateNotifier from "update-notifier";
15726
+ var UPDATE_CACHE_TTL_MS = 60 * 60 * 1e3;
15727
+ var FAILURE_CACHE_TTL_MS = 60 * 1e3;
15728
+ var cachedUpdateInfo;
15729
+ var inflightUpdateInfo;
15730
+ function createAgentUpdateInfoProvider(packageName, cliVersion2) {
15731
+ return async ({ polling }) => {
15732
+ if (polling) {
15733
+ return cachedUpdateInfo?.value;
15734
+ }
15735
+ const now = Date.now();
15736
+ if (cachedUpdateInfo && cachedUpdateInfo.expiresAt > now) {
15737
+ return cachedUpdateInfo.value;
15738
+ }
15739
+ if (!inflightUpdateInfo) {
15740
+ inflightUpdateInfo = fetchLatestVersion(packageName, cliVersion2).finally(
15741
+ () => {
15742
+ inflightUpdateInfo = void 0;
15743
+ }
15744
+ );
15745
+ }
15746
+ return inflightUpdateInfo;
15747
+ };
15748
+ }
15749
+ function createInteractiveUpdateInfoProvider(updateInfo) {
15750
+ return async () => updateInfo;
15751
+ }
15752
+ function renderInteractiveUpdateNotice(updateInfo) {
15753
+ return [
15754
+ "",
15755
+ `Update available for @stripe/link-cli: ${updateInfo.current} -> ${updateInfo.latest}`,
15756
+ "Run: npm install -g @stripe/link-cli",
15757
+ ""
15758
+ ].join("\n");
15759
+ }
15760
+ async function fetchLatestVersion(packageName, cliVersion2) {
15761
+ const previousDisableFlag = process8.env.NO_UPDATE_NOTIFIER;
15762
+ try {
15763
+ process8.env.NO_UPDATE_NOTIFIER = "1";
15764
+ const notifier = updateNotifier({
15765
+ pkg: {
15766
+ name: packageName,
15767
+ version: cliVersion2
15768
+ }
15769
+ });
15770
+ const payload = await notifier.fetchInfo();
15771
+ const latest = payload.latest?.trim();
15772
+ const value = latest && latest.length > 0 && latest !== cliVersion2 ? { current: cliVersion2, latest } : void 0;
15773
+ cacheUpdateInfo(value);
15774
+ return value;
15775
+ } catch {
15776
+ cacheUpdateInfo(void 0, FAILURE_CACHE_TTL_MS);
15777
+ return void 0;
15778
+ } finally {
15779
+ if (previousDisableFlag === void 0) {
15780
+ process8.env.NO_UPDATE_NOTIFIER = void 0;
15781
+ } else {
15782
+ process8.env.NO_UPDATE_NOTIFIER = previousDisableFlag;
15783
+ }
15784
+ }
15785
+ }
15786
+ function cacheUpdateInfo(value, ttlMs = UPDATE_CACHE_TTL_MS) {
15787
+ cachedUpdateInfo = {
15788
+ expiresAt: Date.now() + ttlMs,
15789
+ value
15790
+ };
15791
+ }
15792
+
14947
15793
  // src/cli.tsx
14948
- var cliVersion = "0.4.2";
15794
+ var cliVersion = "0.5.0";
14949
15795
  var buildNumber = "1";
14950
15796
  var cliName = "@stripe/link-cli";
14951
15797
  var defaultHeaders = {
@@ -14953,40 +15799,66 @@ var defaultHeaders = {
14953
15799
  "X-Build-Number": buildNumber
14954
15800
  };
14955
15801
  var verbose = process.argv.includes("--verbose");
14956
- var factory = new ResourceFactory({ verbose, defaultHeaders });
15802
+ var authFileIndex = process.argv.indexOf("--auth");
15803
+ var credentialFilePath = authFileIndex !== -1 ? process.argv[authFileIndex + 1] : process.env.LINK_AUTH_FILE;
15804
+ if (authFileIndex !== -1) {
15805
+ process.argv.splice(authFileIndex, 2);
15806
+ }
15807
+ var authStorage = credentialFilePath ? new Storage({ configPath: credentialFilePath }) : storage;
15808
+ var factory = new ResourceFactory({ verbose, defaultHeaders, authStorage });
14957
15809
  var authRepo = factory.createAuthResource();
14958
15810
  var spendRequestRepo = factory.createSpendRequestResource();
14959
- var notifier = updateNotifier({
14960
- pkg: { name: cliName, version: cliVersion }
14961
- });
14962
- var cli = Cli7.create("link-cli", {
15811
+ var cli = Cli9.create("link-cli", {
14963
15812
  description: "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users.",
14964
15813
  version: `${cliVersion} (build ${buildNumber})`
14965
15814
  });
14966
- cli.command(createAuthCli(authRepo, notifier.update));
14967
- cli.command(createSpendRequestCli(spendRequestRepo));
15815
+ var isAgent = process.argv.includes("--format") || process.argv.includes("--mcp");
15816
+ var agentUpdateInfoProvider = createAgentUpdateInfoProvider(
15817
+ cliName,
15818
+ cliVersion
15819
+ );
15820
+ var getUpdateInfo = agentUpdateInfoProvider;
15821
+ if (!isAgent && process.stdout.isTTY) {
15822
+ const updateInfo = await agentUpdateInfoProvider({ polling: false });
15823
+ getUpdateInfo = createInteractiveUpdateInfoProvider(updateInfo);
15824
+ if (updateInfo) {
15825
+ process.stderr.write(renderInteractiveUpdateNotice(updateInfo));
15826
+ }
15827
+ }
15828
+ cli.command(createAuthCli(authRepo, getUpdateInfo, authStorage));
15829
+ cli.command(createSpendRequestCli(spendRequestRepo, authStorage));
14968
15830
  cli.command(
14969
- createPaymentMethodsCli(() => factory.createPaymentMethodsResource())
15831
+ createPaymentMethodsCli(
15832
+ () => factory.createPaymentMethodsResource(),
15833
+ authStorage
15834
+ )
14970
15835
  );
14971
- cli.command(createMppCli(spendRequestRepo));
15836
+ cli.command(
15837
+ createShippingAddressCli(
15838
+ () => factory.createShippingAddressResource(),
15839
+ authStorage
15840
+ )
15841
+ );
15842
+ cli.command(
15843
+ createUserInfoCli(() => factory.createUserInfoResource(), authStorage)
15844
+ );
15845
+ cli.command(createMppCli(spendRequestRepo, authStorage));
14972
15846
  cli.command(
14973
15847
  createDemoCli(
14974
15848
  authRepo,
14975
15849
  spendRequestRepo,
14976
- () => factory.createPaymentMethodsResource()
15850
+ () => factory.createPaymentMethodsResource(),
15851
+ authStorage
14977
15852
  )
14978
15853
  );
14979
15854
  cli.command(
14980
15855
  createOnboardCli(
14981
15856
  authRepo,
14982
15857
  spendRequestRepo,
14983
- () => factory.createPaymentMethodsResource()
15858
+ () => factory.createPaymentMethodsResource(),
15859
+ authStorage
14984
15860
  )
14985
15861
  );
14986
- var isAgent = process.argv.includes("--format") || process.argv.includes("--mcp");
14987
- if (!isAgent) {
14988
- notifier.notify({ defer: false });
14989
- }
14990
15862
  cli.serve();
14991
15863
  var cli_default = cli;
14992
15864
  export {