@stripe/link-cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -27
  2. package/dist/cli.js +1081 -1547
  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,7 @@ 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
9553
 
9554
9554
  // ../sdk/dist/index.js
9555
9555
  import fs3 from "fs";
@@ -10867,7 +10867,8 @@ var Storage = class {
10867
10867
  this.config = new Conf({
10868
10868
  projectName: "link-cli",
10869
10869
  defaults: {
10870
- auth: null
10870
+ auth: null,
10871
+ pendingDeviceAuth: null
10871
10872
  }
10872
10873
  });
10873
10874
  }
@@ -10885,6 +10886,21 @@ var Storage = class {
10885
10886
  isAuthenticated() {
10886
10887
  return this.getAuth() !== null;
10887
10888
  }
10889
+ getPendingDeviceAuth() {
10890
+ const pending = this.getConfig().get("pendingDeviceAuth");
10891
+ if (!pending) return null;
10892
+ if (Date.now() >= pending.expires_at) {
10893
+ this.clearPendingDeviceAuth();
10894
+ return null;
10895
+ }
10896
+ return pending;
10897
+ }
10898
+ setPendingDeviceAuth(pending) {
10899
+ this.getConfig().set("pendingDeviceAuth", pending);
10900
+ }
10901
+ clearPendingDeviceAuth() {
10902
+ this.getConfig().set("pendingDeviceAuth", null);
10903
+ }
10888
10904
  clearAll() {
10889
10905
  this.getConfig().clear();
10890
10906
  }
@@ -11229,242 +11245,10 @@ var SpendRequestResource = class {
11229
11245
  }
11230
11246
  };
11231
11247
 
11232
- // src/utils/execute-command.tsx
11248
+ // src/commands/auth/index.tsx
11249
+ import { Cli } from "incur";
11233
11250
  import { render } from "ink";
11234
11251
 
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
11252
  // src/commands/auth/login.tsx
11469
11253
  import { Box, Text, useInput } from "ink";
11470
11254
  import Spinner from "ink-spinner";
@@ -11608,14 +11392,24 @@ var Login = ({
11608
11392
  import { Box as Box2, Text as Text2 } from "ink";
11609
11393
  import { useEffect as useEffect2, useState as useState2 } from "react";
11610
11394
  import { jsx as jsx2 } from "react/jsx-runtime";
11611
- var Logout = ({ onComplete }) => {
11395
+ var Logout = ({ authResource, onComplete }) => {
11612
11396
  const [done, setDone] = useState2(false);
11613
11397
  useEffect2(() => {
11614
- storage.clearAuth();
11615
- storage.deleteConfig();
11616
- setDone(true);
11617
- setTimeout(onComplete, 1e3);
11618
- }, [onComplete]);
11398
+ const run = async () => {
11399
+ const auth = storage.getAuth();
11400
+ if (auth?.refresh_token) {
11401
+ try {
11402
+ await authResource.revokeToken(auth.refresh_token);
11403
+ } catch {
11404
+ }
11405
+ }
11406
+ storage.clearAuth();
11407
+ storage.deleteConfig();
11408
+ setDone(true);
11409
+ setTimeout(onComplete, 1e3);
11410
+ };
11411
+ run();
11412
+ }, [authResource, onComplete]);
11619
11413
  if (!done) {
11620
11414
  return null;
11621
11415
  }
@@ -11623,217 +11417,159 @@ var Logout = ({ onComplete }) => {
11623
11417
  };
11624
11418
 
11625
11419
  // 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
- };
11420
+ import { z } from "incur";
11421
+ var loginOptions = z.object({
11422
+ clientName: z.string().default("Link CLI").describe(
11423
+ "Agent or app name shown in the Link app when approving the device connection"
11424
+ )
11425
+ });
11426
+ var statusOptions = z.object({
11427
+ interval: z.coerce.number().default(0).describe(
11428
+ "Poll interval in seconds. When > 0, polls until authenticated or timeout is reached, yielding status on each attempt."
11429
+ ),
11430
+ maxAttempts: z.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
11431
+ timeout: z.coerce.number().default(300).describe("Polling timeout in seconds.")
11432
+ });
11718
11433
 
11719
11434
  // 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
11435
+ import { jsx as jsx3 } from "react/jsx-runtime";
11436
+ function createAuthCli(authResource) {
11437
+ const cli2 = Cli.create("auth", {
11438
+ description: "Authentication commands"
11439
+ });
11440
+ cli2.command("login", {
11441
+ description: "Authenticate with Link",
11442
+ options: loginOptions,
11443
+ outputPolicy: "agent-only",
11444
+ async *run(c) {
11445
+ const clientName = c.options.clientName?.trim();
11446
+ if (!clientName || clientName.length === 0) {
11447
+ return c.error({
11448
+ code: "INVALID_INPUT",
11449
+ message: "client-name must be a non-empty string"
11753
11450
  });
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
- `
11451
+ }
11452
+ if (!c.agent && !c.formatExplicit) {
11453
+ return new Promise((resolve) => {
11454
+ const { waitUntilExit } = render(
11455
+ /* @__PURE__ */ jsx3(
11456
+ Login,
11457
+ {
11458
+ authResource,
11459
+ clientName,
11460
+ onComplete: () => {
11461
+ }
11462
+ }
11463
+ )
11769
11464
  );
11770
- const tokens = await authResource.pollDeviceAuth(
11771
- authRequest.device_code
11465
+ waitUntilExit().then(
11466
+ () => resolve({ authenticated: true, token_type: "Bearer" })
11467
+ );
11468
+ });
11469
+ }
11470
+ const authRequest = await authResource.initiateDeviceAuth(clientName);
11471
+ storage.setPendingDeviceAuth({
11472
+ device_code: authRequest.device_code,
11473
+ interval: authRequest.interval,
11474
+ expires_at: Date.now() + authRequest.expires_in * 1e3,
11475
+ verification_url: authRequest.verification_url_complete,
11476
+ passphrase: authRequest.user_code
11477
+ });
11478
+ yield {
11479
+ verification_url: authRequest.verification_url_complete,
11480
+ passphrase: authRequest.user_code,
11481
+ 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.",
11482
+ _next: {
11483
+ command: "auth status --interval 5 --max-attempts 60",
11484
+ poll_interval_seconds: authRequest.interval,
11485
+ until: "authenticated is true"
11486
+ }
11487
+ };
11488
+ }
11489
+ });
11490
+ cli2.command("logout", {
11491
+ description: "Log out from Link",
11492
+ outputPolicy: "agent-only",
11493
+ async run(c) {
11494
+ const auth = storage.getAuth();
11495
+ if (auth?.refresh_token) {
11496
+ try {
11497
+ await authResource.revokeToken(auth.refresh_token);
11498
+ } catch {
11499
+ }
11500
+ }
11501
+ storage.clearAuth();
11502
+ storage.clearPendingDeviceAuth();
11503
+ storage.deleteConfig();
11504
+ const result = { authenticated: false };
11505
+ if (!c.agent && !c.formatExplicit) {
11506
+ return new Promise((resolve) => {
11507
+ const { waitUntilExit } = render(
11508
+ /* @__PURE__ */ jsx3(Logout, { authResource, onComplete: () => {
11509
+ } })
11772
11510
  );
11511
+ waitUntilExit().then(() => resolve(result));
11512
+ });
11513
+ }
11514
+ return result;
11515
+ }
11516
+ });
11517
+ cli2.command("status", {
11518
+ description: "Check authentication status",
11519
+ options: statusOptions,
11520
+ outputPolicy: "agent-only",
11521
+ async *run(c) {
11522
+ const opts = c.options;
11523
+ const interval = opts.interval;
11524
+ const maxAttempts = opts.maxAttempts;
11525
+ const deadline = Date.now() + opts.timeout * 1e3;
11526
+ let attempts = 0;
11527
+ while (true) {
11528
+ const pending = storage.getPendingDeviceAuth();
11529
+ if (pending && !storage.isAuthenticated()) {
11530
+ const tokens = await authResource.pollDeviceAuth(pending.device_code);
11773
11531
  if (tokens) {
11774
11532
  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: () => {
11533
+ storage.clearPendingDeviceAuth();
11786
11534
  }
11787
11535
  }
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
11536
  const auth = storage.getAuth();
11814
11537
  if (auth) {
11815
- return {
11538
+ yield {
11816
11539
  authenticated: true,
11817
11540
  access_token: `${auth.access_token.substring(0, 20)}...`,
11818
11541
  token_type: auth.token_type,
11819
11542
  credentials_path: storage.getPath()
11820
11543
  };
11544
+ return;
11821
11545
  }
11822
- return { authenticated: false, credentials_path: storage.getPath() };
11823
- },
11824
- renderFn: () => /* @__PURE__ */ jsx4(AuthStatus, { onComplete: () => {
11825
- } })
11826
- });
11546
+ const currentPending = storage.getPendingDeviceAuth();
11547
+ const status = {
11548
+ authenticated: false,
11549
+ credentials_path: storage.getPath(),
11550
+ ...currentPending ? {
11551
+ pending: true,
11552
+ verification_url: currentPending.verification_url,
11553
+ passphrase: currentPending.passphrase
11554
+ } : {}
11555
+ };
11556
+ attempts++;
11557
+ const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
11558
+ if (shouldStop) {
11559
+ yield status;
11560
+ return;
11561
+ }
11562
+ yield status;
11563
+ await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
11564
+ }
11565
+ }
11827
11566
  });
11828
- return authCommand2;
11567
+ return cli2;
11829
11568
  }
11830
11569
 
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
- }
11570
+ // src/commands/mpp/index.tsx
11571
+ import { Cli as Cli2, z as z3 } from "incur";
11572
+ import { render as render2 } from "ink";
11837
11573
 
11838
11574
  // src/commands/mpp/decode.ts
11839
11575
  import { Challenge } from "mppx";
@@ -11863,8 +11599,7 @@ function getMethodDetails(request) {
11863
11599
  }
11864
11600
  return methodDetails;
11865
11601
  }
11866
- function decodeStripeChallenge(challengeHeader) {
11867
- const challenges = Challenge.deserializeList(challengeHeader);
11602
+ function resolveStripeChallenge(challenges) {
11868
11603
  const stripeChallenge = challenges.find(
11869
11604
  (challenge) => challenge.method === "stripe" && challenge.intent === "charge"
11870
11605
  );
@@ -11889,51 +11624,66 @@ function decodeStripeChallenge(challengeHeader) {
11889
11624
  );
11890
11625
  }
11891
11626
  return {
11892
- id: stripeChallenge.id,
11893
- realm: stripeChallenge.realm,
11627
+ challenge: stripeChallenge,
11628
+ networkId,
11629
+ request
11630
+ };
11631
+ }
11632
+ function getStripeChargeChallengeFromResponse(response) {
11633
+ return resolveStripeChallenge(Challenge.fromResponseList(response)).challenge;
11634
+ }
11635
+ function decodeStripeChallenge(challengeHeader) {
11636
+ const { challenge, networkId, request } = resolveStripeChallenge(
11637
+ Challenge.deserializeList(challengeHeader)
11638
+ );
11639
+ return {
11640
+ id: challenge.id,
11641
+ realm: challenge.realm,
11894
11642
  method: "stripe",
11895
11643
  intent: "charge",
11896
- description: stripeChallenge.description,
11897
- digest: stripeChallenge.digest,
11898
- expires: stripeChallenge.expires,
11644
+ description: challenge.description,
11645
+ digest: challenge.digest,
11646
+ expires: challenge.expires,
11899
11647
  network_id: networkId,
11900
11648
  request_json: request
11901
11649
  };
11902
11650
  }
11903
11651
 
11904
11652
  // 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";
11653
+ import { Box as Box3, Text as Text3 } from "ink";
11654
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
11907
11655
  function DecodeChallengeView({
11908
11656
  decoded
11909
11657
  }) {
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: [
11658
+ return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
11659
+ /* @__PURE__ */ jsx4(Text3, { color: "green", children: "\u2713 Stripe challenge decoded" }),
11660
+ /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
11661
+ /* @__PURE__ */ jsxs2(Text3, { children: [
11914
11662
  "ID: ",
11915
- /* @__PURE__ */ jsx5(Text4, { bold: true, children: decoded.id })
11663
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.id })
11916
11664
  ] }),
11917
- /* @__PURE__ */ jsxs3(Text4, { children: [
11665
+ /* @__PURE__ */ jsxs2(Text3, { children: [
11918
11666
  "Realm: ",
11919
- /* @__PURE__ */ jsx5(Text4, { bold: true, children: decoded.realm })
11667
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.realm })
11920
11668
  ] }),
11921
- /* @__PURE__ */ jsxs3(Text4, { children: [
11669
+ /* @__PURE__ */ jsxs2(Text3, { children: [
11922
11670
  "Network ID: ",
11923
- /* @__PURE__ */ jsx5(Text4, { bold: true, children: decoded.network_id })
11671
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.network_id })
11924
11672
  ] }),
11925
- /* @__PURE__ */ jsx5(Text4, { children: "Request JSON:" }),
11926
- /* @__PURE__ */ jsx5(Text4, { children: JSON.stringify(decoded.request_json, null, 2) })
11673
+ /* @__PURE__ */ jsx4(Text3, { children: "Request JSON:" }),
11674
+ /* @__PURE__ */ jsx4(Text3, { children: JSON.stringify(decoded.request_json, null, 2) })
11927
11675
  ] })
11928
11676
  ] });
11929
11677
  }
11930
11678
 
11931
11679
  // src/commands/mpp/pay.tsx
11932
- import { Box as Box5, Text as Text5 } from "ink";
11680
+ import { Box as Box4, Text as Text4 } from "ink";
11933
11681
  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";
11682
+ import { Credential, Method } from "mppx";
11683
+ import { Mppx, Transport } from "mppx/client";
11684
+ import { Methods as StripeMethods } from "mppx/stripe";
11685
+ import { useEffect as useEffect3, useState as useState3 } from "react";
11686
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
11937
11687
  function buildHeaders(data, headers) {
11938
11688
  const result = {};
11939
11689
  if (data !== void 0) {
@@ -11948,29 +11698,66 @@ function buildHeaders(data, headers) {
11948
11698
  }
11949
11699
  return result;
11950
11700
  }
11701
+ async function readPayResult(response, options) {
11702
+ const responseHeaders = Object.fromEntries(response.headers.entries());
11703
+ const body = await response.text();
11704
+ if (options?.failOnError && !response.ok) {
11705
+ throw new Error(
11706
+ `Payment submission failed with status ${response.status}: ${body}`
11707
+ );
11708
+ }
11709
+ return { status: response.status, headers: responseHeaders, body };
11710
+ }
11711
+ function createStripePaymentClient(spt) {
11712
+ const stripeCharge = Method.toClient(StripeMethods.charge, {
11713
+ async createCredential({ challenge }) {
11714
+ return Credential.serialize({
11715
+ challenge,
11716
+ payload: { spt }
11717
+ });
11718
+ }
11719
+ });
11720
+ return Mppx.create({
11721
+ methods: [stripeCharge],
11722
+ polyfill: false,
11723
+ transport: Transport.from({
11724
+ name: "stripe-http",
11725
+ isPaymentRequired(response) {
11726
+ return response.status === 402;
11727
+ },
11728
+ getChallenge(response) {
11729
+ return getStripeChargeChallengeFromResponse(response);
11730
+ },
11731
+ setCredential(request, credential) {
11732
+ const nextHeaders = new Headers(request.headers);
11733
+ nextHeaders.set("Authorization", credential);
11734
+ return { ...request, headers: nextHeaders };
11735
+ }
11736
+ })
11737
+ });
11738
+ }
11951
11739
  async function runMppPay(url, spendRequestId, method, data, headers, repository) {
11952
11740
  const spendRequest = await repository.getSpendRequest(spendRequestId, {
11953
11741
  include: ["shared_payment_token"]
11954
11742
  });
11955
11743
  if (!spendRequest) {
11956
- outputError(`Spend request ${spendRequestId} not found`);
11744
+ throw new Error(`Spend request ${spendRequestId} not found`);
11957
11745
  }
11958
11746
  if (spendRequest.credential_type !== "shared_payment_token") {
11959
11747
  const type = spendRequest.credential_type ?? "card";
11960
- outputError(
11748
+ throw new Error(
11961
11749
  `Spend request ${spendRequestId} must have credential_type 'shared_payment_token' (current: '${type}')`
11962
11750
  );
11963
11751
  }
11964
11752
  if (spendRequest.status !== "approved") {
11965
- outputError(
11753
+ throw new Error(
11966
11754
  `Spend request must be approved (current status: ${spendRequest.status})`
11967
11755
  );
11968
11756
  }
11969
- const sptObj = spendRequest.shared_payment_token;
11970
- if (!sptObj) {
11971
- outputError("Spend request does not have a shared payment token");
11757
+ if (!spendRequest.shared_payment_token) {
11758
+ throw new Error("Spend request does not have a shared payment token");
11972
11759
  }
11973
- const spt = sptObj.id;
11760
+ const spt = spendRequest.shared_payment_token.id;
11974
11761
  const httpMethod = method ?? (data !== void 0 ? "POST" : "GET");
11975
11762
  const requestHeaders = buildHeaders(data, headers);
11976
11763
  const initialResponse = await fetch(url, {
@@ -11979,30 +11766,9 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
11979
11766
  headers: requestHeaders
11980
11767
  });
11981
11768
  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 };
11769
+ return readPayResult(initialResponse);
11987
11770
  }
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);
11771
+ const authHeader = await createStripePaymentClient(spt).createCredential(initialResponse);
12006
11772
  const retryResponse = await fetch(url, {
12007
11773
  method: httpMethod,
12008
11774
  body: data,
@@ -12011,15 +11777,7 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
12011
11777
  Authorization: authHeader
12012
11778
  }
12013
11779
  });
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 };
11780
+ return readPayResult(retryResponse, { failOnError: true });
12023
11781
  }
12024
11782
  function MppPay({
12025
11783
  url,
@@ -12030,10 +11788,10 @@ function MppPay({
12030
11788
  repository,
12031
11789
  onComplete
12032
11790
  }) {
12033
- const [step, setStep] = useState4("retrieving");
12034
- const [result, setResult] = useState4(null);
12035
- const [error, setError] = useState4(null);
12036
- useEffect4(() => {
11791
+ const [step, setStep] = useState3("retrieving");
11792
+ const [result, setResult] = useState3(null);
11793
+ const [error, setError] = useState3(null);
11794
+ useEffect3(() => {
12037
11795
  (async () => {
12038
11796
  try {
12039
11797
  setStep("retrieving");
@@ -12067,38 +11825,15 @@ function MppPay({
12067
11825
  headers: requestHeaders
12068
11826
  });
12069
11827
  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
- });
11828
+ setResult(await readPayResult(initialResponse));
12079
11829
  setStep("done");
12080
11830
  onComplete();
12081
11831
  return;
12082
11832
  }
12083
11833
  setStep("signing");
12084
- const decoded = decodeStripeChallenge(
12085
- initialResponse.headers.get("www-authenticate") ?? ""
11834
+ const authHeader = await createStripePaymentClient(spt).createCredential(
11835
+ initialResponse
12086
11836
  );
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
11837
  setStep("submitting");
12103
11838
  const retryResponse = await fetch(url, {
12104
11839
  method: httpMethod,
@@ -12108,15 +11843,7 @@ function MppPay({
12108
11843
  Authorization: authHeader
12109
11844
  }
12110
11845
  });
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
- });
11846
+ setResult(await readPayResult(retryResponse, { failOnError: true }));
12120
11847
  setStep("done");
12121
11848
  onComplete();
12122
11849
  } catch (err) {
@@ -12133,191 +11860,143 @@ function MppPay({
12133
11860
  done: "Done"
12134
11861
  };
12135
11862
  if (error) {
12136
- return /* @__PURE__ */ jsxs4(Text5, { color: "red", children: [
11863
+ return /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
12137
11864
  "Error: ",
12138
11865
  error
12139
11866
  ] });
12140
11867
  }
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" }),
11868
+ return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
11869
+ step !== "done" && /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsxs3(Text4, { color: "cyan", children: [
11870
+ /* @__PURE__ */ jsx5(Spinner2, { type: "dots" }),
12144
11871
  " ",
12145
11872
  stepLabels[step],
12146
11873
  "..."
12147
11874
  ] }) }),
12148
- result && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
12149
- /* @__PURE__ */ jsxs4(Text5, { color: "green", children: [
11875
+ result && /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
11876
+ /* @__PURE__ */ jsxs3(Text4, { color: "green", children: [
12150
11877
  "HTTP ",
12151
11878
  result.status
12152
11879
  ] }),
12153
- /* @__PURE__ */ jsx6(Text5, { children: result.body })
11880
+ /* @__PURE__ */ jsx5(Text4, { children: result.body })
12154
11881
  ] })
12155
11882
  ] });
12156
11883
  }
12157
11884
 
12158
11885
  // 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
- };
11886
+ import { z as z2 } from "incur";
11887
+ var payOptions = z2.object({
11888
+ spendRequestId: z2.string().describe(
11889
+ 'Approved spend request ID with credential_type "shared_payment_token"'
11890
+ ),
11891
+ method: z2.string().optional().describe("HTTP method (default: GET, or POST if --data is provided)"),
11892
+ data: z2.string().optional().describe("Request body (implies POST if --method is not set)"),
11893
+ header: z2.array(z2.string()).default([]).describe('Request header in "Name: Value" format (repeatable)')
11894
+ });
11895
+ var decodeOptions = z2.object({
11896
+ challenge: z2.string().describe(
11897
+ "Raw WWW-Authenticate header value; may include multiple payment challenges"
11898
+ )
11899
+ });
12222
11900
 
12223
11901
  // 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: () => {
11902
+ import { jsx as jsx6 } from "react/jsx-runtime";
11903
+ function createMppCli(repository) {
11904
+ const cli2 = Cli2.create("mpp", {
11905
+ description: "Machine payment protocol (MPP) commands"
11906
+ });
11907
+ cli2.command("pay", {
11908
+ description: "Complete a machine payment protocol (MPP) payment using an approved spend request",
11909
+ args: z3.object({
11910
+ url: z3.string().describe("URL to pay")
11911
+ }),
11912
+ options: payOptions,
11913
+ alias: { method: "X", data: "d", header: "H" },
11914
+ outputPolicy: "agent-only",
11915
+ async run(c) {
11916
+ if (!storage.isAuthenticated()) {
11917
+ return c.error({
11918
+ code: "NOT_AUTHENTICATED",
11919
+ message: 'Not authenticated. Run "link-cli auth login" first.',
11920
+ cta: {
11921
+ commands: [
11922
+ { command: "auth login", description: "Log in to Link" }
11923
+ ]
12281
11924
  }
12282
- }
12283
- )
12284
- });
11925
+ });
11926
+ }
11927
+ const url = c.args.url;
11928
+ const opts = c.options;
11929
+ const method = opts.method;
11930
+ const data = opts.data;
11931
+ const headers = opts.header?.length ? opts.header : void 0;
11932
+ if (!c.agent && !c.formatExplicit) {
11933
+ return new Promise((resolve) => {
11934
+ const { waitUntilExit } = render2(
11935
+ /* @__PURE__ */ jsx6(
11936
+ MppPay,
11937
+ {
11938
+ url,
11939
+ spendRequestId: opts.spendRequestId,
11940
+ method,
11941
+ data,
11942
+ headers,
11943
+ repository,
11944
+ onComplete: () => {
11945
+ }
11946
+ }
11947
+ )
11948
+ );
11949
+ waitUntilExit().then(async () => {
11950
+ resolve(
11951
+ await runMppPay(
11952
+ url,
11953
+ opts.spendRequestId,
11954
+ method,
11955
+ data,
11956
+ headers,
11957
+ repository
11958
+ )
11959
+ );
11960
+ });
11961
+ });
11962
+ }
11963
+ return runMppPay(
11964
+ url,
11965
+ opts.spendRequestId,
11966
+ method,
11967
+ data,
11968
+ headers,
11969
+ repository
11970
+ );
11971
+ }
12285
11972
  });
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
- });
11973
+ cli2.command("decode", {
11974
+ description: "Decode a stripe WWW-Authenticate challenge and extract network_id",
11975
+ options: decodeOptions,
11976
+ outputPolicy: "agent-only",
11977
+ async run(c) {
11978
+ const decoded = decodeStripeChallenge(c.options.challenge);
11979
+ if (!c.agent && !c.formatExplicit) {
11980
+ return new Promise((resolve) => {
11981
+ const { waitUntilExit } = render2(
11982
+ /* @__PURE__ */ jsx6(DecodeChallengeView, { decoded })
11983
+ );
11984
+ waitUntilExit().then(() => resolve(decoded));
11985
+ });
11986
+ }
11987
+ return decoded;
11988
+ }
12314
11989
  });
12315
- return mppCommand2;
11990
+ return cli2;
12316
11991
  }
12317
11992
 
11993
+ // src/commands/payment-methods/index.tsx
11994
+ import { Cli as Cli3 } from "incur";
11995
+ import { render as render3 } from "ink";
11996
+
12318
11997
  // 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";
11998
+ import { Box as Box5, Text as Text5, useApp, useInput as useInput2 } from "ink";
11999
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
12321
12000
  var WALLET_URL = "https://app.link.com/wallet";
12322
12001
  var AddPaymentMethod = () => {
12323
12002
  const { exit } = useApp();
@@ -12327,10 +12006,10 @@ var AddPaymentMethod = () => {
12327
12006
  exit();
12328
12007
  }
12329
12008
  });
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,
12009
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, children: [
12010
+ /* @__PURE__ */ jsx7(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text5, { bold: true, children: "Add Payment Method" }) }),
12011
+ /* @__PURE__ */ jsxs4(
12012
+ Box5,
12334
12013
  {
12335
12014
  flexDirection: "column",
12336
12015
  borderStyle: "round",
@@ -12338,12 +12017,12 @@ var AddPaymentMethod = () => {
12338
12017
  paddingX: 2,
12339
12018
  paddingY: 1,
12340
12019
  children: [
12341
- /* @__PURE__ */ jsxs5(Text6, { children: [
12020
+ /* @__PURE__ */ jsxs4(Text5, { children: [
12342
12021
  "Open:",
12343
12022
  " ",
12344
- /* @__PURE__ */ jsx8(Text6, { bold: true, color: "cyan", children: WALLET_URL })
12023
+ /* @__PURE__ */ jsx7(Text5, { bold: true, color: "cyan", children: WALLET_URL })
12345
12024
  ] }),
12346
- /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "Press Enter to open in browser" })
12025
+ /* @__PURE__ */ jsx7(Text5, { dimColor: true, children: "Press Enter to open in browser" })
12347
12026
  ]
12348
12027
  }
12349
12028
  )
@@ -12351,20 +12030,20 @@ var AddPaymentMethod = () => {
12351
12030
  };
12352
12031
 
12353
12032
  // src/commands/payment-methods/list.tsx
12354
- import { Box as Box7, Text as Text7 } from "ink";
12033
+ import { Box as Box6, Text as Text6 } from "ink";
12355
12034
  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";
12035
+ import { useEffect as useEffect4, useState as useState4 } from "react";
12036
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
12358
12037
  var PaymentMethodsList = ({
12359
12038
  resource,
12360
12039
  onComplete
12361
12040
  }) => {
12362
- const [status, setStatus] = useState5(
12041
+ const [status, setStatus] = useState4(
12363
12042
  "loading"
12364
12043
  );
12365
- const [methods, setMethods] = useState5([]);
12366
- const [error, setError] = useState5("");
12367
- useEffect5(() => {
12044
+ const [methods, setMethods] = useState4([]);
12045
+ const [error, setError] = useState4("");
12046
+ useEffect4(() => {
12368
12047
  const fetch2 = async () => {
12369
12048
  try {
12370
12049
  const result = await resource.listPaymentMethods();
@@ -12380,168 +12059,167 @@ var PaymentMethodsList = ({
12380
12059
  fetch2();
12381
12060
  }, [resource, onComplete]);
12382
12061
  if (status === "loading") {
12383
- return /* @__PURE__ */ jsx9(Box7, { children: /* @__PURE__ */ jsxs6(Text7, { color: "cyan", children: [
12384
- /* @__PURE__ */ jsx9(Spinner3, { type: "dots" }),
12062
+ return /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
12063
+ /* @__PURE__ */ jsx8(Spinner3, { type: "dots" }),
12385
12064
  " Loading payment methods..."
12386
12065
  ] }) });
12387
12066
  }
12388
12067
  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 })
12068
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12069
+ /* @__PURE__ */ jsx8(Text6, { color: "red", children: "\u2717 Failed to load payment methods" }),
12070
+ /* @__PURE__ */ jsx8(Text6, { color: "red", children: error })
12392
12071
  ] });
12393
12072
  }
12394
12073
  if (methods.length === 0) {
12395
- return /* @__PURE__ */ jsx9(Box7, { children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "No payment methods found" }) });
12074
+ return /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No payment methods found" }) });
12396
12075
  }
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) => {
12076
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
12077
+ /* @__PURE__ */ jsx8(Text6, { bold: true, children: "Payment Methods" }),
12078
+ /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginTop: 1, children: methods.map((pm) => {
12400
12079
  const label = pm.card_details?.brand ?? pm.bank_account_details?.bank_name ?? "Bank account";
12401
12080
  const last4 = pm.card_details?.last4 ?? pm.bank_account_details?.last4;
12402
12081
  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 }),
12082
+ return /* @__PURE__ */ jsx8(Box6, { paddingX: 2, children: /* @__PURE__ */ jsxs5(Text6, { children: [
12083
+ /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: pm.id }),
12405
12084
  " ",
12406
12085
  label,
12407
12086
  " ****",
12408
12087
  last4,
12409
12088
  suffix ? ` ${suffix}` : "",
12410
- pm.is_default ? /* @__PURE__ */ jsx9(Text7, { color: "green", children: " (default)" }) : ""
12089
+ pm.is_default ? /* @__PURE__ */ jsx8(Text6, { color: "green", children: " (default)" }) : ""
12411
12090
  ] }) }, pm.id);
12412
12091
  }) })
12413
12092
  ] });
12414
12093
  };
12415
12094
 
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
12095
  // 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
- });
12096
+ import { jsx as jsx9 } from "react/jsx-runtime";
12097
+ function createPaymentMethodsCli(createResource) {
12098
+ const cli2 = Cli3.create("payment-methods", {
12099
+ description: "Payment methods management commands"
12459
12100
  });
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();
12101
+ cli2.command("list", {
12102
+ description: "List all payment methods on your account",
12103
+ outputPolicy: "agent-only",
12104
+ async run(c) {
12105
+ if (!storage.isAuthenticated()) {
12106
+ return c.error({
12107
+ code: "NOT_AUTHENTICATED",
12108
+ message: 'Not authenticated. Run "link-cli auth login" first.',
12109
+ cta: {
12110
+ commands: [
12111
+ { command: "auth login", description: "Log in to Link" }
12112
+ ]
12113
+ }
12114
+ });
12115
+ }
12116
+ const resource = createResource();
12117
+ if (!c.agent && !c.formatExplicit) {
12118
+ return new Promise((resolve) => {
12119
+ const { waitUntilExit } = render3(
12120
+ /* @__PURE__ */ jsx9(PaymentMethodsList, { resource, onComplete: () => {
12121
+ } })
12122
+ );
12123
+ waitUntilExit().then(async () => {
12124
+ resolve(await resource.listPaymentMethods());
12125
+ });
12126
+ });
12127
+ }
12128
+ return resource.listPaymentMethods();
12471
12129
  }
12472
12130
  });
12473
- return paymentMethodsCommand2;
12474
- }
12475
-
12476
- // src/commands/skill/index.ts
12477
- import { existsSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
12478
- import { join } from "path";
12479
- function registerSkillCommand(program2) {
12480
- return program2.command("skill").description("Output the Link CLI skill file").option("--install", "Install the skill file into .claude or .agents").action((options) => {
12481
- 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## 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\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\nImportant: use the --json method to create the request.\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:** The approved spend request includes a `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. If you need to fetch them again, run `link-cli spend-request retrieve <id> --output-json` and use the returned `card` field.\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- NEVER expose payment credentials (card numbers, SPTs) outside of a secure checkout form or the 402 payment flow \u2014 logging them, passing them to other tools, or including them in summaries creates unnecessary exposure vectors.\n- DO NOT use playwright or other automated browsers to authenticate with Link or approve a request on behalf of the user.\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';
12482
- try {
12483
- const version = `${"0.1.0"}+${"2"}`;
12484
- 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## 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\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\nImportant: use the --json method to create the request.\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:** The approved spend request includes a `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. If you need to fetch them again, run `link-cli spend-request retrieve <id> --output-json` and use the returned `card` field.\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- NEVER expose payment credentials (card numbers, SPTs) outside of a secure checkout form or the 402 payment flow \u2014 logging them, passing them to other tools, or including them in summaries creates unnecessary exposure vectors.\n- DO NOT use playwright or other automated browsers to authenticate with Link or approve a request on behalf of the user.\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(
12485
- "---\n",
12486
- `---
12487
- cli_version: "${version}"
12488
- `
12489
- );
12490
- } catch {
12491
- process.stderr.write(
12492
- "Warning: could not resolve cli_version \u2014 skill installed without version\n"
12493
- );
12494
- }
12495
- if (options.install) {
12496
- const baseDir = existsSync(join(process.cwd(), ".claude")) ? ".claude" : ".agents";
12497
- const destDir = join(
12498
- process.cwd(),
12499
- baseDir,
12500
- "skills",
12501
- "create-payment-credential"
12502
- );
12503
- const destPath = join(destDir, "SKILL.md");
12504
- mkdirSync(destDir, { recursive: true });
12505
- writeFileSync2(destPath, content);
12506
- process.stdout.write(`Skill installed to ${destPath}
12507
- `);
12508
- } else {
12509
- process.stdout.write(content);
12131
+ cli2.command("add", {
12132
+ description: "Open the Link wallet to add a new payment method",
12133
+ outputPolicy: "agent-only",
12134
+ async run(c) {
12135
+ if (!storage.isAuthenticated()) {
12136
+ return c.error({
12137
+ code: "NOT_AUTHENTICATED",
12138
+ message: 'Not authenticated. Run "link-cli auth login" first.',
12139
+ cta: {
12140
+ commands: [
12141
+ { command: "auth login", description: "Log in to Link" }
12142
+ ]
12143
+ }
12144
+ });
12145
+ }
12146
+ if (!c.agent && !c.formatExplicit) {
12147
+ return new Promise((resolve) => {
12148
+ const { waitUntilExit } = render3(/* @__PURE__ */ jsx9(AddPaymentMethod, {}));
12149
+ waitUntilExit().then(() => resolve({ url: WALLET_URL }));
12150
+ });
12151
+ }
12152
+ return { url: WALLET_URL };
12510
12153
  }
12511
12154
  });
12155
+ return cli2;
12512
12156
  }
12513
12157
 
12514
- // src/utils/poll-until-approved.ts
12515
- function pollUntilApproved(repository, id, options = {}) {
12516
- const pollIntervalMs = options.pollIntervalMs ?? 2e3;
12517
- const timeoutMs = options.timeoutMs ?? 3e5;
12518
- const startTime = Date.now();
12519
- const poll = async () => {
12520
- const elapsed = Date.now() - startTime;
12521
- if (elapsed > timeoutMs) {
12522
- throw new Error("Approval polling timed out");
12523
- }
12524
- const request = await repository.getSpendRequest(id);
12525
- if (!request) {
12526
- throw new Error(`Spend request ${id} not found`);
12527
- }
12528
- if (request.status !== "created" && request.status !== "pending_approval") {
12529
- return request;
12158
+ // src/commands/spend-request/index.tsx
12159
+ import { Cli as Cli4, z as z6 } from "incur";
12160
+ import { render as render4 } from "ink";
12161
+
12162
+ // src/utils/line-item-parser.ts
12163
+ import { z as z4 } from "zod";
12164
+ var LineItemSchema = z4.object({
12165
+ name: z4.string(),
12166
+ url: z4.string().optional(),
12167
+ image_url: z4.string().optional(),
12168
+ description: z4.string().optional(),
12169
+ sku: z4.string().optional(),
12170
+ quantity: z4.coerce.number().optional(),
12171
+ unit_amount: z4.coerce.number().optional(),
12172
+ product_url: z4.string().optional()
12173
+ }).strict();
12174
+ var TotalSchema = z4.object({
12175
+ type: z4.string(),
12176
+ display_text: z4.string(),
12177
+ amount: z4.coerce.number()
12178
+ }).strict();
12179
+ function parseKvString(raw) {
12180
+ const result = {};
12181
+ for (const pair of raw.split(",")) {
12182
+ const idx = pair.indexOf(":");
12183
+ if (idx === -1) {
12184
+ throw new Error(`Invalid field (missing ':'): ${pair}`);
12530
12185
  }
12531
- options.onProgress?.(Math.floor(elapsed / 1e3));
12532
- await new Promise((r) => setTimeout(r, pollIntervalMs));
12533
- return poll();
12534
- };
12535
- return poll();
12186
+ result[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
12187
+ }
12188
+ return result;
12189
+ }
12190
+ function formatZodError(err, prefix) {
12191
+ const messages = err.issues.map((issue) => {
12192
+ const key = issue.path[0]?.toString();
12193
+ return key ? `${prefix} ${key}: ${issue.message}` : `${prefix}: ${issue.message}`;
12194
+ });
12195
+ return new Error(messages.join("\n"));
12196
+ }
12197
+ function parseLineItemFlag(raw) {
12198
+ const obj = parseKvString(raw);
12199
+ try {
12200
+ return LineItemSchema.parse(obj);
12201
+ } catch (err) {
12202
+ if (err instanceof z4.ZodError) throw formatZodError(err, "Line item");
12203
+ throw err;
12204
+ }
12205
+ }
12206
+ function parseTotalFlag(raw) {
12207
+ const obj = parseKvString(raw);
12208
+ try {
12209
+ return TotalSchema.parse(obj);
12210
+ } catch (err) {
12211
+ if (err instanceof z4.ZodError) throw formatZodError(err, "Total");
12212
+ throw err;
12213
+ }
12536
12214
  }
12537
12215
 
12538
12216
  // src/commands/spend-request/create.tsx
12539
- import { Box as Box10, Text as Text10 } from "ink";
12217
+ import { Box as Box9, Text as Text9 } from "ink";
12540
12218
  import Spinner5 from "ink-spinner";
12541
- import { useCallback, useEffect as useEffect7, useState as useState6 } from "react";
12219
+ import { useCallback, useEffect as useEffect6, useState as useState5 } from "react";
12542
12220
 
12543
12221
  // src/commands/spend-request/app-download-qr-codes.tsx
12544
- import { Box as Box8, Text as Text8 } from "ink";
12222
+ import { Box as Box7, Text as Text7 } from "ink";
12545
12223
  import { useMemo } from "react";
12546
12224
 
12547
12225
  // src/utils/render-qr-matrix.ts
@@ -12578,32 +12256,32 @@ function renderQrMatrix(url) {
12578
12256
  }
12579
12257
 
12580
12258
  // src/commands/spend-request/app-download-qr-codes.tsx
12581
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
12259
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
12582
12260
  var DOWNLOAD_URL = "https://link.com/download";
12583
12261
  var AppDownloadQrCodes = () => {
12584
12262
  const qrLines = useMemo(() => renderQrMatrix(DOWNLOAD_URL), []);
12585
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", marginTop: 1, children: [
12586
- /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "New! Get the Link app to approve spend requests easily" }),
12587
- /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
12263
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
12264
+ /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: "New! Get the Link app to approve spend requests easily" }),
12265
+ /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
12588
12266
  qrLines.map((line, i) => (
12589
12267
  // biome-ignore lint/suspicious/noArrayIndexKey: stable static array
12590
- /* @__PURE__ */ jsx11(Text8, { children: line }, i)
12268
+ /* @__PURE__ */ jsx10(Text7, { children: line }, i)
12591
12269
  )),
12592
- /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: DOWNLOAD_URL })
12270
+ /* @__PURE__ */ jsx10(Text7, { dimColor: true, children: DOWNLOAD_URL })
12593
12271
  ] })
12594
12272
  ] });
12595
12273
  };
12596
12274
 
12597
12275
  // src/commands/spend-request/approval-waiting-view.tsx
12598
- import { Box as Box9, Text as Text9 } from "ink";
12276
+ import { Box as Box8, Text as Text8 } from "ink";
12599
12277
  import Spinner4 from "ink-spinner";
12600
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
12278
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
12601
12279
  var ApprovalWaitingView = ({
12602
12280
  status,
12603
12281
  approvalUrl
12604
- }) => /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingY: 1, children: [
12605
- /* @__PURE__ */ jsxs8(
12606
- Box9,
12282
+ }) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingY: 1, children: [
12283
+ /* @__PURE__ */ jsxs7(
12284
+ Box8,
12607
12285
  {
12608
12286
  flexDirection: "column",
12609
12287
  borderStyle: "round",
@@ -12611,25 +12289,51 @@ var ApprovalWaitingView = ({
12611
12289
  paddingX: 2,
12612
12290
  paddingY: 1,
12613
12291
  children: [
12614
- /* @__PURE__ */ jsxs8(Text9, { children: [
12292
+ /* @__PURE__ */ jsxs7(Text8, { children: [
12615
12293
  "Approve at:",
12616
12294
  " ",
12617
- /* @__PURE__ */ jsx12(Text9, { bold: true, color: "cyan", children: approvalUrl })
12295
+ /* @__PURE__ */ jsx11(Text8, { bold: true, color: "cyan", children: approvalUrl })
12618
12296
  ] }),
12619
- /* @__PURE__ */ jsx12(Text9, { dimColor: true, children: "Press Enter to open in browser" })
12297
+ /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Press Enter to open in browser" })
12620
12298
  ]
12621
12299
  }
12622
12300
  ),
12623
- /* @__PURE__ */ jsx12(AppDownloadQrCodes, {}),
12624
- /* @__PURE__ */ jsx12(Box9, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
12625
- /* @__PURE__ */ jsx12(Spinner4, { type: "dots" }),
12301
+ /* @__PURE__ */ jsx11(AppDownloadQrCodes, {}),
12302
+ /* @__PURE__ */ jsx11(Box8, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs7(Text8, { color: "cyan", children: [
12303
+ /* @__PURE__ */ jsx11(Spinner4, { type: "dots" }),
12626
12304
  " Waiting for approval..."
12627
- ] }) : /* @__PURE__ */ jsx12(Text9, { dimColor: true, children: "Waiting..." }) })
12305
+ ] }) : /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Waiting..." }) })
12628
12306
  ] });
12629
12307
 
12630
12308
  // src/commands/spend-request/use-approval-polling.ts
12631
12309
  import { useInput as useInput3 } from "ink";
12632
- import { useEffect as useEffect6 } from "react";
12310
+ import { useEffect as useEffect5 } from "react";
12311
+
12312
+ // src/utils/poll-until-approved.ts
12313
+ function pollUntilApproved(repository, id, options = {}) {
12314
+ const pollIntervalMs = options.pollIntervalMs ?? 2e3;
12315
+ const timeoutMs = options.timeoutMs ?? 3e5;
12316
+ const startTime = Date.now();
12317
+ const poll = async () => {
12318
+ const elapsed = Date.now() - startTime;
12319
+ if (elapsed > timeoutMs) {
12320
+ throw new Error("Approval polling timed out");
12321
+ }
12322
+ const request = await repository.getSpendRequest(id);
12323
+ if (!request) {
12324
+ throw new Error(`Spend request ${id} not found`);
12325
+ }
12326
+ if (request.status !== "created" && request.status !== "pending_approval") {
12327
+ return request;
12328
+ }
12329
+ options.onProgress?.(Math.floor(elapsed / 1e3));
12330
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
12331
+ return poll();
12332
+ };
12333
+ return poll();
12334
+ }
12335
+
12336
+ // src/commands/spend-request/use-approval-polling.ts
12633
12337
  function useApprovalPolling({
12634
12338
  status,
12635
12339
  setStatus,
@@ -12647,12 +12351,12 @@ function useApprovalPolling({
12647
12351
  },
12648
12352
  { isActive: isWaiting }
12649
12353
  );
12650
- useEffect6(() => {
12354
+ useEffect5(() => {
12651
12355
  if (status !== "waiting") return;
12652
12356
  const timeout = setTimeout(() => setStatus("polling"), 1e3);
12653
12357
  return () => clearTimeout(timeout);
12654
12358
  }, [status, setStatus]);
12655
- useEffect6(() => {
12359
+ useEffect5(() => {
12656
12360
  if (status !== "polling" || !requestId) return;
12657
12361
  let cancelled = false;
12658
12362
  const poll = async () => {
@@ -12686,16 +12390,16 @@ function useApprovalPolling({
12686
12390
  }
12687
12391
 
12688
12392
  // src/commands/spend-request/create.tsx
12689
- import { Fragment, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
12393
+ import { Fragment, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
12690
12394
  var CreateSpendRequest = ({
12691
12395
  repository,
12692
12396
  params,
12693
12397
  requestApproval = false,
12694
12398
  onComplete
12695
12399
  }) => {
12696
- const [status, setStatus] = useState6("creating");
12697
- const [request, setRequest] = useState6(null);
12698
- const [error, setError] = useState6("");
12400
+ const [status, setStatus] = useState5("creating");
12401
+ const [request, setRequest] = useState5(null);
12402
+ const [error, setError] = useState5("");
12699
12403
  const approvalUrl = request?.approval_url ?? "";
12700
12404
  const onSuccess = useCallback(
12701
12405
  (result) => setRequest(result),
@@ -12712,7 +12416,7 @@ var CreateSpendRequest = ({
12712
12416
  onSuccess,
12713
12417
  onError
12714
12418
  });
12715
- useEffect7(() => {
12419
+ useEffect6(() => {
12716
12420
  const create = async () => {
12717
12421
  try {
12718
12422
  const result = await repository.createSpendRequest(params);
@@ -12732,57 +12436,57 @@ var CreateSpendRequest = ({
12732
12436
  create();
12733
12437
  }, [repository, params, requestApproval, onComplete]);
12734
12438
  if (status === "creating") {
12735
- return /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
12736
- /* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
12439
+ return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
12440
+ /* @__PURE__ */ jsx12(Spinner5, { type: "dots" }),
12737
12441
  " Creating spend request..."
12738
12442
  ] }) });
12739
12443
  }
12740
12444
  if (status === "error") {
12741
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12742
- /* @__PURE__ */ jsx13(Text10, { color: "red", children: "\u2717 Failed to create spend request" }),
12743
- /* @__PURE__ */ jsx13(Text10, { color: "red", children: error })
12445
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
12446
+ /* @__PURE__ */ jsx12(Text9, { color: "red", children: "\u2717 Failed to create spend request" }),
12447
+ /* @__PURE__ */ jsx12(Text9, { color: "red", children: error })
12744
12448
  ] });
12745
12449
  }
12746
12450
  if (status === "success") {
12747
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12748
- /* @__PURE__ */ jsx13(Text10, { color: "green", children: "\u2713 Spend request created" }),
12749
- /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12750
- /* @__PURE__ */ jsxs9(Text10, { children: [
12451
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
12452
+ /* @__PURE__ */ jsx12(Text9, { color: "green", children: "\u2713 Spend request created" }),
12453
+ /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12454
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12751
12455
  "ID: ",
12752
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.id })
12456
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id })
12753
12457
  ] }),
12754
- /* @__PURE__ */ jsxs9(Text10, { children: [
12458
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12755
12459
  "Status: ",
12756
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.status })
12460
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.status })
12757
12461
  ] }),
12758
- /* @__PURE__ */ jsxs9(Text10, { children: [
12462
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12759
12463
  "Amount:",
12760
12464
  " ",
12761
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: (() => {
12465
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: (() => {
12762
12466
  const t = request?.totals.find((t2) => t2.type === "total");
12763
12467
  return t ? String(t.amount) : "N/A";
12764
12468
  })() })
12765
12469
  ] }),
12766
- /* @__PURE__ */ jsxs9(Text10, { children: [
12470
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12767
12471
  "Merchant: ",
12768
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.merchant_name })
12472
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.merchant_name })
12769
12473
  ] }),
12770
- /* @__PURE__ */ jsxs9(Text10, { children: [
12474
+ /* @__PURE__ */ jsxs8(Text9, { children: [
12771
12475
  "Line Items:",
12772
12476
  " ",
12773
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
12477
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
12774
12478
  ] })
12775
12479
  ] }),
12776
- /* @__PURE__ */ jsx13(AppDownloadQrCodes, {})
12480
+ /* @__PURE__ */ jsx12(AppDownloadQrCodes, {})
12777
12481
  ] });
12778
12482
  }
12779
- return /* @__PURE__ */ jsxs9(Fragment, { children: [
12780
- /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "green", children: [
12483
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
12484
+ /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "green", children: [
12781
12485
  "\u2713 Spend request created (ID: ",
12782
- /* @__PURE__ */ jsx13(Text10, { bold: true, children: request?.id }),
12486
+ /* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id }),
12783
12487
  ")"
12784
12488
  ] }) }),
12785
- /* @__PURE__ */ jsx13(
12489
+ /* @__PURE__ */ jsx12(
12786
12490
  ApprovalWaitingView,
12787
12491
  {
12788
12492
  status,
@@ -12793,19 +12497,19 @@ var CreateSpendRequest = ({
12793
12497
  };
12794
12498
 
12795
12499
  // src/commands/spend-request/request-approval.tsx
12796
- import { Box as Box11, Text as Text11 } from "ink";
12500
+ import { Box as Box10, Text as Text10 } from "ink";
12797
12501
  import Spinner6 from "ink-spinner";
12798
- import { useCallback as useCallback2, useEffect as useEffect8, useState as useState7 } from "react";
12799
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
12502
+ import { useCallback as useCallback2, useEffect as useEffect7, useState as useState6 } from "react";
12503
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
12800
12504
  var RequestApproval = ({
12801
12505
  repository,
12802
12506
  id,
12803
12507
  onComplete
12804
12508
  }) => {
12805
- const [status, setStatus] = useState7("requesting");
12806
- const [approvalUrl, setApprovalUrl] = useState7("");
12807
- const [result, setResult] = useState7(null);
12808
- const [error, setError] = useState7("");
12509
+ const [status, setStatus] = useState6("requesting");
12510
+ const [approvalUrl, setApprovalUrl] = useState6("");
12511
+ const [result, setResult] = useState6(null);
12512
+ const [error, setError] = useState6("");
12809
12513
  const onSuccess = useCallback2((r) => setResult(r), []);
12810
12514
  const onError = useCallback2((msg) => setError(msg), []);
12811
12515
  useApprovalPolling({
@@ -12818,7 +12522,7 @@ var RequestApproval = ({
12818
12522
  onSuccess,
12819
12523
  onError
12820
12524
  });
12821
- useEffect8(() => {
12525
+ useEffect7(() => {
12822
12526
  const request = async () => {
12823
12527
  try {
12824
12528
  const res = await repository.requestApproval(id);
@@ -12832,45 +12536,45 @@ var RequestApproval = ({
12832
12536
  request();
12833
12537
  }, [repository, id]);
12834
12538
  if (status === "requesting") {
12835
- return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
12836
- /* @__PURE__ */ jsx14(Spinner6, { type: "dots" }),
12539
+ return /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
12540
+ /* @__PURE__ */ jsx13(Spinner6, { type: "dots" }),
12837
12541
  " Requesting approval..."
12838
12542
  ] }) });
12839
12543
  }
12840
12544
  if (status === "error") {
12841
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12842
- /* @__PURE__ */ jsx14(Text11, { color: "red", children: "\u2717 Failed to request approval" }),
12843
- /* @__PURE__ */ jsx14(Text11, { color: "red", children: error })
12545
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12546
+ /* @__PURE__ */ jsx13(Text10, { color: "red", children: "\u2717 Failed to request approval" }),
12547
+ /* @__PURE__ */ jsx13(Text10, { color: "red", children: error })
12844
12548
  ] });
12845
12549
  }
12846
12550
  if (status === "success") {
12847
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12848
- /* @__PURE__ */ jsx14(Text11, { color: "green", children: "\u2713 Approval completed" }),
12849
- /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12850
- /* @__PURE__ */ jsxs10(Text11, { children: [
12551
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
12552
+ /* @__PURE__ */ jsx13(Text10, { color: "green", children: "\u2713 Approval completed" }),
12553
+ /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12554
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12851
12555
  "ID: ",
12852
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: result?.id })
12556
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.id })
12853
12557
  ] }),
12854
- /* @__PURE__ */ jsxs10(Text11, { children: [
12558
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12855
12559
  "Status: ",
12856
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: result?.status })
12560
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.status })
12857
12561
  ] }),
12858
- /* @__PURE__ */ jsxs10(Text11, { children: [
12562
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12859
12563
  "Amount:",
12860
12564
  " ",
12861
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
12565
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: (() => {
12862
12566
  const t = result?.totals.find((t2) => t2.type === "total");
12863
12567
  return t ? String(t.amount) : "N/A";
12864
12568
  })() })
12865
12569
  ] }),
12866
- /* @__PURE__ */ jsxs10(Text11, { children: [
12570
+ /* @__PURE__ */ jsxs9(Text10, { children: [
12867
12571
  "Merchant: ",
12868
- /* @__PURE__ */ jsx14(Text11, { bold: true, children: result?.merchant_name })
12572
+ /* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.merchant_name })
12869
12573
  ] })
12870
12574
  ] })
12871
12575
  ] });
12872
12576
  }
12873
- return /* @__PURE__ */ jsx14(
12577
+ return /* @__PURE__ */ jsx13(
12874
12578
  ApprovalWaitingView,
12875
12579
  {
12876
12580
  status,
@@ -12880,10 +12584,10 @@ var RequestApproval = ({
12880
12584
  };
12881
12585
 
12882
12586
  // src/commands/spend-request/retrieve.tsx
12883
- import { Box as Box12, Text as Text12 } from "ink";
12587
+ import { Box as Box11, Text as Text11 } from "ink";
12884
12588
  import Spinner7 from "ink-spinner";
12885
- import { useEffect as useEffect9, useRef, useState as useState8 } from "react";
12886
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
12589
+ import { useEffect as useEffect8, useRef, useState as useState7 } from "react";
12590
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
12887
12591
  var RetrieveSpendRequest = ({
12888
12592
  repository,
12889
12593
  id,
@@ -12891,20 +12595,20 @@ var RetrieveSpendRequest = ({
12891
12595
  include,
12892
12596
  onComplete
12893
12597
  }) => {
12894
- const [phase, setPhase] = useState8("fetching");
12895
- const [request, setRequest] = useState8(null);
12896
- const [error, setError] = useState8("");
12897
- const [elapsed, setElapsed] = useState8(0);
12598
+ const [phase, setPhase] = useState7("fetching");
12599
+ const [request, setRequest] = useState7(null);
12600
+ const [error, setError] = useState7("");
12601
+ const [elapsed, setElapsed] = useState7(0);
12898
12602
  const startTimeRef = useRef(Date.now());
12899
12603
  const pollRef = useRef(null);
12900
12604
  const timerRef = useRef(null);
12901
- useEffect9(() => {
12605
+ useEffect8(() => {
12902
12606
  return () => {
12903
12607
  if (pollRef.current) clearInterval(pollRef.current);
12904
12608
  if (timerRef.current) clearInterval(timerRef.current);
12905
12609
  };
12906
12610
  }, []);
12907
- useEffect9(() => {
12611
+ useEffect8(() => {
12908
12612
  const fetch2 = async () => {
12909
12613
  try {
12910
12614
  const result = await repository.getSpendRequest(id, { include });
@@ -12933,7 +12637,7 @@ var RetrieveSpendRequest = ({
12933
12637
  };
12934
12638
  fetch2();
12935
12639
  }, [repository, id, include, onComplete]);
12936
- useEffect9(() => {
12640
+ useEffect8(() => {
12937
12641
  if (phase !== "polling") return;
12938
12642
  timerRef.current = setInterval(() => {
12939
12643
  const secs = Math.floor((Date.now() - startTimeRef.current) / 1e3);
@@ -12972,170 +12676,170 @@ var RetrieveSpendRequest = ({
12972
12676
  };
12973
12677
  }, [phase, repository, id, include, timeout, onComplete]);
12974
12678
  if (phase === "fetching") {
12975
- return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
12976
- /* @__PURE__ */ jsx15(Spinner7, { type: "dots" }),
12679
+ return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
12680
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
12977
12681
  " Retrieving spend request ",
12978
12682
  id,
12979
12683
  "..."
12980
12684
  ] }) });
12981
12685
  }
12982
12686
  if (phase === "error") {
12983
- return /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
12687
+ return /* @__PURE__ */ jsx14(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
12984
12688
  "\u2717 ",
12985
12689
  error
12986
12690
  ] }) });
12987
12691
  }
12988
12692
  if (phase === "timeout") {
12989
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12990
- /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
12693
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12694
+ /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
12991
12695
  "\u2717 Timed out waiting for approval after ",
12992
12696
  timeout,
12993
12697
  "s"
12994
12698
  ] }),
12995
- request && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12996
- /* @__PURE__ */ jsxs11(Text12, { children: [
12699
+ request && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12700
+ /* @__PURE__ */ jsxs10(Text11, { children: [
12997
12701
  "ID: ",
12998
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.id })
12702
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.id })
12999
12703
  ] }),
13000
- /* @__PURE__ */ jsxs11(Text12, { children: [
12704
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13001
12705
  "Status: ",
13002
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.status })
12706
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.status })
13003
12707
  ] })
13004
12708
  ] })
13005
12709
  ] });
13006
12710
  }
13007
12711
  if (phase === "polling") {
13008
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13009
- /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
13010
- /* @__PURE__ */ jsx15(Spinner7, { type: "dots" }),
12712
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12713
+ /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
12714
+ /* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
13011
12715
  " Awaiting approval... (",
13012
12716
  elapsed,
13013
12717
  "s elapsed)"
13014
12718
  ] }) }),
13015
- request?.approval_url && /* @__PURE__ */ jsx15(Box12, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
12719
+ request?.approval_url && /* @__PURE__ */ jsx14(Box11, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
13016
12720
  "Approval URL: ",
13017
- /* @__PURE__ */ jsx15(Text12, { color: "cyan", children: request.approval_url })
12721
+ /* @__PURE__ */ jsx14(Text11, { color: "cyan", children: request.approval_url })
13018
12722
  ] }) })
13019
12723
  ] });
13020
12724
  }
13021
12725
  if (phase === "declined") {
13022
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13023
- /* @__PURE__ */ jsx15(Text12, { color: "red", children: "\u2717 Spend request declined" }),
13024
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13025
- /* @__PURE__ */ jsxs11(Text12, { children: [
12726
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12727
+ /* @__PURE__ */ jsx14(Text11, { color: "red", children: "\u2717 Spend request declined" }),
12728
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12729
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13026
12730
  "ID: ",
13027
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
12731
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
13028
12732
  ] }),
13029
- /* @__PURE__ */ jsxs11(Text12, { children: [
12733
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13030
12734
  "Status:",
13031
12735
  " ",
13032
- /* @__PURE__ */ jsx15(Text12, { bold: true, color: "red", children: request?.status })
12736
+ /* @__PURE__ */ jsx14(Text11, { bold: true, color: "red", children: request?.status })
13033
12737
  ] }),
13034
- /* @__PURE__ */ jsxs11(Text12, { children: [
12738
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13035
12739
  "Amount:",
13036
12740
  " ",
13037
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
12741
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
13038
12742
  const t = request?.totals.find((t2) => t2.type === "total");
13039
12743
  return t ? String(t.amount) : "N/A";
13040
12744
  })() })
13041
12745
  ] }),
13042
- /* @__PURE__ */ jsxs11(Text12, { children: [
12746
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13043
12747
  "Merchant: ",
13044
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
12748
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
13045
12749
  ] })
13046
12750
  ] })
13047
12751
  ] });
13048
12752
  }
13049
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
13050
- /* @__PURE__ */ jsx15(Text12, { color: "green", children: "\u2713 Spend request approved" }),
13051
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13052
- /* @__PURE__ */ jsxs11(Text12, { children: [
12753
+ return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
12754
+ /* @__PURE__ */ jsx14(Text11, { color: "green", children: "\u2713 Spend request approved" }),
12755
+ /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12756
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13053
12757
  "ID: ",
13054
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
12758
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
13055
12759
  ] }),
13056
- /* @__PURE__ */ jsxs11(Text12, { children: [
12760
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13057
12761
  "Status:",
13058
12762
  " ",
13059
- /* @__PURE__ */ jsx15(Text12, { bold: true, color: "green", children: request?.status })
12763
+ /* @__PURE__ */ jsx14(Text11, { bold: true, color: "green", children: request?.status })
13060
12764
  ] }),
13061
- /* @__PURE__ */ jsxs11(Text12, { children: [
12765
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13062
12766
  "Amount:",
13063
12767
  " ",
13064
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
12768
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
13065
12769
  const t = request?.totals.find((t2) => t2.type === "total");
13066
12770
  return t ? String(t.amount) : "N/A";
13067
12771
  })() })
13068
12772
  ] }),
13069
- /* @__PURE__ */ jsxs11(Text12, { children: [
12773
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13070
12774
  "Merchant: ",
13071
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
12775
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
13072
12776
  ] }),
13073
- /* @__PURE__ */ jsxs11(Text12, { children: [
12777
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13074
12778
  "Line Items:",
13075
12779
  " ",
13076
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
12780
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
13077
12781
  ] }),
13078
- request?.shared_payment_token && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
13079
- /* @__PURE__ */ jsxs11(Text12, { bold: true, children: [
12782
+ request?.shared_payment_token && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
12783
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
13080
12784
  "\x1B]8;;https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens\x07",
13081
12785
  "Shared Payment Token",
13082
12786
  "\x1B]8;;\x07",
13083
12787
  ":"
13084
12788
  ] }),
13085
- /* @__PURE__ */ jsxs11(Text12, { children: [
12789
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13086
12790
  " ",
13087
12791
  "Token: ",
13088
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.shared_payment_token.id })
12792
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.shared_payment_token.id })
13089
12793
  ] })
13090
12794
  ] }),
13091
- request?.card && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
13092
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: "Card Details:" }),
13093
- /* @__PURE__ */ jsxs11(Text12, { children: [
12795
+ request?.card && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
12796
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: "Card Details:" }),
12797
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13094
12798
  " ",
13095
12799
  "Number: ",
13096
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.card.number })
12800
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.number })
13097
12801
  ] }),
13098
- /* @__PURE__ */ jsxs11(Text12, { children: [
12802
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13099
12803
  " ",
13100
12804
  "Brand: ",
13101
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.card.brand })
12805
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.brand })
13102
12806
  ] }),
13103
- /* @__PURE__ */ jsxs11(Text12, { children: [
12807
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13104
12808
  " ",
13105
12809
  "Expiry:",
13106
12810
  " ",
13107
- /* @__PURE__ */ jsxs11(Text12, { bold: true, children: [
12811
+ /* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
13108
12812
  String(request?.card.exp_month).padStart(2, "0"),
13109
12813
  "/",
13110
12814
  request?.card.exp_year
13111
12815
  ] })
13112
12816
  ] }),
13113
- request?.card.cvc && /* @__PURE__ */ jsxs11(Text12, { children: [
12817
+ request?.card.cvc && /* @__PURE__ */ jsxs10(Text11, { children: [
13114
12818
  " ",
13115
12819
  "CVC: ",
13116
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: request.card.cvc })
12820
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: request.card.cvc })
13117
12821
  ] }),
13118
- request?.card.valid_until && /* @__PURE__ */ jsxs11(Text12, { children: [
12822
+ request?.card.valid_until && /* @__PURE__ */ jsxs10(Text11, { children: [
13119
12823
  " ",
13120
12824
  "Valid Until:",
13121
12825
  " ",
13122
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: new Date(request.card.valid_until * 1e3).toISOString() })
12826
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: new Date(request.card.valid_until * 1e3).toISOString() })
13123
12827
  ] }),
13124
- request?.card.billing_address && /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, children: [
13125
- /* @__PURE__ */ jsx15(Text12, { bold: true, children: " Billing Address:" }),
13126
- /* @__PURE__ */ jsxs11(Text12, { children: [
12828
+ request?.card.billing_address && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
12829
+ /* @__PURE__ */ jsx14(Text11, { bold: true, children: " Billing Address:" }),
12830
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13127
12831
  " ",
13128
12832
  request.card.billing_address.name
13129
12833
  ] }),
13130
- /* @__PURE__ */ jsxs11(Text12, { children: [
12834
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13131
12835
  " ",
13132
12836
  request.card.billing_address.line1
13133
12837
  ] }),
13134
- request.card.billing_address.line2 && /* @__PURE__ */ jsxs11(Text12, { children: [
12838
+ request.card.billing_address.line2 && /* @__PURE__ */ jsxs10(Text11, { children: [
13135
12839
  " ",
13136
12840
  request.card.billing_address.line2
13137
12841
  ] }),
13138
- /* @__PURE__ */ jsxs11(Text12, { children: [
12842
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13139
12843
  " ",
13140
12844
  [
13141
12845
  request.card.billing_address.city,
@@ -13143,7 +12847,7 @@ var RetrieveSpendRequest = ({
13143
12847
  request.card.billing_address.postal_code
13144
12848
  ].filter(Boolean).join(", ")
13145
12849
  ] }),
13146
- /* @__PURE__ */ jsxs11(Text12, { children: [
12850
+ /* @__PURE__ */ jsxs10(Text11, { children: [
13147
12851
  " ",
13148
12852
  request.card.billing_address.country
13149
12853
  ] })
@@ -13154,226 +12858,69 @@ var RetrieveSpendRequest = ({
13154
12858
  };
13155
12859
 
13156
12860
  // src/commands/spend-request/schema.ts
13157
- import { z as z6 } from "zod";
13158
-
13159
- // src/utils/line-item-parser.ts
13160
- import { z as z5 } from "zod";
13161
- var LineItemSchema = z5.object({
13162
- name: z5.string(),
13163
- url: z5.string().optional(),
13164
- image_url: z5.string().optional(),
13165
- description: z5.string().optional(),
13166
- sku: z5.string().optional(),
13167
- quantity: z5.coerce.number().optional(),
13168
- unit_amount: z5.coerce.number().optional(),
13169
- product_url: z5.string().optional()
13170
- }).strict();
13171
- var TotalSchema = z5.object({
13172
- type: z5.string(),
13173
- display_text: z5.string(),
13174
- amount: z5.coerce.number()
13175
- }).strict();
13176
- function parseKvString(raw) {
13177
- const result = {};
13178
- for (const pair of raw.split(",")) {
13179
- const idx = pair.indexOf(":");
13180
- if (idx === -1) {
13181
- throw new Error(`Invalid field (missing ':'): ${pair}`);
13182
- }
13183
- result[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
13184
- }
13185
- return result;
13186
- }
13187
-
13188
- // src/commands/spend-request/schema.ts
13189
- var SPEND_REQUEST_OUTPUT_SCHEMA = {
13190
- id: { outputExample: '"..."', description: "Spend request ID" },
13191
- status: {
13192
- outputExample: '"created|pending_approval|approved|denied|expired|succeeded|failed"',
13193
- description: "Current status"
13194
- },
13195
- created_at: {
13196
- outputExample: '"2026-04-15T14:17:18Z"',
13197
- description: "Creation timestamp"
13198
- },
13199
- updated_at: {
13200
- outputExample: '"2026-04-15T14:17:18Z"',
13201
- description: "Last update timestamp"
13202
- },
13203
- payment_details: {
13204
- outputExample: '"csmrpd_abcde12345"',
13205
- description: "Payment method ID"
13206
- },
13207
- amount: { outputExample: "1000", description: "Amount in cents" },
13208
- merchant_name: { outputExample: '"Powdur"', description: "Merchant name" },
13209
- line_items: { outputExample: "[...]", description: "Line items" },
13210
- totals: { outputExample: "[...]", description: "Totals" },
13211
- card: {
13212
- 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}',
13213
- 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."
13214
- },
13215
- shared_payment_token: {
13216
- 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"}',
13217
- 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.'
13218
- }
13219
- };
13220
- var CREATE_INPUT_SCHEMA = {
13221
- payment_method_id: {
13222
- schema: z6.string().min(1),
13223
- flag: "--payment-method-id <id>",
13224
- description: "Payment method ID",
13225
- required: true
13226
- },
13227
- credential_type: {
13228
- schema: z6.enum(["shared_payment_token", "card"]),
13229
- flag: "--credential-type <type>",
13230
- description: "Payment credential type",
13231
- jsonDescription: '"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows \u2014 evaluate the merchant site before choosing',
13232
- defaultValue: "card",
13233
- required: true
13234
- },
13235
- network_id: {
13236
- schema: z6.string().min(1),
13237
- flag: "--network-id <id>",
13238
- description: "Network ID (required for shared_payment_token)",
13239
- jsonDescription: "Required for shared_payment_token \u2014 use `link-cli mpp decode --challenge <www-authenticate>` to validate the stripe challenge and extract this value"
13240
- },
13241
- amount: {
13242
- schema: z6.coerce.number().int().positive().max(5e4),
13243
- flag: "--amount <cents>",
13244
- description: "Amount in cents",
13245
- jsonDescription: "Total in cents, max 50000 ($500.00)",
13246
- required: true
13247
- },
13248
- currency: {
13249
- schema: z6.string().length(3),
13250
- flag: "--currency <code>",
13251
- description: "Currency code",
13252
- defaultValue: "usd"
13253
- },
13254
- merchant_name: {
13255
- schema: z6.string().min(3),
13256
- flag: "--merchant-name <name>",
13257
- description: "Merchant name",
13258
- jsonDescription: "Required for card credential type; forbidden for shared_payment_token",
13259
- alias: "-m"
13260
- },
13261
- merchant_url: {
13262
- schema: z6.url(),
13263
- flag: "--merchant-url <url>",
13264
- description: "Merchant URL",
13265
- jsonDescription: "Required for card credential type; forbidden for shared_payment_token"
13266
- },
13267
- context: {
13268
- schema: z6.string().min(100),
13269
- flag: "--context <context>",
13270
- description: "Description of what is being purchased and why",
13271
- jsonDescription: "Min 100 chars \u2014 write a full sentence describing the purchase and rationale; the user reads this when approving",
13272
- required: true
13273
- },
13274
- line_items: {
13275
- schema: z6.array(LineItemSchema),
13276
- flag: "--line-item <item>",
13277
- description: "Line item (repeatable)",
13278
- flagParser: parseKvString
13279
- },
13280
- totals: {
13281
- schema: z6.array(TotalSchema),
13282
- flag: "--total <total>",
13283
- description: "Total (repeatable)",
13284
- flagParser: parseKvString
13285
- },
13286
- request_approval: {
13287
- schema: z6.boolean(),
13288
- flag: "--request-approval",
13289
- description: "Request approval and wait for user to approve/deny",
13290
- jsonDescription: "Polls until approved/denied/expired; blocks until the user acts",
13291
- defaultValue: true
13292
- },
13293
- test: {
13294
- schema: z6.boolean(),
13295
- flag: "--test",
13296
- description: "Use test mode (creates testmode credentials from test card data)",
13297
- jsonDescription: "When true, creates testmode credentials instead of real ones \u2014 safe for development and testing",
13298
- defaultValue: false
13299
- }
13300
- };
13301
- var RETRIEVE_INPUT_SCHEMA = {
13302
- timeout: {
13303
- schema: z6.coerce.number(),
13304
- flag: "--timeout <seconds>",
13305
- description: "Polling timeout in seconds",
13306
- defaultValue: 300
13307
- },
13308
- include: {
13309
- schema: z6.array(z6.string()),
13310
- flag: "--include <value>",
13311
- description: "Include extra data (repeatable, e.g. --include card)"
13312
- }
13313
- };
13314
- var UPDATE_INPUT_SCHEMA = {
13315
- payment_method_id: {
13316
- schema: z6.string().min(1),
13317
- flag: "--payment-method-id <id>",
13318
- description: "Payment method ID",
13319
- required: true
13320
- },
13321
- amount: {
13322
- schema: z6.coerce.number().int().positive(),
13323
- flag: "--amount <cents>",
13324
- description: "Amount in cents"
13325
- },
13326
- merchant_url: {
13327
- schema: z6.string().min(1),
13328
- flag: "--merchant-url <url>",
13329
- description: "Merchant URL"
13330
- },
13331
- profile_id: {
13332
- schema: z6.string().min(1),
13333
- flag: "--profile-id <id>",
13334
- description: "Profile ID"
13335
- },
13336
- merchant_id: {
13337
- schema: z6.string().min(1),
13338
- flag: "--merchant-id <id>",
13339
- description: "Merchant ID"
13340
- },
13341
- currency: {
13342
- schema: z6.string().min(1),
13343
- flag: "--currency <code>",
13344
- description: "Currency code"
13345
- },
13346
- line_items: {
13347
- schema: z6.array(LineItemSchema),
13348
- flag: "--line-item <item>",
13349
- description: "Line item (repeatable)",
13350
- flagParser: parseKvString
13351
- },
13352
- totals: {
13353
- schema: z6.array(TotalSchema),
13354
- flag: "--total <total>",
13355
- description: "Total (repeatable)",
13356
- flagParser: parseKvString
13357
- }
13358
- };
12861
+ import { z as z5 } from "incur";
12862
+ var createOptions = z5.object({
12863
+ paymentMethodId: z5.string().describe("Payment method ID"),
12864
+ credentialType: z5.enum(["shared_payment_token", "card"]).default("card").describe(
12865
+ '"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows'
12866
+ ),
12867
+ networkId: z5.string().optional().describe(
12868
+ "Network ID (required for shared_payment_token) \u2014 use `link-cli mpp decode` to extract"
12869
+ ),
12870
+ amount: z5.coerce.number().int().positive().max(5e4).describe("Amount in cents, max 50000 ($500.00)"),
12871
+ currency: z5.string().length(3).default("usd").describe("Currency code"),
12872
+ merchantName: z5.string().optional().describe(
12873
+ "Merchant name (required for card; forbidden for shared_payment_token)"
12874
+ ),
12875
+ merchantUrl: z5.string().optional().describe(
12876
+ "Merchant URL (required for card; forbidden for shared_payment_token)"
12877
+ ),
12878
+ context: z5.string().min(100).describe(
12879
+ "Min 100 chars \u2014 describe the purchase and rationale; the user reads this when approving"
12880
+ ),
12881
+ lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
12882
+ total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)"),
12883
+ requestApproval: z5.boolean().default(true).describe("Request approval and poll until approved/denied/expired"),
12884
+ test: z5.boolean().default(false).describe(
12885
+ "Use test mode (creates testmode credentials from test card data)"
12886
+ )
12887
+ });
12888
+ var retrieveOptions = z5.object({
12889
+ timeout: z5.coerce.number().default(300).describe("Polling timeout in seconds"),
12890
+ interval: z5.coerce.number().default(0).describe(
12891
+ "Poll interval in seconds. When > 0, polls until status is terminal or timeout is reached, yielding status on each attempt."
12892
+ ),
12893
+ maxAttempts: z5.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
12894
+ include: z5.array(z5.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)")
12895
+ });
12896
+ var updateOptions = z5.object({
12897
+ paymentMethodId: z5.string().optional().describe("Payment method ID"),
12898
+ amount: z5.coerce.number().optional().describe("Amount in cents"),
12899
+ merchantUrl: z5.string().optional().describe("Merchant URL"),
12900
+ profileId: z5.string().optional().describe("Profile ID"),
12901
+ merchantId: z5.string().optional().describe("Merchant ID"),
12902
+ currency: z5.string().optional().describe("Currency code"),
12903
+ lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
12904
+ total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)")
12905
+ });
13359
12906
 
13360
12907
  // src/commands/spend-request/update.tsx
13361
- import { Box as Box13, Text as Text13 } from "ink";
12908
+ import { Box as Box12, Text as Text12 } from "ink";
13362
12909
  import Spinner8 from "ink-spinner";
13363
- import { useEffect as useEffect10, useState as useState9 } from "react";
13364
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
12910
+ import { useEffect as useEffect9, useState as useState8 } from "react";
12911
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
13365
12912
  var UpdateSpendRequest = ({
13366
12913
  repository,
13367
12914
  id,
13368
12915
  params,
13369
12916
  onComplete
13370
12917
  }) => {
13371
- const [status, setStatus] = useState9(
12918
+ const [status, setStatus] = useState8(
13372
12919
  "loading"
13373
12920
  );
13374
- const [request, setRequest] = useState9(null);
13375
- const [error, setError] = useState9("");
13376
- useEffect10(() => {
12921
+ const [request, setRequest] = useState8(null);
12922
+ const [error, setError] = useState8("");
12923
+ useEffect9(() => {
13377
12924
  const update = async () => {
13378
12925
  try {
13379
12926
  const result = await repository.updateSpendRequest(id, params);
@@ -13389,369 +12936,354 @@ var UpdateSpendRequest = ({
13389
12936
  update();
13390
12937
  }, [repository, id, params, onComplete]);
13391
12938
  if (status === "loading") {
13392
- return /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
13393
- /* @__PURE__ */ jsx16(Spinner8, { type: "dots" }),
12939
+ return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
12940
+ /* @__PURE__ */ jsx15(Spinner8, { type: "dots" }),
13394
12941
  " Updating spend request ",
13395
12942
  id,
13396
12943
  "..."
13397
12944
  ] }) });
13398
12945
  }
13399
12946
  if (status === "error") {
13400
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
13401
- /* @__PURE__ */ jsx16(Text13, { color: "red", children: "\u2717 Failed to update spend request" }),
13402
- /* @__PURE__ */ jsx16(Text13, { color: "red", children: error })
12947
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12948
+ /* @__PURE__ */ jsx15(Text12, { color: "red", children: "\u2717 Failed to update spend request" }),
12949
+ /* @__PURE__ */ jsx15(Text12, { color: "red", children: error })
13403
12950
  ] });
13404
12951
  }
13405
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
13406
- /* @__PURE__ */ jsx16(Text13, { color: "green", children: "\u2713 Spend request updated" }),
13407
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
13408
- /* @__PURE__ */ jsxs12(Text13, { children: [
12952
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
12953
+ /* @__PURE__ */ jsx15(Text12, { color: "green", children: "\u2713 Spend request updated" }),
12954
+ /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
12955
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13409
12956
  "ID: ",
13410
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.id })
12957
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
13411
12958
  ] }),
13412
- /* @__PURE__ */ jsxs12(Text13, { children: [
12959
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13413
12960
  "Status: ",
13414
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.status })
12961
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.status })
13415
12962
  ] }),
13416
- /* @__PURE__ */ jsxs12(Text13, { children: [
12963
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13417
12964
  "Amount:",
13418
12965
  " ",
13419
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: (() => {
12966
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
13420
12967
  const t = request?.totals.find((t2) => t2.type === "total");
13421
12968
  return t ? String(t.amount) : "N/A";
13422
12969
  })() })
13423
12970
  ] }),
13424
- /* @__PURE__ */ jsxs12(Text13, { children: [
12971
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13425
12972
  "Merchant: ",
13426
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.merchant_name })
12973
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
13427
12974
  ] }),
13428
- /* @__PURE__ */ jsxs12(Text13, { children: [
12975
+ /* @__PURE__ */ jsxs11(Text12, { children: [
13429
12976
  "Line Items:",
13430
12977
  " ",
13431
- /* @__PURE__ */ jsx16(Text13, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
12978
+ /* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
13432
12979
  ] })
13433
12980
  ] })
13434
12981
  ] });
13435
12982
  };
13436
12983
 
13437
12984
  // src/commands/spend-request/index.tsx
13438
- import { jsx as jsx17 } from "react/jsx-runtime";
13439
- function registerSpendRequestCommands(program2, repository) {
13440
- const spendRequestCommand2 = program2.command("spend-request").description("Spend request management commands").helpCommand(false);
13441
- const createCmd = spendRequestCommand2.command("create").description("Create a new spend request");
13442
- registerSchemaOptions(createCmd, CREATE_INPUT_SCHEMA);
13443
- createCmd.option(
13444
- "--json <json>",
13445
- `JSON input (keys: ${Object.keys(CREATE_INPUT_SCHEMA).join(", ")})`
13446
- ).option(
13447
- "--output-json",
13448
- "Output result as JSON instead of interactive display"
13449
- ).addHelpText(
13450
- "after",
13451
- buildInputHelp(CREATE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
13452
- ).action(async (options) => {
13453
- requireAuth();
13454
- let resolved = {};
13455
- try {
13456
- resolved = resolveInput(options, CREATE_INPUT_SCHEMA);
13457
- } catch (err) {
13458
- if (err instanceof ValidationError)
13459
- outputErrors(err.errors, !!options.outputJson);
13460
- outputError(err.message);
13461
- }
13462
- const requestApproval = !!resolved.request_approval;
13463
- const credentialType = resolved.credential_type;
13464
- const networkId = resolved.network_id;
13465
- if (credentialType === "shared_payment_token" && !networkId) {
13466
- outputError(
13467
- "network-id is required when credential-type is shared_payment_token"
13468
- );
13469
- }
13470
- if (networkId && credentialType !== "shared_payment_token") {
13471
- outputError(
13472
- "network-id can only be used when credential-type is shared_payment_token"
13473
- );
13474
- }
13475
- if (credentialType !== "shared_payment_token" && !resolved.merchant_name) {
13476
- outputError("merchant-name is required when credential-type is card");
13477
- }
13478
- if (credentialType !== "shared_payment_token" && !resolved.merchant_url) {
13479
- outputError("merchant-url is required when credential-type is card");
13480
- }
13481
- const createParams = {
13482
- payment_details: resolved.payment_method_id,
13483
- credential_type: credentialType,
13484
- network_id: networkId,
13485
- amount: resolved.amount,
13486
- currency: resolved.currency,
13487
- merchant_name: resolved.merchant_name,
13488
- merchant_url: resolved.merchant_url,
13489
- context: resolved.context,
13490
- line_items: resolved.line_items,
13491
- totals: resolved.totals,
13492
- request_approval: requestApproval || void 0,
13493
- test: resolved.test ? true : void 0
13494
- };
13495
- await executeCommand({
13496
- outputJson: !!options.outputJson,
13497
- jsonFn: async () => {
13498
- const created = await repository.createSpendRequest(createParams);
13499
- if (requestApproval) {
13500
- outputJson(created);
13501
- return pollUntilApproved(repository, created.id, {
13502
- onProgress: (elapsedSeconds) => {
13503
- process.stderr.write(
13504
- `${JSON.stringify({
13505
- type: "waiting",
13506
- command: "spend_request_approval",
13507
- elapsed_seconds: elapsedSeconds,
13508
- approval_url: created.approval_url ?? null,
13509
- spend_request_id: created.id
13510
- })}
13511
- `
13512
- );
13513
- }
13514
- });
13515
- }
13516
- return created;
13517
- },
13518
- renderFn: () => /* @__PURE__ */ jsx17(
13519
- CreateSpendRequest,
13520
- {
13521
- repository,
13522
- params: createParams,
13523
- requestApproval,
13524
- onComplete: () => {
13525
- }
13526
- }
13527
- )
13528
- });
12985
+ import { jsx as jsx16 } from "react/jsx-runtime";
12986
+ function createSpendRequestCli(repository) {
12987
+ const cli2 = Cli4.create("spend-request", {
12988
+ description: "Spend request management commands"
13529
12989
  });
13530
- const updateCmd = spendRequestCommand2.command("update <id>").description("Update a spend request");
13531
- registerSchemaOptions(updateCmd, UPDATE_INPUT_SCHEMA);
13532
- updateCmd.option(
13533
- "--json <json>",
13534
- `JSON input (keys: ${Object.keys(UPDATE_INPUT_SCHEMA).join(", ")})`
13535
- ).option(
13536
- "--output-json",
13537
- "Output result as JSON instead of interactive display"
13538
- ).addHelpText(
13539
- "after",
13540
- buildInputHelp(UPDATE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
13541
- ).action(async (id, options) => {
13542
- requireAuth();
13543
- let resolved = {};
13544
- try {
13545
- resolved = resolveInput(options, UPDATE_INPUT_SCHEMA);
13546
- } catch (err) {
13547
- if (err instanceof ValidationError)
13548
- outputErrors(err.errors, !!options.outputJson);
13549
- outputError(err.message);
13550
- }
13551
- const params = {};
13552
- if (resolved.payment_method_id !== void 0)
13553
- params.payment_details = resolved.payment_method_id;
13554
- if (resolved.amount !== void 0) params.amount = resolved.amount;
13555
- if (resolved.merchant_url !== void 0)
13556
- params.merchant_url = resolved.merchant_url;
13557
- if (resolved.profile_id !== void 0)
13558
- params.profile_id = resolved.profile_id;
13559
- if (resolved.merchant_id !== void 0)
13560
- params.merchant_id = resolved.merchant_id;
13561
- if (resolved.currency !== void 0) params.currency = resolved.currency;
13562
- if (resolved.line_items !== void 0)
13563
- params.line_items = resolved.line_items;
13564
- if (resolved.totals !== void 0) params.totals = resolved.totals;
13565
- await executeCommand({
13566
- outputJson: !!options.outputJson,
13567
- jsonFn: async () => {
13568
- return repository.updateSpendRequest(id, params);
13569
- },
13570
- renderFn: () => /* @__PURE__ */ jsx17(
13571
- UpdateSpendRequest,
13572
- {
13573
- repository,
13574
- id,
13575
- params,
13576
- onComplete: () => {
12990
+ cli2.command("create", {
12991
+ description: "Create a new spend request",
12992
+ options: createOptions,
12993
+ alias: { merchantName: "m" },
12994
+ outputPolicy: "agent-only",
12995
+ async *run(c) {
12996
+ if (!storage.isAuthenticated()) {
12997
+ return c.error({
12998
+ code: "NOT_AUTHENTICATED",
12999
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13000
+ cta: {
13001
+ commands: [
13002
+ { command: "auth login", description: "Log in to Link" }
13003
+ ]
13004
+ }
13005
+ });
13006
+ }
13007
+ const opts = c.options;
13008
+ const requestApproval = !!opts.requestApproval;
13009
+ const credentialType = opts.credentialType;
13010
+ const networkId = opts.networkId;
13011
+ if (credentialType === "shared_payment_token" && !networkId) {
13012
+ return c.error({
13013
+ code: "INVALID_INPUT",
13014
+ message: "network-id is required when credential-type is shared_payment_token",
13015
+ cta: {
13016
+ commands: [
13017
+ {
13018
+ command: "mpp decode",
13019
+ description: "Decode a WWW-Authenticate challenge to extract network-id"
13020
+ }
13021
+ ]
13577
13022
  }
13023
+ });
13024
+ }
13025
+ if (networkId && credentialType !== "shared_payment_token") {
13026
+ return c.error({
13027
+ code: "INVALID_INPUT",
13028
+ message: "network-id can only be used when credential-type is shared_payment_token"
13029
+ });
13030
+ }
13031
+ if (credentialType !== "shared_payment_token" && !opts.merchantName) {
13032
+ return c.error({
13033
+ code: "INVALID_INPUT",
13034
+ message: "merchant-name is required when credential-type is card"
13035
+ });
13036
+ }
13037
+ if (credentialType !== "shared_payment_token" && !opts.merchantUrl) {
13038
+ return c.error({
13039
+ code: "INVALID_INPUT",
13040
+ message: "merchant-url is required when credential-type is card"
13041
+ });
13042
+ }
13043
+ const lineItems = opts.lineItem?.length ? opts.lineItem.map(
13044
+ (item) => typeof item === "string" ? parseLineItemFlag(item) : item
13045
+ ) : void 0;
13046
+ const totals = opts.total?.length ? opts.total.map(
13047
+ (item) => typeof item === "string" ? parseTotalFlag(item) : item
13048
+ ) : void 0;
13049
+ const createParams = {
13050
+ payment_details: opts.paymentMethodId,
13051
+ credential_type: credentialType,
13052
+ network_id: networkId,
13053
+ amount: opts.amount,
13054
+ currency: opts.currency,
13055
+ merchant_name: opts.merchantName,
13056
+ merchant_url: opts.merchantUrl,
13057
+ context: opts.context,
13058
+ line_items: lineItems,
13059
+ totals,
13060
+ request_approval: requestApproval || void 0,
13061
+ test: opts.test ? true : void 0
13062
+ };
13063
+ if (!c.agent && !c.formatExplicit) {
13064
+ return new Promise((resolve) => {
13065
+ const { waitUntilExit } = render4(
13066
+ /* @__PURE__ */ jsx16(
13067
+ CreateSpendRequest,
13068
+ {
13069
+ repository,
13070
+ params: createParams,
13071
+ requestApproval,
13072
+ onComplete: () => {
13073
+ }
13074
+ }
13075
+ )
13076
+ );
13077
+ waitUntilExit().then(async () => {
13078
+ const created2 = await repository.createSpendRequest(createParams);
13079
+ resolve(created2);
13080
+ });
13081
+ });
13082
+ }
13083
+ const created = await repository.createSpendRequest(createParams);
13084
+ if (!requestApproval) {
13085
+ yield created;
13086
+ return;
13087
+ }
13088
+ yield {
13089
+ ...created,
13090
+ 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.`,
13091
+ _next: {
13092
+ command: `spend-request retrieve ${created.id} --interval 2 --max-attempts 150`,
13093
+ until: "status changes from pending_approval"
13578
13094
  }
13579
- )
13580
- });
13095
+ };
13096
+ }
13581
13097
  });
13582
- spendRequestCommand2.command("request-approval <id>").description("Request approval for a spend request").option(
13583
- "--output-json",
13584
- "Output result as JSON instead of interactive display"
13585
- ).addHelpText("after", buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)).action(async (id, options) => {
13586
- requireAuth();
13587
- await executeCommand({
13588
- outputJson: !!options.outputJson,
13589
- jsonFn: async () => {
13590
- const approval = await repository.requestApproval(id);
13591
- outputJson(approval);
13592
- return pollUntilApproved(repository, id, {
13593
- onProgress: (elapsedSeconds) => {
13594
- process.stderr.write(
13595
- `${JSON.stringify({
13596
- type: "waiting",
13597
- command: "spend_request_approval",
13598
- elapsed_seconds: elapsedSeconds,
13599
- approval_url: approval.approval_link ?? null,
13600
- spend_request_id: id
13601
- })}
13602
- `
13603
- );
13098
+ cli2.command("update", {
13099
+ description: "Update a spend request",
13100
+ args: z6.object({
13101
+ id: z6.string().describe("Spend request ID")
13102
+ }),
13103
+ options: updateOptions,
13104
+ outputPolicy: "agent-only",
13105
+ async run(c) {
13106
+ if (!storage.isAuthenticated()) {
13107
+ return c.error({
13108
+ code: "NOT_AUTHENTICATED",
13109
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13110
+ cta: {
13111
+ commands: [
13112
+ { command: "auth login", description: "Log in to Link" }
13113
+ ]
13604
13114
  }
13605
13115
  });
13606
- },
13607
- renderFn: () => /* @__PURE__ */ jsx17(
13608
- RequestApproval,
13609
- {
13610
- repository,
13611
- id,
13612
- onComplete: () => {
13116
+ }
13117
+ const id = c.args.id;
13118
+ const opts = c.options;
13119
+ const params = {};
13120
+ if (opts.paymentMethodId !== void 0)
13121
+ params.payment_details = opts.paymentMethodId;
13122
+ if (opts.amount !== void 0) params.amount = opts.amount;
13123
+ if (opts.merchantUrl !== void 0)
13124
+ params.merchant_url = opts.merchantUrl;
13125
+ if (opts.profileId !== void 0) params.profile_id = opts.profileId;
13126
+ if (opts.merchantId !== void 0) params.merchant_id = opts.merchantId;
13127
+ if (opts.currency !== void 0) params.currency = opts.currency;
13128
+ if (opts.lineItem?.length)
13129
+ params.line_items = opts.lineItem.map(
13130
+ (item) => typeof item === "string" ? parseLineItemFlag(item) : item
13131
+ );
13132
+ if (opts.total?.length)
13133
+ params.totals = opts.total.map(
13134
+ (item) => typeof item === "string" ? parseTotalFlag(item) : item
13135
+ );
13136
+ if (!c.agent && !c.formatExplicit) {
13137
+ return new Promise((resolve) => {
13138
+ const { waitUntilExit } = render4(
13139
+ /* @__PURE__ */ jsx16(
13140
+ UpdateSpendRequest,
13141
+ {
13142
+ repository,
13143
+ id,
13144
+ params,
13145
+ onComplete: () => {
13146
+ }
13147
+ }
13148
+ )
13149
+ );
13150
+ waitUntilExit().then(async () => {
13151
+ resolve(await repository.updateSpendRequest(id, params));
13152
+ });
13153
+ });
13154
+ }
13155
+ return repository.updateSpendRequest(id, params);
13156
+ }
13157
+ });
13158
+ cli2.command("request-approval", {
13159
+ description: "Request approval for a spend request",
13160
+ args: z6.object({
13161
+ id: z6.string().describe("Spend request ID")
13162
+ }),
13163
+ outputPolicy: "agent-only",
13164
+ async *run(c) {
13165
+ if (!storage.isAuthenticated()) {
13166
+ return c.error({
13167
+ code: "NOT_AUTHENTICATED",
13168
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13169
+ cta: {
13170
+ commands: [
13171
+ { command: "auth login", description: "Log in to Link" }
13172
+ ]
13613
13173
  }
13174
+ });
13175
+ }
13176
+ const id = c.args.id;
13177
+ if (!c.agent && !c.formatExplicit) {
13178
+ return new Promise((resolve) => {
13179
+ const { waitUntilExit } = render4(
13180
+ /* @__PURE__ */ jsx16(
13181
+ RequestApproval,
13182
+ {
13183
+ repository,
13184
+ id,
13185
+ onComplete: () => {
13186
+ }
13187
+ }
13188
+ )
13189
+ );
13190
+ waitUntilExit().then(async () => {
13191
+ const approval2 = await repository.requestApproval(id);
13192
+ resolve(approval2);
13193
+ });
13194
+ });
13195
+ }
13196
+ const approval = await repository.requestApproval(id);
13197
+ yield {
13198
+ ...approval,
13199
+ 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.`,
13200
+ _next: {
13201
+ command: `spend-request retrieve ${id} --interval 2 --max-attempts 150`,
13202
+ until: "status changes from pending_approval"
13614
13203
  }
13615
- )
13616
- });
13204
+ };
13205
+ }
13617
13206
  });
13618
- const retrieveCmd = spendRequestCommand2.command("retrieve <id>").description("Retrieve a spend request");
13619
- registerSchemaOptions(retrieveCmd, RETRIEVE_INPUT_SCHEMA);
13620
- retrieveCmd.option(
13621
- "--json <json>",
13622
- `JSON input (keys: ${Object.keys(RETRIEVE_INPUT_SCHEMA).join(", ")})`
13623
- ).option(
13624
- "--output-json",
13625
- "Output result as JSON instead of interactive display"
13626
- ).addHelpText(
13627
- "after",
13628
- buildInputHelp(RETRIEVE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
13629
- ).action(async (id, options) => {
13630
- requireAuth();
13631
- let resolved = {};
13632
- try {
13633
- resolved = resolveInput(options, RETRIEVE_INPUT_SCHEMA);
13634
- } catch (err) {
13635
- if (err instanceof ValidationError)
13636
- outputErrors(err.errors, !!options.outputJson);
13637
- outputError(err.message);
13638
- }
13639
- const timeout = resolved.timeout;
13640
- const includeArr = resolved.include;
13641
- const include = includeArr?.length ? includeArr : void 0;
13642
- await executeCommand({
13643
- outputJson: !!options.outputJson,
13644
- jsonFn: async () => {
13207
+ cli2.command("retrieve", {
13208
+ description: "Retrieve a spend request",
13209
+ args: z6.object({
13210
+ id: z6.string().describe("Spend request ID")
13211
+ }),
13212
+ options: retrieveOptions,
13213
+ outputPolicy: "agent-only",
13214
+ async *run(c) {
13215
+ if (!storage.isAuthenticated()) {
13216
+ return c.error({
13217
+ code: "NOT_AUTHENTICATED",
13218
+ message: 'Not authenticated. Run "link-cli auth login" first.',
13219
+ cta: {
13220
+ commands: [
13221
+ { command: "auth login", description: "Log in to Link" }
13222
+ ]
13223
+ }
13224
+ });
13225
+ }
13226
+ const id = c.args.id;
13227
+ const opts = c.options;
13228
+ const timeout = opts.timeout;
13229
+ const interval = opts.interval;
13230
+ const maxAttempts = opts.maxAttempts;
13231
+ const includeArr = opts.include;
13232
+ const include = includeArr?.length ? includeArr : void 0;
13233
+ if (!c.agent && !c.formatExplicit) {
13234
+ return new Promise((resolve) => {
13235
+ const { waitUntilExit } = render4(
13236
+ /* @__PURE__ */ jsx16(
13237
+ RetrieveSpendRequest,
13238
+ {
13239
+ repository,
13240
+ id,
13241
+ timeout,
13242
+ include,
13243
+ onComplete: () => {
13244
+ }
13245
+ }
13246
+ )
13247
+ );
13248
+ waitUntilExit().then(async () => {
13249
+ const request = await repository.getSpendRequest(id, { include });
13250
+ resolve(request);
13251
+ });
13252
+ });
13253
+ }
13254
+ const terminalStatuses = /* @__PURE__ */ new Set([
13255
+ "approved",
13256
+ "denied",
13257
+ "expired",
13258
+ "succeeded",
13259
+ "failed"
13260
+ ]);
13261
+ const deadline = Date.now() + timeout * 1e3;
13262
+ let attempts = 0;
13263
+ while (true) {
13645
13264
  const request = await repository.getSpendRequest(id, { include });
13646
13265
  if (!request) {
13647
- throw new Error(`Spend request ${id} not found`);
13266
+ return c.error({
13267
+ code: "NOT_FOUND",
13268
+ message: `Spend request ${id} not found`
13269
+ });
13648
13270
  }
13649
- return request;
13650
- },
13651
- renderFn: () => /* @__PURE__ */ jsx17(
13652
- RetrieveSpendRequest,
13653
- {
13654
- repository,
13655
- id,
13656
- timeout,
13657
- include,
13658
- onComplete: () => {
13659
- }
13271
+ if (terminalStatuses.has(request.status)) {
13272
+ yield request;
13273
+ return;
13660
13274
  }
13661
- )
13662
- });
13663
- });
13664
- return spendRequestCommand2;
13665
- }
13666
-
13667
- // src/utils/configure-root-help.ts
13668
- function configureRootHelp(program2, authCommand2, spendIntentCommand, paymentMethodsCommand2, skillCommand2, mppCommand2) {
13669
- program2.configureHelp({
13670
- formatHelp(cmd, helper) {
13671
- const helpWidth = helper.helpWidth || 80;
13672
- const itemIndent = 2;
13673
- const itemSeparator = 2;
13674
- function formatItem(term, termWidth, description) {
13675
- if (description) {
13676
- const fullText = `${term.padEnd(termWidth + itemSeparator)}${description}`;
13677
- return helper.wrap(
13678
- fullText,
13679
- helpWidth - itemIndent,
13680
- termWidth + itemSeparator
13681
- );
13275
+ attempts++;
13276
+ const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
13277
+ if (shouldStop) {
13278
+ yield request;
13279
+ return;
13682
13280
  }
13683
- return term;
13684
- }
13685
- function formatList(items) {
13686
- return items.join("\n").replace(/^/gm, " ".repeat(itemIndent));
13687
- }
13688
- const output = [`Usage: ${helper.commandUsage(cmd)}`, ""];
13689
- const desc = helper.commandDescription(cmd);
13690
- if (desc.length > 0) {
13691
- output.push(helper.wrap(desc, helpWidth, 0), "");
13281
+ yield request;
13282
+ await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
13692
13283
  }
13693
- output.push(
13694
- "Getting started:",
13695
- formatList([
13696
- "As an agent, you MUST run `link-cli skill` to fully understand how to get setup.",
13697
- "Optional: Run `link-cli skill --install` to install the skill for future use."
13698
- ]),
13699
- ""
13700
- );
13701
- const optTermWidth = helper.longestOptionTermLength(cmd, helper);
13702
- const optionList = helper.visibleOptions(cmd).map(
13703
- (option) => formatItem(
13704
- helper.optionTerm(option),
13705
- optTermWidth,
13706
- helper.optionDescription(option)
13707
- )
13708
- );
13709
- if (optionList.length > 0) {
13710
- output.push("Options:", formatList(optionList), "");
13711
- }
13712
- const commandGroups = [
13713
- { heading: "Auth:", parent: authCommand2 },
13714
- { heading: "Spend Requests:", parent: spendIntentCommand },
13715
- { heading: "Payment Methods:", parent: paymentMethodsCommand2 },
13716
- { heading: "MPP:", parent: mppCommand2 }
13717
- ];
13718
- const allLeafCmds = commandGroups.flatMap(
13719
- ({ parent }) => helper.visibleCommands(parent)
13720
- );
13721
- const maxTermWidth = allLeafCmds.reduce(
13722
- (max, sub) => Math.max(
13723
- max,
13724
- // biome-ignore lint/style/noNonNullAssertion: sub is always a subcommand and always has a parent
13725
- `${sub.parent.name()} ${helper.subcommandTerm(sub)}`.length
13726
- ),
13727
- skillCommand2.name().length
13728
- );
13729
- for (const { heading, parent } of commandGroups) {
13730
- const cmds = helper.visibleCommands(parent);
13731
- if (cmds.length === 0) continue;
13732
- const list = cmds.map(
13733
- (sub) => formatItem(
13734
- `${parent.name()} ${helper.subcommandTerm(sub)}`,
13735
- maxTermWidth,
13736
- helper.subcommandDescription(sub)
13737
- )
13738
- );
13739
- output.push(heading, formatList(list), "");
13740
- }
13741
- output.push(
13742
- "Other:",
13743
- formatList([
13744
- formatItem(
13745
- skillCommand2.name(),
13746
- maxTermWidth,
13747
- skillCommand2.description()
13748
- )
13749
- ]),
13750
- ""
13751
- );
13752
- return output.join("\n");
13753
13284
  }
13754
13285
  });
13286
+ return cli2;
13755
13287
  }
13756
13288
 
13757
13289
  // src/auth/auth-resource.ts
@@ -13926,6 +13458,26 @@ ${JSON.stringify(redacted, null, 2)}`
13926
13458
  }
13927
13459
  );
13928
13460
  }
13461
+ async revokeToken(token) {
13462
+ const { status, data, rawBody } = await this.postForm(
13463
+ `${this.config.authBaseUrl}/device/revoke`,
13464
+ {
13465
+ client_id: CLIENT_ID,
13466
+ token
13467
+ }
13468
+ );
13469
+ if (status < 200 || status >= 300) {
13470
+ throw new LinkApiError(
13471
+ formatOAuthError("Token revocation failed", status, data, rawBody),
13472
+ {
13473
+ status,
13474
+ code: data?.error,
13475
+ rawBody,
13476
+ details: data
13477
+ }
13478
+ );
13479
+ }
13480
+ }
13929
13481
  async refreshToken(refreshToken) {
13930
13482
  const { status, data, rawBody } = await this.postForm(
13931
13483
  `${this.config.authBaseUrl}/device/token`,
@@ -14034,46 +13586,28 @@ var ResourceFactory = class {
14034
13586
  };
14035
13587
 
14036
13588
  // src/cli.tsx
14037
- var cliVersion = "0.1.0";
14038
- var buildNumber = "2";
13589
+ var cliVersion = "0.2.0";
13590
+ var buildNumber = "1";
14039
13591
  var defaultHeaders = {
14040
13592
  "User-Agent": `link-cli/${cliVersion} (build ${buildNumber})`,
14041
13593
  "X-Build-Number": buildNumber
14042
13594
  };
14043
- var program = new Command();
14044
13595
  var verbose = process.argv.includes("--verbose");
14045
13596
  var factory = new ResourceFactory({ verbose, defaultHeaders });
14046
13597
  var authRepo = factory.createAuthResource();
14047
13598
  var spendRequestRepo = factory.createSpendRequestResource();
14048
- program.name("link-cli").description(
14049
- "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users."
14050
- ).version(`${cliVersion} (build ${buildNumber})`).option("--verbose", "Print API request and response details to stderr").helpCommand(false).configureOutput({
14051
- outputError: (str, write) => {
14052
- write(str);
14053
- const isJsonMode = process.argv.includes("--output-json");
14054
- if (str.includes("unknown command") && !isJsonMode) {
14055
- write("\nRun 'link-cli --help' to see available commands.\n");
14056
- write("Run 'link-cli --skill' for full instructions.\n");
14057
- }
14058
- }
14059
- });
14060
- var authCommand = registerAuthCommands(program, authRepo);
14061
- var spendRequestCommand = registerSpendRequestCommands(
14062
- program,
14063
- spendRequestRepo
13599
+ var cli = Cli5.create("link-cli", {
13600
+ description: "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users.",
13601
+ version: `${cliVersion} (build ${buildNumber})`
13602
+ });
13603
+ cli.command(createAuthCli(authRepo));
13604
+ cli.command(createSpendRequestCli(spendRequestRepo));
13605
+ cli.command(
13606
+ createPaymentMethodsCli(() => factory.createPaymentMethodsResource())
14064
13607
  );
14065
- var paymentMethodsCommand = registerPaymentMethodsCommands(
14066
- program,
14067
- () => factory.createPaymentMethodsResource()
14068
- );
14069
- var skillCommand = registerSkillCommand(program);
14070
- var mppCommand = registerMppCommands(program, spendRequestRepo);
14071
- configureRootHelp(
14072
- program,
14073
- authCommand,
14074
- spendRequestCommand,
14075
- paymentMethodsCommand,
14076
- skillCommand,
14077
- mppCommand
14078
- );
14079
- program.parse();
13608
+ cli.command(createMppCli(spendRequestRepo));
13609
+ cli.serve();
13610
+ var cli_default = cli;
13611
+ export {
13612
+ cli_default as default
13613
+ };