@stripe/link-cli 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -27
  2. package/dist/cli.js +1098 -1530
  3. package/package.json +4 -2
package/dist/cli.js CHANGED
@@ -2470,9 +2470,9 @@ var require_validate = __commonJS({
2470
2470
  }
2471
2471
  }
2472
2472
  function returnResults(it) {
2473
- const { gen, schemaEnv, validateName, ValidationError: ValidationError2, opts } = it;
2473
+ const { gen, schemaEnv, validateName, ValidationError, opts } = it;
2474
2474
  if (schemaEnv.$async) {
2475
- gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError2}(${names_1.default.vErrors})`));
2475
+ gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError}(${names_1.default.vErrors})`));
2476
2476
  } else {
2477
2477
  gen.assign((0, codegen_1._)`${validateName}.errors`, names_1.default.vErrors);
2478
2478
  if (opts.unevaluated)
@@ -2824,14 +2824,14 @@ var require_validation_error = __commonJS({
2824
2824
  "../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/runtime/validation_error.js"(exports) {
2825
2825
  "use strict";
2826
2826
  Object.defineProperty(exports, "__esModule", { value: true });
2827
- var ValidationError2 = class extends Error {
2827
+ var ValidationError = class extends Error {
2828
2828
  constructor(errors) {
2829
2829
  super("validation failed");
2830
2830
  this.errors = errors;
2831
2831
  this.ajv = this.validation = true;
2832
2832
  }
2833
2833
  };
2834
- exports.default = ValidationError2;
2834
+ exports.default = ValidationError;
2835
2835
  }
2836
2836
  });
2837
2837
 
@@ -9549,7 +9549,8 @@ var require_semver2 = __commonJS({
9549
9549
  });
9550
9550
 
9551
9551
  // src/cli.tsx
9552
- import { Command } from "commander";
9552
+ import { Cli as Cli5 } from "incur";
9553
+ import updateNotifier from "update-notifier";
9553
9554
 
9554
9555
  // ../sdk/dist/index.js
9555
9556
  import fs3 from "fs";
@@ -10867,7 +10868,8 @@ var Storage = class {
10867
10868
  this.config = new Conf({
10868
10869
  projectName: "link-cli",
10869
10870
  defaults: {
10870
- auth: null
10871
+ auth: null,
10872
+ pendingDeviceAuth: null
10871
10873
  }
10872
10874
  });
10873
10875
  }
@@ -10885,6 +10887,21 @@ var Storage = class {
10885
10887
  isAuthenticated() {
10886
10888
  return this.getAuth() !== null;
10887
10889
  }
10890
+ getPendingDeviceAuth() {
10891
+ const pending = this.getConfig().get("pendingDeviceAuth");
10892
+ if (!pending) return null;
10893
+ if (Date.now() >= pending.expires_at) {
10894
+ this.clearPendingDeviceAuth();
10895
+ return null;
10896
+ }
10897
+ return pending;
10898
+ }
10899
+ setPendingDeviceAuth(pending) {
10900
+ this.getConfig().set("pendingDeviceAuth", pending);
10901
+ }
10902
+ clearPendingDeviceAuth() {
10903
+ this.getConfig().set("pendingDeviceAuth", null);
10904
+ }
10888
10905
  clearAll() {
10889
10906
  this.getConfig().clear();
10890
10907
  }
@@ -11229,242 +11246,10 @@ var SpendRequestResource = class {
11229
11246
  }
11230
11247
  };
11231
11248
 
11232
- // src/utils/execute-command.tsx
11249
+ // src/commands/auth/index.tsx
11250
+ import { Cli } from "incur";
11233
11251
  import { render } from "ink";
11234
11252
 
11235
- // src/utils/json-options.ts
11236
- import { z } from "zod";
11237
- var ValidationError = class extends Error {
11238
- errors;
11239
- constructor(errors) {
11240
- super(errors.join("\n"));
11241
- this.errors = errors;
11242
- }
11243
- };
11244
- function flagToCommanderKey(flag) {
11245
- const name = flag.split(" ")[0].replace(/^--/, "");
11246
- return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
11247
- }
11248
- function registerSchemaOptions(cmd, schema) {
11249
- function collect(value, previous) {
11250
- return previous.concat([value]);
11251
- }
11252
- for (const [, def] of Object.entries(schema)) {
11253
- const flags = def.alias ? `${def.alias}, ${def.flag}` : def.flag;
11254
- if (def.schema instanceof z.ZodArray) {
11255
- cmd.option(flags, def.description, collect, []);
11256
- } else if (def.defaultValue !== void 0) {
11257
- cmd.option(flags, def.description, def.defaultValue);
11258
- } else {
11259
- cmd.option(flags, def.description);
11260
- }
11261
- }
11262
- }
11263
- function resolveInput(options, schema) {
11264
- const entries = Object.entries(schema);
11265
- let rawInput;
11266
- if (options.json !== void 0) {
11267
- const conflicts = entries.filter(([, def]) => {
11268
- const val = options[flagToCommanderKey(def.flag)];
11269
- if (def.schema instanceof z.ZodArray)
11270
- return Array.isArray(val) && val.length > 0;
11271
- if (def.defaultValue !== void 0)
11272
- return val !== void 0 && val !== def.defaultValue;
11273
- return val !== void 0;
11274
- });
11275
- if (conflicts.length > 0) {
11276
- const names = conflicts.map(([, def]) => def.flag.split(" ")[0]).join(", ");
11277
- throw new Error(`Cannot combine --json with individual flags (${names})`);
11278
- }
11279
- try {
11280
- rawInput = JSON.parse(options.json);
11281
- } catch {
11282
- throw new Error(`Invalid JSON: ${options.json}`);
11283
- }
11284
- for (const [key, def] of entries) {
11285
- if (rawInput[key] === void 0 && def.defaultValue !== void 0) {
11286
- rawInput[key] = def.defaultValue;
11287
- }
11288
- }
11289
- } else {
11290
- rawInput = {};
11291
- for (const [key, def] of entries) {
11292
- const val = options[flagToCommanderKey(def.flag)];
11293
- if (def.schema instanceof z.ZodArray) {
11294
- const arr = val ?? [];
11295
- if (arr.length > 0) {
11296
- rawInput[key] = def.flagParser ? arr.map(def.flagParser) : arr;
11297
- }
11298
- } else if (val !== void 0) {
11299
- rawInput[key] = val;
11300
- }
11301
- }
11302
- }
11303
- const zodShape = Object.fromEntries(
11304
- entries.map(([key, def]) => [
11305
- key,
11306
- def.required ? def.schema : def.schema.optional()
11307
- ])
11308
- );
11309
- const fieldToFlag = Object.fromEntries(
11310
- entries.map(([key, def]) => [key, def.flag.split(" ")[0]])
11311
- );
11312
- const useJsonKeys = !!options.outputJson;
11313
- try {
11314
- return z.object(zodShape).strict().parse(rawInput);
11315
- } catch (err) {
11316
- if (err instanceof z.ZodError) {
11317
- const messages = err.issues.map((issue) => {
11318
- const fieldName = issue.path[0];
11319
- const label = !useJsonKeys && fieldName && fieldToFlag[fieldName] ? fieldToFlag[fieldName] : issue.path.join(".");
11320
- return label ? `${label}: ${issue.message}` : issue.message;
11321
- });
11322
- throw new ValidationError(messages);
11323
- }
11324
- throw err;
11325
- }
11326
- }
11327
-
11328
- // src/utils/execute-command.tsx
11329
- function outputJson(data) {
11330
- process.stdout.write(`${JSON.stringify(data, null, 2)}
11331
-
11332
- `);
11333
- }
11334
- function outputError(message, code) {
11335
- process.stderr.write(
11336
- `${JSON.stringify({ error: message, ...code && { code } })}
11337
- `
11338
- );
11339
- process.exit(1);
11340
- }
11341
- function outputErrors(errors, asJson) {
11342
- if (asJson) {
11343
- process.stderr.write(`${JSON.stringify({ errors })}
11344
- `);
11345
- } else {
11346
- process.stderr.write(`${errors.join("\n")}
11347
- `);
11348
- }
11349
- process.exit(1);
11350
- }
11351
- async function executeCommand(opts) {
11352
- try {
11353
- if (opts.outputJson) {
11354
- const data = await opts.jsonFn();
11355
- outputJson(data);
11356
- } else if (!process.stdout.isTTY) {
11357
- process.stderr.write("No TTY detected \u2014 falling back to JSON output.\n");
11358
- process.stderr.write(
11359
- "Run 'link-cli skill' to read the full Link CLI skill file.\n"
11360
- );
11361
- const data = await opts.jsonFn();
11362
- outputJson(data);
11363
- } else {
11364
- const { waitUntilExit } = render(opts.renderFn());
11365
- await waitUntilExit();
11366
- }
11367
- } catch (err) {
11368
- if (err instanceof ValidationError) {
11369
- outputErrors(err.errors, opts.outputJson);
11370
- }
11371
- if (err instanceof LinkAuthenticationError) {
11372
- outputError(
11373
- "Not authenticated. Please run `link login` first.",
11374
- err.code
11375
- );
11376
- }
11377
- const sdkErr = err;
11378
- outputError(err.message, sdkErr?.code);
11379
- }
11380
- }
11381
-
11382
- // src/utils/help-text.ts
11383
- import { z as z2 } from "zod";
11384
- function buildInputHelp(schema) {
11385
- const entries = Object.entries(schema);
11386
- const nonArrayEntries = entries.filter(
11387
- ([, def]) => !(def.schema instanceof z2.ZodArray)
11388
- );
11389
- const arrayEntries = entries.filter(
11390
- ([, def]) => def.schema instanceof z2.ZodArray
11391
- );
11392
- const nonArrayFlags = nonArrayEntries.map(([, def]) => {
11393
- const flag = def.flag.split(" ")[0];
11394
- return def.required ? `${flag} (required)` : flag;
11395
- });
11396
- const flagPrefix = " Flags: ";
11397
- const indent = " ".repeat(flagPrefix.length);
11398
- const arrayFlagLines = arrayEntries.map(
11399
- ([, def]) => `${indent}${def.flag} (repeatable)`
11400
- );
11401
- const flagsLine = `${flagPrefix}${nonArrayFlags.join(" ")}`;
11402
- const flagsSection = arrayFlagLines.length > 0 ? `${flagsLine}
11403
- ${arrayFlagLines.join("\n")}` : flagsLine;
11404
- const jsonIndent = " ";
11405
- const jsonFields = entries.map(([key, def], i) => {
11406
- const comma = i < entries.length - 1 ? "," : "";
11407
- const desc = def.jsonDescription ?? def.description;
11408
- const requiredNote = def.required ? "required" : "";
11409
- const descNote = desc ? desc : "";
11410
- const commentParts = [requiredNote, descNote].filter(Boolean);
11411
- const comment = commentParts.length > 0 ? ` // ${commentParts.join(" \u2014 ")}` : "";
11412
- if (def.schema instanceof z2.ZodArray) {
11413
- return `${jsonIndent}"${key}": [...]${comma}${comment}`;
11414
- }
11415
- const placeholder = def.defaultValue !== void 0 ? JSON.stringify(def.defaultValue) : '"..."';
11416
- return `${jsonIndent}"${key}": ${placeholder}${comma}${comment}`;
11417
- });
11418
- const jsonLine = ` JSON: --json '{
11419
- ${jsonFields.join("\n")}
11420
- }'`;
11421
- const arrayDetails = [];
11422
- for (const [key, def] of arrayEntries) {
11423
- const element = def.schema.element;
11424
- if (!(element instanceof z2.ZodObject)) continue;
11425
- const keys = Object.keys(element.shape);
11426
- const flagFormat = `"key:<value>,key:<value>,..."`;
11427
- const jsonExample = keys.slice(0, 3).map((k) => `"${k}": "..."`).join(", ");
11428
- const jsonFormat = `"${key}": [{ ${jsonExample}${keys.length > 3 ? ", ..." : ""} }]`;
11429
- arrayDetails.push(
11430
- ` ${def.flag}
11431
- Keys: ${keys.join(", ")}
11432
- Flag: ${flagFormat}
11433
- JSON: ${jsonFormat}`
11434
- );
11435
- }
11436
- const parts = ["\nInput formats:", flagsSection, jsonLine];
11437
- if (arrayDetails.length > 0) {
11438
- parts.push("", ...arrayDetails);
11439
- }
11440
- return `${parts.join("\n")}
11441
- `;
11442
- }
11443
- function buildOutputHelp(schema, isArray = false) {
11444
- const fields = Object.entries(schema);
11445
- const lines = ["\nOutput (--output-json):"];
11446
- const outerIndent = " ";
11447
- const innerIndent = isArray ? " " : " ";
11448
- if (isArray) {
11449
- lines.push(`${outerIndent}[`);
11450
- lines.push(`${outerIndent} {`);
11451
- } else {
11452
- lines.push(`${outerIndent}{`);
11453
- }
11454
- fields.forEach(([key, def], i) => {
11455
- const comma = i < fields.length - 1 ? "," : "";
11456
- lines.push(`${innerIndent}"${key}": ${def.outputExample}${comma}`);
11457
- });
11458
- if (isArray) {
11459
- lines.push(`${outerIndent} }`);
11460
- lines.push(`${outerIndent}]`);
11461
- } else {
11462
- lines.push(`${outerIndent}}`);
11463
- }
11464
- return `${lines.join("\n")}
11465
- `;
11466
- }
11467
-
11468
11253
  // src/commands/auth/login.tsx
11469
11254
  import { Box, Text, useInput } from "ink";
11470
11255
  import Spinner from "ink-spinner";
@@ -11590,7 +11375,7 @@ var Login = ({
11590
11375
  ] }),
11591
11376
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press Enter to open in browser" }),
11592
11377
  /* @__PURE__ */ jsxs(Text, { children: [
11593
- "Enter passphrase:",
11378
+ "Enter phrase:",
11594
11379
  " ",
11595
11380
  /* @__PURE__ */ jsx(Text, { bold: true, color: "yellow", children: userCode })
11596
11381
  ] })
@@ -11608,14 +11393,24 @@ var Login = ({
11608
11393
  import { Box as Box2, Text as Text2 } from "ink";
11609
11394
  import { useEffect as useEffect2, useState as useState2 } from "react";
11610
11395
  import { jsx as jsx2 } from "react/jsx-runtime";
11611
- var Logout = ({ onComplete }) => {
11396
+ var Logout = ({ authResource, onComplete }) => {
11612
11397
  const [done, setDone] = useState2(false);
11613
11398
  useEffect2(() => {
11614
- storage.clearAuth();
11615
- storage.deleteConfig();
11616
- setDone(true);
11617
- setTimeout(onComplete, 1e3);
11618
- }, [onComplete]);
11399
+ 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
+ }
11406
+ }
11407
+ storage.clearAuth();
11408
+ storage.deleteConfig();
11409
+ setDone(true);
11410
+ setTimeout(onComplete, 1e3);
11411
+ };
11412
+ run();
11413
+ }, [authResource, onComplete]);
11619
11414
  if (!done) {
11620
11415
  return null;
11621
11416
  }
@@ -11623,217 +11418,166 @@ var Logout = ({ onComplete }) => {
11623
11418
  };
11624
11419
 
11625
11420
  // src/commands/auth/schema.ts
11626
- import { z as z3 } from "zod";
11627
- var LOGIN_INPUT_SCHEMA = {
11628
- client_name: {
11629
- schema: z3.string().trim().min(1),
11630
- flag: "--client-name <name>",
11631
- description: "Agent or app name shown in the Link app",
11632
- jsonDescription: 'Shown to the user when approving the device connection \u2014 use a short, recognizable name (e.g. "Personal Assistant")',
11633
- defaultValue: "Link CLI"
11634
- }
11635
- };
11636
- var LOGIN_SCHEMA = {
11637
- authenticated: {
11638
- outputExample: "true",
11639
- description: "Is the user authenticated with Link"
11640
- },
11641
- token_type: { outputExample: '"..."', description: "Token type" }
11642
- };
11643
- var LOGOUT_SCHEMA = {
11644
- authenticated: {
11645
- outputExample: "false",
11646
- description: "Is the user authenticated with Link"
11647
- }
11648
- };
11649
- var AUTH_STATUS_SCHEMA = {
11650
- authenticated: {
11651
- outputExample: "true",
11652
- description: "Is the user authenticated with Link"
11653
- },
11654
- access_token: {
11655
- outputExample: '"liwltoken_abdec12345..." (truncated)',
11656
- description: "Access token (truncated)"
11657
- },
11658
- token_type: { outputExample: '"Bearer"', description: "Token type" },
11659
- credentials_path: {
11660
- outputExample: '"~/.link-cli-nodejs/config.json"',
11661
- description: "Path to credentials file"
11662
- }
11663
- };
11664
-
11665
- // src/commands/auth/status.tsx
11666
- import { Box as Box3, Text as Text3 } from "ink";
11667
- import { useEffect as useEffect3, useState as useState3 } from "react";
11668
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
11669
- var AuthStatus = ({ onComplete }) => {
11670
- const [checked, setChecked] = useState3(false);
11671
- const [authenticated, setAuthenticated] = useState3(false);
11672
- const [tokenPreview, setTokenPreview] = useState3("");
11673
- const [tokenType, setTokenType] = useState3("");
11674
- const [credentialsPath, setCredentialsPath] = useState3("");
11675
- useEffect3(() => {
11676
- const auth = storage.getAuth();
11677
- const credentialsPath2 = storage.getPath();
11678
- if (auth) {
11679
- setAuthenticated(true);
11680
- setTokenPreview(`${auth.access_token.substring(0, 20)}...`);
11681
- setTokenType(auth.token_type);
11682
- }
11683
- setCredentialsPath(credentialsPath2);
11684
- setChecked(true);
11685
- setTimeout(onComplete, 1e3);
11686
- }, [onComplete]);
11687
- if (!checked) {
11688
- return null;
11689
- }
11690
- if (authenticated) {
11691
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
11692
- /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Authenticated" }),
11693
- /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
11694
- /* @__PURE__ */ jsxs2(Text3, { children: [
11695
- "Access token: ",
11696
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: tokenPreview })
11697
- ] }),
11698
- /* @__PURE__ */ jsxs2(Text3, { children: [
11699
- "Token type: ",
11700
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: tokenType })
11701
- ] }),
11702
- /* @__PURE__ */ jsxs2(Text3, { children: [
11703
- "Credentials: ",
11704
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: credentialsPath })
11705
- ] })
11706
- ] })
11707
- ] });
11708
- }
11709
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
11710
- /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u2717 Not authenticated" }),
11711
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: 'Run "link-cli auth login" to authenticate' }),
11712
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs2(Text3, { children: [
11713
- "Credentials: ",
11714
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: credentialsPath })
11715
- ] }) })
11716
- ] });
11717
- };
11421
+ import { z } from "incur";
11422
+ var loginOptions = z.object({
11423
+ clientName: z.string().default("Link CLI").describe(
11424
+ "Agent or app name shown in the Link app when approving the device connection"
11425
+ )
11426
+ });
11427
+ var statusOptions = z.object({
11428
+ interval: z.coerce.number().default(0).describe(
11429
+ "Poll interval in seconds. When > 0, polls until authenticated or timeout is reached, yielding status on each attempt."
11430
+ ),
11431
+ maxAttempts: z.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
11432
+ timeout: z.coerce.number().default(300).describe("Polling timeout in seconds.")
11433
+ });
11718
11434
 
11719
11435
  // src/commands/auth/index.tsx
11720
- import { jsx as jsx4 } from "react/jsx-runtime";
11721
- function registerAuthCommands(program2, authResource) {
11722
- const authCommand2 = program2.command("auth").description("Authentication commands").helpCommand(false);
11723
- const loginCmd = authCommand2.command("login").description("Authenticate with Link");
11724
- registerSchemaOptions(loginCmd, LOGIN_INPUT_SCHEMA);
11725
- loginCmd.option(
11726
- "--json <json>",
11727
- `JSON input (keys: ${Object.keys(LOGIN_INPUT_SCHEMA).join(", ")})`
11728
- ).option(
11729
- "--output-json",
11730
- "Output result as JSON instead of interactive display"
11731
- ).addHelpText(
11732
- "after",
11733
- buildInputHelp(LOGIN_INPUT_SCHEMA) + buildOutputHelp(LOGIN_SCHEMA)
11734
- ).action(async (options) => {
11735
- let input = {};
11736
- try {
11737
- input = resolveInput(options, LOGIN_INPUT_SCHEMA);
11738
- } catch (err) {
11739
- if (err instanceof ValidationError) {
11740
- outputErrors(err.errors, !!options.outputJson);
11741
- process.exit(1);
11742
- }
11743
- throw err;
11744
- }
11745
- const clientName = input.client_name;
11746
- await executeCommand({
11747
- outputJson: !!options.outputJson,
11748
- jsonFn: async () => {
11749
- const authRequest = await authResource.initiateDeviceAuth(clientName);
11750
- outputJson({
11751
- verification_url: authRequest.verification_url_complete,
11752
- passphrase: authRequest.user_code
11436
+ import { jsx as jsx3 } from "react/jsx-runtime";
11437
+ function createAuthCli(authResource, updateInfo) {
11438
+ const cli2 = Cli.create("auth", {
11439
+ description: "Authentication commands"
11440
+ });
11441
+ cli2.command("login", {
11442
+ description: "Authenticate with Link",
11443
+ options: loginOptions,
11444
+ outputPolicy: "agent-only",
11445
+ async *run(c) {
11446
+ const clientName = c.options.clientName?.trim();
11447
+ if (!clientName || clientName.length === 0) {
11448
+ return c.error({
11449
+ code: "INVALID_INPUT",
11450
+ message: "client-name must be a non-empty string"
11753
11451
  });
11754
- const pollInterval = authRequest.interval * 1e3;
11755
- const expiresAt = Date.now() + authRequest.expires_in * 1e3;
11756
- const startTime = Date.now();
11757
- while (Date.now() < expiresAt) {
11758
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
11759
- const elapsedSeconds = Math.floor((Date.now() - startTime) / 1e3);
11760
- process.stderr.write(
11761
- `${JSON.stringify({
11762
- type: "waiting",
11763
- command: "auth_login",
11764
- elapsed_seconds: elapsedSeconds,
11765
- verification_url: authRequest.verification_url_complete,
11766
- passphrase: authRequest.user_code
11767
- })}
11768
- `
11452
+ }
11453
+ if (!c.agent && !c.formatExplicit) {
11454
+ return new Promise((resolve) => {
11455
+ const { waitUntilExit } = render(
11456
+ /* @__PURE__ */ jsx3(
11457
+ Login,
11458
+ {
11459
+ authResource,
11460
+ clientName,
11461
+ onComplete: () => {
11462
+ }
11463
+ }
11464
+ )
11769
11465
  );
11770
- const tokens = await authResource.pollDeviceAuth(
11771
- authRequest.device_code
11466
+ waitUntilExit().then(
11467
+ () => resolve({ authenticated: true, token_type: "Bearer" })
11468
+ );
11469
+ });
11470
+ }
11471
+ const authRequest = await authResource.initiateDeviceAuth(clientName);
11472
+ storage.setPendingDeviceAuth({
11473
+ device_code: authRequest.device_code,
11474
+ interval: authRequest.interval,
11475
+ expires_at: Date.now() + authRequest.expires_in * 1e3,
11476
+ verification_url: authRequest.verification_url_complete,
11477
+ phrase: authRequest.user_code
11478
+ });
11479
+ yield {
11480
+ verification_url: authRequest.verification_url_complete,
11481
+ phrase: authRequest.user_code,
11482
+ instruction: "Present the verification_url to the user and ask them to approve in the Link app. Then call `auth status --interval 5 --max-attempts 60` to poll until authenticated. Do not wait for the user to reply \u2014 start polling immediately.",
11483
+ _next: {
11484
+ command: "auth status --interval 5 --max-attempts 60",
11485
+ poll_interval_seconds: authRequest.interval,
11486
+ until: "authenticated is true"
11487
+ }
11488
+ };
11489
+ }
11490
+ });
11491
+ cli2.command("logout", {
11492
+ description: "Log out from Link",
11493
+ outputPolicy: "agent-only",
11494
+ async run(c) {
11495
+ const auth = storage.getAuth();
11496
+ if (auth?.refresh_token) {
11497
+ try {
11498
+ await authResource.revokeToken(auth.refresh_token);
11499
+ } catch {
11500
+ }
11501
+ }
11502
+ storage.clearAuth();
11503
+ storage.clearPendingDeviceAuth();
11504
+ storage.deleteConfig();
11505
+ const result = { authenticated: false };
11506
+ if (!c.agent && !c.formatExplicit) {
11507
+ return new Promise((resolve) => {
11508
+ const { waitUntilExit } = render(
11509
+ /* @__PURE__ */ jsx3(Logout, { authResource, onComplete: () => {
11510
+ } })
11772
11511
  );
11512
+ waitUntilExit().then(() => resolve(result));
11513
+ });
11514
+ }
11515
+ return result;
11516
+ }
11517
+ });
11518
+ cli2.command("status", {
11519
+ description: "Check authentication status",
11520
+ options: statusOptions,
11521
+ outputPolicy: "agent-only",
11522
+ async *run(c) {
11523
+ const opts = c.options;
11524
+ const interval = opts.interval;
11525
+ const maxAttempts = opts.maxAttempts;
11526
+ const deadline = Date.now() + opts.timeout * 1e3;
11527
+ let attempts = 0;
11528
+ while (true) {
11529
+ const pending = storage.getPendingDeviceAuth();
11530
+ if (pending && !storage.isAuthenticated()) {
11531
+ const tokens = await authResource.pollDeviceAuth(pending.device_code);
11773
11532
  if (tokens) {
11774
11533
  storage.setAuth(tokens);
11775
- return { authenticated: true, token_type: tokens.token_type };
11776
- }
11777
- }
11778
- throw new Error("Device authorization timed out");
11779
- },
11780
- renderFn: () => /* @__PURE__ */ jsx4(
11781
- Login,
11782
- {
11783
- authResource,
11784
- clientName,
11785
- onComplete: () => {
11534
+ storage.clearPendingDeviceAuth();
11786
11535
  }
11787
11536
  }
11788
- )
11789
- });
11790
- });
11791
- authCommand2.command("logout").description("Log out from Link").option(
11792
- "--output-json",
11793
- "Output result as JSON instead of interactive display"
11794
- ).addHelpText("after", buildOutputHelp(LOGOUT_SCHEMA)).action(async (options) => {
11795
- await executeCommand({
11796
- outputJson: !!options.outputJson,
11797
- jsonFn: async () => {
11798
- storage.clearAuth();
11799
- storage.deleteConfig();
11800
- return { authenticated: false };
11801
- },
11802
- renderFn: () => /* @__PURE__ */ jsx4(Logout, { onComplete: () => {
11803
- } })
11804
- });
11805
- });
11806
- authCommand2.command("status").description("Check authentication status").option(
11807
- "--output-json",
11808
- "Output result as JSON instead of interactive display"
11809
- ).addHelpText("after", buildOutputHelp(AUTH_STATUS_SCHEMA)).action(async (options) => {
11810
- await executeCommand({
11811
- outputJson: !!options.outputJson,
11812
- jsonFn: async () => {
11813
11537
  const auth = storage.getAuth();
11538
+ const update = updateInfo ? {
11539
+ current_version: updateInfo.current,
11540
+ latest_version: updateInfo.latest,
11541
+ update_command: "npm install -g @stripe/link-cli"
11542
+ } : void 0;
11814
11543
  if (auth) {
11815
- return {
11544
+ yield {
11816
11545
  authenticated: true,
11817
11546
  access_token: `${auth.access_token.substring(0, 20)}...`,
11818
11547
  token_type: auth.token_type,
11819
- credentials_path: storage.getPath()
11548
+ credentials_path: storage.getPath(),
11549
+ ...update && { update }
11820
11550
  };
11551
+ return;
11821
11552
  }
11822
- return { authenticated: false, credentials_path: storage.getPath() };
11823
- },
11824
- renderFn: () => /* @__PURE__ */ jsx4(AuthStatus, { onComplete: () => {
11825
- } })
11826
- });
11553
+ const currentPending = storage.getPendingDeviceAuth();
11554
+ const status = {
11555
+ authenticated: false,
11556
+ credentials_path: storage.getPath(),
11557
+ ...update && { update },
11558
+ ...currentPending ? {
11559
+ pending: true,
11560
+ verification_url: currentPending.verification_url,
11561
+ phrase: currentPending.phrase
11562
+ } : {}
11563
+ };
11564
+ attempts++;
11565
+ const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
11566
+ if (shouldStop) {
11567
+ yield status;
11568
+ return;
11569
+ }
11570
+ yield status;
11571
+ await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
11572
+ }
11573
+ }
11827
11574
  });
11828
- return authCommand2;
11575
+ return cli2;
11829
11576
  }
11830
11577
 
11831
- // src/utils/require-auth.ts
11832
- function requireAuth() {
11833
- if (!storage.isAuthenticated()) {
11834
- outputError('Not authenticated. Run "link-cli auth login" first.');
11835
- }
11836
- }
11578
+ // src/commands/mpp/index.tsx
11579
+ import { Cli as Cli2, z as z3 } from "incur";
11580
+ import { render as render2 } from "ink";
11837
11581
 
11838
11582
  // src/commands/mpp/decode.ts
11839
11583
  import { Challenge } from "mppx";
@@ -11863,8 +11607,7 @@ function getMethodDetails(request) {
11863
11607
  }
11864
11608
  return methodDetails;
11865
11609
  }
11866
- function decodeStripeChallenge(challengeHeader) {
11867
- const challenges = Challenge.deserializeList(challengeHeader);
11610
+ function resolveStripeChallenge(challenges) {
11868
11611
  const stripeChallenge = challenges.find(
11869
11612
  (challenge) => challenge.method === "stripe" && challenge.intent === "charge"
11870
11613
  );
@@ -11889,51 +11632,66 @@ function decodeStripeChallenge(challengeHeader) {
11889
11632
  );
11890
11633
  }
11891
11634
  return {
11892
- id: stripeChallenge.id,
11893
- realm: stripeChallenge.realm,
11635
+ challenge: stripeChallenge,
11636
+ networkId,
11637
+ request
11638
+ };
11639
+ }
11640
+ function getStripeChargeChallengeFromResponse(response) {
11641
+ return resolveStripeChallenge(Challenge.fromResponseList(response)).challenge;
11642
+ }
11643
+ function decodeStripeChallenge(challengeHeader) {
11644
+ const { challenge, networkId, request } = resolveStripeChallenge(
11645
+ Challenge.deserializeList(challengeHeader)
11646
+ );
11647
+ return {
11648
+ id: challenge.id,
11649
+ realm: challenge.realm,
11894
11650
  method: "stripe",
11895
11651
  intent: "charge",
11896
- description: stripeChallenge.description,
11897
- digest: stripeChallenge.digest,
11898
- expires: stripeChallenge.expires,
11652
+ description: challenge.description,
11653
+ digest: challenge.digest,
11654
+ expires: challenge.expires,
11899
11655
  network_id: networkId,
11900
11656
  request_json: request
11901
11657
  };
11902
11658
  }
11903
11659
 
11904
11660
  // src/commands/mpp/decode-view.tsx
11905
- import { Box as Box4, Text as Text4 } from "ink";
11906
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
11661
+ import { Box as Box3, Text as Text3 } from "ink";
11662
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
11907
11663
  function DecodeChallengeView({
11908
11664
  decoded
11909
11665
  }) {
11910
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
11911
- /* @__PURE__ */ jsx5(Text4, { color: "green", children: "\u2713 Stripe challenge decoded" }),
11912
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
11913
- /* @__PURE__ */ jsxs3(Text4, { children: [
11666
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
11667
+ /* @__PURE__ */ jsx4(Text3, { color: "green", children: "\u2713 Stripe challenge decoded" }),
11668
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
11669
+ /* @__PURE__ */ jsxs2(Text3, { children: [
11914
11670
  "ID: ",
11915
- /* @__PURE__ */ jsx5(Text4, { bold: true, children: decoded.id })
11671
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.id })
11916
11672
  ] }),
11917
- /* @__PURE__ */ jsxs3(Text4, { children: [
11673
+ /* @__PURE__ */ jsxs2(Text3, { children: [
11918
11674
  "Realm: ",
11919
- /* @__PURE__ */ jsx5(Text4, { bold: true, children: decoded.realm })
11675
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.realm })
11920
11676
  ] }),
11921
- /* @__PURE__ */ jsxs3(Text4, { children: [
11677
+ /* @__PURE__ */ jsxs2(Text3, { children: [
11922
11678
  "Network ID: ",
11923
- /* @__PURE__ */ jsx5(Text4, { bold: true, children: decoded.network_id })
11679
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.network_id })
11924
11680
  ] }),
11925
- /* @__PURE__ */ jsx5(Text4, { children: "Request JSON:" }),
11926
- /* @__PURE__ */ jsx5(Text4, { children: JSON.stringify(decoded.request_json, null, 2) })
11681
+ /* @__PURE__ */ jsx4(Text3, { children: "Request JSON:" }),
11682
+ /* @__PURE__ */ jsx4(Text3, { children: JSON.stringify(decoded.request_json, null, 2) })
11927
11683
  ] })
11928
11684
  ] });
11929
11685
  }
11930
11686
 
11931
11687
  // src/commands/mpp/pay.tsx
11932
- import { Box as Box5, Text as Text5 } from "ink";
11688
+ import { Box as Box4, Text as Text4 } from "ink";
11933
11689
  import Spinner2 from "ink-spinner";
11934
- import { Challenge as Challenge2, Credential } from "mppx";
11935
- import { useEffect as useEffect4, useState as useState4 } from "react";
11936
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
11690
+ import { Credential, Method } from "mppx";
11691
+ import { Mppx, Transport } from "mppx/client";
11692
+ import { Methods as StripeMethods } from "mppx/stripe";
11693
+ import { useEffect as useEffect3, useState as useState3 } from "react";
11694
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
11937
11695
  function buildHeaders(data, headers) {
11938
11696
  const result = {};
11939
11697
  if (data !== void 0) {
@@ -11948,29 +11706,66 @@ function buildHeaders(data, headers) {
11948
11706
  }
11949
11707
  return result;
11950
11708
  }
11709
+ async function readPayResult(response, options) {
11710
+ const responseHeaders = Object.fromEntries(response.headers.entries());
11711
+ const body = await response.text();
11712
+ if (options?.failOnError && !response.ok) {
11713
+ throw new Error(
11714
+ `Payment submission failed with status ${response.status}: ${body}`
11715
+ );
11716
+ }
11717
+ return { status: response.status, headers: responseHeaders, body };
11718
+ }
11719
+ function createStripePaymentClient(spt) {
11720
+ const stripeCharge = Method.toClient(StripeMethods.charge, {
11721
+ async createCredential({ challenge }) {
11722
+ return Credential.serialize({
11723
+ challenge,
11724
+ payload: { spt }
11725
+ });
11726
+ }
11727
+ });
11728
+ return Mppx.create({
11729
+ methods: [stripeCharge],
11730
+ polyfill: false,
11731
+ transport: Transport.from({
11732
+ name: "stripe-http",
11733
+ isPaymentRequired(response) {
11734
+ return response.status === 402;
11735
+ },
11736
+ getChallenge(response) {
11737
+ return getStripeChargeChallengeFromResponse(response);
11738
+ },
11739
+ setCredential(request, credential) {
11740
+ const nextHeaders = new Headers(request.headers);
11741
+ nextHeaders.set("Authorization", credential);
11742
+ return { ...request, headers: nextHeaders };
11743
+ }
11744
+ })
11745
+ });
11746
+ }
11951
11747
  async function runMppPay(url, spendRequestId, method, data, headers, repository) {
11952
11748
  const spendRequest = await repository.getSpendRequest(spendRequestId, {
11953
11749
  include: ["shared_payment_token"]
11954
11750
  });
11955
11751
  if (!spendRequest) {
11956
- outputError(`Spend request ${spendRequestId} not found`);
11752
+ throw new Error(`Spend request ${spendRequestId} not found`);
11957
11753
  }
11958
11754
  if (spendRequest.credential_type !== "shared_payment_token") {
11959
11755
  const type = spendRequest.credential_type ?? "card";
11960
- outputError(
11756
+ throw new Error(
11961
11757
  `Spend request ${spendRequestId} must have credential_type 'shared_payment_token' (current: '${type}')`
11962
11758
  );
11963
11759
  }
11964
11760
  if (spendRequest.status !== "approved") {
11965
- outputError(
11761
+ throw new Error(
11966
11762
  `Spend request must be approved (current status: ${spendRequest.status})`
11967
11763
  );
11968
11764
  }
11969
- const sptObj = spendRequest.shared_payment_token;
11970
- if (!sptObj) {
11971
- outputError("Spend request does not have a shared payment token");
11765
+ if (!spendRequest.shared_payment_token) {
11766
+ throw new Error("Spend request does not have a shared payment token");
11972
11767
  }
11973
- const spt = sptObj.id;
11768
+ const spt = spendRequest.shared_payment_token.id;
11974
11769
  const httpMethod = method ?? (data !== void 0 ? "POST" : "GET");
11975
11770
  const requestHeaders = buildHeaders(data, headers);
11976
11771
  const initialResponse = await fetch(url, {
@@ -11979,30 +11774,9 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
11979
11774
  headers: requestHeaders
11980
11775
  });
11981
11776
  if (initialResponse.status !== 402) {
11982
- const responseHeaders2 = Object.fromEntries(
11983
- initialResponse.headers.entries()
11984
- );
11985
- const body2 = await initialResponse.text();
11986
- return { status: initialResponse.status, headers: responseHeaders2, body: body2 };
11777
+ return readPayResult(initialResponse);
11987
11778
  }
11988
- const decoded = decodeStripeChallenge(
11989
- initialResponse.headers.get("www-authenticate") ?? ""
11990
- );
11991
- const stripeChallenge = Challenge2.from({
11992
- id: decoded.id,
11993
- realm: decoded.realm,
11994
- method: decoded.method,
11995
- intent: decoded.intent,
11996
- request: decoded.request_json,
11997
- ...decoded.description ? { description: decoded.description } : {},
11998
- ...decoded.digest ? { digest: decoded.digest } : {},
11999
- ...decoded.expires ? { expires: decoded.expires } : {}
12000
- });
12001
- const credential = Credential.from({
12002
- challenge: stripeChallenge,
12003
- payload: { spt }
12004
- });
12005
- const authHeader = Credential.serialize(credential);
11779
+ const authHeader = await createStripePaymentClient(spt).createCredential(initialResponse);
12006
11780
  const retryResponse = await fetch(url, {
12007
11781
  method: httpMethod,
12008
11782
  body: data,
@@ -12011,15 +11785,7 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
12011
11785
  Authorization: authHeader
12012
11786
  }
12013
11787
  });
12014
- if (!retryResponse.ok) {
12015
- const body2 = await retryResponse.text();
12016
- outputError(
12017
- `Payment submission failed with status ${retryResponse.status}: ${body2}`
12018
- );
12019
- }
12020
- const responseHeaders = Object.fromEntries(retryResponse.headers.entries());
12021
- const body = await retryResponse.text();
12022
- return { status: retryResponse.status, headers: responseHeaders, body };
11788
+ return readPayResult(retryResponse, { failOnError: true });
12023
11789
  }
12024
11790
  function MppPay({
12025
11791
  url,
@@ -12030,10 +11796,10 @@ function MppPay({
12030
11796
  repository,
12031
11797
  onComplete
12032
11798
  }) {
12033
- const [step, setStep] = useState4("retrieving");
12034
- const [result, setResult] = useState4(null);
12035
- const [error, setError] = useState4(null);
12036
- useEffect4(() => {
11799
+ const [step, setStep] = useState3("retrieving");
11800
+ const [result, setResult] = useState3(null);
11801
+ const [error, setError] = useState3(null);
11802
+ useEffect3(() => {
12037
11803
  (async () => {
12038
11804
  try {
12039
11805
  setStep("retrieving");
@@ -12067,38 +11833,15 @@ function MppPay({
12067
11833
  headers: requestHeaders
12068
11834
  });
12069
11835
  if (initialResponse.status !== 402) {
12070
- const responseHeaders2 = Object.fromEntries(
12071
- initialResponse.headers.entries()
12072
- );
12073
- const body2 = await initialResponse.text();
12074
- setResult({
12075
- status: initialResponse.status,
12076
- headers: responseHeaders2,
12077
- body: body2
12078
- });
11836
+ setResult(await readPayResult(initialResponse));
12079
11837
  setStep("done");
12080
11838
  onComplete();
12081
11839
  return;
12082
11840
  }
12083
11841
  setStep("signing");
12084
- const decoded = decodeStripeChallenge(
12085
- initialResponse.headers.get("www-authenticate") ?? ""
11842
+ const authHeader = await createStripePaymentClient(spt).createCredential(
11843
+ initialResponse
12086
11844
  );
12087
- const stripeChallenge = Challenge2.from({
12088
- id: decoded.id,
12089
- realm: decoded.realm,
12090
- method: decoded.method,
12091
- intent: decoded.intent,
12092
- request: decoded.request_json,
12093
- ...decoded.description ? { description: decoded.description } : {},
12094
- ...decoded.digest ? { digest: decoded.digest } : {},
12095
- ...decoded.expires ? { expires: decoded.expires } : {}
12096
- });
12097
- const credential = Credential.from({
12098
- challenge: stripeChallenge,
12099
- payload: { spt }
12100
- });
12101
- const authHeader = Credential.serialize(credential);
12102
11845
  setStep("submitting");
12103
11846
  const retryResponse = await fetch(url, {
12104
11847
  method: httpMethod,
@@ -12108,15 +11851,7 @@ function MppPay({
12108
11851
  Authorization: authHeader
12109
11852
  }
12110
11853
  });
12111
- const responseHeaders = Object.fromEntries(
12112
- retryResponse.headers.entries()
12113
- );
12114
- const body = await retryResponse.text();
12115
- setResult({
12116
- status: retryResponse.status,
12117
- headers: responseHeaders,
12118
- body
12119
- });
11854
+ setResult(await readPayResult(retryResponse, { failOnError: true }));
12120
11855
  setStep("done");
12121
11856
  onComplete();
12122
11857
  } catch (err) {
@@ -12133,191 +11868,143 @@ function MppPay({
12133
11868
  done: "Done"
12134
11869
  };
12135
11870
  if (error) {
12136
- return /* @__PURE__ */ jsxs4(Text5, { color: "red", children: [
11871
+ return /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
12137
11872
  "Error: ",
12138
11873
  error
12139
11874
  ] });
12140
11875
  }
12141
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12142
- step !== "done" && /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
12143
- /* @__PURE__ */ jsx6(Spinner2, { type: "dots" }),
11876
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
11877
+ step !== "done" && /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsxs3(Text4, { color: "cyan", children: [
11878
+ /* @__PURE__ */ jsx5(Spinner2, { type: "dots" }),
12144
11879
  " ",
12145
11880
  stepLabels[step],
12146
11881
  "..."
12147
11882
  ] }) }),
12148
- result && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12149
- /* @__PURE__ */ jsxs4(Text5, { color: "green", children: [
11883
+ result && /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
11884
+ /* @__PURE__ */ jsxs3(Text4, { color: "green", children: [
12150
11885
  "HTTP ",
12151
11886
  result.status
12152
11887
  ] }),
12153
- /* @__PURE__ */ jsx6(Text5, { children: result.body })
11888
+ /* @__PURE__ */ jsx5(Text4, { children: result.body })
12154
11889
  ] })
12155
11890
  ] });
12156
11891
  }
12157
11892
 
12158
11893
  // src/commands/mpp/schema.ts
12159
- import { z as z4 } from "zod";
12160
- var DECODE_OUTPUT_SCHEMA = {
12161
- id: { outputExample: '"ch_123"', description: "Challenge ID" },
12162
- realm: {
12163
- outputExample: '"merchant.example"',
12164
- description: "Challenge realm"
12165
- },
12166
- method: { outputExample: '"stripe"', description: "Payment method" },
12167
- intent: { outputExample: '"charge"', description: "Challenge intent" },
12168
- network_id: {
12169
- outputExample: '"net_prod_123"',
12170
- description: "Extracted Stripe network ID"
12171
- },
12172
- request_json: {
12173
- outputExample: '{"networkId":"net_prod_123","amount":"1000","currency":"usd","decimals":2,"paymentMethodTypes":["card"]}',
12174
- description: "Decoded request payload from the stripe challenge before normalization"
12175
- }
12176
- };
12177
- var PAY_INPUT_SCHEMA = {
12178
- spend_request_id: {
12179
- schema: z4.string().min(1),
12180
- flag: "--spend-request-id <id>",
12181
- description: "Approved spend request ID with shared_payment_token credential",
12182
- jsonDescription: 'Must be an approved spend request with credential_type "shared_payment_token" \u2014 the SPT is one-time use; create a new request if payment fails',
12183
- required: true
12184
- },
12185
- method: {
12186
- schema: z4.string().min(1),
12187
- flag: "--method <method>",
12188
- alias: "-X",
12189
- description: "HTTP method (default: GET, or POST if --data is provided)"
12190
- },
12191
- data: {
12192
- schema: z4.string().min(1),
12193
- flag: "--data <body>",
12194
- alias: "-d",
12195
- description: "Request body (implies POST if --method is not set)"
12196
- },
12197
- headers: {
12198
- schema: z4.array(z4.string().min(1)),
12199
- flag: "--header <header>",
12200
- alias: "-H",
12201
- description: 'Request header in "Name: Value" format (repeatable)',
12202
- jsonDescription: 'Repeatable; "Name: Value" format \u2014 Content-Type is auto-set when --data is provided; user headers take precedence'
12203
- }
12204
- };
12205
- var DECODE_INPUT_SCHEMA = {
12206
- challenge: {
12207
- schema: z4.string().min(1),
12208
- flag: "--challenge <header>",
12209
- description: "Raw WWW-Authenticate header value to decode",
12210
- jsonDescription: "Raw WWW-Authenticate header value; may include multiple payment challenges",
12211
- required: true
12212
- }
12213
- };
12214
- var PAY_OUTPUT_SCHEMA = {
12215
- status: { outputExample: "200", description: "HTTP response status code" },
12216
- headers: {
12217
- outputExample: '{"content-type":"application/json"}',
12218
- description: "Response headers"
12219
- },
12220
- body: { outputExample: '"..."', description: "Response body" }
12221
- };
11894
+ import { z as z2 } from "incur";
11895
+ var payOptions = z2.object({
11896
+ spendRequestId: z2.string().describe(
11897
+ 'Approved spend request ID with credential_type "shared_payment_token"'
11898
+ ),
11899
+ method: z2.string().optional().describe("HTTP method (default: GET, or POST if --data is provided)"),
11900
+ data: z2.string().optional().describe("Request body (implies POST if --method is not set)"),
11901
+ header: z2.array(z2.string()).default([]).describe('Request header in "Name: Value" format (repeatable)')
11902
+ });
11903
+ var decodeOptions = z2.object({
11904
+ challenge: z2.string().describe(
11905
+ "Raw WWW-Authenticate header value; may include multiple payment challenges"
11906
+ )
11907
+ });
12222
11908
 
12223
11909
  // src/commands/mpp/index.tsx
12224
- import { jsx as jsx7 } from "react/jsx-runtime";
12225
- function registerMppCommands(program2, repository) {
12226
- const mppCommand2 = program2.command("mpp").description("Machine payment protocol (MPP) commands").helpCommand(false);
12227
- const payCmd = mppCommand2.command("pay <url>").description(
12228
- "Complete a machine payment protocol (MPP) payment using an approved spend request"
12229
- );
12230
- registerSchemaOptions(payCmd, PAY_INPUT_SCHEMA);
12231
- payCmd.option(
12232
- "--json <json>",
12233
- `JSON input (keys: ${Object.keys(PAY_INPUT_SCHEMA).join(", ")})`
12234
- ).option(
12235
- "--output-json",
12236
- "Output result as JSON instead of interactive display"
12237
- ).addHelpText(
12238
- "after",
12239
- buildInputHelp(PAY_INPUT_SCHEMA) + buildOutputHelp(PAY_OUTPUT_SCHEMA)
12240
- ).action(async (url, options) => {
12241
- requireAuth();
12242
- let resolved = {};
12243
- try {
12244
- resolved = resolveInput(options, PAY_INPUT_SCHEMA);
12245
- } catch (err) {
12246
- if (err instanceof ValidationError)
12247
- process.stderr.write(`${err.errors.join("\n")}
12248
- `);
12249
- process.stderr.write(
12250
- `${JSON.stringify({ error: err.message })}
12251
- `
12252
- );
12253
- process.exit(1);
12254
- }
12255
- const spendRequestId = resolved.spend_request_id;
12256
- const method = resolved.method;
12257
- const data = resolved.data;
12258
- const headers = resolved.headers;
12259
- await executeCommand({
12260
- outputJson: !!options.outputJson,
12261
- jsonFn: async () => {
12262
- return runMppPay(
12263
- url,
12264
- spendRequestId,
12265
- method,
12266
- data,
12267
- headers,
12268
- repository
12269
- );
12270
- },
12271
- renderFn: () => /* @__PURE__ */ jsx7(
12272
- MppPay,
12273
- {
12274
- url,
12275
- spendRequestId,
12276
- method,
12277
- data,
12278
- headers,
12279
- repository,
12280
- onComplete: () => {
11910
+ import { jsx as jsx6 } from "react/jsx-runtime";
11911
+ function createMppCli(repository) {
11912
+ const cli2 = Cli2.create("mpp", {
11913
+ description: "Machine payment protocol (MPP) commands"
11914
+ });
11915
+ cli2.command("pay", {
11916
+ description: "Complete a machine payment protocol (MPP) payment using an approved spend request",
11917
+ args: z3.object({
11918
+ url: z3.string().describe("URL to pay")
11919
+ }),
11920
+ options: payOptions,
11921
+ alias: { method: "X", data: "d", header: "H" },
11922
+ outputPolicy: "agent-only",
11923
+ async run(c) {
11924
+ if (!storage.isAuthenticated()) {
11925
+ return c.error({
11926
+ code: "NOT_AUTHENTICATED",
11927
+ message: 'Not authenticated. Run "link-cli auth login" first.',
11928
+ cta: {
11929
+ commands: [
11930
+ { command: "auth login", description: "Log in to Link" }
11931
+ ]
12281
11932
  }
12282
- }
12283
- )
12284
- });
11933
+ });
11934
+ }
11935
+ const url = c.args.url;
11936
+ const opts = c.options;
11937
+ const method = opts.method;
11938
+ const data = opts.data;
11939
+ const headers = opts.header?.length ? opts.header : void 0;
11940
+ if (!c.agent && !c.formatExplicit) {
11941
+ return new Promise((resolve) => {
11942
+ const { waitUntilExit } = render2(
11943
+ /* @__PURE__ */ jsx6(
11944
+ MppPay,
11945
+ {
11946
+ url,
11947
+ spendRequestId: opts.spendRequestId,
11948
+ method,
11949
+ data,
11950
+ headers,
11951
+ repository,
11952
+ onComplete: () => {
11953
+ }
11954
+ }
11955
+ )
11956
+ );
11957
+ waitUntilExit().then(async () => {
11958
+ resolve(
11959
+ await runMppPay(
11960
+ url,
11961
+ opts.spendRequestId,
11962
+ method,
11963
+ data,
11964
+ headers,
11965
+ repository
11966
+ )
11967
+ );
11968
+ });
11969
+ });
11970
+ }
11971
+ return runMppPay(
11972
+ url,
11973
+ opts.spendRequestId,
11974
+ method,
11975
+ data,
11976
+ headers,
11977
+ repository
11978
+ );
11979
+ }
12285
11980
  });
12286
- const decodeCmd = mppCommand2.command("decode").description(
12287
- "Decode a stripe WWW-Authenticate challenge and extract network_id"
12288
- );
12289
- registerSchemaOptions(decodeCmd, DECODE_INPUT_SCHEMA);
12290
- decodeCmd.option(
12291
- "--json <json>",
12292
- `JSON input (keys: ${Object.keys(DECODE_INPUT_SCHEMA).join(", ")})`
12293
- ).option(
12294
- "--output-json",
12295
- "Output result as JSON instead of interactive display"
12296
- ).addHelpText(
12297
- "after",
12298
- buildInputHelp(DECODE_INPUT_SCHEMA) + buildOutputHelp(DECODE_OUTPUT_SCHEMA)
12299
- ).action(async (options) => {
12300
- let resolved = {};
12301
- try {
12302
- resolved = resolveInput(options, DECODE_INPUT_SCHEMA);
12303
- } catch (err) {
12304
- if (err instanceof ValidationError)
12305
- outputErrors(err.errors, !!options.outputJson);
12306
- outputError(err.message);
12307
- }
12308
- const challenge = resolved.challenge;
12309
- await executeCommand({
12310
- outputJson: !!options.outputJson,
12311
- jsonFn: async () => decodeStripeChallenge(challenge),
12312
- renderFn: () => /* @__PURE__ */ jsx7(DecodeChallengeView, { decoded: decodeStripeChallenge(challenge) })
12313
- });
11981
+ cli2.command("decode", {
11982
+ description: "Decode a stripe WWW-Authenticate challenge and extract network_id",
11983
+ options: decodeOptions,
11984
+ outputPolicy: "agent-only",
11985
+ async run(c) {
11986
+ const decoded = decodeStripeChallenge(c.options.challenge);
11987
+ if (!c.agent && !c.formatExplicit) {
11988
+ return new Promise((resolve) => {
11989
+ const { waitUntilExit } = render2(
11990
+ /* @__PURE__ */ jsx6(DecodeChallengeView, { decoded })
11991
+ );
11992
+ waitUntilExit().then(() => resolve(decoded));
11993
+ });
11994
+ }
11995
+ return decoded;
11996
+ }
12314
11997
  });
12315
- return mppCommand2;
11998
+ return cli2;
12316
11999
  }
12317
12000
 
12001
+ // src/commands/payment-methods/index.tsx
12002
+ import { Cli as Cli3 } from "incur";
12003
+ import { render as render3 } from "ink";
12004
+
12318
12005
  // src/commands/payment-methods/add.tsx
12319
- import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
12320
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
12006
+ import { Box as Box5, Text as Text5, useApp, useInput as useInput2 } from "ink";
12007
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
12321
12008
  var WALLET_URL = "https://app.link.com/wallet";
12322
12009
  var AddPaymentMethod = () => {
12323
12010
  const { exit } = useApp();
@@ -12327,10 +12014,10 @@ var AddPaymentMethod = () => {
12327
12014
  exit();
12328
12015
  }
12329
12016
  });
12330
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingY: 1, children: [
12331
- /* @__PURE__ */ jsx8(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx8(Text6, { bold: true, children: "Add Payment Method" }) }),
12332
- /* @__PURE__ */ jsxs5(
12333
- Box6,
12017
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, children: [
12018
+ /* @__PURE__ */ jsx7(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text5, { bold: true, children: "Add Payment Method" }) }),
12019
+ /* @__PURE__ */ jsxs4(
12020
+ Box5,
12334
12021
  {
12335
12022
  flexDirection: "column",
12336
12023
  borderStyle: "round",
@@ -12338,12 +12025,12 @@ var AddPaymentMethod = () => {
12338
12025
  paddingX: 2,
12339
12026
  paddingY: 1,
12340
12027
  children: [
12341
- /* @__PURE__ */ jsxs5(Text6, { children: [
12028
+ /* @__PURE__ */ jsxs4(Text5, { children: [
12342
12029
  "Open:",
12343
12030
  " ",
12344
- /* @__PURE__ */ jsx8(Text6, { bold: true, color: "cyan", children: WALLET_URL })
12031
+ /* @__PURE__ */ jsx7(Text5, { bold: true, color: "cyan", children: WALLET_URL })
12345
12032
  ] }),
12346
- /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "Press Enter to open in browser" })
12033
+ /* @__PURE__ */ jsx7(Text5, { dimColor: true, children: "Press Enter to open in browser" })
12347
12034
  ]
12348
12035
  }
12349
12036
  )
@@ -12351,20 +12038,20 @@ var AddPaymentMethod = () => {
12351
12038
  };
12352
12039
 
12353
12040
  // src/commands/payment-methods/list.tsx
12354
- import { Box as Box7, Text as Text7 } from "ink";
12041
+ import { Box as Box6, Text as Text6 } from "ink";
12355
12042
  import Spinner3 from "ink-spinner";
12356
- import { useEffect as useEffect5, useState as useState5 } from "react";
12357
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
12043
+ import { useEffect as useEffect4, useState as useState4 } from "react";
12044
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
12358
12045
  var PaymentMethodsList = ({
12359
12046
  resource,
12360
12047
  onComplete
12361
12048
  }) => {
12362
- const [status, setStatus] = useState5(
12049
+ const [status, setStatus] = useState4(
12363
12050
  "loading"
12364
12051
  );
12365
- const [methods, setMethods] = useState5([]);
12366
- const [error, setError] = useState5("");
12367
- useEffect5(() => {
12052
+ const [methods, setMethods] = useState4([]);
12053
+ const [error, setError] = useState4("");
12054
+ useEffect4(() => {
12368
12055
  const fetch2 = async () => {
12369
12056
  try {
12370
12057
  const result = await resource.listPaymentMethods();
@@ -12380,150 +12067,167 @@ var PaymentMethodsList = ({
12380
12067
  fetch2();
12381
12068
  }, [resource, onComplete]);
12382
12069
  if (status === "loading") {
12383
- return /* @__PURE__ */ jsx9(Box7, { children: /* @__PURE__ */ jsxs6(Text7, { color: "cyan", children: [
12384
- /* @__PURE__ */ jsx9(Spinner3, { type: "dots" }),
12070
+ return /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
12071
+ /* @__PURE__ */ jsx8(Spinner3, { type: "dots" }),
12385
12072
  " Loading payment methods..."
12386
12073
  ] }) });
12387
12074
  }
12388
12075
  if (status === "error") {
12389
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
12390
- /* @__PURE__ */ jsx9(Text7, { color: "red", children: "\u2717 Failed to load payment methods" }),
12391
- /* @__PURE__ */ jsx9(Text7, { color: "red", children: error })
12076
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12077
+ /* @__PURE__ */ jsx8(Text6, { color: "red", children: "\u2717 Failed to load payment methods" }),
12078
+ /* @__PURE__ */ jsx8(Text6, { color: "red", children: error })
12392
12079
  ] });
12393
12080
  }
12394
12081
  if (methods.length === 0) {
12395
- return /* @__PURE__ */ jsx9(Box7, { children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "No payment methods found" }) });
12082
+ return /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No payment methods found" }) });
12396
12083
  }
12397
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
12398
- /* @__PURE__ */ jsx9(Text7, { bold: true, children: "Payment Methods" }),
12399
- /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginTop: 1, children: methods.map((pm) => {
12084
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12085
+ /* @__PURE__ */ jsx8(Text6, { bold: true, children: "Payment Methods" }),
12086
+ /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginTop: 1, children: methods.map((pm) => {
12400
12087
  const label = pm.card_details?.brand ?? pm.bank_account_details?.bank_name ?? "Bank account";
12401
12088
  const last4 = pm.card_details?.last4 ?? pm.bank_account_details?.last4;
12402
12089
  const suffix = [pm.nickname ? `(${pm.nickname})` : ""].filter(Boolean).join(" ");
12403
- return /* @__PURE__ */ jsx9(Box7, { paddingX: 2, children: /* @__PURE__ */ jsxs6(Text7, { children: [
12404
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: pm.id }),
12090
+ return /* @__PURE__ */ jsx8(Box6, { paddingX: 2, children: /* @__PURE__ */ jsxs5(Text6, { children: [
12091
+ /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: pm.id }),
12405
12092
  " ",
12406
12093
  label,
12407
12094
  " ****",
12408
12095
  last4,
12409
12096
  suffix ? ` ${suffix}` : "",
12410
- pm.is_default ? /* @__PURE__ */ jsx9(Text7, { color: "green", children: " (default)" }) : ""
12097
+ pm.is_default ? /* @__PURE__ */ jsx8(Text6, { color: "green", children: " (default)" }) : ""
12411
12098
  ] }) }, pm.id);
12412
12099
  }) })
12413
12100
  ] });
12414
12101
  };
12415
12102
 
12416
- // src/commands/payment-methods/schema.ts
12417
- var PAYMENT_METHOD_SCHEMA = {
12418
- id: { outputExample: '"..."', description: "Payment method ID" },
12419
- type: {
12420
- outputExample: '"card|bank_account"',
12421
- description: "Payment method type"
12422
- },
12423
- is_default: {
12424
- outputExample: "boolean",
12425
- description: "Whether this is the default payment method"
12426
- },
12427
- nickname: {
12428
- outputExample: '"..."',
12429
- description: "Optional nickname for the payment method"
12430
- },
12431
- card_details: {
12432
- outputExample: "{ brand, last4, exp_month, exp_year }",
12433
- description: "Present when type is card"
12434
- },
12435
- bank_account_details: {
12436
- outputExample: "{ last4, bank_name }",
12437
- description: "Present when type is bank_account"
12438
- }
12439
- };
12440
-
12441
12103
  // src/commands/payment-methods/index.tsx
12442
- import { jsx as jsx10 } from "react/jsx-runtime";
12443
- function registerPaymentMethodsCommands(program2, createResource) {
12444
- const paymentMethodsCommand2 = program2.command("payment-methods").description("Payment methods management commands").helpCommand(false);
12445
- paymentMethodsCommand2.command("list").description("List all payment methods on your account").option(
12446
- "--output-json",
12447
- "Output result as JSON instead of interactive display"
12448
- ).addHelpText("after", buildOutputHelp(PAYMENT_METHOD_SCHEMA, true)).action(async (options) => {
12449
- requireAuth();
12450
- const resource = createResource();
12451
- await executeCommand({
12452
- outputJson: !!options.outputJson,
12453
- jsonFn: async () => {
12454
- return resource.listPaymentMethods();
12455
- },
12456
- renderFn: () => /* @__PURE__ */ jsx10(PaymentMethodsList, { resource, onComplete: () => {
12457
- } })
12458
- });
12104
+ import { jsx as jsx9 } from "react/jsx-runtime";
12105
+ function createPaymentMethodsCli(createResource) {
12106
+ const cli2 = Cli3.create("payment-methods", {
12107
+ description: "Payment methods management commands"
12459
12108
  });
12460
- paymentMethodsCommand2.command("add").description("Open the Link wallet to add a new payment method").option(
12461
- "--output-json",
12462
- "Output result as JSON instead of interactive display"
12463
- ).action(async (options) => {
12464
- requireAuth();
12465
- if (options.outputJson) {
12466
- outputJson({ url: WALLET_URL });
12467
- } else {
12468
- const { render: render2 } = await import("ink");
12469
- const { waitUntilExit } = render2(/* @__PURE__ */ jsx10(AddPaymentMethod, {}));
12470
- await waitUntilExit();
12109
+ cli2.command("list", {
12110
+ description: "List all payment methods on your account",
12111
+ outputPolicy: "agent-only",
12112
+ async run(c) {
12113
+ if (!storage.isAuthenticated()) {
12114
+ return c.error({
12115
+ code: "NOT_AUTHENTICATED",
12116
+ message: 'Not authenticated. Run "link-cli auth login" first.',
12117
+ cta: {
12118
+ commands: [
12119
+ { command: "auth login", description: "Log in to Link" }
12120
+ ]
12121
+ }
12122
+ });
12123
+ }
12124
+ const resource = createResource();
12125
+ if (!c.agent && !c.formatExplicit) {
12126
+ return new Promise((resolve) => {
12127
+ const { waitUntilExit } = render3(
12128
+ /* @__PURE__ */ jsx9(PaymentMethodsList, { resource, onComplete: () => {
12129
+ } })
12130
+ );
12131
+ waitUntilExit().then(async () => {
12132
+ resolve(await resource.listPaymentMethods());
12133
+ });
12134
+ });
12135
+ }
12136
+ return resource.listPaymentMethods();
12471
12137
  }
12472
12138
  });
12473
- return paymentMethodsCommand2;
12474
- }
12475
-
12476
- // src/commands/skill/index.ts
12477
- function registerSkillCommand(program2) {
12478
- return program2.command("skill").description("Output the Link CLI skill file").action(() => {
12479
- let content = '---\nname: create-payment-credential\ndescription: |\n Gets secure, one-time-use payment credentials (cards, tokens) from a Link wallet so agents can complete purchases on behalf of users. Use when the user says "get me a card", "buy something", "pay for X", "make a purchase", "I need to pay", "complete checkout", or asks to transact on any merchant site. Use when the user asks to connect or log in to or sign up for their Link account.\nallowed-tools:\n - Bash(link-cli:*)\n - Bash(npx:*)\n - Bash(npm:*)\nlicense: Complete terms in LICENSE\nmetadata:\n author: stripe\n url: link.com/agents\n openclaw:\n emoji: "\u{1F4B3}"\n homepage: https://link.com/agents\n requires:\n bins:\n - link-cli\n install:\n - kind: node\n package: "@stripe/link-cli"\n bins: [link-cli]\nuser-invocable: true\n---\n\n# Creating Payment Credentials\n\nUse the Link CLI to get secure, one-time-use payment credentials from a Link wallet to complete purchases.\n\n## Installation\n\nInstall the CLI with\n\n```bash\nnpm install -g @stripe/link-cli\n```\n\nInstall the skill file with\n\n```bash\nnpx skills add stripe/link-cli\n```\n\n## Running commands\n\nAll commands support `--output-json` for machine-readable output. Use `--json` to pass structured input. Always run `link-cli <command> --help` before running the command to see full schema details, including all fields, types, and constraints.\n\nIMPORTANT: Run `auth login`, `spend-request create`, and `spend-request request-approval` with `run_in_background=true` (or `TaskOutput(task_id, block: false)`). These commands emit JSON to stdout before they exit, then keep running while they poll for user action.\n\nThe JSON stream contract for these long-running commands is:\n\n- `auth login --output-json`: first object contains `verification_url` and `passphrase`; final object contains authentication result after approval succeeds\n- `spend-request create --request-approval --output-json`: first object is the created spend request; final object is the terminal spend request after polling completes\n- `spend-request request-approval --output-json`: first object contains the approval link; final object is the terminal spend request after polling completes\n\nAlways keep reading stdout until the process exits. Do not assume the first JSON object is the full result. The user MUST visit the verification or approval URL to continue, and you should always show that full URL in clear text.\n\n## Core flow\n\nCopy this checklist and track progress:\n\n- Step 1: Authenticate with Link\n- Step 2: Evaluate merchant site (determine credential type)\n- Step 3: Get payment methods\n- Step 4: Create spend request with correct credential type\n- Step 5: Complete payment\n\n### Step 1: Authenticate with Link\n\nCheck auth status:\n\n```bash\nlink-cli auth status --output-json\n```\n\nIf not authenticated:\n\n```bash\nlink-cli auth login --client-name "<your-agent-name>" --output-json\n```\n\nReplace `<your-agent-name>` with the name of your agent or application (e.g. `"Personal Assistant", "Shopping Bot"`). This name appears in the user\'s Link app when they approve the connection. Use a clear, unique, identifiable name. Display the url and passphase to the user, with the guidance "Please visit the following URL to approve secure access to Link.\u201D\n\nDO NOT PROCEED until the user is authenticated with Link.\n\nAlways check the current authentication status before starting a new login flow - the user may already be logged in.\n\n### Step 2: Evaluate the merchant site BEFORE creating a spend request\n\n**CRITICAL \u2014 You MUST complete this step before calling `spend-request create`.** Do NOT default to `card` credential type. The merchant determines the credential type \u2014 you cannot know it without checking first. Skipping this step will produce a spend request with the wrong credential type.\n\nDetermine how the merchant accepts payment:\n\n1. **Navigate to the merchant page** \u2014 browse it, read the page content, and understand how the site accepts payment.\n2. **If the page has a credit card form, Stripe Elements, or traditional checkout UI** \u2014 use `card`.\n3. **If the page describes an API or programmatic payment flow** \u2014 make a request to the relevant endpoint. If it returns **HTTP 402** with a `www-authenticate` header, use `shared_payment_token`.\n\nWhat you find determines which credential type to use:\n\n| What you see | Credential type | What to request |\n|---|---|---|\n| Credit card form / Stripe Elements | `card` (default) | Card |\n| HTTP 402 with `method="stripe"` in `www-authenticate` | `shared_payment_token` | Shared payment token (SPT) |\n| HTTP 402 without `method="stripe"` in `www-authenticate` | not supported | Do not continue |\n\n**For 402 responses:** The `www-authenticate` header may contain **multiple** payment challenges (e.g. `tempo`, `stripe`) in a single header value. Do not try to decode the payload manually. Pass the **full raw `WWW-Authenticate` header value** to Link CLI and let `mpp decode` select and validate the `method="stripe"` challenge.\n\nTo derive `network_id`, use Link CLI\'s challenge decoder:\n\n```bash\nlink-cli mpp decode --challenge \'<raw WWW-Authenticate header>\' --output-json\n```\n\nThis validates the Stripe challenge, decodes the `request` payload, and returns both the extracted `network_id` and the decoded request JSON. Pass the full header exactly as received, even if it also contains non-Stripe or multiple `Payment` challenges.\n\n### Step 3: Get payment methods\n\nUse the default payment method, unless the user explicitly asks to select a different one.\n\n```bash\nlink-cli payment-methods list --output-json\n```\n\n### Step 4: Create the spend request with the right credential type\n\n```bash\nlink-cli spend-request create --json "{request}" --output-json\n```\n\nWait until the user has approved the spend request. If they deny, ask for clarification what to do next.\n\nRecommend the user approves with the [Link app](https://link.com/download). Show the download URL.\n\n**Test mode:** Add `"test": true` to the JSON input (or `--test` flag) to create testmode credentials instead of real ones. Useful for development and integration testing.\n\n### Step 5: Complete payment\n\n**Card:** Run `link-cli spend-request retrieve <id> --include card --output-json` to get the `card` object with `number`, `cvc`, `exp_month`, `exp_year`, `billing_address` (name, line1, line2, city, state, postal_code, country), and `valid_until` (unix timestamp \u2014 the card stops working after this time). Enter these details into the merchant\'s checkout form.\n\n**SPT with 402 flow:** The SPT is **one-time use** \u2014 if the payment fails, you need a new spend request and new SPT.\n\n```bash\nlink-cli mpp pay <url> --spend-request-id <id> [--method POST] [--data \'{"amount":100}\'] [--header \'Name: Value\'] --output-json\n```\n\n`mpp pay` handles the full 402 flow automatically: probes the URL, parses the `www-authenticate` header, builds the `Authorization: Payment` credential using the SPT, and retries.\n\n\n## Important\n\n- Treat the user\'s payment methods and credentials extremely carefully \u2014 card numbers and SPTs grant real spending power; leaking them outside a secure checkout could result in unauthorized charges the user cannot reverse.\n- Respect `/agents.txt` and `/llm.txt` and other directives on sites you browse \u2014 these files declare whether the site permits automated agent interactions; ignoring them may violate the merchant\'s terms.\n- Avoid suspicious merchants, checkout pages and websites \u2014 phishing pages that mimic legitimate merchants can steal credentials; if anything about the page feels off (mismatched domain, unusual redirect, unexpected login prompt), stop and ask the user to verify.\n- When outputting card information to the user apply basic masking to the card number and address to protect their information. Only reveal the raw values if directly requested to do so.\n\n## Errors\n\nAll errors go to stderr as `{"error": "..."}` with exit code 1.\n\n### Common errors and recovery\n\n| Error / Symptom | Cause | Recovery |\n|---|---|---|\n| `verification-failed` in error body from `mpp pay` | SPT was already consumed (one-time use) | Create a new spend request with `credential_type: "shared_payment_token"` \u2014 do not retry with the same spend request ID |\n| `context` validation error on `spend-request create` | `context` field is under 100 characters | Rewrite `context` as a full sentence explaining what is being purchased and why; the user reads this when approving |\n| API rejects `merchant_name` or `merchant_url` | These fields are forbidden when `credential_type` is `shared_payment_token` | Remove both fields from the request; SPT flows identify the merchant via `network_id` instead |\n| Command hangs indefinitely | `auth login` or `spend-request create` run synchronously | Always run these commands with `run_in_background=true` \u2014 they block until the user acts, so synchronous execution freezes the agent |\n| Spend request approved but payment fails immediately | Wrong credential type for the merchant (e.g. `card` on a 402-only endpoint) | Go back to Step 2, re-evaluate the merchant, create a new spend request with the correct `credential_type` |\n| Auth token expired mid-session (exit code 1 during approval polling) | Token refresh failure during background polling | Re-authenticate with `auth login`, then retrieve the existing spend request or resume polling. Only create a new spend request if the original one expired, was denied, or its shared payment token was already consumed |\n\n## Further docs\n\n- MPP/x402 protocol: https://mpp.dev/protocol.md, https://mpp.dev/protocol/http-402.md, https://mpp.dev/protocol/challenges.md\n- Link: https://link.com/agents\n- Link App (for account management): https://app.link.com\n- Link support (if the user needs help with Link): https://support.link.com/topics/about-link\n';
12480
- try {
12481
- const version = `${"0.1.2"}+${"1"}`;
12482
- content = '---\nname: create-payment-credential\ndescription: |\n Gets secure, one-time-use payment credentials (cards, tokens) from a Link wallet so agents can complete purchases on behalf of users. Use when the user says "get me a card", "buy something", "pay for X", "make a purchase", "I need to pay", "complete checkout", or asks to transact on any merchant site. Use when the user asks to connect or log in to or sign up for their Link account.\nallowed-tools:\n - Bash(link-cli:*)\n - Bash(npx:*)\n - Bash(npm:*)\nlicense: Complete terms in LICENSE\nmetadata:\n author: stripe\n url: link.com/agents\n openclaw:\n emoji: "\u{1F4B3}"\n homepage: https://link.com/agents\n requires:\n bins:\n - link-cli\n install:\n - kind: node\n package: "@stripe/link-cli"\n bins: [link-cli]\nuser-invocable: true\n---\n\n# Creating Payment Credentials\n\nUse the Link CLI to get secure, one-time-use payment credentials from a Link wallet to complete purchases.\n\n## Installation\n\nInstall the CLI with\n\n```bash\nnpm install -g @stripe/link-cli\n```\n\nInstall the skill file with\n\n```bash\nnpx skills add stripe/link-cli\n```\n\n## Running commands\n\nAll commands support `--output-json` for machine-readable output. Use `--json` to pass structured input. Always run `link-cli <command> --help` before running the command to see full schema details, including all fields, types, and constraints.\n\nIMPORTANT: Run `auth login`, `spend-request create`, and `spend-request request-approval` with `run_in_background=true` (or `TaskOutput(task_id, block: false)`). These commands emit JSON to stdout before they exit, then keep running while they poll for user action.\n\nThe JSON stream contract for these long-running commands is:\n\n- `auth login --output-json`: first object contains `verification_url` and `passphrase`; final object contains authentication result after approval succeeds\n- `spend-request create --request-approval --output-json`: first object is the created spend request; final object is the terminal spend request after polling completes\n- `spend-request request-approval --output-json`: first object contains the approval link; final object is the terminal spend request after polling completes\n\nAlways keep reading stdout until the process exits. Do not assume the first JSON object is the full result. The user MUST visit the verification or approval URL to continue, and you should always show that full URL in clear text.\n\n## Core flow\n\nCopy this checklist and track progress:\n\n- Step 1: Authenticate with Link\n- Step 2: Evaluate merchant site (determine credential type)\n- Step 3: Get payment methods\n- Step 4: Create spend request with correct credential type\n- Step 5: Complete payment\n\n### Step 1: Authenticate with Link\n\nCheck auth status:\n\n```bash\nlink-cli auth status --output-json\n```\n\nIf not authenticated:\n\n```bash\nlink-cli auth login --client-name "<your-agent-name>" --output-json\n```\n\nReplace `<your-agent-name>` with the name of your agent or application (e.g. `"Personal Assistant", "Shopping Bot"`). This name appears in the user\'s Link app when they approve the connection. Use a clear, unique, identifiable name. Display the url and passphase to the user, with the guidance "Please visit the following URL to approve secure access to Link.\u201D\n\nDO NOT PROCEED until the user is authenticated with Link.\n\nAlways check the current authentication status before starting a new login flow - the user may already be logged in.\n\n### Step 2: Evaluate the merchant site BEFORE creating a spend request\n\n**CRITICAL \u2014 You MUST complete this step before calling `spend-request create`.** Do NOT default to `card` credential type. The merchant determines the credential type \u2014 you cannot know it without checking first. Skipping this step will produce a spend request with the wrong credential type.\n\nDetermine how the merchant accepts payment:\n\n1. **Navigate to the merchant page** \u2014 browse it, read the page content, and understand how the site accepts payment.\n2. **If the page has a credit card form, Stripe Elements, or traditional checkout UI** \u2014 use `card`.\n3. **If the page describes an API or programmatic payment flow** \u2014 make a request to the relevant endpoint. If it returns **HTTP 402** with a `www-authenticate` header, use `shared_payment_token`.\n\nWhat you find determines which credential type to use:\n\n| What you see | Credential type | What to request |\n|---|---|---|\n| Credit card form / Stripe Elements | `card` (default) | Card |\n| HTTP 402 with `method="stripe"` in `www-authenticate` | `shared_payment_token` | Shared payment token (SPT) |\n| HTTP 402 without `method="stripe"` in `www-authenticate` | not supported | Do not continue |\n\n**For 402 responses:** The `www-authenticate` header may contain **multiple** payment challenges (e.g. `tempo`, `stripe`) in a single header value. Do not try to decode the payload manually. Pass the **full raw `WWW-Authenticate` header value** to Link CLI and let `mpp decode` select and validate the `method="stripe"` challenge.\n\nTo derive `network_id`, use Link CLI\'s challenge decoder:\n\n```bash\nlink-cli mpp decode --challenge \'<raw WWW-Authenticate header>\' --output-json\n```\n\nThis validates the Stripe challenge, decodes the `request` payload, and returns both the extracted `network_id` and the decoded request JSON. Pass the full header exactly as received, even if it also contains non-Stripe or multiple `Payment` challenges.\n\n### Step 3: Get payment methods\n\nUse the default payment method, unless the user explicitly asks to select a different one.\n\n```bash\nlink-cli payment-methods list --output-json\n```\n\n### Step 4: Create the spend request with the right credential type\n\n```bash\nlink-cli spend-request create --json "{request}" --output-json\n```\n\nWait until the user has approved the spend request. If they deny, ask for clarification what to do next.\n\nRecommend the user approves with the [Link app](https://link.com/download). Show the download URL.\n\n**Test mode:** Add `"test": true` to the JSON input (or `--test` flag) to create testmode credentials instead of real ones. Useful for development and integration testing.\n\n### Step 5: Complete payment\n\n**Card:** Run `link-cli spend-request retrieve <id> --include card --output-json` to get the `card` object with `number`, `cvc`, `exp_month`, `exp_year`, `billing_address` (name, line1, line2, city, state, postal_code, country), and `valid_until` (unix timestamp \u2014 the card stops working after this time). Enter these details into the merchant\'s checkout form.\n\n**SPT with 402 flow:** The SPT is **one-time use** \u2014 if the payment fails, you need a new spend request and new SPT.\n\n```bash\nlink-cli mpp pay <url> --spend-request-id <id> [--method POST] [--data \'{"amount":100}\'] [--header \'Name: Value\'] --output-json\n```\n\n`mpp pay` handles the full 402 flow automatically: probes the URL, parses the `www-authenticate` header, builds the `Authorization: Payment` credential using the SPT, and retries.\n\n\n## Important\n\n- Treat the user\'s payment methods and credentials extremely carefully \u2014 card numbers and SPTs grant real spending power; leaking them outside a secure checkout could result in unauthorized charges the user cannot reverse.\n- Respect `/agents.txt` and `/llm.txt` and other directives on sites you browse \u2014 these files declare whether the site permits automated agent interactions; ignoring them may violate the merchant\'s terms.\n- Avoid suspicious merchants, checkout pages and websites \u2014 phishing pages that mimic legitimate merchants can steal credentials; if anything about the page feels off (mismatched domain, unusual redirect, unexpected login prompt), stop and ask the user to verify.\n- When outputting card information to the user apply basic masking to the card number and address to protect their information. Only reveal the raw values if directly requested to do so.\n\n## Errors\n\nAll errors go to stderr as `{"error": "..."}` with exit code 1.\n\n### Common errors and recovery\n\n| Error / Symptom | Cause | Recovery |\n|---|---|---|\n| `verification-failed` in error body from `mpp pay` | SPT was already consumed (one-time use) | Create a new spend request with `credential_type: "shared_payment_token"` \u2014 do not retry with the same spend request ID |\n| `context` validation error on `spend-request create` | `context` field is under 100 characters | Rewrite `context` as a full sentence explaining what is being purchased and why; the user reads this when approving |\n| API rejects `merchant_name` or `merchant_url` | These fields are forbidden when `credential_type` is `shared_payment_token` | Remove both fields from the request; SPT flows identify the merchant via `network_id` instead |\n| Command hangs indefinitely | `auth login` or `spend-request create` run synchronously | Always run these commands with `run_in_background=true` \u2014 they block until the user acts, so synchronous execution freezes the agent |\n| Spend request approved but payment fails immediately | Wrong credential type for the merchant (e.g. `card` on a 402-only endpoint) | Go back to Step 2, re-evaluate the merchant, create a new spend request with the correct `credential_type` |\n| Auth token expired mid-session (exit code 1 during approval polling) | Token refresh failure during background polling | Re-authenticate with `auth login`, then retrieve the existing spend request or resume polling. Only create a new spend request if the original one expired, was denied, or its shared payment token was already consumed |\n\n## Further docs\n\n- MPP/x402 protocol: https://mpp.dev/protocol.md, https://mpp.dev/protocol/http-402.md, https://mpp.dev/protocol/challenges.md\n- Link: https://link.com/agents\n- Link App (for account management): https://app.link.com\n- Link support (if the user needs help with Link): https://support.link.com/topics/about-link\n'.replace(
12483
- "---\n",
12484
- `---
12485
- cli_version: "${version}"
12486
- `
12487
- );
12488
- } catch {
12489
- process.stderr.write(
12490
- "Warning: could not resolve cli_version \u2014 skill output without version\n"
12491
- );
12139
+ cli2.command("add", {
12140
+ description: "Open the Link wallet to add a new payment method",
12141
+ outputPolicy: "agent-only",
12142
+ async run(c) {
12143
+ if (!storage.isAuthenticated()) {
12144
+ return c.error({
12145
+ code: "NOT_AUTHENTICATED",
12146
+ message: 'Not authenticated. Run "link-cli auth login" first.',
12147
+ cta: {
12148
+ commands: [
12149
+ { command: "auth login", description: "Log in to Link" }
12150
+ ]
12151
+ }
12152
+ });
12153
+ }
12154
+ if (!c.agent && !c.formatExplicit) {
12155
+ return new Promise((resolve) => {
12156
+ const { waitUntilExit } = render3(/* @__PURE__ */ jsx9(AddPaymentMethod, {}));
12157
+ waitUntilExit().then(() => resolve({ url: WALLET_URL }));
12158
+ });
12159
+ }
12160
+ return { url: WALLET_URL };
12492
12161
  }
12493
12162
  });
12163
+ return cli2;
12494
12164
  }
12495
12165
 
12496
- // src/utils/poll-until-approved.ts
12497
- function pollUntilApproved(repository, id, options = {}) {
12498
- const pollIntervalMs = options.pollIntervalMs ?? 2e3;
12499
- const timeoutMs = options.timeoutMs ?? 3e5;
12500
- const startTime = Date.now();
12501
- const poll = async () => {
12502
- const elapsed = Date.now() - startTime;
12503
- if (elapsed > timeoutMs) {
12504
- throw new Error("Approval polling timed out");
12505
- }
12506
- const request = await repository.getSpendRequest(id);
12507
- if (!request) {
12508
- throw new Error(`Spend request ${id} not found`);
12509
- }
12510
- if (request.status !== "created" && request.status !== "pending_approval") {
12511
- return request;
12166
+ // src/commands/spend-request/index.tsx
12167
+ import { Cli as Cli4, z as z6 } from "incur";
12168
+ import { render as render4 } from "ink";
12169
+
12170
+ // src/utils/line-item-parser.ts
12171
+ import { z as z4 } from "zod";
12172
+ var LineItemSchema = z4.object({
12173
+ name: z4.string(),
12174
+ url: z4.string().optional(),
12175
+ image_url: z4.string().optional(),
12176
+ description: z4.string().optional(),
12177
+ sku: z4.string().optional(),
12178
+ quantity: z4.coerce.number().optional(),
12179
+ unit_amount: z4.coerce.number().optional(),
12180
+ product_url: z4.string().optional()
12181
+ }).strict();
12182
+ var TotalSchema = z4.object({
12183
+ type: z4.string(),
12184
+ display_text: z4.string(),
12185
+ amount: z4.coerce.number()
12186
+ }).strict();
12187
+ function parseKvString(raw) {
12188
+ const result = {};
12189
+ for (const pair of raw.split(",")) {
12190
+ const idx = pair.indexOf(":");
12191
+ if (idx === -1) {
12192
+ throw new Error(`Invalid field (missing ':'): ${pair}`);
12512
12193
  }
12513
- options.onProgress?.(Math.floor(elapsed / 1e3));
12514
- await new Promise((r) => setTimeout(r, pollIntervalMs));
12515
- return poll();
12516
- };
12517
- return poll();
12194
+ result[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
12195
+ }
12196
+ return result;
12197
+ }
12198
+ function formatZodError(err, prefix) {
12199
+ const messages = err.issues.map((issue) => {
12200
+ const key = issue.path[0]?.toString();
12201
+ return key ? `${prefix} ${key}: ${issue.message}` : `${prefix}: ${issue.message}`;
12202
+ });
12203
+ return new Error(messages.join("\n"));
12204
+ }
12205
+ function parseLineItemFlag(raw) {
12206
+ const obj = parseKvString(raw);
12207
+ try {
12208
+ return LineItemSchema.parse(obj);
12209
+ } catch (err) {
12210
+ if (err instanceof z4.ZodError) throw formatZodError(err, "Line item");
12211
+ throw err;
12212
+ }
12213
+ }
12214
+ function parseTotalFlag(raw) {
12215
+ const obj = parseKvString(raw);
12216
+ try {
12217
+ return TotalSchema.parse(obj);
12218
+ } catch (err) {
12219
+ if (err instanceof z4.ZodError) throw formatZodError(err, "Total");
12220
+ throw err;
12221
+ }
12518
12222
  }
12519
12223
 
12520
12224
  // src/commands/spend-request/create.tsx
12521
- import { Box as Box10, Text as Text10 } from "ink";
12225
+ import { Box as Box9, Text as Text9 } from "ink";
12522
12226
  import Spinner5 from "ink-spinner";
12523
- import { useCallback, useEffect as useEffect7, useState as useState6 } from "react";
12227
+ import { useCallback, useEffect as useEffect6, useState as useState5 } from "react";
12524
12228
 
12525
12229
  // src/commands/spend-request/app-download-qr-codes.tsx
12526
- import { Box as Box8, Text as Text8 } from "ink";
12230
+ import { Box as Box7, Text as Text7 } from "ink";
12527
12231
  import { useMemo } from "react";
12528
12232
 
12529
12233
  // src/utils/render-qr-matrix.ts
@@ -12560,32 +12264,32 @@ function renderQrMatrix(url) {
12560
12264
  }
12561
12265
 
12562
12266
  // src/commands/spend-request/app-download-qr-codes.tsx
12563
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
12267
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
12564
12268
  var DOWNLOAD_URL = "https://link.com/download";
12565
12269
  var AppDownloadQrCodes = () => {
12566
12270
  const qrLines = useMemo(() => renderQrMatrix(DOWNLOAD_URL), []);
12567
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
12568
- /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "New! Get the Link app to approve spend requests easily" }),
12569
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
12271
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
12272
+ /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: "New! Get the Link app to approve spend requests easily" }),
12273
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
12570
12274
  qrLines.map((line, i) => (
12571
12275
  // biome-ignore lint/suspicious/noArrayIndexKey: stable static array
12572
- /* @__PURE__ */ jsx11(Text8, { children: line }, i)
12276
+ /* @__PURE__ */ jsx10(Text7, { children: line }, i)
12573
12277
  )),
12574
- /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: DOWNLOAD_URL })
12278
+ /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: DOWNLOAD_URL })
12575
12279
  ] })
12576
12280
  ] });
12577
12281
  };
12578
12282
 
12579
12283
  // src/commands/spend-request/approval-waiting-view.tsx
12580
- import { Box as Box9, Text as Text9 } from "ink";
12284
+ import { Box as Box8, Text as Text8 } from "ink";
12581
12285
  import Spinner4 from "ink-spinner";
12582
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
12286
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
12583
12287
  var ApprovalWaitingView = ({
12584
12288
  status,
12585
12289
  approvalUrl
12586
- }) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingY: 1, children: [
12587
- /* @__PURE__ */ jsxs8(
12588
- Box9,
12290
+ }) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingY: 1, children: [
12291
+ /* @__PURE__ */ jsxs7(
12292
+ Box8,
12589
12293
  {
12590
12294
  flexDirection: "column",
12591
12295
  borderStyle: "round",
@@ -12593,25 +12297,51 @@ var ApprovalWaitingView = ({
12593
12297
  paddingX: 2,
12594
12298
  paddingY: 1,
12595
12299
  children: [
12596
- /* @__PURE__ */ jsxs8(Text9, { children: [
12300
+ /* @__PURE__ */ jsxs7(Text8, { children: [
12597
12301
  "Approve at:",
12598
12302
  " ",
12599
- /* @__PURE__ */ jsx12(Text9, { bold: true, color: "cyan", children: approvalUrl })
12303
+ /* @__PURE__ */ jsx11(Text8, { bold: true, color: "cyan", children: approvalUrl })
12600
12304
  ] }),
12601
- /* @__PURE__ */ jsx12(Text9, { dimColor: true, children: "Press Enter to open in browser" })
12305
+ /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Press Enter to open in browser" })
12602
12306
  ]
12603
12307
  }
12604
12308
  ),
12605
- /* @__PURE__ */ jsx12(AppDownloadQrCodes, {}),
12606
- /* @__PURE__ */ jsx12(Box9, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
12607
- /* @__PURE__ */ jsx12(Spinner4, { type: "dots" }),
12309
+ /* @__PURE__ */ jsx11(AppDownloadQrCodes, {}),
12310
+ /* @__PURE__ */ jsx11(Box8, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs7(Text8, { color: "cyan", children: [
12311
+ /* @__PURE__ */ jsx11(Spinner4, { type: "dots" }),
12608
12312
  " Waiting for approval..."
12609
- ] }) : /* @__PURE__ */ jsx12(Text9, { dimColor: true, children: "Waiting..." }) })
12313
+ ] }) : /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Waiting..." }) })
12610
12314
  ] });
12611
12315
 
12612
12316
  // src/commands/spend-request/use-approval-polling.ts
12613
12317
  import { useInput as useInput3 } from "ink";
12614
- import { useEffect as useEffect6 } from "react";
12318
+ import { useEffect as useEffect5 } from "react";
12319
+
12320
+ // src/utils/poll-until-approved.ts
12321
+ function pollUntilApproved(repository, id, options = {}) {
12322
+ const pollIntervalMs = options.pollIntervalMs ?? 2e3;
12323
+ const timeoutMs = options.timeoutMs ?? 3e5;
12324
+ const startTime = Date.now();
12325
+ const poll = async () => {
12326
+ const elapsed = Date.now() - startTime;
12327
+ if (elapsed > timeoutMs) {
12328
+ throw new Error("Approval polling timed out");
12329
+ }
12330
+ const request = await repository.getSpendRequest(id);
12331
+ if (!request) {
12332
+ throw new Error(`Spend request ${id} not found`);
12333
+ }
12334
+ if (request.status !== "created" && request.status !== "pending_approval") {
12335
+ return request;
12336
+ }
12337
+ options.onProgress?.(Math.floor(elapsed / 1e3));
12338
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
12339
+ return poll();
12340
+ };
12341
+ return poll();
12342
+ }
12343
+
12344
+ // src/commands/spend-request/use-approval-polling.ts
12615
12345
  function useApprovalPolling({
12616
12346
  status,
12617
12347
  setStatus,
@@ -12629,12 +12359,12 @@ function useApprovalPolling({
12629
12359
  },
12630
12360
  { isActive: isWaiting }
12631
12361
  );
12632
- useEffect6(() => {
12362
+ useEffect5(() => {
12633
12363
  if (status !== "waiting") return;
12634
12364
  const timeout = setTimeout(() => setStatus("polling"), 1e3);
12635
12365
  return () => clearTimeout(timeout);
12636
12366
  }, [status, setStatus]);
12637
- useEffect6(() => {
12367
+ useEffect5(() => {
12638
12368
  if (status !== "polling" || !requestId) return;
12639
12369
  let cancelled = false;
12640
12370
  const poll = async () => {
@@ -12668,16 +12398,16 @@ function useApprovalPolling({
12668
12398
  }
12669
12399
 
12670
12400
  // src/commands/spend-request/create.tsx
12671
- import { Fragment, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
12401
+ import { Fragment, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
12672
12402
  var CreateSpendRequest = ({
12673
12403
  repository,
12674
12404
  params,
12675
12405
  requestApproval = false,
12676
12406
  onComplete
12677
12407
  }) => {
12678
- const [status, setStatus] = useState6("creating");
12679
- const [request, setRequest] = useState6(null);
12680
- const [error, setError] = useState6("");
12408
+ const [status, setStatus] = useState5("creating");
12409
+ const [request, setRequest] = useState5(null);
12410
+ const [error, setError] = useState5("");
12681
12411
  const approvalUrl = request?.approval_url ?? "";
12682
12412
  const onSuccess = useCallback(
12683
12413
  (result) => setRequest(result),
@@ -12694,7 +12424,7 @@ var CreateSpendRequest = ({
12694
12424
  onSuccess,
12695
12425
  onError
12696
12426
  });
12697
- useEffect7(() => {
12427
+ useEffect6(() => {
12698
12428
  const create = async () => {
12699
12429
  try {
12700
12430
  const result = await repository.createSpendRequest(params);
@@ -12714,57 +12444,57 @@ var CreateSpendRequest = ({
12714
12444
  create();
12715
12445
  }, [repository, params, requestApproval, onComplete]);
12716
12446
  if (status === "creating") {
12717
- return /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
12718
- /* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
12447
+ return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
12448
+ /* @__PURE__ */ jsx12(Spinner5, { type: "dots" }),
12719
12449
  " Creating spend request..."
12720
12450
  ] }) });
12721
12451
  }
12722
12452
  if (status === "error") {
12723
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12724
- /* @__PURE__ */ jsx13(Text10, { color: "red", children: "\u2717 Failed to create spend request" }),
12725
- /* @__PURE__ */ jsx13(Text10, { color: "red", children: error })
12453
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
12454
+ /* @__PURE__ */ jsx12(Text9, { color: "red", children: "\u2717 Failed to create spend request" }),
12455
+ /* @__PURE__ */ jsx12(Text9, { color: "red", children: error })
12726
12456
  ] });
12727
12457
  }
12728
12458
  if (status === "success") {
12729
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12730
- /* @__PURE__ */ jsx13(Text10, { color: "green", children: "\u2713 Spend request created" }),
12731
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12732
- /* @__PURE__ */ jsxs9(Text10, { children: [
12459
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
12460
+ /* @__PURE__ */ jsx12(Text9, { color: "green", children: "\u2713 Spend request created" }),
12461
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12462
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12733
12463
  "ID: ",
12734
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.id })
12464
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id })
12735
12465
  ] }),
12736
- /* @__PURE__ */ jsxs9(Text10, { children: [
12466
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12737
12467
  "Status: ",
12738
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.status })
12468
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.status })
12739
12469
  ] }),
12740
- /* @__PURE__ */ jsxs9(Text10, { children: [
12470
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12741
12471
  "Amount:",
12742
12472
  " ",
12743
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: (() => {
12473
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: (() => {
12744
12474
  const t = request?.totals.find((t2) => t2.type === "total");
12745
12475
  return t ? String(t.amount) : "N/A";
12746
12476
  })() })
12747
12477
  ] }),
12748
- /* @__PURE__ */ jsxs9(Text10, { children: [
12478
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12749
12479
  "Merchant: ",
12750
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.merchant_name })
12480
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.merchant_name })
12751
12481
  ] }),
12752
- /* @__PURE__ */ jsxs9(Text10, { children: [
12482
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12753
12483
  "Line Items:",
12754
12484
  " ",
12755
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
12485
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
12756
12486
  ] })
12757
12487
  ] }),
12758
- /* @__PURE__ */ jsx13(AppDownloadQrCodes, {})
12488
+ /* @__PURE__ */ jsx12(AppDownloadQrCodes, {})
12759
12489
  ] });
12760
12490
  }
12761
- return /* @__PURE__ */ jsxs9(Fragment, { children: [
12762
- /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "green", children: [
12491
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
12492
+ /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "green", children: [
12763
12493
  "\u2713 Spend request created (ID: ",
12764
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.id }),
12494
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id }),
12765
12495
  ")"
12766
12496
  ] }) }),
12767
- /* @__PURE__ */ jsx13(
12497
+ /* @__PURE__ */ jsx12(
12768
12498
  ApprovalWaitingView,
12769
12499
  {
12770
12500
  status,
@@ -12775,19 +12505,19 @@ var CreateSpendRequest = ({
12775
12505
  };
12776
12506
 
12777
12507
  // src/commands/spend-request/request-approval.tsx
12778
- import { Box as Box11, Text as Text11 } from "ink";
12508
+ import { Box as Box10, Text as Text10 } from "ink";
12779
12509
  import Spinner6 from "ink-spinner";
12780
- import { useCallback as useCallback2, useEffect as useEffect8, useState as useState7 } from "react";
12781
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
12510
+ import { useCallback as useCallback2, useEffect as useEffect7, useState as useState6 } from "react";
12511
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
12782
12512
  var RequestApproval = ({
12783
12513
  repository,
12784
12514
  id,
12785
12515
  onComplete
12786
12516
  }) => {
12787
- const [status, setStatus] = useState7("requesting");
12788
- const [approvalUrl, setApprovalUrl] = useState7("");
12789
- const [result, setResult] = useState7(null);
12790
- const [error, setError] = useState7("");
12517
+ const [status, setStatus] = useState6("requesting");
12518
+ const [approvalUrl, setApprovalUrl] = useState6("");
12519
+ const [result, setResult] = useState6(null);
12520
+ const [error, setError] = useState6("");
12791
12521
  const onSuccess = useCallback2((r) => setResult(r), []);
12792
12522
  const onError = useCallback2((msg) => setError(msg), []);
12793
12523
  useApprovalPolling({
@@ -12800,7 +12530,7 @@ var RequestApproval = ({
12800
12530
  onSuccess,
12801
12531
  onError
12802
12532
  });
12803
- useEffect8(() => {
12533
+ useEffect7(() => {
12804
12534
  const request = async () => {
12805
12535
  try {
12806
12536
  const res = await repository.requestApproval(id);
@@ -12814,45 +12544,45 @@ var RequestApproval = ({
12814
12544
  request();
12815
12545
  }, [repository, id]);
12816
12546
  if (status === "requesting") {
12817
- return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
12818
- /* @__PURE__ */ jsx14(Spinner6, { type: "dots" }),
12547
+ return /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
12548
+ /* @__PURE__ */ jsx13(Spinner6, { type: "dots" }),
12819
12549
  " Requesting approval..."
12820
12550
  ] }) });
12821
12551
  }
12822
12552
  if (status === "error") {
12823
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12824
- /* @__PURE__ */ jsx14(Text11, { color: "red", children: "\u2717 Failed to request approval" }),
12825
- /* @__PURE__ */ jsx14(Text11, { color: "red", children: error })
12553
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12554
+ /* @__PURE__ */ jsx13(Text10, { color: "red", children: "\u2717 Failed to request approval" }),
12555
+ /* @__PURE__ */ jsx13(Text10, { color: "red", children: error })
12826
12556
  ] });
12827
12557
  }
12828
12558
  if (status === "success") {
12829
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12830
- /* @__PURE__ */ jsx14(Text11, { color: "green", children: "\u2713 Approval completed" }),
12831
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12832
- /* @__PURE__ */ jsxs10(Text11, { children: [
12559
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12560
+ /* @__PURE__ */ jsx13(Text10, { color: "green", children: "\u2713 Approval completed" }),
12561
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12562
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12833
12563
  "ID: ",
12834
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: result?.id })
12564
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.id })
12835
12565
  ] }),
12836
- /* @__PURE__ */ jsxs10(Text11, { children: [
12566
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12837
12567
  "Status: ",
12838
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: result?.status })
12568
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.status })
12839
12569
  ] }),
12840
- /* @__PURE__ */ jsxs10(Text11, { children: [
12570
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12841
12571
  "Amount:",
12842
12572
  " ",
12843
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
12573
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: (() => {
12844
12574
  const t = result?.totals.find((t2) => t2.type === "total");
12845
12575
  return t ? String(t.amount) : "N/A";
12846
12576
  })() })
12847
12577
  ] }),
12848
- /* @__PURE__ */ jsxs10(Text11, { children: [
12578
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12849
12579
  "Merchant: ",
12850
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: result?.merchant_name })
12580
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.merchant_name })
12851
12581
  ] })
12852
12582
  ] })
12853
12583
  ] });
12854
12584
  }
12855
- return /* @__PURE__ */ jsx14(
12585
+ return /* @__PURE__ */ jsx13(
12856
12586
  ApprovalWaitingView,
12857
12587
  {
12858
12588
  status,
@@ -12862,10 +12592,10 @@ var RequestApproval = ({
12862
12592
  };
12863
12593
 
12864
12594
  // src/commands/spend-request/retrieve.tsx
12865
- import { Box as Box12, Text as Text12 } from "ink";
12595
+ import { Box as Box11, Text as Text11 } from "ink";
12866
12596
  import Spinner7 from "ink-spinner";
12867
- import { useEffect as useEffect9, useRef, useState as useState8 } from "react";
12868
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
12597
+ import { useEffect as useEffect8, useRef, useState as useState7 } from "react";
12598
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
12869
12599
  var RetrieveSpendRequest = ({
12870
12600
  repository,
12871
12601
  id,
@@ -12873,20 +12603,20 @@ var RetrieveSpendRequest = ({
12873
12603
  include,
12874
12604
  onComplete
12875
12605
  }) => {
12876
- const [phase, setPhase] = useState8("fetching");
12877
- const [request, setRequest] = useState8(null);
12878
- const [error, setError] = useState8("");
12879
- const [elapsed, setElapsed] = useState8(0);
12606
+ const [phase, setPhase] = useState7("fetching");
12607
+ const [request, setRequest] = useState7(null);
12608
+ const [error, setError] = useState7("");
12609
+ const [elapsed, setElapsed] = useState7(0);
12880
12610
  const startTimeRef = useRef(Date.now());
12881
12611
  const pollRef = useRef(null);
12882
12612
  const timerRef = useRef(null);
12883
- useEffect9(() => {
12613
+ useEffect8(() => {
12884
12614
  return () => {
12885
12615
  if (pollRef.current) clearInterval(pollRef.current);
12886
12616
  if (timerRef.current) clearInterval(timerRef.current);
12887
12617
  };
12888
12618
  }, []);
12889
- useEffect9(() => {
12619
+ useEffect8(() => {
12890
12620
  const fetch2 = async () => {
12891
12621
  try {
12892
12622
  const result = await repository.getSpendRequest(id, { include });
@@ -12915,7 +12645,7 @@ var RetrieveSpendRequest = ({
12915
12645
  };
12916
12646
  fetch2();
12917
12647
  }, [repository, id, include, onComplete]);
12918
- useEffect9(() => {
12648
+ useEffect8(() => {
12919
12649
  if (phase !== "polling") return;
12920
12650
  timerRef.current = setInterval(() => {
12921
12651
  const secs = Math.floor((Date.now() - startTimeRef.current) / 1e3);
@@ -12954,170 +12684,170 @@ var RetrieveSpendRequest = ({
12954
12684
  };
12955
12685
  }, [phase, repository, id, include, timeout, onComplete]);
12956
12686
  if (phase === "fetching") {
12957
- return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
12958
- /* @__PURE__ */ jsx15(Spinner7, { type: "dots" }),
12687
+ return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
12688
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
12959
12689
  " Retrieving spend request ",
12960
12690
  id,
12961
12691
  "..."
12962
12692
  ] }) });
12963
12693
  }
12964
12694
  if (phase === "error") {
12965
- return /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
12695
+ return /* @__PURE__ */ jsx14(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
12966
12696
  "\u2717 ",
12967
12697
  error
12968
12698
  ] }) });
12969
12699
  }
12970
12700
  if (phase === "timeout") {
12971
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12972
- /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
12701
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12702
+ /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
12973
12703
  "\u2717 Timed out waiting for approval after ",
12974
12704
  timeout,
12975
12705
  "s"
12976
12706
  ] }),
12977
- request && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12978
- /* @__PURE__ */ jsxs11(Text12, { children: [
12707
+ request && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12708
+ /* @__PURE__ */ jsxs10(Text11, { children: [
12979
12709
  "ID: ",
12980
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.id })
12710
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.id })
12981
12711
  ] }),
12982
- /* @__PURE__ */ jsxs11(Text12, { children: [
12712
+ /* @__PURE__ */ jsxs10(Text11, { children: [
12983
12713
  "Status: ",
12984
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.status })
12714
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.status })
12985
12715
  ] })
12986
12716
  ] })
12987
12717
  ] });
12988
12718
  }
12989
12719
  if (phase === "polling") {
12990
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12991
- /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
12992
- /* @__PURE__ */ jsx15(Spinner7, { type: "dots" }),
12720
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12721
+ /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
12722
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
12993
12723
  " Awaiting approval... (",
12994
12724
  elapsed,
12995
12725
  "s elapsed)"
12996
12726
  ] }) }),
12997
- request?.approval_url && /* @__PURE__ */ jsx15(Box12, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
12727
+ request?.approval_url && /* @__PURE__ */ jsx14(Box11, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
12998
12728
  "Approval URL: ",
12999
- /* @__PURE__ */ jsx15(Text12, { color: "cyan", children: request.approval_url })
12729
+ /* @__PURE__ */ jsx14(Text11, { color: "cyan", children: request.approval_url })
13000
12730
  ] }) })
13001
12731
  ] });
13002
12732
  }
13003
12733
  if (phase === "declined") {
13004
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13005
- /* @__PURE__ */ jsx15(Text12, { color: "red", children: "\u2717 Spend request declined" }),
13006
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13007
- /* @__PURE__ */ jsxs11(Text12, { children: [
12734
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12735
+ /* @__PURE__ */ jsx14(Text11, { color: "red", children: "\u2717 Spend request declined" }),
12736
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12737
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13008
12738
  "ID: ",
13009
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
12739
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
13010
12740
  ] }),
13011
- /* @__PURE__ */ jsxs11(Text12, { children: [
12741
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13012
12742
  "Status:",
13013
12743
  " ",
13014
- /* @__PURE__ */ jsx15(Text12, { bold: true, color: "red", children: request?.status })
12744
+ /* @__PURE__ */ jsx14(Text11, { bold: true, color: "red", children: request?.status })
13015
12745
  ] }),
13016
- /* @__PURE__ */ jsxs11(Text12, { children: [
12746
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13017
12747
  "Amount:",
13018
12748
  " ",
13019
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
12749
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
13020
12750
  const t = request?.totals.find((t2) => t2.type === "total");
13021
12751
  return t ? String(t.amount) : "N/A";
13022
12752
  })() })
13023
12753
  ] }),
13024
- /* @__PURE__ */ jsxs11(Text12, { children: [
12754
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13025
12755
  "Merchant: ",
13026
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
12756
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
13027
12757
  ] })
13028
12758
  ] })
13029
12759
  ] });
13030
12760
  }
13031
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13032
- /* @__PURE__ */ jsx15(Text12, { color: "green", children: "\u2713 Spend request approved" }),
13033
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13034
- /* @__PURE__ */ jsxs11(Text12, { children: [
12761
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12762
+ /* @__PURE__ */ jsx14(Text11, { color: "green", children: "\u2713 Spend request approved" }),
12763
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12764
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13035
12765
  "ID: ",
13036
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
12766
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
13037
12767
  ] }),
13038
- /* @__PURE__ */ jsxs11(Text12, { children: [
12768
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13039
12769
  "Status:",
13040
12770
  " ",
13041
- /* @__PURE__ */ jsx15(Text12, { bold: true, color: "green", children: request?.status })
12771
+ /* @__PURE__ */ jsx14(Text11, { bold: true, color: "green", children: request?.status })
13042
12772
  ] }),
13043
- /* @__PURE__ */ jsxs11(Text12, { children: [
12773
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13044
12774
  "Amount:",
13045
12775
  " ",
13046
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
12776
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
13047
12777
  const t = request?.totals.find((t2) => t2.type === "total");
13048
12778
  return t ? String(t.amount) : "N/A";
13049
12779
  })() })
13050
12780
  ] }),
13051
- /* @__PURE__ */ jsxs11(Text12, { children: [
12781
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13052
12782
  "Merchant: ",
13053
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
12783
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
13054
12784
  ] }),
13055
- /* @__PURE__ */ jsxs11(Text12, { children: [
12785
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13056
12786
  "Line Items:",
13057
12787
  " ",
13058
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
12788
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
13059
12789
  ] }),
13060
- request?.shared_payment_token && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
13061
- /* @__PURE__ */ jsxs11(Text12, { bold: true, children: [
12790
+ request?.shared_payment_token && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
12791
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
13062
12792
  "\x1B]8;;https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens\x07",
13063
12793
  "Shared Payment Token",
13064
12794
  "\x1B]8;;\x07",
13065
12795
  ":"
13066
12796
  ] }),
13067
- /* @__PURE__ */ jsxs11(Text12, { children: [
12797
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13068
12798
  " ",
13069
12799
  "Token: ",
13070
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.shared_payment_token.id })
12800
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.shared_payment_token.id })
13071
12801
  ] })
13072
12802
  ] }),
13073
- request?.card && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
13074
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: "Card Details:" }),
13075
- /* @__PURE__ */ jsxs11(Text12, { children: [
12803
+ request?.card && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
12804
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: "Card Details:" }),
12805
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13076
12806
  " ",
13077
12807
  "Number: ",
13078
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.card.number })
12808
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.number })
13079
12809
  ] }),
13080
- /* @__PURE__ */ jsxs11(Text12, { children: [
12810
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13081
12811
  " ",
13082
12812
  "Brand: ",
13083
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.card.brand })
12813
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.brand })
13084
12814
  ] }),
13085
- /* @__PURE__ */ jsxs11(Text12, { children: [
12815
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13086
12816
  " ",
13087
12817
  "Expiry:",
13088
12818
  " ",
13089
- /* @__PURE__ */ jsxs11(Text12, { bold: true, children: [
12819
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
13090
12820
  String(request?.card.exp_month).padStart(2, "0"),
13091
12821
  "/",
13092
12822
  request?.card.exp_year
13093
12823
  ] })
13094
12824
  ] }),
13095
- request?.card.cvc && /* @__PURE__ */ jsxs11(Text12, { children: [
12825
+ request?.card.cvc && /* @__PURE__ */ jsxs10(Text11, { children: [
13096
12826
  " ",
13097
12827
  "CVC: ",
13098
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.card.cvc })
12828
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.card.cvc })
13099
12829
  ] }),
13100
- request?.card.valid_until && /* @__PURE__ */ jsxs11(Text12, { children: [
12830
+ request?.card.valid_until && /* @__PURE__ */ jsxs10(Text11, { children: [
13101
12831
  " ",
13102
12832
  "Valid Until:",
13103
12833
  " ",
13104
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: new Date(request.card.valid_until * 1e3).toISOString() })
12834
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: new Date(request.card.valid_until * 1e3).toISOString() })
13105
12835
  ] }),
13106
- request?.card.billing_address && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
13107
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: " Billing Address:" }),
13108
- /* @__PURE__ */ jsxs11(Text12, { children: [
12836
+ request?.card.billing_address && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
12837
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: " Billing Address:" }),
12838
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13109
12839
  " ",
13110
12840
  request.card.billing_address.name
13111
12841
  ] }),
13112
- /* @__PURE__ */ jsxs11(Text12, { children: [
12842
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13113
12843
  " ",
13114
12844
  request.card.billing_address.line1
13115
12845
  ] }),
13116
- request.card.billing_address.line2 && /* @__PURE__ */ jsxs11(Text12, { children: [
12846
+ request.card.billing_address.line2 && /* @__PURE__ */ jsxs10(Text11, { children: [
13117
12847
  " ",
13118
12848
  request.card.billing_address.line2
13119
12849
  ] }),
13120
- /* @__PURE__ */ jsxs11(Text12, { children: [
12850
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13121
12851
  " ",
13122
12852
  [
13123
12853
  request.card.billing_address.city,
@@ -13125,7 +12855,7 @@ var RetrieveSpendRequest = ({
13125
12855
  request.card.billing_address.postal_code
13126
12856
  ].filter(Boolean).join(", ")
13127
12857
  ] }),
13128
- /* @__PURE__ */ jsxs11(Text12, { children: [
12858
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13129
12859
  " ",
13130
12860
  request.card.billing_address.country
13131
12861
  ] })
@@ -13136,226 +12866,69 @@ var RetrieveSpendRequest = ({
13136
12866
  };
13137
12867
 
13138
12868
  // src/commands/spend-request/schema.ts
13139
- import { z as z6 } from "zod";
13140
-
13141
- // src/utils/line-item-parser.ts
13142
- import { z as z5 } from "zod";
13143
- var LineItemSchema = z5.object({
13144
- name: z5.string(),
13145
- url: z5.string().optional(),
13146
- image_url: z5.string().optional(),
13147
- description: z5.string().optional(),
13148
- sku: z5.string().optional(),
13149
- quantity: z5.coerce.number().optional(),
13150
- unit_amount: z5.coerce.number().optional(),
13151
- product_url: z5.string().optional()
13152
- }).strict();
13153
- var TotalSchema = z5.object({
13154
- type: z5.string(),
13155
- display_text: z5.string(),
13156
- amount: z5.coerce.number()
13157
- }).strict();
13158
- function parseKvString(raw) {
13159
- const result = {};
13160
- for (const pair of raw.split(",")) {
13161
- const idx = pair.indexOf(":");
13162
- if (idx === -1) {
13163
- throw new Error(`Invalid field (missing ':'): ${pair}`);
13164
- }
13165
- result[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
13166
- }
13167
- return result;
13168
- }
13169
-
13170
- // src/commands/spend-request/schema.ts
13171
- var SPEND_REQUEST_OUTPUT_SCHEMA = {
13172
- id: { outputExample: '"..."', description: "Spend request ID" },
13173
- status: {
13174
- outputExample: '"created|pending_approval|approved|denied|expired|succeeded|failed"',
13175
- description: "Current status"
13176
- },
13177
- created_at: {
13178
- outputExample: '"2026-04-15T14:17:18Z"',
13179
- description: "Creation timestamp"
13180
- },
13181
- updated_at: {
13182
- outputExample: '"2026-04-15T14:17:18Z"',
13183
- description: "Last update timestamp"
13184
- },
13185
- payment_details: {
13186
- outputExample: '"csmrpd_abcde12345"',
13187
- description: "Payment method ID"
13188
- },
13189
- amount: { outputExample: "1000", description: "Amount in cents" },
13190
- merchant_name: { outputExample: '"Powdur"', description: "Merchant name" },
13191
- line_items: { outputExample: "[...]", description: "Line items" },
13192
- totals: { outputExample: "[...]", description: "Totals" },
13193
- card: {
13194
- outputExample: '{"number":"4242424242424242","exp_month":12,"exp_year":2027,"cvc":"123","billing_address":{"name":"Jane Doe","line1":"123 Main St","city":"San Francisco","state":"CA","postal_code":"94111","country":"US"},"valid_until":1750000000}',
13195
- description: "Card credentials (present when credential_type is card and status is approved). Includes billing_address (name, line1, line2, city, state, postal_code, country) and valid_until (unix timestamp) when available."
13196
- },
13197
- shared_payment_token: {
13198
- outputExample: '{"id":"spt_xxx","billing_address":{"name":"Jane Doe","line1":"123 Main St","city":"San Francisco","state":"CA","postal_code":"94111","country":"US"},"valid_until":"2026-04-21T20:46:58Z"}',
13199
- description: 'Shared payment token object (present when credential_type is shared_payment_token and status is approved). Use the "id" field as the SPT value.'
13200
- }
13201
- };
13202
- var CREATE_INPUT_SCHEMA = {
13203
- payment_method_id: {
13204
- schema: z6.string().min(1),
13205
- flag: "--payment-method-id <id>",
13206
- description: "Payment method ID",
13207
- required: true
13208
- },
13209
- credential_type: {
13210
- schema: z6.enum(["shared_payment_token", "card"]),
13211
- flag: "--credential-type <type>",
13212
- description: "Payment credential type",
13213
- jsonDescription: '"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows \u2014 evaluate the merchant site before choosing',
13214
- defaultValue: "card",
13215
- required: true
13216
- },
13217
- network_id: {
13218
- schema: z6.string().min(1),
13219
- flag: "--network-id <id>",
13220
- description: "Network ID (required for shared_payment_token)",
13221
- jsonDescription: "Required for shared_payment_token \u2014 use `link-cli mpp decode --challenge <www-authenticate>` to validate the stripe challenge and extract this value"
13222
- },
13223
- amount: {
13224
- schema: z6.coerce.number().int().positive().max(5e4),
13225
- flag: "--amount <cents>",
13226
- description: "Amount in cents",
13227
- jsonDescription: "Total in cents, max 50000 ($500.00)",
13228
- required: true
13229
- },
13230
- currency: {
13231
- schema: z6.string().length(3),
13232
- flag: "--currency <code>",
13233
- description: "Currency code",
13234
- defaultValue: "usd"
13235
- },
13236
- merchant_name: {
13237
- schema: z6.string().min(3),
13238
- flag: "--merchant-name <name>",
13239
- description: "Merchant name",
13240
- jsonDescription: "Required for card credential type; forbidden for shared_payment_token",
13241
- alias: "-m"
13242
- },
13243
- merchant_url: {
13244
- schema: z6.url(),
13245
- flag: "--merchant-url <url>",
13246
- description: "Merchant URL",
13247
- jsonDescription: "Required for card credential type; forbidden for shared_payment_token"
13248
- },
13249
- context: {
13250
- schema: z6.string().min(100),
13251
- flag: "--context <context>",
13252
- description: "Description of what is being purchased and why",
13253
- jsonDescription: "Min 100 chars \u2014 write a full sentence describing the purchase and rationale; the user reads this when approving",
13254
- required: true
13255
- },
13256
- line_items: {
13257
- schema: z6.array(LineItemSchema),
13258
- flag: "--line-item <item>",
13259
- description: "Line item (repeatable)",
13260
- flagParser: parseKvString
13261
- },
13262
- totals: {
13263
- schema: z6.array(TotalSchema),
13264
- flag: "--total <total>",
13265
- description: "Total (repeatable)",
13266
- flagParser: parseKvString
13267
- },
13268
- request_approval: {
13269
- schema: z6.boolean(),
13270
- flag: "--request-approval",
13271
- description: "Request approval and wait for user to approve/deny",
13272
- jsonDescription: "Polls until approved/denied/expired; blocks until the user acts",
13273
- defaultValue: true
13274
- },
13275
- test: {
13276
- schema: z6.boolean(),
13277
- flag: "--test",
13278
- description: "Use test mode (creates testmode credentials from test card data)",
13279
- jsonDescription: "When true, creates testmode credentials instead of real ones \u2014 safe for development and testing",
13280
- defaultValue: false
13281
- }
13282
- };
13283
- var RETRIEVE_INPUT_SCHEMA = {
13284
- timeout: {
13285
- schema: z6.coerce.number(),
13286
- flag: "--timeout <seconds>",
13287
- description: "Polling timeout in seconds",
13288
- defaultValue: 300
13289
- },
13290
- include: {
13291
- schema: z6.array(z6.string()),
13292
- flag: "--include <value>",
13293
- description: "Include extra data (repeatable, e.g. --include card)"
13294
- }
13295
- };
13296
- var UPDATE_INPUT_SCHEMA = {
13297
- payment_method_id: {
13298
- schema: z6.string().min(1),
13299
- flag: "--payment-method-id <id>",
13300
- description: "Payment method ID",
13301
- required: true
13302
- },
13303
- amount: {
13304
- schema: z6.coerce.number().int().positive(),
13305
- flag: "--amount <cents>",
13306
- description: "Amount in cents"
13307
- },
13308
- merchant_url: {
13309
- schema: z6.string().min(1),
13310
- flag: "--merchant-url <url>",
13311
- description: "Merchant URL"
13312
- },
13313
- profile_id: {
13314
- schema: z6.string().min(1),
13315
- flag: "--profile-id <id>",
13316
- description: "Profile ID"
13317
- },
13318
- merchant_id: {
13319
- schema: z6.string().min(1),
13320
- flag: "--merchant-id <id>",
13321
- description: "Merchant ID"
13322
- },
13323
- currency: {
13324
- schema: z6.string().min(1),
13325
- flag: "--currency <code>",
13326
- description: "Currency code"
13327
- },
13328
- line_items: {
13329
- schema: z6.array(LineItemSchema),
13330
- flag: "--line-item <item>",
13331
- description: "Line item (repeatable)",
13332
- flagParser: parseKvString
13333
- },
13334
- totals: {
13335
- schema: z6.array(TotalSchema),
13336
- flag: "--total <total>",
13337
- description: "Total (repeatable)",
13338
- flagParser: parseKvString
13339
- }
13340
- };
12869
+ import { z as z5 } from "incur";
12870
+ var createOptions = z5.object({
12871
+ paymentMethodId: z5.string().describe("Payment method ID"),
12872
+ credentialType: z5.enum(["shared_payment_token", "card"]).default("card").describe(
12873
+ '"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows'
12874
+ ),
12875
+ networkId: z5.string().optional().describe(
12876
+ "Network ID (required for shared_payment_token) \u2014 use `link-cli mpp decode` to extract"
12877
+ ),
12878
+ amount: z5.coerce.number().int().positive().max(5e4).describe("Amount in cents, max 50000 ($500.00)"),
12879
+ currency: z5.string().length(3).default("usd").describe("Currency code"),
12880
+ merchantName: z5.string().optional().describe(
12881
+ "Merchant name (required for card; forbidden for shared_payment_token)"
12882
+ ),
12883
+ merchantUrl: z5.string().optional().describe(
12884
+ "Merchant URL (required for card; forbidden for shared_payment_token)"
12885
+ ),
12886
+ context: z5.string().min(100).describe(
12887
+ "Min 100 chars \u2014 describe the purchase and rationale; the user reads this when approving"
12888
+ ),
12889
+ lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
12890
+ total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)"),
12891
+ requestApproval: z5.boolean().default(true).describe("Request approval and poll until approved/denied/expired"),
12892
+ test: z5.boolean().default(false).describe(
12893
+ "Use test mode (creates testmode credentials from test card data)"
12894
+ )
12895
+ });
12896
+ var retrieveOptions = z5.object({
12897
+ timeout: z5.coerce.number().default(300).describe("Polling timeout in seconds"),
12898
+ interval: z5.coerce.number().default(0).describe(
12899
+ "Poll interval in seconds. When > 0, polls until status is terminal or timeout is reached, yielding status on each attempt."
12900
+ ),
12901
+ maxAttempts: z5.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
12902
+ include: z5.array(z5.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)")
12903
+ });
12904
+ var updateOptions = z5.object({
12905
+ paymentMethodId: z5.string().optional().describe("Payment method ID"),
12906
+ amount: z5.coerce.number().optional().describe("Amount in cents"),
12907
+ merchantUrl: z5.string().optional().describe("Merchant URL"),
12908
+ profileId: z5.string().optional().describe("Profile ID"),
12909
+ merchantId: z5.string().optional().describe("Merchant ID"),
12910
+ currency: z5.string().optional().describe("Currency code"),
12911
+ lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
12912
+ total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)")
12913
+ });
13341
12914
 
13342
12915
  // src/commands/spend-request/update.tsx
13343
- import { Box as Box13, Text as Text13 } from "ink";
12916
+ import { Box as Box12, Text as Text12 } from "ink";
13344
12917
  import Spinner8 from "ink-spinner";
13345
- import { useEffect as useEffect10, useState as useState9 } from "react";
13346
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
12918
+ import { useEffect as useEffect9, useState as useState8 } from "react";
12919
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
13347
12920
  var UpdateSpendRequest = ({
13348
12921
  repository,
13349
12922
  id,
13350
12923
  params,
13351
12924
  onComplete
13352
12925
  }) => {
13353
- const [status, setStatus] = useState9(
12926
+ const [status, setStatus] = useState8(
13354
12927
  "loading"
13355
12928
  );
13356
- const [request, setRequest] = useState9(null);
13357
- const [error, setError] = useState9("");
13358
- useEffect10(() => {
12929
+ const [request, setRequest] = useState8(null);
12930
+ const [error, setError] = useState8("");
12931
+ useEffect9(() => {
13359
12932
  const update = async () => {
13360
12933
  try {
13361
12934
  const result = await repository.updateSpendRequest(id, params);
@@ -13371,369 +12944,354 @@ var UpdateSpendRequest = ({
13371
12944
  update();
13372
12945
  }, [repository, id, params, onComplete]);
13373
12946
  if (status === "loading") {
13374
- return /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
13375
- /* @__PURE__ */ jsx16(Spinner8, { type: "dots" }),
12947
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
12948
+ /* @__PURE__ */ jsx15(Spinner8, { type: "dots" }),
13376
12949
  " Updating spend request ",
13377
12950
  id,
13378
12951
  "..."
13379
12952
  ] }) });
13380
12953
  }
13381
12954
  if (status === "error") {
13382
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
13383
- /* @__PURE__ */ jsx16(Text13, { color: "red", children: "\u2717 Failed to update spend request" }),
13384
- /* @__PURE__ */ jsx16(Text13, { color: "red", children: error })
12955
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12956
+ /* @__PURE__ */ jsx15(Text12, { color: "red", children: "\u2717 Failed to update spend request" }),
12957
+ /* @__PURE__ */ jsx15(Text12, { color: "red", children: error })
13385
12958
  ] });
13386
12959
  }
13387
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
13388
- /* @__PURE__ */ jsx16(Text13, { color: "green", children: "\u2713 Spend request updated" }),
13389
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13390
- /* @__PURE__ */ jsxs12(Text13, { children: [
12960
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12961
+ /* @__PURE__ */ jsx15(Text12, { color: "green", children: "\u2713 Spend request updated" }),
12962
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12963
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13391
12964
  "ID: ",
13392
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.id })
12965
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
13393
12966
  ] }),
13394
- /* @__PURE__ */ jsxs12(Text13, { children: [
12967
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13395
12968
  "Status: ",
13396
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.status })
12969
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.status })
13397
12970
  ] }),
13398
- /* @__PURE__ */ jsxs12(Text13, { children: [
12971
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13399
12972
  "Amount:",
13400
12973
  " ",
13401
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: (() => {
12974
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
13402
12975
  const t = request?.totals.find((t2) => t2.type === "total");
13403
12976
  return t ? String(t.amount) : "N/A";
13404
12977
  })() })
13405
12978
  ] }),
13406
- /* @__PURE__ */ jsxs12(Text13, { children: [
12979
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13407
12980
  "Merchant: ",
13408
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.merchant_name })
12981
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
13409
12982
  ] }),
13410
- /* @__PURE__ */ jsxs12(Text13, { children: [
12983
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13411
12984
  "Line Items:",
13412
12985
  " ",
13413
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
12986
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
13414
12987
  ] })
13415
12988
  ] })
13416
12989
  ] });
13417
12990
  };
13418
12991
 
13419
12992
  // src/commands/spend-request/index.tsx
13420
- import { jsx as jsx17 } from "react/jsx-runtime";
13421
- function registerSpendRequestCommands(program2, repository) {
13422
- const spendRequestCommand2 = program2.command("spend-request").description("Spend request management commands").helpCommand(false);
13423
- const createCmd = spendRequestCommand2.command("create").description("Create a new spend request");
13424
- registerSchemaOptions(createCmd, CREATE_INPUT_SCHEMA);
13425
- createCmd.option(
13426
- "--json <json>",
13427
- `JSON input (keys: ${Object.keys(CREATE_INPUT_SCHEMA).join(", ")})`
13428
- ).option(
13429
- "--output-json",
13430
- "Output result as JSON instead of interactive display"
13431
- ).addHelpText(
13432
- "after",
13433
- buildInputHelp(CREATE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
13434
- ).action(async (options) => {
13435
- requireAuth();
13436
- let resolved = {};
13437
- try {
13438
- resolved = resolveInput(options, CREATE_INPUT_SCHEMA);
13439
- } catch (err) {
13440
- if (err instanceof ValidationError)
13441
- outputErrors(err.errors, !!options.outputJson);
13442
- outputError(err.message);
13443
- }
13444
- const requestApproval = !!resolved.request_approval;
13445
- const credentialType = resolved.credential_type;
13446
- const networkId = resolved.network_id;
13447
- if (credentialType === "shared_payment_token" && !networkId) {
13448
- outputError(
13449
- "network-id is required when credential-type is shared_payment_token"
13450
- );
13451
- }
13452
- if (networkId && credentialType !== "shared_payment_token") {
13453
- outputError(
13454
- "network-id can only be used when credential-type is shared_payment_token"
13455
- );
13456
- }
13457
- if (credentialType !== "shared_payment_token" && !resolved.merchant_name) {
13458
- outputError("merchant-name is required when credential-type is card");
13459
- }
13460
- if (credentialType !== "shared_payment_token" && !resolved.merchant_url) {
13461
- outputError("merchant-url is required when credential-type is card");
13462
- }
13463
- const createParams = {
13464
- payment_details: resolved.payment_method_id,
13465
- credential_type: credentialType,
13466
- network_id: networkId,
13467
- amount: resolved.amount,
13468
- currency: resolved.currency,
13469
- merchant_name: resolved.merchant_name,
13470
- merchant_url: resolved.merchant_url,
13471
- context: resolved.context,
13472
- line_items: resolved.line_items,
13473
- totals: resolved.totals,
13474
- request_approval: requestApproval || void 0,
13475
- test: resolved.test ? true : void 0
13476
- };
13477
- await executeCommand({
13478
- outputJson: !!options.outputJson,
13479
- jsonFn: async () => {
13480
- const created = await repository.createSpendRequest(createParams);
13481
- if (requestApproval) {
13482
- outputJson(created);
13483
- return pollUntilApproved(repository, created.id, {
13484
- onProgress: (elapsedSeconds) => {
13485
- process.stderr.write(
13486
- `${JSON.stringify({
13487
- type: "waiting",
13488
- command: "spend_request_approval",
13489
- elapsed_seconds: elapsedSeconds,
13490
- approval_url: created.approval_url ?? null,
13491
- spend_request_id: created.id
13492
- })}
13493
- `
13494
- );
13495
- }
13496
- });
13497
- }
13498
- return created;
13499
- },
13500
- renderFn: () => /* @__PURE__ */ jsx17(
13501
- CreateSpendRequest,
13502
- {
13503
- repository,
13504
- params: createParams,
13505
- requestApproval,
13506
- onComplete: () => {
13507
- }
13508
- }
13509
- )
13510
- });
12993
+ import { jsx as jsx16 } from "react/jsx-runtime";
12994
+ function createSpendRequestCli(repository) {
12995
+ const cli2 = Cli4.create("spend-request", {
12996
+ description: "Spend request management commands"
13511
12997
  });
13512
- const updateCmd = spendRequestCommand2.command("update <id>").description("Update a spend request");
13513
- registerSchemaOptions(updateCmd, UPDATE_INPUT_SCHEMA);
13514
- updateCmd.option(
13515
- "--json <json>",
13516
- `JSON input (keys: ${Object.keys(UPDATE_INPUT_SCHEMA).join(", ")})`
13517
- ).option(
13518
- "--output-json",
13519
- "Output result as JSON instead of interactive display"
13520
- ).addHelpText(
13521
- "after",
13522
- buildInputHelp(UPDATE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
13523
- ).action(async (id, options) => {
13524
- requireAuth();
13525
- let resolved = {};
13526
- try {
13527
- resolved = resolveInput(options, UPDATE_INPUT_SCHEMA);
13528
- } catch (err) {
13529
- if (err instanceof ValidationError)
13530
- outputErrors(err.errors, !!options.outputJson);
13531
- outputError(err.message);
13532
- }
13533
- const params = {};
13534
- if (resolved.payment_method_id !== void 0)
13535
- params.payment_details = resolved.payment_method_id;
13536
- if (resolved.amount !== void 0) params.amount = resolved.amount;
13537
- if (resolved.merchant_url !== void 0)
13538
- params.merchant_url = resolved.merchant_url;
13539
- if (resolved.profile_id !== void 0)
13540
- params.profile_id = resolved.profile_id;
13541
- if (resolved.merchant_id !== void 0)
13542
- params.merchant_id = resolved.merchant_id;
13543
- if (resolved.currency !== void 0) params.currency = resolved.currency;
13544
- if (resolved.line_items !== void 0)
13545
- params.line_items = resolved.line_items;
13546
- if (resolved.totals !== void 0) params.totals = resolved.totals;
13547
- await executeCommand({
13548
- outputJson: !!options.outputJson,
13549
- jsonFn: async () => {
13550
- return repository.updateSpendRequest(id, params);
13551
- },
13552
- renderFn: () => /* @__PURE__ */ jsx17(
13553
- UpdateSpendRequest,
13554
- {
13555
- repository,
13556
- id,
13557
- params,
13558
- onComplete: () => {
12998
+ cli2.command("create", {
12999
+ description: "Create a new spend request",
13000
+ options: createOptions,
13001
+ alias: { merchantName: "m" },
13002
+ outputPolicy: "agent-only",
13003
+ async *run(c) {
13004
+ if (!storage.isAuthenticated()) {
13005
+ return c.error({
13006
+ code: "NOT_AUTHENTICATED",
13007
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13008
+ cta: {
13009
+ commands: [
13010
+ { command: "auth login", description: "Log in to Link" }
13011
+ ]
13559
13012
  }
13013
+ });
13014
+ }
13015
+ const opts = c.options;
13016
+ const requestApproval = !!opts.requestApproval;
13017
+ const credentialType = opts.credentialType;
13018
+ const networkId = opts.networkId;
13019
+ if (credentialType === "shared_payment_token" && !networkId) {
13020
+ return c.error({
13021
+ code: "INVALID_INPUT",
13022
+ message: "network-id is required when credential-type is shared_payment_token",
13023
+ cta: {
13024
+ commands: [
13025
+ {
13026
+ command: "mpp decode",
13027
+ description: "Decode a WWW-Authenticate challenge to extract network-id"
13028
+ }
13029
+ ]
13030
+ }
13031
+ });
13032
+ }
13033
+ if (networkId && credentialType !== "shared_payment_token") {
13034
+ return c.error({
13035
+ code: "INVALID_INPUT",
13036
+ message: "network-id can only be used when credential-type is shared_payment_token"
13037
+ });
13038
+ }
13039
+ if (credentialType !== "shared_payment_token" && !opts.merchantName) {
13040
+ return c.error({
13041
+ code: "INVALID_INPUT",
13042
+ message: "merchant-name is required when credential-type is card"
13043
+ });
13044
+ }
13045
+ if (credentialType !== "shared_payment_token" && !opts.merchantUrl) {
13046
+ return c.error({
13047
+ code: "INVALID_INPUT",
13048
+ message: "merchant-url is required when credential-type is card"
13049
+ });
13050
+ }
13051
+ const lineItems = opts.lineItem?.length ? opts.lineItem.map(
13052
+ (item) => typeof item === "string" ? parseLineItemFlag(item) : item
13053
+ ) : void 0;
13054
+ const totals = opts.total?.length ? opts.total.map(
13055
+ (item) => typeof item === "string" ? parseTotalFlag(item) : item
13056
+ ) : void 0;
13057
+ const createParams = {
13058
+ payment_details: opts.paymentMethodId,
13059
+ credential_type: credentialType,
13060
+ network_id: networkId,
13061
+ amount: opts.amount,
13062
+ currency: opts.currency,
13063
+ merchant_name: opts.merchantName,
13064
+ merchant_url: opts.merchantUrl,
13065
+ context: opts.context,
13066
+ line_items: lineItems,
13067
+ totals,
13068
+ request_approval: requestApproval || void 0,
13069
+ test: opts.test ? true : void 0
13070
+ };
13071
+ if (!c.agent && !c.formatExplicit) {
13072
+ return new Promise((resolve) => {
13073
+ const { waitUntilExit } = render4(
13074
+ /* @__PURE__ */ jsx16(
13075
+ CreateSpendRequest,
13076
+ {
13077
+ repository,
13078
+ params: createParams,
13079
+ requestApproval,
13080
+ onComplete: () => {
13081
+ }
13082
+ }
13083
+ )
13084
+ );
13085
+ waitUntilExit().then(async () => {
13086
+ const created2 = await repository.createSpendRequest(createParams);
13087
+ resolve(created2);
13088
+ });
13089
+ });
13090
+ }
13091
+ const created = await repository.createSpendRequest(createParams);
13092
+ if (!requestApproval) {
13093
+ yield created;
13094
+ return;
13095
+ }
13096
+ yield {
13097
+ ...created,
13098
+ 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.`,
13099
+ _next: {
13100
+ command: `spend-request retrieve ${created.id} --interval 2 --max-attempts 150`,
13101
+ until: "status changes from pending_approval"
13560
13102
  }
13561
- )
13562
- });
13103
+ };
13104
+ }
13563
13105
  });
13564
- spendRequestCommand2.command("request-approval <id>").description("Request approval for a spend request").option(
13565
- "--output-json",
13566
- "Output result as JSON instead of interactive display"
13567
- ).addHelpText("after", buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)).action(async (id, options) => {
13568
- requireAuth();
13569
- await executeCommand({
13570
- outputJson: !!options.outputJson,
13571
- jsonFn: async () => {
13572
- const approval = await repository.requestApproval(id);
13573
- outputJson(approval);
13574
- return pollUntilApproved(repository, id, {
13575
- onProgress: (elapsedSeconds) => {
13576
- process.stderr.write(
13577
- `${JSON.stringify({
13578
- type: "waiting",
13579
- command: "spend_request_approval",
13580
- elapsed_seconds: elapsedSeconds,
13581
- approval_url: approval.approval_link ?? null,
13582
- spend_request_id: id
13583
- })}
13584
- `
13585
- );
13106
+ cli2.command("update", {
13107
+ description: "Update a spend request",
13108
+ args: z6.object({
13109
+ id: z6.string().describe("Spend request ID")
13110
+ }),
13111
+ options: updateOptions,
13112
+ outputPolicy: "agent-only",
13113
+ async run(c) {
13114
+ if (!storage.isAuthenticated()) {
13115
+ return c.error({
13116
+ code: "NOT_AUTHENTICATED",
13117
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13118
+ cta: {
13119
+ commands: [
13120
+ { command: "auth login", description: "Log in to Link" }
13121
+ ]
13586
13122
  }
13587
13123
  });
13588
- },
13589
- renderFn: () => /* @__PURE__ */ jsx17(
13590
- RequestApproval,
13591
- {
13592
- repository,
13593
- id,
13594
- onComplete: () => {
13124
+ }
13125
+ const id = c.args.id;
13126
+ const opts = c.options;
13127
+ const params = {};
13128
+ if (opts.paymentMethodId !== void 0)
13129
+ params.payment_details = opts.paymentMethodId;
13130
+ if (opts.amount !== void 0) params.amount = opts.amount;
13131
+ if (opts.merchantUrl !== void 0)
13132
+ params.merchant_url = opts.merchantUrl;
13133
+ if (opts.profileId !== void 0) params.profile_id = opts.profileId;
13134
+ if (opts.merchantId !== void 0) params.merchant_id = opts.merchantId;
13135
+ if (opts.currency !== void 0) params.currency = opts.currency;
13136
+ if (opts.lineItem?.length)
13137
+ params.line_items = opts.lineItem.map(
13138
+ (item) => typeof item === "string" ? parseLineItemFlag(item) : item
13139
+ );
13140
+ if (opts.total?.length)
13141
+ params.totals = opts.total.map(
13142
+ (item) => typeof item === "string" ? parseTotalFlag(item) : item
13143
+ );
13144
+ if (!c.agent && !c.formatExplicit) {
13145
+ return new Promise((resolve) => {
13146
+ const { waitUntilExit } = render4(
13147
+ /* @__PURE__ */ jsx16(
13148
+ UpdateSpendRequest,
13149
+ {
13150
+ repository,
13151
+ id,
13152
+ params,
13153
+ onComplete: () => {
13154
+ }
13155
+ }
13156
+ )
13157
+ );
13158
+ waitUntilExit().then(async () => {
13159
+ resolve(await repository.updateSpendRequest(id, params));
13160
+ });
13161
+ });
13162
+ }
13163
+ return repository.updateSpendRequest(id, params);
13164
+ }
13165
+ });
13166
+ cli2.command("request-approval", {
13167
+ description: "Request approval for a spend request",
13168
+ args: z6.object({
13169
+ id: z6.string().describe("Spend request ID")
13170
+ }),
13171
+ outputPolicy: "agent-only",
13172
+ async *run(c) {
13173
+ if (!storage.isAuthenticated()) {
13174
+ return c.error({
13175
+ code: "NOT_AUTHENTICATED",
13176
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13177
+ cta: {
13178
+ commands: [
13179
+ { command: "auth login", description: "Log in to Link" }
13180
+ ]
13595
13181
  }
13182
+ });
13183
+ }
13184
+ const id = c.args.id;
13185
+ if (!c.agent && !c.formatExplicit) {
13186
+ return new Promise((resolve) => {
13187
+ const { waitUntilExit } = render4(
13188
+ /* @__PURE__ */ jsx16(
13189
+ RequestApproval,
13190
+ {
13191
+ repository,
13192
+ id,
13193
+ onComplete: () => {
13194
+ }
13195
+ }
13196
+ )
13197
+ );
13198
+ waitUntilExit().then(async () => {
13199
+ const approval2 = await repository.requestApproval(id);
13200
+ resolve(approval2);
13201
+ });
13202
+ });
13203
+ }
13204
+ const approval = await repository.requestApproval(id);
13205
+ yield {
13206
+ ...approval,
13207
+ 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.`,
13208
+ _next: {
13209
+ command: `spend-request retrieve ${id} --interval 2 --max-attempts 150`,
13210
+ until: "status changes from pending_approval"
13596
13211
  }
13597
- )
13598
- });
13212
+ };
13213
+ }
13599
13214
  });
13600
- const retrieveCmd = spendRequestCommand2.command("retrieve <id>").description("Retrieve a spend request");
13601
- registerSchemaOptions(retrieveCmd, RETRIEVE_INPUT_SCHEMA);
13602
- retrieveCmd.option(
13603
- "--json <json>",
13604
- `JSON input (keys: ${Object.keys(RETRIEVE_INPUT_SCHEMA).join(", ")})`
13605
- ).option(
13606
- "--output-json",
13607
- "Output result as JSON instead of interactive display"
13608
- ).addHelpText(
13609
- "after",
13610
- buildInputHelp(RETRIEVE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
13611
- ).action(async (id, options) => {
13612
- requireAuth();
13613
- let resolved = {};
13614
- try {
13615
- resolved = resolveInput(options, RETRIEVE_INPUT_SCHEMA);
13616
- } catch (err) {
13617
- if (err instanceof ValidationError)
13618
- outputErrors(err.errors, !!options.outputJson);
13619
- outputError(err.message);
13620
- }
13621
- const timeout = resolved.timeout;
13622
- const includeArr = resolved.include;
13623
- const include = includeArr?.length ? includeArr : void 0;
13624
- await executeCommand({
13625
- outputJson: !!options.outputJson,
13626
- jsonFn: async () => {
13215
+ cli2.command("retrieve", {
13216
+ description: "Retrieve a spend request",
13217
+ args: z6.object({
13218
+ id: z6.string().describe("Spend request ID")
13219
+ }),
13220
+ options: retrieveOptions,
13221
+ outputPolicy: "agent-only",
13222
+ async *run(c) {
13223
+ if (!storage.isAuthenticated()) {
13224
+ return c.error({
13225
+ code: "NOT_AUTHENTICATED",
13226
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13227
+ cta: {
13228
+ commands: [
13229
+ { command: "auth login", description: "Log in to Link" }
13230
+ ]
13231
+ }
13232
+ });
13233
+ }
13234
+ const id = c.args.id;
13235
+ const opts = c.options;
13236
+ const timeout = opts.timeout;
13237
+ const interval = opts.interval;
13238
+ const maxAttempts = opts.maxAttempts;
13239
+ const includeArr = opts.include;
13240
+ const include = includeArr?.length ? includeArr : void 0;
13241
+ if (!c.agent && !c.formatExplicit) {
13242
+ return new Promise((resolve) => {
13243
+ const { waitUntilExit } = render4(
13244
+ /* @__PURE__ */ jsx16(
13245
+ RetrieveSpendRequest,
13246
+ {
13247
+ repository,
13248
+ id,
13249
+ timeout,
13250
+ include,
13251
+ onComplete: () => {
13252
+ }
13253
+ }
13254
+ )
13255
+ );
13256
+ waitUntilExit().then(async () => {
13257
+ const request = await repository.getSpendRequest(id, { include });
13258
+ resolve(request);
13259
+ });
13260
+ });
13261
+ }
13262
+ const terminalStatuses = /* @__PURE__ */ new Set([
13263
+ "approved",
13264
+ "denied",
13265
+ "expired",
13266
+ "succeeded",
13267
+ "failed"
13268
+ ]);
13269
+ const deadline = Date.now() + timeout * 1e3;
13270
+ let attempts = 0;
13271
+ while (true) {
13627
13272
  const request = await repository.getSpendRequest(id, { include });
13628
13273
  if (!request) {
13629
- throw new Error(`Spend request ${id} not found`);
13274
+ return c.error({
13275
+ code: "NOT_FOUND",
13276
+ message: `Spend request ${id} not found`
13277
+ });
13630
13278
  }
13631
- return request;
13632
- },
13633
- renderFn: () => /* @__PURE__ */ jsx17(
13634
- RetrieveSpendRequest,
13635
- {
13636
- repository,
13637
- id,
13638
- timeout,
13639
- include,
13640
- onComplete: () => {
13641
- }
13279
+ if (terminalStatuses.has(request.status)) {
13280
+ yield request;
13281
+ return;
13642
13282
  }
13643
- )
13644
- });
13645
- });
13646
- return spendRequestCommand2;
13647
- }
13648
-
13649
- // src/utils/configure-root-help.ts
13650
- function configureRootHelp(program2, authCommand2, spendIntentCommand, paymentMethodsCommand2, skillCommand2, mppCommand2) {
13651
- program2.configureHelp({
13652
- formatHelp(cmd, helper) {
13653
- const helpWidth = helper.helpWidth || 80;
13654
- const itemIndent = 2;
13655
- const itemSeparator = 2;
13656
- function formatItem(term, termWidth, description) {
13657
- if (description) {
13658
- const fullText = `${term.padEnd(termWidth + itemSeparator)}${description}`;
13659
- return helper.wrap(
13660
- fullText,
13661
- helpWidth - itemIndent,
13662
- termWidth + itemSeparator
13663
- );
13283
+ attempts++;
13284
+ const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
13285
+ if (shouldStop) {
13286
+ yield request;
13287
+ return;
13664
13288
  }
13665
- return term;
13666
- }
13667
- function formatList(items) {
13668
- return items.join("\n").replace(/^/gm, " ".repeat(itemIndent));
13669
- }
13670
- const output = [`Usage: ${helper.commandUsage(cmd)}`, ""];
13671
- const desc = helper.commandDescription(cmd);
13672
- if (desc.length > 0) {
13673
- output.push(helper.wrap(desc, helpWidth, 0), "");
13289
+ yield request;
13290
+ await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
13674
13291
  }
13675
- output.push(
13676
- "Getting started:",
13677
- formatList([
13678
- "As an agent, you MUST run `link-cli skill` to fully understand how to get setup.",
13679
- "Optional: Run `npx skills add stripe/link-cli` to install the skill for future use."
13680
- ]),
13681
- ""
13682
- );
13683
- const optTermWidth = helper.longestOptionTermLength(cmd, helper);
13684
- const optionList = helper.visibleOptions(cmd).map(
13685
- (option) => formatItem(
13686
- helper.optionTerm(option),
13687
- optTermWidth,
13688
- helper.optionDescription(option)
13689
- )
13690
- );
13691
- if (optionList.length > 0) {
13692
- output.push("Options:", formatList(optionList), "");
13693
- }
13694
- const commandGroups = [
13695
- { heading: "Auth:", parent: authCommand2 },
13696
- { heading: "Spend Requests:", parent: spendIntentCommand },
13697
- { heading: "Payment Methods:", parent: paymentMethodsCommand2 },
13698
- { heading: "MPP:", parent: mppCommand2 }
13699
- ];
13700
- const allLeafCmds = commandGroups.flatMap(
13701
- ({ parent }) => helper.visibleCommands(parent)
13702
- );
13703
- const maxTermWidth = allLeafCmds.reduce(
13704
- (max, sub) => Math.max(
13705
- max,
13706
- // biome-ignore lint/style/noNonNullAssertion: sub is always a subcommand and always has a parent
13707
- `${sub.parent.name()} ${helper.subcommandTerm(sub)}`.length
13708
- ),
13709
- skillCommand2.name().length
13710
- );
13711
- for (const { heading, parent } of commandGroups) {
13712
- const cmds = helper.visibleCommands(parent);
13713
- if (cmds.length === 0) continue;
13714
- const list = cmds.map(
13715
- (sub) => formatItem(
13716
- `${parent.name()} ${helper.subcommandTerm(sub)}`,
13717
- maxTermWidth,
13718
- helper.subcommandDescription(sub)
13719
- )
13720
- );
13721
- output.push(heading, formatList(list), "");
13722
- }
13723
- output.push(
13724
- "Other:",
13725
- formatList([
13726
- formatItem(
13727
- skillCommand2.name(),
13728
- maxTermWidth,
13729
- skillCommand2.description()
13730
- )
13731
- ]),
13732
- ""
13733
- );
13734
- return output.join("\n");
13735
13292
  }
13736
13293
  });
13294
+ return cli2;
13737
13295
  }
13738
13296
 
13739
13297
  // src/auth/auth-resource.ts
@@ -13908,6 +13466,26 @@ ${JSON.stringify(redacted, null, 2)}`
13908
13466
  }
13909
13467
  );
13910
13468
  }
13469
+ async revokeToken(token) {
13470
+ const { status, data, rawBody } = await this.postForm(
13471
+ `${this.config.authBaseUrl}/device/revoke`,
13472
+ {
13473
+ client_id: CLIENT_ID,
13474
+ token
13475
+ }
13476
+ );
13477
+ if (status < 200 || status >= 300) {
13478
+ throw new LinkApiError(
13479
+ formatOAuthError("Token revocation failed", status, data, rawBody),
13480
+ {
13481
+ status,
13482
+ code: data?.error,
13483
+ rawBody,
13484
+ details: data
13485
+ }
13486
+ );
13487
+ }
13488
+ }
13911
13489
  async refreshToken(refreshToken) {
13912
13490
  const { status, data, rawBody } = await this.postForm(
13913
13491
  `${this.config.authBaseUrl}/device/token`,
@@ -14016,46 +13594,36 @@ var ResourceFactory = class {
14016
13594
  };
14017
13595
 
14018
13596
  // src/cli.tsx
14019
- var cliVersion = "0.1.2";
13597
+ var cliVersion = "0.2.1";
14020
13598
  var buildNumber = "1";
13599
+ var cliName = "@stripe/link-cli";
14021
13600
  var defaultHeaders = {
14022
13601
  "User-Agent": `link-cli/${cliVersion} (build ${buildNumber})`,
14023
13602
  "X-Build-Number": buildNumber
14024
13603
  };
14025
- var program = new Command();
14026
13604
  var verbose = process.argv.includes("--verbose");
14027
13605
  var factory = new ResourceFactory({ verbose, defaultHeaders });
14028
13606
  var authRepo = factory.createAuthResource();
14029
13607
  var spendRequestRepo = factory.createSpendRequestResource();
14030
- program.name("link-cli").description(
14031
- "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users."
14032
- ).version(`${cliVersion} (build ${buildNumber})`).option("--verbose", "Print API request and response details to stderr").helpCommand(false).configureOutput({
14033
- outputError: (str, write) => {
14034
- write(str);
14035
- const isJsonMode = process.argv.includes("--output-json");
14036
- if (str.includes("unknown command") && !isJsonMode) {
14037
- write("\nRun 'link-cli --help' to see available commands.\n");
14038
- write("Run 'link-cli --skill' for full instructions.\n");
14039
- }
14040
- }
14041
- });
14042
- var authCommand = registerAuthCommands(program, authRepo);
14043
- var spendRequestCommand = registerSpendRequestCommands(
14044
- program,
14045
- spendRequestRepo
14046
- );
14047
- var paymentMethodsCommand = registerPaymentMethodsCommands(
14048
- program,
14049
- () => factory.createPaymentMethodsResource()
14050
- );
14051
- var skillCommand = registerSkillCommand(program);
14052
- var mppCommand = registerMppCommands(program, spendRequestRepo);
14053
- configureRootHelp(
14054
- program,
14055
- authCommand,
14056
- spendRequestCommand,
14057
- paymentMethodsCommand,
14058
- skillCommand,
14059
- mppCommand
13608
+ var notifier = updateNotifier({
13609
+ pkg: { name: cliName, version: cliVersion }
13610
+ });
13611
+ var cli = Cli5.create("link-cli", {
13612
+ description: "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users.",
13613
+ version: `${cliVersion} (build ${buildNumber})`
13614
+ });
13615
+ cli.command(createAuthCli(authRepo, notifier.update));
13616
+ cli.command(createSpendRequestCli(spendRequestRepo));
13617
+ cli.command(
13618
+ createPaymentMethodsCli(() => factory.createPaymentMethodsResource())
14060
13619
  );
14061
- program.parse();
13620
+ cli.command(createMppCli(spendRequestRepo));
13621
+ var isAgent = process.argv.includes("--format") || process.argv.includes("--mcp");
13622
+ if (!isAgent) {
13623
+ notifier.notify({ defer: false });
13624
+ }
13625
+ cli.serve();
13626
+ var cli_default = cli;
13627
+ export {
13628
+ cli_default as default
13629
+ };