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