@stripe/link-cli 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -26
- package/dist/cli.js +1080 -1528
- 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,7 @@ var require_semver2 = __commonJS({
|
|
|
9549
9549
|
});
|
|
9550
9550
|
|
|
9551
9551
|
// src/cli.tsx
|
|
9552
|
-
import {
|
|
9552
|
+
import { Cli as Cli5 } from "incur";
|
|
9553
9553
|
|
|
9554
9554
|
// ../sdk/dist/index.js
|
|
9555
9555
|
import fs3 from "fs";
|
|
@@ -10867,7 +10867,8 @@ var Storage = class {
|
|
|
10867
10867
|
this.config = new Conf({
|
|
10868
10868
|
projectName: "link-cli",
|
|
10869
10869
|
defaults: {
|
|
10870
|
-
auth: null
|
|
10870
|
+
auth: null,
|
|
10871
|
+
pendingDeviceAuth: null
|
|
10871
10872
|
}
|
|
10872
10873
|
});
|
|
10873
10874
|
}
|
|
@@ -10885,6 +10886,21 @@ var Storage = class {
|
|
|
10885
10886
|
isAuthenticated() {
|
|
10886
10887
|
return this.getAuth() !== null;
|
|
10887
10888
|
}
|
|
10889
|
+
getPendingDeviceAuth() {
|
|
10890
|
+
const pending = this.getConfig().get("pendingDeviceAuth");
|
|
10891
|
+
if (!pending) return null;
|
|
10892
|
+
if (Date.now() >= pending.expires_at) {
|
|
10893
|
+
this.clearPendingDeviceAuth();
|
|
10894
|
+
return null;
|
|
10895
|
+
}
|
|
10896
|
+
return pending;
|
|
10897
|
+
}
|
|
10898
|
+
setPendingDeviceAuth(pending) {
|
|
10899
|
+
this.getConfig().set("pendingDeviceAuth", pending);
|
|
10900
|
+
}
|
|
10901
|
+
clearPendingDeviceAuth() {
|
|
10902
|
+
this.getConfig().set("pendingDeviceAuth", null);
|
|
10903
|
+
}
|
|
10888
10904
|
clearAll() {
|
|
10889
10905
|
this.getConfig().clear();
|
|
10890
10906
|
}
|
|
@@ -11229,242 +11245,10 @@ var SpendRequestResource = class {
|
|
|
11229
11245
|
}
|
|
11230
11246
|
};
|
|
11231
11247
|
|
|
11232
|
-
// src/
|
|
11248
|
+
// src/commands/auth/index.tsx
|
|
11249
|
+
import { Cli } from "incur";
|
|
11233
11250
|
import { render } from "ink";
|
|
11234
11251
|
|
|
11235
|
-
// src/utils/json-options.ts
|
|
11236
|
-
import { z } from "zod";
|
|
11237
|
-
var ValidationError = class extends Error {
|
|
11238
|
-
errors;
|
|
11239
|
-
constructor(errors) {
|
|
11240
|
-
super(errors.join("\n"));
|
|
11241
|
-
this.errors = errors;
|
|
11242
|
-
}
|
|
11243
|
-
};
|
|
11244
|
-
function flagToCommanderKey(flag) {
|
|
11245
|
-
const name = flag.split(" ")[0].replace(/^--/, "");
|
|
11246
|
-
return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
11247
|
-
}
|
|
11248
|
-
function registerSchemaOptions(cmd, schema) {
|
|
11249
|
-
function collect(value, previous) {
|
|
11250
|
-
return previous.concat([value]);
|
|
11251
|
-
}
|
|
11252
|
-
for (const [, def] of Object.entries(schema)) {
|
|
11253
|
-
const flags = def.alias ? `${def.alias}, ${def.flag}` : def.flag;
|
|
11254
|
-
if (def.schema instanceof z.ZodArray) {
|
|
11255
|
-
cmd.option(flags, def.description, collect, []);
|
|
11256
|
-
} else if (def.defaultValue !== void 0) {
|
|
11257
|
-
cmd.option(flags, def.description, def.defaultValue);
|
|
11258
|
-
} else {
|
|
11259
|
-
cmd.option(flags, def.description);
|
|
11260
|
-
}
|
|
11261
|
-
}
|
|
11262
|
-
}
|
|
11263
|
-
function resolveInput(options, schema) {
|
|
11264
|
-
const entries = Object.entries(schema);
|
|
11265
|
-
let rawInput;
|
|
11266
|
-
if (options.json !== void 0) {
|
|
11267
|
-
const conflicts = entries.filter(([, def]) => {
|
|
11268
|
-
const val = options[flagToCommanderKey(def.flag)];
|
|
11269
|
-
if (def.schema instanceof z.ZodArray)
|
|
11270
|
-
return Array.isArray(val) && val.length > 0;
|
|
11271
|
-
if (def.defaultValue !== void 0)
|
|
11272
|
-
return val !== void 0 && val !== def.defaultValue;
|
|
11273
|
-
return val !== void 0;
|
|
11274
|
-
});
|
|
11275
|
-
if (conflicts.length > 0) {
|
|
11276
|
-
const names = conflicts.map(([, def]) => def.flag.split(" ")[0]).join(", ");
|
|
11277
|
-
throw new Error(`Cannot combine --json with individual flags (${names})`);
|
|
11278
|
-
}
|
|
11279
|
-
try {
|
|
11280
|
-
rawInput = JSON.parse(options.json);
|
|
11281
|
-
} catch {
|
|
11282
|
-
throw new Error(`Invalid JSON: ${options.json}`);
|
|
11283
|
-
}
|
|
11284
|
-
for (const [key, def] of entries) {
|
|
11285
|
-
if (rawInput[key] === void 0 && def.defaultValue !== void 0) {
|
|
11286
|
-
rawInput[key] = def.defaultValue;
|
|
11287
|
-
}
|
|
11288
|
-
}
|
|
11289
|
-
} else {
|
|
11290
|
-
rawInput = {};
|
|
11291
|
-
for (const [key, def] of entries) {
|
|
11292
|
-
const val = options[flagToCommanderKey(def.flag)];
|
|
11293
|
-
if (def.schema instanceof z.ZodArray) {
|
|
11294
|
-
const arr = val ?? [];
|
|
11295
|
-
if (arr.length > 0) {
|
|
11296
|
-
rawInput[key] = def.flagParser ? arr.map(def.flagParser) : arr;
|
|
11297
|
-
}
|
|
11298
|
-
} else if (val !== void 0) {
|
|
11299
|
-
rawInput[key] = val;
|
|
11300
|
-
}
|
|
11301
|
-
}
|
|
11302
|
-
}
|
|
11303
|
-
const zodShape = Object.fromEntries(
|
|
11304
|
-
entries.map(([key, def]) => [
|
|
11305
|
-
key,
|
|
11306
|
-
def.required ? def.schema : def.schema.optional()
|
|
11307
|
-
])
|
|
11308
|
-
);
|
|
11309
|
-
const fieldToFlag = Object.fromEntries(
|
|
11310
|
-
entries.map(([key, def]) => [key, def.flag.split(" ")[0]])
|
|
11311
|
-
);
|
|
11312
|
-
const useJsonKeys = !!options.outputJson;
|
|
11313
|
-
try {
|
|
11314
|
-
return z.object(zodShape).strict().parse(rawInput);
|
|
11315
|
-
} catch (err) {
|
|
11316
|
-
if (err instanceof z.ZodError) {
|
|
11317
|
-
const messages = err.issues.map((issue) => {
|
|
11318
|
-
const fieldName = issue.path[0];
|
|
11319
|
-
const label = !useJsonKeys && fieldName && fieldToFlag[fieldName] ? fieldToFlag[fieldName] : issue.path.join(".");
|
|
11320
|
-
return label ? `${label}: ${issue.message}` : issue.message;
|
|
11321
|
-
});
|
|
11322
|
-
throw new ValidationError(messages);
|
|
11323
|
-
}
|
|
11324
|
-
throw err;
|
|
11325
|
-
}
|
|
11326
|
-
}
|
|
11327
|
-
|
|
11328
|
-
// src/utils/execute-command.tsx
|
|
11329
|
-
function outputJson(data) {
|
|
11330
|
-
process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
11331
|
-
|
|
11332
|
-
`);
|
|
11333
|
-
}
|
|
11334
|
-
function outputError(message, code) {
|
|
11335
|
-
process.stderr.write(
|
|
11336
|
-
`${JSON.stringify({ error: message, ...code && { code } })}
|
|
11337
|
-
`
|
|
11338
|
-
);
|
|
11339
|
-
process.exit(1);
|
|
11340
|
-
}
|
|
11341
|
-
function outputErrors(errors, asJson) {
|
|
11342
|
-
if (asJson) {
|
|
11343
|
-
process.stderr.write(`${JSON.stringify({ errors })}
|
|
11344
|
-
`);
|
|
11345
|
-
} else {
|
|
11346
|
-
process.stderr.write(`${errors.join("\n")}
|
|
11347
|
-
`);
|
|
11348
|
-
}
|
|
11349
|
-
process.exit(1);
|
|
11350
|
-
}
|
|
11351
|
-
async function executeCommand(opts) {
|
|
11352
|
-
try {
|
|
11353
|
-
if (opts.outputJson) {
|
|
11354
|
-
const data = await opts.jsonFn();
|
|
11355
|
-
outputJson(data);
|
|
11356
|
-
} else if (!process.stdout.isTTY) {
|
|
11357
|
-
process.stderr.write("No TTY detected \u2014 falling back to JSON output.\n");
|
|
11358
|
-
process.stderr.write(
|
|
11359
|
-
"Run 'link-cli skill' to read the full Link CLI skill file.\n"
|
|
11360
|
-
);
|
|
11361
|
-
const data = await opts.jsonFn();
|
|
11362
|
-
outputJson(data);
|
|
11363
|
-
} else {
|
|
11364
|
-
const { waitUntilExit } = render(opts.renderFn());
|
|
11365
|
-
await waitUntilExit();
|
|
11366
|
-
}
|
|
11367
|
-
} catch (err) {
|
|
11368
|
-
if (err instanceof ValidationError) {
|
|
11369
|
-
outputErrors(err.errors, opts.outputJson);
|
|
11370
|
-
}
|
|
11371
|
-
if (err instanceof LinkAuthenticationError) {
|
|
11372
|
-
outputError(
|
|
11373
|
-
"Not authenticated. Please run `link login` first.",
|
|
11374
|
-
err.code
|
|
11375
|
-
);
|
|
11376
|
-
}
|
|
11377
|
-
const sdkErr = err;
|
|
11378
|
-
outputError(err.message, sdkErr?.code);
|
|
11379
|
-
}
|
|
11380
|
-
}
|
|
11381
|
-
|
|
11382
|
-
// src/utils/help-text.ts
|
|
11383
|
-
import { z as z2 } from "zod";
|
|
11384
|
-
function buildInputHelp(schema) {
|
|
11385
|
-
const entries = Object.entries(schema);
|
|
11386
|
-
const nonArrayEntries = entries.filter(
|
|
11387
|
-
([, def]) => !(def.schema instanceof z2.ZodArray)
|
|
11388
|
-
);
|
|
11389
|
-
const arrayEntries = entries.filter(
|
|
11390
|
-
([, def]) => def.schema instanceof z2.ZodArray
|
|
11391
|
-
);
|
|
11392
|
-
const nonArrayFlags = nonArrayEntries.map(([, def]) => {
|
|
11393
|
-
const flag = def.flag.split(" ")[0];
|
|
11394
|
-
return def.required ? `${flag} (required)` : flag;
|
|
11395
|
-
});
|
|
11396
|
-
const flagPrefix = " Flags: ";
|
|
11397
|
-
const indent = " ".repeat(flagPrefix.length);
|
|
11398
|
-
const arrayFlagLines = arrayEntries.map(
|
|
11399
|
-
([, def]) => `${indent}${def.flag} (repeatable)`
|
|
11400
|
-
);
|
|
11401
|
-
const flagsLine = `${flagPrefix}${nonArrayFlags.join(" ")}`;
|
|
11402
|
-
const flagsSection = arrayFlagLines.length > 0 ? `${flagsLine}
|
|
11403
|
-
${arrayFlagLines.join("\n")}` : flagsLine;
|
|
11404
|
-
const jsonIndent = " ";
|
|
11405
|
-
const jsonFields = entries.map(([key, def], i) => {
|
|
11406
|
-
const comma = i < entries.length - 1 ? "," : "";
|
|
11407
|
-
const desc = def.jsonDescription ?? def.description;
|
|
11408
|
-
const requiredNote = def.required ? "required" : "";
|
|
11409
|
-
const descNote = desc ? desc : "";
|
|
11410
|
-
const commentParts = [requiredNote, descNote].filter(Boolean);
|
|
11411
|
-
const comment = commentParts.length > 0 ? ` // ${commentParts.join(" \u2014 ")}` : "";
|
|
11412
|
-
if (def.schema instanceof z2.ZodArray) {
|
|
11413
|
-
return `${jsonIndent}"${key}": [...]${comma}${comment}`;
|
|
11414
|
-
}
|
|
11415
|
-
const placeholder = def.defaultValue !== void 0 ? JSON.stringify(def.defaultValue) : '"..."';
|
|
11416
|
-
return `${jsonIndent}"${key}": ${placeholder}${comma}${comment}`;
|
|
11417
|
-
});
|
|
11418
|
-
const jsonLine = ` JSON: --json '{
|
|
11419
|
-
${jsonFields.join("\n")}
|
|
11420
|
-
}'`;
|
|
11421
|
-
const arrayDetails = [];
|
|
11422
|
-
for (const [key, def] of arrayEntries) {
|
|
11423
|
-
const element = def.schema.element;
|
|
11424
|
-
if (!(element instanceof z2.ZodObject)) continue;
|
|
11425
|
-
const keys = Object.keys(element.shape);
|
|
11426
|
-
const flagFormat = `"key:<value>,key:<value>,..."`;
|
|
11427
|
-
const jsonExample = keys.slice(0, 3).map((k) => `"${k}": "..."`).join(", ");
|
|
11428
|
-
const jsonFormat = `"${key}": [{ ${jsonExample}${keys.length > 3 ? ", ..." : ""} }]`;
|
|
11429
|
-
arrayDetails.push(
|
|
11430
|
-
` ${def.flag}
|
|
11431
|
-
Keys: ${keys.join(", ")}
|
|
11432
|
-
Flag: ${flagFormat}
|
|
11433
|
-
JSON: ${jsonFormat}`
|
|
11434
|
-
);
|
|
11435
|
-
}
|
|
11436
|
-
const parts = ["\nInput formats:", flagsSection, jsonLine];
|
|
11437
|
-
if (arrayDetails.length > 0) {
|
|
11438
|
-
parts.push("", ...arrayDetails);
|
|
11439
|
-
}
|
|
11440
|
-
return `${parts.join("\n")}
|
|
11441
|
-
`;
|
|
11442
|
-
}
|
|
11443
|
-
function buildOutputHelp(schema, isArray = false) {
|
|
11444
|
-
const fields = Object.entries(schema);
|
|
11445
|
-
const lines = ["\nOutput (--output-json):"];
|
|
11446
|
-
const outerIndent = " ";
|
|
11447
|
-
const innerIndent = isArray ? " " : " ";
|
|
11448
|
-
if (isArray) {
|
|
11449
|
-
lines.push(`${outerIndent}[`);
|
|
11450
|
-
lines.push(`${outerIndent} {`);
|
|
11451
|
-
} else {
|
|
11452
|
-
lines.push(`${outerIndent}{`);
|
|
11453
|
-
}
|
|
11454
|
-
fields.forEach(([key, def], i) => {
|
|
11455
|
-
const comma = i < fields.length - 1 ? "," : "";
|
|
11456
|
-
lines.push(`${innerIndent}"${key}": ${def.outputExample}${comma}`);
|
|
11457
|
-
});
|
|
11458
|
-
if (isArray) {
|
|
11459
|
-
lines.push(`${outerIndent} }`);
|
|
11460
|
-
lines.push(`${outerIndent}]`);
|
|
11461
|
-
} else {
|
|
11462
|
-
lines.push(`${outerIndent}}`);
|
|
11463
|
-
}
|
|
11464
|
-
return `${lines.join("\n")}
|
|
11465
|
-
`;
|
|
11466
|
-
}
|
|
11467
|
-
|
|
11468
11252
|
// src/commands/auth/login.tsx
|
|
11469
11253
|
import { Box, Text, useInput } from "ink";
|
|
11470
11254
|
import Spinner from "ink-spinner";
|
|
@@ -11608,14 +11392,24 @@ var Login = ({
|
|
|
11608
11392
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
11609
11393
|
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
11610
11394
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
11611
|
-
var Logout = ({ onComplete }) => {
|
|
11395
|
+
var Logout = ({ authResource, onComplete }) => {
|
|
11612
11396
|
const [done, setDone] = useState2(false);
|
|
11613
11397
|
useEffect2(() => {
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11398
|
+
const run = async () => {
|
|
11399
|
+
const auth = storage.getAuth();
|
|
11400
|
+
if (auth?.refresh_token) {
|
|
11401
|
+
try {
|
|
11402
|
+
await authResource.revokeToken(auth.refresh_token);
|
|
11403
|
+
} catch {
|
|
11404
|
+
}
|
|
11405
|
+
}
|
|
11406
|
+
storage.clearAuth();
|
|
11407
|
+
storage.deleteConfig();
|
|
11408
|
+
setDone(true);
|
|
11409
|
+
setTimeout(onComplete, 1e3);
|
|
11410
|
+
};
|
|
11411
|
+
run();
|
|
11412
|
+
}, [authResource, onComplete]);
|
|
11619
11413
|
if (!done) {
|
|
11620
11414
|
return null;
|
|
11621
11415
|
}
|
|
@@ -11623,217 +11417,159 @@ var Logout = ({ onComplete }) => {
|
|
|
11623
11417
|
};
|
|
11624
11418
|
|
|
11625
11419
|
// src/commands/auth/schema.ts
|
|
11626
|
-
import { z
|
|
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
|
-
};
|
|
11420
|
+
import { z } from "incur";
|
|
11421
|
+
var loginOptions = z.object({
|
|
11422
|
+
clientName: z.string().default("Link CLI").describe(
|
|
11423
|
+
"Agent or app name shown in the Link app when approving the device connection"
|
|
11424
|
+
)
|
|
11425
|
+
});
|
|
11426
|
+
var statusOptions = z.object({
|
|
11427
|
+
interval: z.coerce.number().default(0).describe(
|
|
11428
|
+
"Poll interval in seconds. When > 0, polls until authenticated or timeout is reached, yielding status on each attempt."
|
|
11429
|
+
),
|
|
11430
|
+
maxAttempts: z.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
|
|
11431
|
+
timeout: z.coerce.number().default(300).describe("Polling timeout in seconds.")
|
|
11432
|
+
});
|
|
11718
11433
|
|
|
11719
11434
|
// src/commands/auth/index.tsx
|
|
11720
|
-
import { jsx as
|
|
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
|
|
11435
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
11436
|
+
function createAuthCli(authResource) {
|
|
11437
|
+
const cli2 = Cli.create("auth", {
|
|
11438
|
+
description: "Authentication commands"
|
|
11439
|
+
});
|
|
11440
|
+
cli2.command("login", {
|
|
11441
|
+
description: "Authenticate with Link",
|
|
11442
|
+
options: loginOptions,
|
|
11443
|
+
outputPolicy: "agent-only",
|
|
11444
|
+
async *run(c) {
|
|
11445
|
+
const clientName = c.options.clientName?.trim();
|
|
11446
|
+
if (!clientName || clientName.length === 0) {
|
|
11447
|
+
return c.error({
|
|
11448
|
+
code: "INVALID_INPUT",
|
|
11449
|
+
message: "client-name must be a non-empty string"
|
|
11753
11450
|
});
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
})}
|
|
11768
|
-
`
|
|
11451
|
+
}
|
|
11452
|
+
if (!c.agent && !c.formatExplicit) {
|
|
11453
|
+
return new Promise((resolve) => {
|
|
11454
|
+
const { waitUntilExit } = render(
|
|
11455
|
+
/* @__PURE__ */ jsx3(
|
|
11456
|
+
Login,
|
|
11457
|
+
{
|
|
11458
|
+
authResource,
|
|
11459
|
+
clientName,
|
|
11460
|
+
onComplete: () => {
|
|
11461
|
+
}
|
|
11462
|
+
}
|
|
11463
|
+
)
|
|
11769
11464
|
);
|
|
11770
|
-
|
|
11771
|
-
|
|
11465
|
+
waitUntilExit().then(
|
|
11466
|
+
() => resolve({ authenticated: true, token_type: "Bearer" })
|
|
11467
|
+
);
|
|
11468
|
+
});
|
|
11469
|
+
}
|
|
11470
|
+
const authRequest = await authResource.initiateDeviceAuth(clientName);
|
|
11471
|
+
storage.setPendingDeviceAuth({
|
|
11472
|
+
device_code: authRequest.device_code,
|
|
11473
|
+
interval: authRequest.interval,
|
|
11474
|
+
expires_at: Date.now() + authRequest.expires_in * 1e3,
|
|
11475
|
+
verification_url: authRequest.verification_url_complete,
|
|
11476
|
+
passphrase: authRequest.user_code
|
|
11477
|
+
});
|
|
11478
|
+
yield {
|
|
11479
|
+
verification_url: authRequest.verification_url_complete,
|
|
11480
|
+
passphrase: authRequest.user_code,
|
|
11481
|
+
instruction: "Present the verification_url to the user and ask them to approve in the Link app. Then call `auth status --interval 5 --max-attempts 60` to poll until authenticated. Do not wait for the user to reply \u2014 start polling immediately.",
|
|
11482
|
+
_next: {
|
|
11483
|
+
command: "auth status --interval 5 --max-attempts 60",
|
|
11484
|
+
poll_interval_seconds: authRequest.interval,
|
|
11485
|
+
until: "authenticated is true"
|
|
11486
|
+
}
|
|
11487
|
+
};
|
|
11488
|
+
}
|
|
11489
|
+
});
|
|
11490
|
+
cli2.command("logout", {
|
|
11491
|
+
description: "Log out from Link",
|
|
11492
|
+
outputPolicy: "agent-only",
|
|
11493
|
+
async run(c) {
|
|
11494
|
+
const auth = storage.getAuth();
|
|
11495
|
+
if (auth?.refresh_token) {
|
|
11496
|
+
try {
|
|
11497
|
+
await authResource.revokeToken(auth.refresh_token);
|
|
11498
|
+
} catch {
|
|
11499
|
+
}
|
|
11500
|
+
}
|
|
11501
|
+
storage.clearAuth();
|
|
11502
|
+
storage.clearPendingDeviceAuth();
|
|
11503
|
+
storage.deleteConfig();
|
|
11504
|
+
const result = { authenticated: false };
|
|
11505
|
+
if (!c.agent && !c.formatExplicit) {
|
|
11506
|
+
return new Promise((resolve) => {
|
|
11507
|
+
const { waitUntilExit } = render(
|
|
11508
|
+
/* @__PURE__ */ jsx3(Logout, { authResource, onComplete: () => {
|
|
11509
|
+
} })
|
|
11772
11510
|
);
|
|
11511
|
+
waitUntilExit().then(() => resolve(result));
|
|
11512
|
+
});
|
|
11513
|
+
}
|
|
11514
|
+
return result;
|
|
11515
|
+
}
|
|
11516
|
+
});
|
|
11517
|
+
cli2.command("status", {
|
|
11518
|
+
description: "Check authentication status",
|
|
11519
|
+
options: statusOptions,
|
|
11520
|
+
outputPolicy: "agent-only",
|
|
11521
|
+
async *run(c) {
|
|
11522
|
+
const opts = c.options;
|
|
11523
|
+
const interval = opts.interval;
|
|
11524
|
+
const maxAttempts = opts.maxAttempts;
|
|
11525
|
+
const deadline = Date.now() + opts.timeout * 1e3;
|
|
11526
|
+
let attempts = 0;
|
|
11527
|
+
while (true) {
|
|
11528
|
+
const pending = storage.getPendingDeviceAuth();
|
|
11529
|
+
if (pending && !storage.isAuthenticated()) {
|
|
11530
|
+
const tokens = await authResource.pollDeviceAuth(pending.device_code);
|
|
11773
11531
|
if (tokens) {
|
|
11774
11532
|
storage.setAuth(tokens);
|
|
11775
|
-
|
|
11776
|
-
}
|
|
11777
|
-
}
|
|
11778
|
-
throw new Error("Device authorization timed out");
|
|
11779
|
-
},
|
|
11780
|
-
renderFn: () => /* @__PURE__ */ jsx4(
|
|
11781
|
-
Login,
|
|
11782
|
-
{
|
|
11783
|
-
authResource,
|
|
11784
|
-
clientName,
|
|
11785
|
-
onComplete: () => {
|
|
11533
|
+
storage.clearPendingDeviceAuth();
|
|
11786
11534
|
}
|
|
11787
11535
|
}
|
|
11788
|
-
)
|
|
11789
|
-
});
|
|
11790
|
-
});
|
|
11791
|
-
authCommand2.command("logout").description("Log out from Link").option(
|
|
11792
|
-
"--output-json",
|
|
11793
|
-
"Output result as JSON instead of interactive display"
|
|
11794
|
-
).addHelpText("after", buildOutputHelp(LOGOUT_SCHEMA)).action(async (options) => {
|
|
11795
|
-
await executeCommand({
|
|
11796
|
-
outputJson: !!options.outputJson,
|
|
11797
|
-
jsonFn: async () => {
|
|
11798
|
-
storage.clearAuth();
|
|
11799
|
-
storage.deleteConfig();
|
|
11800
|
-
return { authenticated: false };
|
|
11801
|
-
},
|
|
11802
|
-
renderFn: () => /* @__PURE__ */ jsx4(Logout, { onComplete: () => {
|
|
11803
|
-
} })
|
|
11804
|
-
});
|
|
11805
|
-
});
|
|
11806
|
-
authCommand2.command("status").description("Check authentication status").option(
|
|
11807
|
-
"--output-json",
|
|
11808
|
-
"Output result as JSON instead of interactive display"
|
|
11809
|
-
).addHelpText("after", buildOutputHelp(AUTH_STATUS_SCHEMA)).action(async (options) => {
|
|
11810
|
-
await executeCommand({
|
|
11811
|
-
outputJson: !!options.outputJson,
|
|
11812
|
-
jsonFn: async () => {
|
|
11813
11536
|
const auth = storage.getAuth();
|
|
11814
11537
|
if (auth) {
|
|
11815
|
-
|
|
11538
|
+
yield {
|
|
11816
11539
|
authenticated: true,
|
|
11817
11540
|
access_token: `${auth.access_token.substring(0, 20)}...`,
|
|
11818
11541
|
token_type: auth.token_type,
|
|
11819
11542
|
credentials_path: storage.getPath()
|
|
11820
11543
|
};
|
|
11544
|
+
return;
|
|
11821
11545
|
}
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11546
|
+
const currentPending = storage.getPendingDeviceAuth();
|
|
11547
|
+
const status = {
|
|
11548
|
+
authenticated: false,
|
|
11549
|
+
credentials_path: storage.getPath(),
|
|
11550
|
+
...currentPending ? {
|
|
11551
|
+
pending: true,
|
|
11552
|
+
verification_url: currentPending.verification_url,
|
|
11553
|
+
passphrase: currentPending.passphrase
|
|
11554
|
+
} : {}
|
|
11555
|
+
};
|
|
11556
|
+
attempts++;
|
|
11557
|
+
const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
|
|
11558
|
+
if (shouldStop) {
|
|
11559
|
+
yield status;
|
|
11560
|
+
return;
|
|
11561
|
+
}
|
|
11562
|
+
yield status;
|
|
11563
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
|
|
11564
|
+
}
|
|
11565
|
+
}
|
|
11827
11566
|
});
|
|
11828
|
-
return
|
|
11567
|
+
return cli2;
|
|
11829
11568
|
}
|
|
11830
11569
|
|
|
11831
|
-
// src/
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
outputError('Not authenticated. Run "link-cli auth login" first.');
|
|
11835
|
-
}
|
|
11836
|
-
}
|
|
11570
|
+
// src/commands/mpp/index.tsx
|
|
11571
|
+
import { Cli as Cli2, z as z3 } from "incur";
|
|
11572
|
+
import { render as render2 } from "ink";
|
|
11837
11573
|
|
|
11838
11574
|
// src/commands/mpp/decode.ts
|
|
11839
11575
|
import { Challenge } from "mppx";
|
|
@@ -11863,8 +11599,7 @@ function getMethodDetails(request) {
|
|
|
11863
11599
|
}
|
|
11864
11600
|
return methodDetails;
|
|
11865
11601
|
}
|
|
11866
|
-
function
|
|
11867
|
-
const challenges = Challenge.deserializeList(challengeHeader);
|
|
11602
|
+
function resolveStripeChallenge(challenges) {
|
|
11868
11603
|
const stripeChallenge = challenges.find(
|
|
11869
11604
|
(challenge) => challenge.method === "stripe" && challenge.intent === "charge"
|
|
11870
11605
|
);
|
|
@@ -11889,51 +11624,66 @@ function decodeStripeChallenge(challengeHeader) {
|
|
|
11889
11624
|
);
|
|
11890
11625
|
}
|
|
11891
11626
|
return {
|
|
11892
|
-
|
|
11893
|
-
|
|
11627
|
+
challenge: stripeChallenge,
|
|
11628
|
+
networkId,
|
|
11629
|
+
request
|
|
11630
|
+
};
|
|
11631
|
+
}
|
|
11632
|
+
function getStripeChargeChallengeFromResponse(response) {
|
|
11633
|
+
return resolveStripeChallenge(Challenge.fromResponseList(response)).challenge;
|
|
11634
|
+
}
|
|
11635
|
+
function decodeStripeChallenge(challengeHeader) {
|
|
11636
|
+
const { challenge, networkId, request } = resolveStripeChallenge(
|
|
11637
|
+
Challenge.deserializeList(challengeHeader)
|
|
11638
|
+
);
|
|
11639
|
+
return {
|
|
11640
|
+
id: challenge.id,
|
|
11641
|
+
realm: challenge.realm,
|
|
11894
11642
|
method: "stripe",
|
|
11895
11643
|
intent: "charge",
|
|
11896
|
-
description:
|
|
11897
|
-
digest:
|
|
11898
|
-
expires:
|
|
11644
|
+
description: challenge.description,
|
|
11645
|
+
digest: challenge.digest,
|
|
11646
|
+
expires: challenge.expires,
|
|
11899
11647
|
network_id: networkId,
|
|
11900
11648
|
request_json: request
|
|
11901
11649
|
};
|
|
11902
11650
|
}
|
|
11903
11651
|
|
|
11904
11652
|
// src/commands/mpp/decode-view.tsx
|
|
11905
|
-
import { Box as
|
|
11906
|
-
import { jsx as
|
|
11653
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
11654
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
11907
11655
|
function DecodeChallengeView({
|
|
11908
11656
|
decoded
|
|
11909
11657
|
}) {
|
|
11910
|
-
return /* @__PURE__ */
|
|
11911
|
-
/* @__PURE__ */
|
|
11912
|
-
/* @__PURE__ */
|
|
11913
|
-
/* @__PURE__ */
|
|
11658
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
11659
|
+
/* @__PURE__ */ jsx4(Text3, { color: "green", children: "\u2713 Stripe challenge decoded" }),
|
|
11660
|
+
/* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
11661
|
+
/* @__PURE__ */ jsxs2(Text3, { children: [
|
|
11914
11662
|
"ID: ",
|
|
11915
|
-
/* @__PURE__ */
|
|
11663
|
+
/* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.id })
|
|
11916
11664
|
] }),
|
|
11917
|
-
/* @__PURE__ */
|
|
11665
|
+
/* @__PURE__ */ jsxs2(Text3, { children: [
|
|
11918
11666
|
"Realm: ",
|
|
11919
|
-
/* @__PURE__ */
|
|
11667
|
+
/* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.realm })
|
|
11920
11668
|
] }),
|
|
11921
|
-
/* @__PURE__ */
|
|
11669
|
+
/* @__PURE__ */ jsxs2(Text3, { children: [
|
|
11922
11670
|
"Network ID: ",
|
|
11923
|
-
/* @__PURE__ */
|
|
11671
|
+
/* @__PURE__ */ jsx4(Text3, { bold: true, children: decoded.network_id })
|
|
11924
11672
|
] }),
|
|
11925
|
-
/* @__PURE__ */
|
|
11926
|
-
/* @__PURE__ */
|
|
11673
|
+
/* @__PURE__ */ jsx4(Text3, { children: "Request JSON:" }),
|
|
11674
|
+
/* @__PURE__ */ jsx4(Text3, { children: JSON.stringify(decoded.request_json, null, 2) })
|
|
11927
11675
|
] })
|
|
11928
11676
|
] });
|
|
11929
11677
|
}
|
|
11930
11678
|
|
|
11931
11679
|
// src/commands/mpp/pay.tsx
|
|
11932
|
-
import { Box as
|
|
11680
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
11933
11681
|
import Spinner2 from "ink-spinner";
|
|
11934
|
-
import {
|
|
11935
|
-
import {
|
|
11936
|
-
import {
|
|
11682
|
+
import { Credential, Method } from "mppx";
|
|
11683
|
+
import { Mppx, Transport } from "mppx/client";
|
|
11684
|
+
import { Methods as StripeMethods } from "mppx/stripe";
|
|
11685
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
11686
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
11937
11687
|
function buildHeaders(data, headers) {
|
|
11938
11688
|
const result = {};
|
|
11939
11689
|
if (data !== void 0) {
|
|
@@ -11948,29 +11698,66 @@ function buildHeaders(data, headers) {
|
|
|
11948
11698
|
}
|
|
11949
11699
|
return result;
|
|
11950
11700
|
}
|
|
11701
|
+
async function readPayResult(response, options) {
|
|
11702
|
+
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
11703
|
+
const body = await response.text();
|
|
11704
|
+
if (options?.failOnError && !response.ok) {
|
|
11705
|
+
throw new Error(
|
|
11706
|
+
`Payment submission failed with status ${response.status}: ${body}`
|
|
11707
|
+
);
|
|
11708
|
+
}
|
|
11709
|
+
return { status: response.status, headers: responseHeaders, body };
|
|
11710
|
+
}
|
|
11711
|
+
function createStripePaymentClient(spt) {
|
|
11712
|
+
const stripeCharge = Method.toClient(StripeMethods.charge, {
|
|
11713
|
+
async createCredential({ challenge }) {
|
|
11714
|
+
return Credential.serialize({
|
|
11715
|
+
challenge,
|
|
11716
|
+
payload: { spt }
|
|
11717
|
+
});
|
|
11718
|
+
}
|
|
11719
|
+
});
|
|
11720
|
+
return Mppx.create({
|
|
11721
|
+
methods: [stripeCharge],
|
|
11722
|
+
polyfill: false,
|
|
11723
|
+
transport: Transport.from({
|
|
11724
|
+
name: "stripe-http",
|
|
11725
|
+
isPaymentRequired(response) {
|
|
11726
|
+
return response.status === 402;
|
|
11727
|
+
},
|
|
11728
|
+
getChallenge(response) {
|
|
11729
|
+
return getStripeChargeChallengeFromResponse(response);
|
|
11730
|
+
},
|
|
11731
|
+
setCredential(request, credential) {
|
|
11732
|
+
const nextHeaders = new Headers(request.headers);
|
|
11733
|
+
nextHeaders.set("Authorization", credential);
|
|
11734
|
+
return { ...request, headers: nextHeaders };
|
|
11735
|
+
}
|
|
11736
|
+
})
|
|
11737
|
+
});
|
|
11738
|
+
}
|
|
11951
11739
|
async function runMppPay(url, spendRequestId, method, data, headers, repository) {
|
|
11952
11740
|
const spendRequest = await repository.getSpendRequest(spendRequestId, {
|
|
11953
11741
|
include: ["shared_payment_token"]
|
|
11954
11742
|
});
|
|
11955
11743
|
if (!spendRequest) {
|
|
11956
|
-
|
|
11744
|
+
throw new Error(`Spend request ${spendRequestId} not found`);
|
|
11957
11745
|
}
|
|
11958
11746
|
if (spendRequest.credential_type !== "shared_payment_token") {
|
|
11959
11747
|
const type = spendRequest.credential_type ?? "card";
|
|
11960
|
-
|
|
11748
|
+
throw new Error(
|
|
11961
11749
|
`Spend request ${spendRequestId} must have credential_type 'shared_payment_token' (current: '${type}')`
|
|
11962
11750
|
);
|
|
11963
11751
|
}
|
|
11964
11752
|
if (spendRequest.status !== "approved") {
|
|
11965
|
-
|
|
11753
|
+
throw new Error(
|
|
11966
11754
|
`Spend request must be approved (current status: ${spendRequest.status})`
|
|
11967
11755
|
);
|
|
11968
11756
|
}
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
outputError("Spend request does not have a shared payment token");
|
|
11757
|
+
if (!spendRequest.shared_payment_token) {
|
|
11758
|
+
throw new Error("Spend request does not have a shared payment token");
|
|
11972
11759
|
}
|
|
11973
|
-
const spt =
|
|
11760
|
+
const spt = spendRequest.shared_payment_token.id;
|
|
11974
11761
|
const httpMethod = method ?? (data !== void 0 ? "POST" : "GET");
|
|
11975
11762
|
const requestHeaders = buildHeaders(data, headers);
|
|
11976
11763
|
const initialResponse = await fetch(url, {
|
|
@@ -11979,30 +11766,9 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
|
|
|
11979
11766
|
headers: requestHeaders
|
|
11980
11767
|
});
|
|
11981
11768
|
if (initialResponse.status !== 402) {
|
|
11982
|
-
|
|
11983
|
-
initialResponse.headers.entries()
|
|
11984
|
-
);
|
|
11985
|
-
const body2 = await initialResponse.text();
|
|
11986
|
-
return { status: initialResponse.status, headers: responseHeaders2, body: body2 };
|
|
11769
|
+
return readPayResult(initialResponse);
|
|
11987
11770
|
}
|
|
11988
|
-
const
|
|
11989
|
-
initialResponse.headers.get("www-authenticate") ?? ""
|
|
11990
|
-
);
|
|
11991
|
-
const stripeChallenge = Challenge2.from({
|
|
11992
|
-
id: decoded.id,
|
|
11993
|
-
realm: decoded.realm,
|
|
11994
|
-
method: decoded.method,
|
|
11995
|
-
intent: decoded.intent,
|
|
11996
|
-
request: decoded.request_json,
|
|
11997
|
-
...decoded.description ? { description: decoded.description } : {},
|
|
11998
|
-
...decoded.digest ? { digest: decoded.digest } : {},
|
|
11999
|
-
...decoded.expires ? { expires: decoded.expires } : {}
|
|
12000
|
-
});
|
|
12001
|
-
const credential = Credential.from({
|
|
12002
|
-
challenge: stripeChallenge,
|
|
12003
|
-
payload: { spt }
|
|
12004
|
-
});
|
|
12005
|
-
const authHeader = Credential.serialize(credential);
|
|
11771
|
+
const authHeader = await createStripePaymentClient(spt).createCredential(initialResponse);
|
|
12006
11772
|
const retryResponse = await fetch(url, {
|
|
12007
11773
|
method: httpMethod,
|
|
12008
11774
|
body: data,
|
|
@@ -12011,15 +11777,7 @@ async function runMppPay(url, spendRequestId, method, data, headers, repository)
|
|
|
12011
11777
|
Authorization: authHeader
|
|
12012
11778
|
}
|
|
12013
11779
|
});
|
|
12014
|
-
|
|
12015
|
-
const body2 = await retryResponse.text();
|
|
12016
|
-
outputError(
|
|
12017
|
-
`Payment submission failed with status ${retryResponse.status}: ${body2}`
|
|
12018
|
-
);
|
|
12019
|
-
}
|
|
12020
|
-
const responseHeaders = Object.fromEntries(retryResponse.headers.entries());
|
|
12021
|
-
const body = await retryResponse.text();
|
|
12022
|
-
return { status: retryResponse.status, headers: responseHeaders, body };
|
|
11780
|
+
return readPayResult(retryResponse, { failOnError: true });
|
|
12023
11781
|
}
|
|
12024
11782
|
function MppPay({
|
|
12025
11783
|
url,
|
|
@@ -12030,10 +11788,10 @@ function MppPay({
|
|
|
12030
11788
|
repository,
|
|
12031
11789
|
onComplete
|
|
12032
11790
|
}) {
|
|
12033
|
-
const [step, setStep] =
|
|
12034
|
-
const [result, setResult] =
|
|
12035
|
-
const [error, setError] =
|
|
12036
|
-
|
|
11791
|
+
const [step, setStep] = useState3("retrieving");
|
|
11792
|
+
const [result, setResult] = useState3(null);
|
|
11793
|
+
const [error, setError] = useState3(null);
|
|
11794
|
+
useEffect3(() => {
|
|
12037
11795
|
(async () => {
|
|
12038
11796
|
try {
|
|
12039
11797
|
setStep("retrieving");
|
|
@@ -12067,38 +11825,15 @@ function MppPay({
|
|
|
12067
11825
|
headers: requestHeaders
|
|
12068
11826
|
});
|
|
12069
11827
|
if (initialResponse.status !== 402) {
|
|
12070
|
-
|
|
12071
|
-
initialResponse.headers.entries()
|
|
12072
|
-
);
|
|
12073
|
-
const body2 = await initialResponse.text();
|
|
12074
|
-
setResult({
|
|
12075
|
-
status: initialResponse.status,
|
|
12076
|
-
headers: responseHeaders2,
|
|
12077
|
-
body: body2
|
|
12078
|
-
});
|
|
11828
|
+
setResult(await readPayResult(initialResponse));
|
|
12079
11829
|
setStep("done");
|
|
12080
11830
|
onComplete();
|
|
12081
11831
|
return;
|
|
12082
11832
|
}
|
|
12083
11833
|
setStep("signing");
|
|
12084
|
-
const
|
|
12085
|
-
initialResponse
|
|
11834
|
+
const authHeader = await createStripePaymentClient(spt).createCredential(
|
|
11835
|
+
initialResponse
|
|
12086
11836
|
);
|
|
12087
|
-
const stripeChallenge = Challenge2.from({
|
|
12088
|
-
id: decoded.id,
|
|
12089
|
-
realm: decoded.realm,
|
|
12090
|
-
method: decoded.method,
|
|
12091
|
-
intent: decoded.intent,
|
|
12092
|
-
request: decoded.request_json,
|
|
12093
|
-
...decoded.description ? { description: decoded.description } : {},
|
|
12094
|
-
...decoded.digest ? { digest: decoded.digest } : {},
|
|
12095
|
-
...decoded.expires ? { expires: decoded.expires } : {}
|
|
12096
|
-
});
|
|
12097
|
-
const credential = Credential.from({
|
|
12098
|
-
challenge: stripeChallenge,
|
|
12099
|
-
payload: { spt }
|
|
12100
|
-
});
|
|
12101
|
-
const authHeader = Credential.serialize(credential);
|
|
12102
11837
|
setStep("submitting");
|
|
12103
11838
|
const retryResponse = await fetch(url, {
|
|
12104
11839
|
method: httpMethod,
|
|
@@ -12108,15 +11843,7 @@ function MppPay({
|
|
|
12108
11843
|
Authorization: authHeader
|
|
12109
11844
|
}
|
|
12110
11845
|
});
|
|
12111
|
-
|
|
12112
|
-
retryResponse.headers.entries()
|
|
12113
|
-
);
|
|
12114
|
-
const body = await retryResponse.text();
|
|
12115
|
-
setResult({
|
|
12116
|
-
status: retryResponse.status,
|
|
12117
|
-
headers: responseHeaders,
|
|
12118
|
-
body
|
|
12119
|
-
});
|
|
11846
|
+
setResult(await readPayResult(retryResponse, { failOnError: true }));
|
|
12120
11847
|
setStep("done");
|
|
12121
11848
|
onComplete();
|
|
12122
11849
|
} catch (err) {
|
|
@@ -12133,191 +11860,143 @@ function MppPay({
|
|
|
12133
11860
|
done: "Done"
|
|
12134
11861
|
};
|
|
12135
11862
|
if (error) {
|
|
12136
|
-
return /* @__PURE__ */
|
|
11863
|
+
return /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
|
|
12137
11864
|
"Error: ",
|
|
12138
11865
|
error
|
|
12139
11866
|
] });
|
|
12140
11867
|
}
|
|
12141
|
-
return /* @__PURE__ */
|
|
12142
|
-
step !== "done" && /* @__PURE__ */
|
|
12143
|
-
/* @__PURE__ */
|
|
11868
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
11869
|
+
step !== "done" && /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsxs3(Text4, { color: "cyan", children: [
|
|
11870
|
+
/* @__PURE__ */ jsx5(Spinner2, { type: "dots" }),
|
|
12144
11871
|
" ",
|
|
12145
11872
|
stepLabels[step],
|
|
12146
11873
|
"..."
|
|
12147
11874
|
] }) }),
|
|
12148
|
-
result && /* @__PURE__ */
|
|
12149
|
-
/* @__PURE__ */
|
|
11875
|
+
result && /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
11876
|
+
/* @__PURE__ */ jsxs3(Text4, { color: "green", children: [
|
|
12150
11877
|
"HTTP ",
|
|
12151
11878
|
result.status
|
|
12152
11879
|
] }),
|
|
12153
|
-
/* @__PURE__ */
|
|
11880
|
+
/* @__PURE__ */ jsx5(Text4, { children: result.body })
|
|
12154
11881
|
] })
|
|
12155
11882
|
] });
|
|
12156
11883
|
}
|
|
12157
11884
|
|
|
12158
11885
|
// src/commands/mpp/schema.ts
|
|
12159
|
-
import { z as
|
|
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
|
-
};
|
|
11886
|
+
import { z as z2 } from "incur";
|
|
11887
|
+
var payOptions = z2.object({
|
|
11888
|
+
spendRequestId: z2.string().describe(
|
|
11889
|
+
'Approved spend request ID with credential_type "shared_payment_token"'
|
|
11890
|
+
),
|
|
11891
|
+
method: z2.string().optional().describe("HTTP method (default: GET, or POST if --data is provided)"),
|
|
11892
|
+
data: z2.string().optional().describe("Request body (implies POST if --method is not set)"),
|
|
11893
|
+
header: z2.array(z2.string()).default([]).describe('Request header in "Name: Value" format (repeatable)')
|
|
11894
|
+
});
|
|
11895
|
+
var decodeOptions = z2.object({
|
|
11896
|
+
challenge: z2.string().describe(
|
|
11897
|
+
"Raw WWW-Authenticate header value; may include multiple payment challenges"
|
|
11898
|
+
)
|
|
11899
|
+
});
|
|
12222
11900
|
|
|
12223
11901
|
// src/commands/mpp/index.tsx
|
|
12224
|
-
import { jsx as
|
|
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: () => {
|
|
11902
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
11903
|
+
function createMppCli(repository) {
|
|
11904
|
+
const cli2 = Cli2.create("mpp", {
|
|
11905
|
+
description: "Machine payment protocol (MPP) commands"
|
|
11906
|
+
});
|
|
11907
|
+
cli2.command("pay", {
|
|
11908
|
+
description: "Complete a machine payment protocol (MPP) payment using an approved spend request",
|
|
11909
|
+
args: z3.object({
|
|
11910
|
+
url: z3.string().describe("URL to pay")
|
|
11911
|
+
}),
|
|
11912
|
+
options: payOptions,
|
|
11913
|
+
alias: { method: "X", data: "d", header: "H" },
|
|
11914
|
+
outputPolicy: "agent-only",
|
|
11915
|
+
async run(c) {
|
|
11916
|
+
if (!storage.isAuthenticated()) {
|
|
11917
|
+
return c.error({
|
|
11918
|
+
code: "NOT_AUTHENTICATED",
|
|
11919
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
11920
|
+
cta: {
|
|
11921
|
+
commands: [
|
|
11922
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
11923
|
+
]
|
|
12281
11924
|
}
|
|
12282
|
-
}
|
|
12283
|
-
|
|
12284
|
-
|
|
11925
|
+
});
|
|
11926
|
+
}
|
|
11927
|
+
const url = c.args.url;
|
|
11928
|
+
const opts = c.options;
|
|
11929
|
+
const method = opts.method;
|
|
11930
|
+
const data = opts.data;
|
|
11931
|
+
const headers = opts.header?.length ? opts.header : void 0;
|
|
11932
|
+
if (!c.agent && !c.formatExplicit) {
|
|
11933
|
+
return new Promise((resolve) => {
|
|
11934
|
+
const { waitUntilExit } = render2(
|
|
11935
|
+
/* @__PURE__ */ jsx6(
|
|
11936
|
+
MppPay,
|
|
11937
|
+
{
|
|
11938
|
+
url,
|
|
11939
|
+
spendRequestId: opts.spendRequestId,
|
|
11940
|
+
method,
|
|
11941
|
+
data,
|
|
11942
|
+
headers,
|
|
11943
|
+
repository,
|
|
11944
|
+
onComplete: () => {
|
|
11945
|
+
}
|
|
11946
|
+
}
|
|
11947
|
+
)
|
|
11948
|
+
);
|
|
11949
|
+
waitUntilExit().then(async () => {
|
|
11950
|
+
resolve(
|
|
11951
|
+
await runMppPay(
|
|
11952
|
+
url,
|
|
11953
|
+
opts.spendRequestId,
|
|
11954
|
+
method,
|
|
11955
|
+
data,
|
|
11956
|
+
headers,
|
|
11957
|
+
repository
|
|
11958
|
+
)
|
|
11959
|
+
);
|
|
11960
|
+
});
|
|
11961
|
+
});
|
|
11962
|
+
}
|
|
11963
|
+
return runMppPay(
|
|
11964
|
+
url,
|
|
11965
|
+
opts.spendRequestId,
|
|
11966
|
+
method,
|
|
11967
|
+
data,
|
|
11968
|
+
headers,
|
|
11969
|
+
repository
|
|
11970
|
+
);
|
|
11971
|
+
}
|
|
12285
11972
|
});
|
|
12286
|
-
|
|
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
|
-
});
|
|
11973
|
+
cli2.command("decode", {
|
|
11974
|
+
description: "Decode a stripe WWW-Authenticate challenge and extract network_id",
|
|
11975
|
+
options: decodeOptions,
|
|
11976
|
+
outputPolicy: "agent-only",
|
|
11977
|
+
async run(c) {
|
|
11978
|
+
const decoded = decodeStripeChallenge(c.options.challenge);
|
|
11979
|
+
if (!c.agent && !c.formatExplicit) {
|
|
11980
|
+
return new Promise((resolve) => {
|
|
11981
|
+
const { waitUntilExit } = render2(
|
|
11982
|
+
/* @__PURE__ */ jsx6(DecodeChallengeView, { decoded })
|
|
11983
|
+
);
|
|
11984
|
+
waitUntilExit().then(() => resolve(decoded));
|
|
11985
|
+
});
|
|
11986
|
+
}
|
|
11987
|
+
return decoded;
|
|
11988
|
+
}
|
|
12314
11989
|
});
|
|
12315
|
-
return
|
|
11990
|
+
return cli2;
|
|
12316
11991
|
}
|
|
12317
11992
|
|
|
11993
|
+
// src/commands/payment-methods/index.tsx
|
|
11994
|
+
import { Cli as Cli3 } from "incur";
|
|
11995
|
+
import { render as render3 } from "ink";
|
|
11996
|
+
|
|
12318
11997
|
// src/commands/payment-methods/add.tsx
|
|
12319
|
-
import { Box as
|
|
12320
|
-
import { jsx as
|
|
11998
|
+
import { Box as Box5, Text as Text5, useApp, useInput as useInput2 } from "ink";
|
|
11999
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
12321
12000
|
var WALLET_URL = "https://app.link.com/wallet";
|
|
12322
12001
|
var AddPaymentMethod = () => {
|
|
12323
12002
|
const { exit } = useApp();
|
|
@@ -12327,10 +12006,10 @@ var AddPaymentMethod = () => {
|
|
|
12327
12006
|
exit();
|
|
12328
12007
|
}
|
|
12329
12008
|
});
|
|
12330
|
-
return /* @__PURE__ */
|
|
12331
|
-
/* @__PURE__ */
|
|
12332
|
-
/* @__PURE__ */
|
|
12333
|
-
|
|
12009
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingY: 1, children: [
|
|
12010
|
+
/* @__PURE__ */ jsx7(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text5, { bold: true, children: "Add Payment Method" }) }),
|
|
12011
|
+
/* @__PURE__ */ jsxs4(
|
|
12012
|
+
Box5,
|
|
12334
12013
|
{
|
|
12335
12014
|
flexDirection: "column",
|
|
12336
12015
|
borderStyle: "round",
|
|
@@ -12338,12 +12017,12 @@ var AddPaymentMethod = () => {
|
|
|
12338
12017
|
paddingX: 2,
|
|
12339
12018
|
paddingY: 1,
|
|
12340
12019
|
children: [
|
|
12341
|
-
/* @__PURE__ */
|
|
12020
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
12342
12021
|
"Open:",
|
|
12343
12022
|
" ",
|
|
12344
|
-
/* @__PURE__ */
|
|
12023
|
+
/* @__PURE__ */ jsx7(Text5, { bold: true, color: "cyan", children: WALLET_URL })
|
|
12345
12024
|
] }),
|
|
12346
|
-
/* @__PURE__ */
|
|
12025
|
+
/* @__PURE__ */ jsx7(Text5, { dimColor: true, children: "Press Enter to open in browser" })
|
|
12347
12026
|
]
|
|
12348
12027
|
}
|
|
12349
12028
|
)
|
|
@@ -12351,20 +12030,20 @@ var AddPaymentMethod = () => {
|
|
|
12351
12030
|
};
|
|
12352
12031
|
|
|
12353
12032
|
// src/commands/payment-methods/list.tsx
|
|
12354
|
-
import { Box as
|
|
12033
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
12355
12034
|
import Spinner3 from "ink-spinner";
|
|
12356
|
-
import { useEffect as
|
|
12357
|
-
import { jsx as
|
|
12035
|
+
import { useEffect as useEffect4, useState as useState4 } from "react";
|
|
12036
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
12358
12037
|
var PaymentMethodsList = ({
|
|
12359
12038
|
resource,
|
|
12360
12039
|
onComplete
|
|
12361
12040
|
}) => {
|
|
12362
|
-
const [status, setStatus] =
|
|
12041
|
+
const [status, setStatus] = useState4(
|
|
12363
12042
|
"loading"
|
|
12364
12043
|
);
|
|
12365
|
-
const [methods, setMethods] =
|
|
12366
|
-
const [error, setError] =
|
|
12367
|
-
|
|
12044
|
+
const [methods, setMethods] = useState4([]);
|
|
12045
|
+
const [error, setError] = useState4("");
|
|
12046
|
+
useEffect4(() => {
|
|
12368
12047
|
const fetch2 = async () => {
|
|
12369
12048
|
try {
|
|
12370
12049
|
const result = await resource.listPaymentMethods();
|
|
@@ -12380,150 +12059,167 @@ var PaymentMethodsList = ({
|
|
|
12380
12059
|
fetch2();
|
|
12381
12060
|
}, [resource, onComplete]);
|
|
12382
12061
|
if (status === "loading") {
|
|
12383
|
-
return /* @__PURE__ */
|
|
12384
|
-
/* @__PURE__ */
|
|
12062
|
+
return /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
|
|
12063
|
+
/* @__PURE__ */ jsx8(Spinner3, { type: "dots" }),
|
|
12385
12064
|
" Loading payment methods..."
|
|
12386
12065
|
] }) });
|
|
12387
12066
|
}
|
|
12388
12067
|
if (status === "error") {
|
|
12389
|
-
return /* @__PURE__ */
|
|
12390
|
-
/* @__PURE__ */
|
|
12391
|
-
/* @__PURE__ */
|
|
12068
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
12069
|
+
/* @__PURE__ */ jsx8(Text6, { color: "red", children: "\u2717 Failed to load payment methods" }),
|
|
12070
|
+
/* @__PURE__ */ jsx8(Text6, { color: "red", children: error })
|
|
12392
12071
|
] });
|
|
12393
12072
|
}
|
|
12394
12073
|
if (methods.length === 0) {
|
|
12395
|
-
return /* @__PURE__ */
|
|
12074
|
+
return /* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsx8(Text6, { dimColor: true, children: "No payment methods found" }) });
|
|
12396
12075
|
}
|
|
12397
|
-
return /* @__PURE__ */
|
|
12398
|
-
/* @__PURE__ */
|
|
12399
|
-
/* @__PURE__ */
|
|
12076
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
12077
|
+
/* @__PURE__ */ jsx8(Text6, { bold: true, children: "Payment Methods" }),
|
|
12078
|
+
/* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginTop: 1, children: methods.map((pm) => {
|
|
12400
12079
|
const label = pm.card_details?.brand ?? pm.bank_account_details?.bank_name ?? "Bank account";
|
|
12401
12080
|
const last4 = pm.card_details?.last4 ?? pm.bank_account_details?.last4;
|
|
12402
12081
|
const suffix = [pm.nickname ? `(${pm.nickname})` : ""].filter(Boolean).join(" ");
|
|
12403
|
-
return /* @__PURE__ */
|
|
12404
|
-
/* @__PURE__ */
|
|
12082
|
+
return /* @__PURE__ */ jsx8(Box6, { paddingX: 2, children: /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
12083
|
+
/* @__PURE__ */ jsx8(Text6, { dimColor: true, children: pm.id }),
|
|
12405
12084
|
" ",
|
|
12406
12085
|
label,
|
|
12407
12086
|
" ****",
|
|
12408
12087
|
last4,
|
|
12409
12088
|
suffix ? ` ${suffix}` : "",
|
|
12410
|
-
pm.is_default ? /* @__PURE__ */
|
|
12089
|
+
pm.is_default ? /* @__PURE__ */ jsx8(Text6, { color: "green", children: " (default)" }) : ""
|
|
12411
12090
|
] }) }, pm.id);
|
|
12412
12091
|
}) })
|
|
12413
12092
|
] });
|
|
12414
12093
|
};
|
|
12415
12094
|
|
|
12416
|
-
// src/commands/payment-methods/schema.ts
|
|
12417
|
-
var PAYMENT_METHOD_SCHEMA = {
|
|
12418
|
-
id: { outputExample: '"..."', description: "Payment method ID" },
|
|
12419
|
-
type: {
|
|
12420
|
-
outputExample: '"card|bank_account"',
|
|
12421
|
-
description: "Payment method type"
|
|
12422
|
-
},
|
|
12423
|
-
is_default: {
|
|
12424
|
-
outputExample: "boolean",
|
|
12425
|
-
description: "Whether this is the default payment method"
|
|
12426
|
-
},
|
|
12427
|
-
nickname: {
|
|
12428
|
-
outputExample: '"..."',
|
|
12429
|
-
description: "Optional nickname for the payment method"
|
|
12430
|
-
},
|
|
12431
|
-
card_details: {
|
|
12432
|
-
outputExample: "{ brand, last4, exp_month, exp_year }",
|
|
12433
|
-
description: "Present when type is card"
|
|
12434
|
-
},
|
|
12435
|
-
bank_account_details: {
|
|
12436
|
-
outputExample: "{ last4, bank_name }",
|
|
12437
|
-
description: "Present when type is bank_account"
|
|
12438
|
-
}
|
|
12439
|
-
};
|
|
12440
|
-
|
|
12441
12095
|
// src/commands/payment-methods/index.tsx
|
|
12442
|
-
import { jsx as
|
|
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
|
-
});
|
|
12096
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
12097
|
+
function createPaymentMethodsCli(createResource) {
|
|
12098
|
+
const cli2 = Cli3.create("payment-methods", {
|
|
12099
|
+
description: "Payment methods management commands"
|
|
12459
12100
|
});
|
|
12460
|
-
|
|
12461
|
-
"
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12101
|
+
cli2.command("list", {
|
|
12102
|
+
description: "List all payment methods on your account",
|
|
12103
|
+
outputPolicy: "agent-only",
|
|
12104
|
+
async run(c) {
|
|
12105
|
+
if (!storage.isAuthenticated()) {
|
|
12106
|
+
return c.error({
|
|
12107
|
+
code: "NOT_AUTHENTICATED",
|
|
12108
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
12109
|
+
cta: {
|
|
12110
|
+
commands: [
|
|
12111
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
12112
|
+
]
|
|
12113
|
+
}
|
|
12114
|
+
});
|
|
12115
|
+
}
|
|
12116
|
+
const resource = createResource();
|
|
12117
|
+
if (!c.agent && !c.formatExplicit) {
|
|
12118
|
+
return new Promise((resolve) => {
|
|
12119
|
+
const { waitUntilExit } = render3(
|
|
12120
|
+
/* @__PURE__ */ jsx9(PaymentMethodsList, { resource, onComplete: () => {
|
|
12121
|
+
} })
|
|
12122
|
+
);
|
|
12123
|
+
waitUntilExit().then(async () => {
|
|
12124
|
+
resolve(await resource.listPaymentMethods());
|
|
12125
|
+
});
|
|
12126
|
+
});
|
|
12127
|
+
}
|
|
12128
|
+
return resource.listPaymentMethods();
|
|
12471
12129
|
}
|
|
12472
12130
|
});
|
|
12473
|
-
|
|
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
|
-
|
|
12131
|
+
cli2.command("add", {
|
|
12132
|
+
description: "Open the Link wallet to add a new payment method",
|
|
12133
|
+
outputPolicy: "agent-only",
|
|
12134
|
+
async run(c) {
|
|
12135
|
+
if (!storage.isAuthenticated()) {
|
|
12136
|
+
return c.error({
|
|
12137
|
+
code: "NOT_AUTHENTICATED",
|
|
12138
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
12139
|
+
cta: {
|
|
12140
|
+
commands: [
|
|
12141
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
12142
|
+
]
|
|
12143
|
+
}
|
|
12144
|
+
});
|
|
12145
|
+
}
|
|
12146
|
+
if (!c.agent && !c.formatExplicit) {
|
|
12147
|
+
return new Promise((resolve) => {
|
|
12148
|
+
const { waitUntilExit } = render3(/* @__PURE__ */ jsx9(AddPaymentMethod, {}));
|
|
12149
|
+
waitUntilExit().then(() => resolve({ url: WALLET_URL }));
|
|
12150
|
+
});
|
|
12151
|
+
}
|
|
12152
|
+
return { url: WALLET_URL };
|
|
12492
12153
|
}
|
|
12493
12154
|
});
|
|
12155
|
+
return cli2;
|
|
12494
12156
|
}
|
|
12495
12157
|
|
|
12496
|
-
// src/
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12158
|
+
// src/commands/spend-request/index.tsx
|
|
12159
|
+
import { Cli as Cli4, z as z6 } from "incur";
|
|
12160
|
+
import { render as render4 } from "ink";
|
|
12161
|
+
|
|
12162
|
+
// src/utils/line-item-parser.ts
|
|
12163
|
+
import { z as z4 } from "zod";
|
|
12164
|
+
var LineItemSchema = z4.object({
|
|
12165
|
+
name: z4.string(),
|
|
12166
|
+
url: z4.string().optional(),
|
|
12167
|
+
image_url: z4.string().optional(),
|
|
12168
|
+
description: z4.string().optional(),
|
|
12169
|
+
sku: z4.string().optional(),
|
|
12170
|
+
quantity: z4.coerce.number().optional(),
|
|
12171
|
+
unit_amount: z4.coerce.number().optional(),
|
|
12172
|
+
product_url: z4.string().optional()
|
|
12173
|
+
}).strict();
|
|
12174
|
+
var TotalSchema = z4.object({
|
|
12175
|
+
type: z4.string(),
|
|
12176
|
+
display_text: z4.string(),
|
|
12177
|
+
amount: z4.coerce.number()
|
|
12178
|
+
}).strict();
|
|
12179
|
+
function parseKvString(raw) {
|
|
12180
|
+
const result = {};
|
|
12181
|
+
for (const pair of raw.split(",")) {
|
|
12182
|
+
const idx = pair.indexOf(":");
|
|
12183
|
+
if (idx === -1) {
|
|
12184
|
+
throw new Error(`Invalid field (missing ':'): ${pair}`);
|
|
12512
12185
|
}
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12186
|
+
result[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
|
|
12187
|
+
}
|
|
12188
|
+
return result;
|
|
12189
|
+
}
|
|
12190
|
+
function formatZodError(err, prefix) {
|
|
12191
|
+
const messages = err.issues.map((issue) => {
|
|
12192
|
+
const key = issue.path[0]?.toString();
|
|
12193
|
+
return key ? `${prefix} ${key}: ${issue.message}` : `${prefix}: ${issue.message}`;
|
|
12194
|
+
});
|
|
12195
|
+
return new Error(messages.join("\n"));
|
|
12196
|
+
}
|
|
12197
|
+
function parseLineItemFlag(raw) {
|
|
12198
|
+
const obj = parseKvString(raw);
|
|
12199
|
+
try {
|
|
12200
|
+
return LineItemSchema.parse(obj);
|
|
12201
|
+
} catch (err) {
|
|
12202
|
+
if (err instanceof z4.ZodError) throw formatZodError(err, "Line item");
|
|
12203
|
+
throw err;
|
|
12204
|
+
}
|
|
12205
|
+
}
|
|
12206
|
+
function parseTotalFlag(raw) {
|
|
12207
|
+
const obj = parseKvString(raw);
|
|
12208
|
+
try {
|
|
12209
|
+
return TotalSchema.parse(obj);
|
|
12210
|
+
} catch (err) {
|
|
12211
|
+
if (err instanceof z4.ZodError) throw formatZodError(err, "Total");
|
|
12212
|
+
throw err;
|
|
12213
|
+
}
|
|
12518
12214
|
}
|
|
12519
12215
|
|
|
12520
12216
|
// src/commands/spend-request/create.tsx
|
|
12521
|
-
import { Box as
|
|
12217
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
12522
12218
|
import Spinner5 from "ink-spinner";
|
|
12523
|
-
import { useCallback, useEffect as
|
|
12219
|
+
import { useCallback, useEffect as useEffect6, useState as useState5 } from "react";
|
|
12524
12220
|
|
|
12525
12221
|
// src/commands/spend-request/app-download-qr-codes.tsx
|
|
12526
|
-
import { Box as
|
|
12222
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
12527
12223
|
import { useMemo } from "react";
|
|
12528
12224
|
|
|
12529
12225
|
// src/utils/render-qr-matrix.ts
|
|
@@ -12560,32 +12256,32 @@ function renderQrMatrix(url) {
|
|
|
12560
12256
|
}
|
|
12561
12257
|
|
|
12562
12258
|
// src/commands/spend-request/app-download-qr-codes.tsx
|
|
12563
|
-
import { jsx as
|
|
12259
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
12564
12260
|
var DOWNLOAD_URL = "https://link.com/download";
|
|
12565
12261
|
var AppDownloadQrCodes = () => {
|
|
12566
12262
|
const qrLines = useMemo(() => renderQrMatrix(DOWNLOAD_URL), []);
|
|
12567
|
-
return /* @__PURE__ */
|
|
12568
|
-
/* @__PURE__ */
|
|
12569
|
-
/* @__PURE__ */
|
|
12263
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
12264
|
+
/* @__PURE__ */ jsx10(Text7, { dimColor: true, children: "New! Get the Link app to approve spend requests easily" }),
|
|
12265
|
+
/* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", alignItems: "flex-start", marginTop: 1, children: [
|
|
12570
12266
|
qrLines.map((line, i) => (
|
|
12571
12267
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable static array
|
|
12572
|
-
/* @__PURE__ */
|
|
12268
|
+
/* @__PURE__ */ jsx10(Text7, { children: line }, i)
|
|
12573
12269
|
)),
|
|
12574
|
-
/* @__PURE__ */
|
|
12270
|
+
/* @__PURE__ */ jsx10(Text7, { dimColor: true, children: DOWNLOAD_URL })
|
|
12575
12271
|
] })
|
|
12576
12272
|
] });
|
|
12577
12273
|
};
|
|
12578
12274
|
|
|
12579
12275
|
// src/commands/spend-request/approval-waiting-view.tsx
|
|
12580
|
-
import { Box as
|
|
12276
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
12581
12277
|
import Spinner4 from "ink-spinner";
|
|
12582
|
-
import { jsx as
|
|
12278
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
12583
12279
|
var ApprovalWaitingView = ({
|
|
12584
12280
|
status,
|
|
12585
12281
|
approvalUrl
|
|
12586
|
-
}) => /* @__PURE__ */
|
|
12587
|
-
/* @__PURE__ */
|
|
12588
|
-
|
|
12282
|
+
}) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingY: 1, children: [
|
|
12283
|
+
/* @__PURE__ */ jsxs7(
|
|
12284
|
+
Box8,
|
|
12589
12285
|
{
|
|
12590
12286
|
flexDirection: "column",
|
|
12591
12287
|
borderStyle: "round",
|
|
@@ -12593,25 +12289,51 @@ var ApprovalWaitingView = ({
|
|
|
12593
12289
|
paddingX: 2,
|
|
12594
12290
|
paddingY: 1,
|
|
12595
12291
|
children: [
|
|
12596
|
-
/* @__PURE__ */
|
|
12292
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
12597
12293
|
"Approve at:",
|
|
12598
12294
|
" ",
|
|
12599
|
-
/* @__PURE__ */
|
|
12295
|
+
/* @__PURE__ */ jsx11(Text8, { bold: true, color: "cyan", children: approvalUrl })
|
|
12600
12296
|
] }),
|
|
12601
|
-
/* @__PURE__ */
|
|
12297
|
+
/* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Press Enter to open in browser" })
|
|
12602
12298
|
]
|
|
12603
12299
|
}
|
|
12604
12300
|
),
|
|
12605
|
-
/* @__PURE__ */
|
|
12606
|
-
/* @__PURE__ */
|
|
12607
|
-
/* @__PURE__ */
|
|
12301
|
+
/* @__PURE__ */ jsx11(AppDownloadQrCodes, {}),
|
|
12302
|
+
/* @__PURE__ */ jsx11(Box8, { marginTop: 1, children: status === "polling" ? /* @__PURE__ */ jsxs7(Text8, { color: "cyan", children: [
|
|
12303
|
+
/* @__PURE__ */ jsx11(Spinner4, { type: "dots" }),
|
|
12608
12304
|
" Waiting for approval..."
|
|
12609
|
-
] }) : /* @__PURE__ */
|
|
12305
|
+
] }) : /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Waiting..." }) })
|
|
12610
12306
|
] });
|
|
12611
12307
|
|
|
12612
12308
|
// src/commands/spend-request/use-approval-polling.ts
|
|
12613
12309
|
import { useInput as useInput3 } from "ink";
|
|
12614
|
-
import { useEffect as
|
|
12310
|
+
import { useEffect as useEffect5 } from "react";
|
|
12311
|
+
|
|
12312
|
+
// src/utils/poll-until-approved.ts
|
|
12313
|
+
function pollUntilApproved(repository, id, options = {}) {
|
|
12314
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
12315
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
12316
|
+
const startTime = Date.now();
|
|
12317
|
+
const poll = async () => {
|
|
12318
|
+
const elapsed = Date.now() - startTime;
|
|
12319
|
+
if (elapsed > timeoutMs) {
|
|
12320
|
+
throw new Error("Approval polling timed out");
|
|
12321
|
+
}
|
|
12322
|
+
const request = await repository.getSpendRequest(id);
|
|
12323
|
+
if (!request) {
|
|
12324
|
+
throw new Error(`Spend request ${id} not found`);
|
|
12325
|
+
}
|
|
12326
|
+
if (request.status !== "created" && request.status !== "pending_approval") {
|
|
12327
|
+
return request;
|
|
12328
|
+
}
|
|
12329
|
+
options.onProgress?.(Math.floor(elapsed / 1e3));
|
|
12330
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
12331
|
+
return poll();
|
|
12332
|
+
};
|
|
12333
|
+
return poll();
|
|
12334
|
+
}
|
|
12335
|
+
|
|
12336
|
+
// src/commands/spend-request/use-approval-polling.ts
|
|
12615
12337
|
function useApprovalPolling({
|
|
12616
12338
|
status,
|
|
12617
12339
|
setStatus,
|
|
@@ -12629,12 +12351,12 @@ function useApprovalPolling({
|
|
|
12629
12351
|
},
|
|
12630
12352
|
{ isActive: isWaiting }
|
|
12631
12353
|
);
|
|
12632
|
-
|
|
12354
|
+
useEffect5(() => {
|
|
12633
12355
|
if (status !== "waiting") return;
|
|
12634
12356
|
const timeout = setTimeout(() => setStatus("polling"), 1e3);
|
|
12635
12357
|
return () => clearTimeout(timeout);
|
|
12636
12358
|
}, [status, setStatus]);
|
|
12637
|
-
|
|
12359
|
+
useEffect5(() => {
|
|
12638
12360
|
if (status !== "polling" || !requestId) return;
|
|
12639
12361
|
let cancelled = false;
|
|
12640
12362
|
const poll = async () => {
|
|
@@ -12668,16 +12390,16 @@ function useApprovalPolling({
|
|
|
12668
12390
|
}
|
|
12669
12391
|
|
|
12670
12392
|
// src/commands/spend-request/create.tsx
|
|
12671
|
-
import { Fragment, jsx as
|
|
12393
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
12672
12394
|
var CreateSpendRequest = ({
|
|
12673
12395
|
repository,
|
|
12674
12396
|
params,
|
|
12675
12397
|
requestApproval = false,
|
|
12676
12398
|
onComplete
|
|
12677
12399
|
}) => {
|
|
12678
|
-
const [status, setStatus] =
|
|
12679
|
-
const [request, setRequest] =
|
|
12680
|
-
const [error, setError] =
|
|
12400
|
+
const [status, setStatus] = useState5("creating");
|
|
12401
|
+
const [request, setRequest] = useState5(null);
|
|
12402
|
+
const [error, setError] = useState5("");
|
|
12681
12403
|
const approvalUrl = request?.approval_url ?? "";
|
|
12682
12404
|
const onSuccess = useCallback(
|
|
12683
12405
|
(result) => setRequest(result),
|
|
@@ -12694,7 +12416,7 @@ var CreateSpendRequest = ({
|
|
|
12694
12416
|
onSuccess,
|
|
12695
12417
|
onError
|
|
12696
12418
|
});
|
|
12697
|
-
|
|
12419
|
+
useEffect6(() => {
|
|
12698
12420
|
const create = async () => {
|
|
12699
12421
|
try {
|
|
12700
12422
|
const result = await repository.createSpendRequest(params);
|
|
@@ -12714,57 +12436,57 @@ var CreateSpendRequest = ({
|
|
|
12714
12436
|
create();
|
|
12715
12437
|
}, [repository, params, requestApproval, onComplete]);
|
|
12716
12438
|
if (status === "creating") {
|
|
12717
|
-
return /* @__PURE__ */
|
|
12718
|
-
/* @__PURE__ */
|
|
12439
|
+
return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
|
|
12440
|
+
/* @__PURE__ */ jsx12(Spinner5, { type: "dots" }),
|
|
12719
12441
|
" Creating spend request..."
|
|
12720
12442
|
] }) });
|
|
12721
12443
|
}
|
|
12722
12444
|
if (status === "error") {
|
|
12723
|
-
return /* @__PURE__ */
|
|
12724
|
-
/* @__PURE__ */
|
|
12725
|
-
/* @__PURE__ */
|
|
12445
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
12446
|
+
/* @__PURE__ */ jsx12(Text9, { color: "red", children: "\u2717 Failed to create spend request" }),
|
|
12447
|
+
/* @__PURE__ */ jsx12(Text9, { color: "red", children: error })
|
|
12726
12448
|
] });
|
|
12727
12449
|
}
|
|
12728
12450
|
if (status === "success") {
|
|
12729
|
-
return /* @__PURE__ */
|
|
12730
|
-
/* @__PURE__ */
|
|
12731
|
-
/* @__PURE__ */
|
|
12732
|
-
/* @__PURE__ */
|
|
12451
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
12452
|
+
/* @__PURE__ */ jsx12(Text9, { color: "green", children: "\u2713 Spend request created" }),
|
|
12453
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12454
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12733
12455
|
"ID: ",
|
|
12734
|
-
/* @__PURE__ */
|
|
12456
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id })
|
|
12735
12457
|
] }),
|
|
12736
|
-
/* @__PURE__ */
|
|
12458
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12737
12459
|
"Status: ",
|
|
12738
|
-
/* @__PURE__ */
|
|
12460
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.status })
|
|
12739
12461
|
] }),
|
|
12740
|
-
/* @__PURE__ */
|
|
12462
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12741
12463
|
"Amount:",
|
|
12742
12464
|
" ",
|
|
12743
|
-
/* @__PURE__ */
|
|
12465
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: (() => {
|
|
12744
12466
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
12745
12467
|
return t ? String(t.amount) : "N/A";
|
|
12746
12468
|
})() })
|
|
12747
12469
|
] }),
|
|
12748
|
-
/* @__PURE__ */
|
|
12470
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12749
12471
|
"Merchant: ",
|
|
12750
|
-
/* @__PURE__ */
|
|
12472
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.merchant_name })
|
|
12751
12473
|
] }),
|
|
12752
|
-
/* @__PURE__ */
|
|
12474
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12753
12475
|
"Line Items:",
|
|
12754
12476
|
" ",
|
|
12755
|
-
/* @__PURE__ */
|
|
12477
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
|
|
12756
12478
|
] })
|
|
12757
12479
|
] }),
|
|
12758
|
-
/* @__PURE__ */
|
|
12480
|
+
/* @__PURE__ */ jsx12(AppDownloadQrCodes, {})
|
|
12759
12481
|
] });
|
|
12760
12482
|
}
|
|
12761
|
-
return /* @__PURE__ */
|
|
12762
|
-
/* @__PURE__ */
|
|
12483
|
+
return /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
12484
|
+
/* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "green", children: [
|
|
12763
12485
|
"\u2713 Spend request created (ID: ",
|
|
12764
|
-
/* @__PURE__ */
|
|
12486
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id }),
|
|
12765
12487
|
")"
|
|
12766
12488
|
] }) }),
|
|
12767
|
-
/* @__PURE__ */
|
|
12489
|
+
/* @__PURE__ */ jsx12(
|
|
12768
12490
|
ApprovalWaitingView,
|
|
12769
12491
|
{
|
|
12770
12492
|
status,
|
|
@@ -12775,19 +12497,19 @@ var CreateSpendRequest = ({
|
|
|
12775
12497
|
};
|
|
12776
12498
|
|
|
12777
12499
|
// src/commands/spend-request/request-approval.tsx
|
|
12778
|
-
import { Box as
|
|
12500
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
12779
12501
|
import Spinner6 from "ink-spinner";
|
|
12780
|
-
import { useCallback as useCallback2, useEffect as
|
|
12781
|
-
import { jsx as
|
|
12502
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useState as useState6 } from "react";
|
|
12503
|
+
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
12782
12504
|
var RequestApproval = ({
|
|
12783
12505
|
repository,
|
|
12784
12506
|
id,
|
|
12785
12507
|
onComplete
|
|
12786
12508
|
}) => {
|
|
12787
|
-
const [status, setStatus] =
|
|
12788
|
-
const [approvalUrl, setApprovalUrl] =
|
|
12789
|
-
const [result, setResult] =
|
|
12790
|
-
const [error, setError] =
|
|
12509
|
+
const [status, setStatus] = useState6("requesting");
|
|
12510
|
+
const [approvalUrl, setApprovalUrl] = useState6("");
|
|
12511
|
+
const [result, setResult] = useState6(null);
|
|
12512
|
+
const [error, setError] = useState6("");
|
|
12791
12513
|
const onSuccess = useCallback2((r) => setResult(r), []);
|
|
12792
12514
|
const onError = useCallback2((msg) => setError(msg), []);
|
|
12793
12515
|
useApprovalPolling({
|
|
@@ -12800,7 +12522,7 @@ var RequestApproval = ({
|
|
|
12800
12522
|
onSuccess,
|
|
12801
12523
|
onError
|
|
12802
12524
|
});
|
|
12803
|
-
|
|
12525
|
+
useEffect7(() => {
|
|
12804
12526
|
const request = async () => {
|
|
12805
12527
|
try {
|
|
12806
12528
|
const res = await repository.requestApproval(id);
|
|
@@ -12814,45 +12536,45 @@ var RequestApproval = ({
|
|
|
12814
12536
|
request();
|
|
12815
12537
|
}, [repository, id]);
|
|
12816
12538
|
if (status === "requesting") {
|
|
12817
|
-
return /* @__PURE__ */
|
|
12818
|
-
/* @__PURE__ */
|
|
12539
|
+
return /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
|
|
12540
|
+
/* @__PURE__ */ jsx13(Spinner6, { type: "dots" }),
|
|
12819
12541
|
" Requesting approval..."
|
|
12820
12542
|
] }) });
|
|
12821
12543
|
}
|
|
12822
12544
|
if (status === "error") {
|
|
12823
|
-
return /* @__PURE__ */
|
|
12824
|
-
/* @__PURE__ */
|
|
12825
|
-
/* @__PURE__ */
|
|
12545
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
12546
|
+
/* @__PURE__ */ jsx13(Text10, { color: "red", children: "\u2717 Failed to request approval" }),
|
|
12547
|
+
/* @__PURE__ */ jsx13(Text10, { color: "red", children: error })
|
|
12826
12548
|
] });
|
|
12827
12549
|
}
|
|
12828
12550
|
if (status === "success") {
|
|
12829
|
-
return /* @__PURE__ */
|
|
12830
|
-
/* @__PURE__ */
|
|
12831
|
-
/* @__PURE__ */
|
|
12832
|
-
/* @__PURE__ */
|
|
12551
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
12552
|
+
/* @__PURE__ */ jsx13(Text10, { color: "green", children: "\u2713 Approval completed" }),
|
|
12553
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12554
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12833
12555
|
"ID: ",
|
|
12834
|
-
/* @__PURE__ */
|
|
12556
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.id })
|
|
12835
12557
|
] }),
|
|
12836
|
-
/* @__PURE__ */
|
|
12558
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12837
12559
|
"Status: ",
|
|
12838
|
-
/* @__PURE__ */
|
|
12560
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.status })
|
|
12839
12561
|
] }),
|
|
12840
|
-
/* @__PURE__ */
|
|
12562
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12841
12563
|
"Amount:",
|
|
12842
12564
|
" ",
|
|
12843
|
-
/* @__PURE__ */
|
|
12565
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: (() => {
|
|
12844
12566
|
const t = result?.totals.find((t2) => t2.type === "total");
|
|
12845
12567
|
return t ? String(t.amount) : "N/A";
|
|
12846
12568
|
})() })
|
|
12847
12569
|
] }),
|
|
12848
|
-
/* @__PURE__ */
|
|
12570
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12849
12571
|
"Merchant: ",
|
|
12850
|
-
/* @__PURE__ */
|
|
12572
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.merchant_name })
|
|
12851
12573
|
] })
|
|
12852
12574
|
] })
|
|
12853
12575
|
] });
|
|
12854
12576
|
}
|
|
12855
|
-
return /* @__PURE__ */
|
|
12577
|
+
return /* @__PURE__ */ jsx13(
|
|
12856
12578
|
ApprovalWaitingView,
|
|
12857
12579
|
{
|
|
12858
12580
|
status,
|
|
@@ -12862,10 +12584,10 @@ var RequestApproval = ({
|
|
|
12862
12584
|
};
|
|
12863
12585
|
|
|
12864
12586
|
// src/commands/spend-request/retrieve.tsx
|
|
12865
|
-
import { Box as
|
|
12587
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
12866
12588
|
import Spinner7 from "ink-spinner";
|
|
12867
|
-
import { useEffect as
|
|
12868
|
-
import { jsx as
|
|
12589
|
+
import { useEffect as useEffect8, useRef, useState as useState7 } from "react";
|
|
12590
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
12869
12591
|
var RetrieveSpendRequest = ({
|
|
12870
12592
|
repository,
|
|
12871
12593
|
id,
|
|
@@ -12873,20 +12595,20 @@ var RetrieveSpendRequest = ({
|
|
|
12873
12595
|
include,
|
|
12874
12596
|
onComplete
|
|
12875
12597
|
}) => {
|
|
12876
|
-
const [phase, setPhase] =
|
|
12877
|
-
const [request, setRequest] =
|
|
12878
|
-
const [error, setError] =
|
|
12879
|
-
const [elapsed, setElapsed] =
|
|
12598
|
+
const [phase, setPhase] = useState7("fetching");
|
|
12599
|
+
const [request, setRequest] = useState7(null);
|
|
12600
|
+
const [error, setError] = useState7("");
|
|
12601
|
+
const [elapsed, setElapsed] = useState7(0);
|
|
12880
12602
|
const startTimeRef = useRef(Date.now());
|
|
12881
12603
|
const pollRef = useRef(null);
|
|
12882
12604
|
const timerRef = useRef(null);
|
|
12883
|
-
|
|
12605
|
+
useEffect8(() => {
|
|
12884
12606
|
return () => {
|
|
12885
12607
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
12886
12608
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
12887
12609
|
};
|
|
12888
12610
|
}, []);
|
|
12889
|
-
|
|
12611
|
+
useEffect8(() => {
|
|
12890
12612
|
const fetch2 = async () => {
|
|
12891
12613
|
try {
|
|
12892
12614
|
const result = await repository.getSpendRequest(id, { include });
|
|
@@ -12915,7 +12637,7 @@ var RetrieveSpendRequest = ({
|
|
|
12915
12637
|
};
|
|
12916
12638
|
fetch2();
|
|
12917
12639
|
}, [repository, id, include, onComplete]);
|
|
12918
|
-
|
|
12640
|
+
useEffect8(() => {
|
|
12919
12641
|
if (phase !== "polling") return;
|
|
12920
12642
|
timerRef.current = setInterval(() => {
|
|
12921
12643
|
const secs = Math.floor((Date.now() - startTimeRef.current) / 1e3);
|
|
@@ -12954,170 +12676,170 @@ var RetrieveSpendRequest = ({
|
|
|
12954
12676
|
};
|
|
12955
12677
|
}, [phase, repository, id, include, timeout, onComplete]);
|
|
12956
12678
|
if (phase === "fetching") {
|
|
12957
|
-
return /* @__PURE__ */
|
|
12958
|
-
/* @__PURE__ */
|
|
12679
|
+
return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
|
|
12680
|
+
/* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
|
|
12959
12681
|
" Retrieving spend request ",
|
|
12960
12682
|
id,
|
|
12961
12683
|
"..."
|
|
12962
12684
|
] }) });
|
|
12963
12685
|
}
|
|
12964
12686
|
if (phase === "error") {
|
|
12965
|
-
return /* @__PURE__ */
|
|
12687
|
+
return /* @__PURE__ */ jsx14(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
|
|
12966
12688
|
"\u2717 ",
|
|
12967
12689
|
error
|
|
12968
12690
|
] }) });
|
|
12969
12691
|
}
|
|
12970
12692
|
if (phase === "timeout") {
|
|
12971
|
-
return /* @__PURE__ */
|
|
12972
|
-
/* @__PURE__ */
|
|
12693
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
12694
|
+
/* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
|
|
12973
12695
|
"\u2717 Timed out waiting for approval after ",
|
|
12974
12696
|
timeout,
|
|
12975
12697
|
"s"
|
|
12976
12698
|
] }),
|
|
12977
|
-
request && /* @__PURE__ */
|
|
12978
|
-
/* @__PURE__ */
|
|
12699
|
+
request && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12700
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
12979
12701
|
"ID: ",
|
|
12980
|
-
/* @__PURE__ */
|
|
12702
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.id })
|
|
12981
12703
|
] }),
|
|
12982
|
-
/* @__PURE__ */
|
|
12704
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
12983
12705
|
"Status: ",
|
|
12984
|
-
/* @__PURE__ */
|
|
12706
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.status })
|
|
12985
12707
|
] })
|
|
12986
12708
|
] })
|
|
12987
12709
|
] });
|
|
12988
12710
|
}
|
|
12989
12711
|
if (phase === "polling") {
|
|
12990
|
-
return /* @__PURE__ */
|
|
12991
|
-
/* @__PURE__ */
|
|
12992
|
-
/* @__PURE__ */
|
|
12712
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
12713
|
+
/* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
|
|
12714
|
+
/* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
|
|
12993
12715
|
" Awaiting approval... (",
|
|
12994
12716
|
elapsed,
|
|
12995
12717
|
"s elapsed)"
|
|
12996
12718
|
] }) }),
|
|
12997
|
-
request?.approval_url && /* @__PURE__ */
|
|
12719
|
+
request?.approval_url && /* @__PURE__ */ jsx14(Box11, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
12998
12720
|
"Approval URL: ",
|
|
12999
|
-
/* @__PURE__ */
|
|
12721
|
+
/* @__PURE__ */ jsx14(Text11, { color: "cyan", children: request.approval_url })
|
|
13000
12722
|
] }) })
|
|
13001
12723
|
] });
|
|
13002
12724
|
}
|
|
13003
12725
|
if (phase === "declined") {
|
|
13004
|
-
return /* @__PURE__ */
|
|
13005
|
-
/* @__PURE__ */
|
|
13006
|
-
/* @__PURE__ */
|
|
13007
|
-
/* @__PURE__ */
|
|
12726
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
12727
|
+
/* @__PURE__ */ jsx14(Text11, { color: "red", children: "\u2717 Spend request declined" }),
|
|
12728
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12729
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13008
12730
|
"ID: ",
|
|
13009
|
-
/* @__PURE__ */
|
|
12731
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
|
|
13010
12732
|
] }),
|
|
13011
|
-
/* @__PURE__ */
|
|
12733
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13012
12734
|
"Status:",
|
|
13013
12735
|
" ",
|
|
13014
|
-
/* @__PURE__ */
|
|
12736
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, color: "red", children: request?.status })
|
|
13015
12737
|
] }),
|
|
13016
|
-
/* @__PURE__ */
|
|
12738
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13017
12739
|
"Amount:",
|
|
13018
12740
|
" ",
|
|
13019
|
-
/* @__PURE__ */
|
|
12741
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
|
|
13020
12742
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
13021
12743
|
return t ? String(t.amount) : "N/A";
|
|
13022
12744
|
})() })
|
|
13023
12745
|
] }),
|
|
13024
|
-
/* @__PURE__ */
|
|
12746
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13025
12747
|
"Merchant: ",
|
|
13026
|
-
/* @__PURE__ */
|
|
12748
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
|
|
13027
12749
|
] })
|
|
13028
12750
|
] })
|
|
13029
12751
|
] });
|
|
13030
12752
|
}
|
|
13031
|
-
return /* @__PURE__ */
|
|
13032
|
-
/* @__PURE__ */
|
|
13033
|
-
/* @__PURE__ */
|
|
13034
|
-
/* @__PURE__ */
|
|
12753
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
12754
|
+
/* @__PURE__ */ jsx14(Text11, { color: "green", children: "\u2713 Spend request approved" }),
|
|
12755
|
+
/* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12756
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13035
12757
|
"ID: ",
|
|
13036
|
-
/* @__PURE__ */
|
|
12758
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
|
|
13037
12759
|
] }),
|
|
13038
|
-
/* @__PURE__ */
|
|
12760
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13039
12761
|
"Status:",
|
|
13040
12762
|
" ",
|
|
13041
|
-
/* @__PURE__ */
|
|
12763
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, color: "green", children: request?.status })
|
|
13042
12764
|
] }),
|
|
13043
|
-
/* @__PURE__ */
|
|
12765
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13044
12766
|
"Amount:",
|
|
13045
12767
|
" ",
|
|
13046
|
-
/* @__PURE__ */
|
|
12768
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
|
|
13047
12769
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
13048
12770
|
return t ? String(t.amount) : "N/A";
|
|
13049
12771
|
})() })
|
|
13050
12772
|
] }),
|
|
13051
|
-
/* @__PURE__ */
|
|
12773
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13052
12774
|
"Merchant: ",
|
|
13053
|
-
/* @__PURE__ */
|
|
12775
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
|
|
13054
12776
|
] }),
|
|
13055
|
-
/* @__PURE__ */
|
|
12777
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13056
12778
|
"Line Items:",
|
|
13057
12779
|
" ",
|
|
13058
|
-
/* @__PURE__ */
|
|
12780
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
|
|
13059
12781
|
] }),
|
|
13060
|
-
request?.shared_payment_token && /* @__PURE__ */
|
|
13061
|
-
/* @__PURE__ */
|
|
12782
|
+
request?.shared_payment_token && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
12783
|
+
/* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
|
|
13062
12784
|
"\x1B]8;;https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens\x07",
|
|
13063
12785
|
"Shared Payment Token",
|
|
13064
12786
|
"\x1B]8;;\x07",
|
|
13065
12787
|
":"
|
|
13066
12788
|
] }),
|
|
13067
|
-
/* @__PURE__ */
|
|
12789
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13068
12790
|
" ",
|
|
13069
12791
|
"Token: ",
|
|
13070
|
-
/* @__PURE__ */
|
|
12792
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.shared_payment_token.id })
|
|
13071
12793
|
] })
|
|
13072
12794
|
] }),
|
|
13073
|
-
request?.card && /* @__PURE__ */
|
|
13074
|
-
/* @__PURE__ */
|
|
13075
|
-
/* @__PURE__ */
|
|
12795
|
+
request?.card && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
12796
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: "Card Details:" }),
|
|
12797
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13076
12798
|
" ",
|
|
13077
12799
|
"Number: ",
|
|
13078
|
-
/* @__PURE__ */
|
|
12800
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.number })
|
|
13079
12801
|
] }),
|
|
13080
|
-
/* @__PURE__ */
|
|
12802
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13081
12803
|
" ",
|
|
13082
12804
|
"Brand: ",
|
|
13083
|
-
/* @__PURE__ */
|
|
12805
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.brand })
|
|
13084
12806
|
] }),
|
|
13085
|
-
/* @__PURE__ */
|
|
12807
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13086
12808
|
" ",
|
|
13087
12809
|
"Expiry:",
|
|
13088
12810
|
" ",
|
|
13089
|
-
/* @__PURE__ */
|
|
12811
|
+
/* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
|
|
13090
12812
|
String(request?.card.exp_month).padStart(2, "0"),
|
|
13091
12813
|
"/",
|
|
13092
12814
|
request?.card.exp_year
|
|
13093
12815
|
] })
|
|
13094
12816
|
] }),
|
|
13095
|
-
request?.card.cvc && /* @__PURE__ */
|
|
12817
|
+
request?.card.cvc && /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13096
12818
|
" ",
|
|
13097
12819
|
"CVC: ",
|
|
13098
|
-
/* @__PURE__ */
|
|
12820
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.card.cvc })
|
|
13099
12821
|
] }),
|
|
13100
|
-
request?.card.valid_until && /* @__PURE__ */
|
|
12822
|
+
request?.card.valid_until && /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13101
12823
|
" ",
|
|
13102
12824
|
"Valid Until:",
|
|
13103
12825
|
" ",
|
|
13104
|
-
/* @__PURE__ */
|
|
12826
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: new Date(request.card.valid_until * 1e3).toISOString() })
|
|
13105
12827
|
] }),
|
|
13106
|
-
request?.card.billing_address && /* @__PURE__ */
|
|
13107
|
-
/* @__PURE__ */
|
|
13108
|
-
/* @__PURE__ */
|
|
12828
|
+
request?.card.billing_address && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
12829
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: " Billing Address:" }),
|
|
12830
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13109
12831
|
" ",
|
|
13110
12832
|
request.card.billing_address.name
|
|
13111
12833
|
] }),
|
|
13112
|
-
/* @__PURE__ */
|
|
12834
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13113
12835
|
" ",
|
|
13114
12836
|
request.card.billing_address.line1
|
|
13115
12837
|
] }),
|
|
13116
|
-
request.card.billing_address.line2 && /* @__PURE__ */
|
|
12838
|
+
request.card.billing_address.line2 && /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13117
12839
|
" ",
|
|
13118
12840
|
request.card.billing_address.line2
|
|
13119
12841
|
] }),
|
|
13120
|
-
/* @__PURE__ */
|
|
12842
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13121
12843
|
" ",
|
|
13122
12844
|
[
|
|
13123
12845
|
request.card.billing_address.city,
|
|
@@ -13125,7 +12847,7 @@ var RetrieveSpendRequest = ({
|
|
|
13125
12847
|
request.card.billing_address.postal_code
|
|
13126
12848
|
].filter(Boolean).join(", ")
|
|
13127
12849
|
] }),
|
|
13128
|
-
/* @__PURE__ */
|
|
12850
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13129
12851
|
" ",
|
|
13130
12852
|
request.card.billing_address.country
|
|
13131
12853
|
] })
|
|
@@ -13136,226 +12858,69 @@ var RetrieveSpendRequest = ({
|
|
|
13136
12858
|
};
|
|
13137
12859
|
|
|
13138
12860
|
// 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
|
-
};
|
|
12861
|
+
import { z as z5 } from "incur";
|
|
12862
|
+
var createOptions = z5.object({
|
|
12863
|
+
paymentMethodId: z5.string().describe("Payment method ID"),
|
|
12864
|
+
credentialType: z5.enum(["shared_payment_token", "card"]).default("card").describe(
|
|
12865
|
+
'"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows'
|
|
12866
|
+
),
|
|
12867
|
+
networkId: z5.string().optional().describe(
|
|
12868
|
+
"Network ID (required for shared_payment_token) \u2014 use `link-cli mpp decode` to extract"
|
|
12869
|
+
),
|
|
12870
|
+
amount: z5.coerce.number().int().positive().max(5e4).describe("Amount in cents, max 50000 ($500.00)"),
|
|
12871
|
+
currency: z5.string().length(3).default("usd").describe("Currency code"),
|
|
12872
|
+
merchantName: z5.string().optional().describe(
|
|
12873
|
+
"Merchant name (required for card; forbidden for shared_payment_token)"
|
|
12874
|
+
),
|
|
12875
|
+
merchantUrl: z5.string().optional().describe(
|
|
12876
|
+
"Merchant URL (required for card; forbidden for shared_payment_token)"
|
|
12877
|
+
),
|
|
12878
|
+
context: z5.string().min(100).describe(
|
|
12879
|
+
"Min 100 chars \u2014 describe the purchase and rationale; the user reads this when approving"
|
|
12880
|
+
),
|
|
12881
|
+
lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
|
|
12882
|
+
total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)"),
|
|
12883
|
+
requestApproval: z5.boolean().default(true).describe("Request approval and poll until approved/denied/expired"),
|
|
12884
|
+
test: z5.boolean().default(false).describe(
|
|
12885
|
+
"Use test mode (creates testmode credentials from test card data)"
|
|
12886
|
+
)
|
|
12887
|
+
});
|
|
12888
|
+
var retrieveOptions = z5.object({
|
|
12889
|
+
timeout: z5.coerce.number().default(300).describe("Polling timeout in seconds"),
|
|
12890
|
+
interval: z5.coerce.number().default(0).describe(
|
|
12891
|
+
"Poll interval in seconds. When > 0, polls until status is terminal or timeout is reached, yielding status on each attempt."
|
|
12892
|
+
),
|
|
12893
|
+
maxAttempts: z5.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
|
|
12894
|
+
include: z5.array(z5.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)")
|
|
12895
|
+
});
|
|
12896
|
+
var updateOptions = z5.object({
|
|
12897
|
+
paymentMethodId: z5.string().optional().describe("Payment method ID"),
|
|
12898
|
+
amount: z5.coerce.number().optional().describe("Amount in cents"),
|
|
12899
|
+
merchantUrl: z5.string().optional().describe("Merchant URL"),
|
|
12900
|
+
profileId: z5.string().optional().describe("Profile ID"),
|
|
12901
|
+
merchantId: z5.string().optional().describe("Merchant ID"),
|
|
12902
|
+
currency: z5.string().optional().describe("Currency code"),
|
|
12903
|
+
lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
|
|
12904
|
+
total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)")
|
|
12905
|
+
});
|
|
13341
12906
|
|
|
13342
12907
|
// src/commands/spend-request/update.tsx
|
|
13343
|
-
import { Box as
|
|
12908
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
13344
12909
|
import Spinner8 from "ink-spinner";
|
|
13345
|
-
import { useEffect as
|
|
13346
|
-
import { jsx as
|
|
12910
|
+
import { useEffect as useEffect9, useState as useState8 } from "react";
|
|
12911
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
13347
12912
|
var UpdateSpendRequest = ({
|
|
13348
12913
|
repository,
|
|
13349
12914
|
id,
|
|
13350
12915
|
params,
|
|
13351
12916
|
onComplete
|
|
13352
12917
|
}) => {
|
|
13353
|
-
const [status, setStatus] =
|
|
12918
|
+
const [status, setStatus] = useState8(
|
|
13354
12919
|
"loading"
|
|
13355
12920
|
);
|
|
13356
|
-
const [request, setRequest] =
|
|
13357
|
-
const [error, setError] =
|
|
13358
|
-
|
|
12921
|
+
const [request, setRequest] = useState8(null);
|
|
12922
|
+
const [error, setError] = useState8("");
|
|
12923
|
+
useEffect9(() => {
|
|
13359
12924
|
const update = async () => {
|
|
13360
12925
|
try {
|
|
13361
12926
|
const result = await repository.updateSpendRequest(id, params);
|
|
@@ -13371,369 +12936,354 @@ var UpdateSpendRequest = ({
|
|
|
13371
12936
|
update();
|
|
13372
12937
|
}, [repository, id, params, onComplete]);
|
|
13373
12938
|
if (status === "loading") {
|
|
13374
|
-
return /* @__PURE__ */
|
|
13375
|
-
/* @__PURE__ */
|
|
12939
|
+
return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
|
|
12940
|
+
/* @__PURE__ */ jsx15(Spinner8, { type: "dots" }),
|
|
13376
12941
|
" Updating spend request ",
|
|
13377
12942
|
id,
|
|
13378
12943
|
"..."
|
|
13379
12944
|
] }) });
|
|
13380
12945
|
}
|
|
13381
12946
|
if (status === "error") {
|
|
13382
|
-
return /* @__PURE__ */
|
|
13383
|
-
/* @__PURE__ */
|
|
13384
|
-
/* @__PURE__ */
|
|
12947
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
|
|
12948
|
+
/* @__PURE__ */ jsx15(Text12, { color: "red", children: "\u2717 Failed to update spend request" }),
|
|
12949
|
+
/* @__PURE__ */ jsx15(Text12, { color: "red", children: error })
|
|
13385
12950
|
] });
|
|
13386
12951
|
}
|
|
13387
|
-
return /* @__PURE__ */
|
|
13388
|
-
/* @__PURE__ */
|
|
13389
|
-
/* @__PURE__ */
|
|
13390
|
-
/* @__PURE__ */
|
|
12952
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
|
|
12953
|
+
/* @__PURE__ */ jsx15(Text12, { color: "green", children: "\u2713 Spend request updated" }),
|
|
12954
|
+
/* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12955
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13391
12956
|
"ID: ",
|
|
13392
|
-
/* @__PURE__ */
|
|
12957
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
|
|
13393
12958
|
] }),
|
|
13394
|
-
/* @__PURE__ */
|
|
12959
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13395
12960
|
"Status: ",
|
|
13396
|
-
/* @__PURE__ */
|
|
12961
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.status })
|
|
13397
12962
|
] }),
|
|
13398
|
-
/* @__PURE__ */
|
|
12963
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13399
12964
|
"Amount:",
|
|
13400
12965
|
" ",
|
|
13401
|
-
/* @__PURE__ */
|
|
12966
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
|
|
13402
12967
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
13403
12968
|
return t ? String(t.amount) : "N/A";
|
|
13404
12969
|
})() })
|
|
13405
12970
|
] }),
|
|
13406
|
-
/* @__PURE__ */
|
|
12971
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13407
12972
|
"Merchant: ",
|
|
13408
|
-
/* @__PURE__ */
|
|
12973
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
|
|
13409
12974
|
] }),
|
|
13410
|
-
/* @__PURE__ */
|
|
12975
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13411
12976
|
"Line Items:",
|
|
13412
12977
|
" ",
|
|
13413
|
-
/* @__PURE__ */
|
|
12978
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
|
|
13414
12979
|
] })
|
|
13415
12980
|
] })
|
|
13416
12981
|
] });
|
|
13417
12982
|
};
|
|
13418
12983
|
|
|
13419
12984
|
// 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
|
-
});
|
|
12985
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
12986
|
+
function createSpendRequestCli(repository) {
|
|
12987
|
+
const cli2 = Cli4.create("spend-request", {
|
|
12988
|
+
description: "Spend request management commands"
|
|
13511
12989
|
});
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
"
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
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: () => {
|
|
12990
|
+
cli2.command("create", {
|
|
12991
|
+
description: "Create a new spend request",
|
|
12992
|
+
options: createOptions,
|
|
12993
|
+
alias: { merchantName: "m" },
|
|
12994
|
+
outputPolicy: "agent-only",
|
|
12995
|
+
async *run(c) {
|
|
12996
|
+
if (!storage.isAuthenticated()) {
|
|
12997
|
+
return c.error({
|
|
12998
|
+
code: "NOT_AUTHENTICATED",
|
|
12999
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13000
|
+
cta: {
|
|
13001
|
+
commands: [
|
|
13002
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13003
|
+
]
|
|
13004
|
+
}
|
|
13005
|
+
});
|
|
13006
|
+
}
|
|
13007
|
+
const opts = c.options;
|
|
13008
|
+
const requestApproval = !!opts.requestApproval;
|
|
13009
|
+
const credentialType = opts.credentialType;
|
|
13010
|
+
const networkId = opts.networkId;
|
|
13011
|
+
if (credentialType === "shared_payment_token" && !networkId) {
|
|
13012
|
+
return c.error({
|
|
13013
|
+
code: "INVALID_INPUT",
|
|
13014
|
+
message: "network-id is required when credential-type is shared_payment_token",
|
|
13015
|
+
cta: {
|
|
13016
|
+
commands: [
|
|
13017
|
+
{
|
|
13018
|
+
command: "mpp decode",
|
|
13019
|
+
description: "Decode a WWW-Authenticate challenge to extract network-id"
|
|
13020
|
+
}
|
|
13021
|
+
]
|
|
13559
13022
|
}
|
|
13023
|
+
});
|
|
13024
|
+
}
|
|
13025
|
+
if (networkId && credentialType !== "shared_payment_token") {
|
|
13026
|
+
return c.error({
|
|
13027
|
+
code: "INVALID_INPUT",
|
|
13028
|
+
message: "network-id can only be used when credential-type is shared_payment_token"
|
|
13029
|
+
});
|
|
13030
|
+
}
|
|
13031
|
+
if (credentialType !== "shared_payment_token" && !opts.merchantName) {
|
|
13032
|
+
return c.error({
|
|
13033
|
+
code: "INVALID_INPUT",
|
|
13034
|
+
message: "merchant-name is required when credential-type is card"
|
|
13035
|
+
});
|
|
13036
|
+
}
|
|
13037
|
+
if (credentialType !== "shared_payment_token" && !opts.merchantUrl) {
|
|
13038
|
+
return c.error({
|
|
13039
|
+
code: "INVALID_INPUT",
|
|
13040
|
+
message: "merchant-url is required when credential-type is card"
|
|
13041
|
+
});
|
|
13042
|
+
}
|
|
13043
|
+
const lineItems = opts.lineItem?.length ? opts.lineItem.map(
|
|
13044
|
+
(item) => typeof item === "string" ? parseLineItemFlag(item) : item
|
|
13045
|
+
) : void 0;
|
|
13046
|
+
const totals = opts.total?.length ? opts.total.map(
|
|
13047
|
+
(item) => typeof item === "string" ? parseTotalFlag(item) : item
|
|
13048
|
+
) : void 0;
|
|
13049
|
+
const createParams = {
|
|
13050
|
+
payment_details: opts.paymentMethodId,
|
|
13051
|
+
credential_type: credentialType,
|
|
13052
|
+
network_id: networkId,
|
|
13053
|
+
amount: opts.amount,
|
|
13054
|
+
currency: opts.currency,
|
|
13055
|
+
merchant_name: opts.merchantName,
|
|
13056
|
+
merchant_url: opts.merchantUrl,
|
|
13057
|
+
context: opts.context,
|
|
13058
|
+
line_items: lineItems,
|
|
13059
|
+
totals,
|
|
13060
|
+
request_approval: requestApproval || void 0,
|
|
13061
|
+
test: opts.test ? true : void 0
|
|
13062
|
+
};
|
|
13063
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13064
|
+
return new Promise((resolve) => {
|
|
13065
|
+
const { waitUntilExit } = render4(
|
|
13066
|
+
/* @__PURE__ */ jsx16(
|
|
13067
|
+
CreateSpendRequest,
|
|
13068
|
+
{
|
|
13069
|
+
repository,
|
|
13070
|
+
params: createParams,
|
|
13071
|
+
requestApproval,
|
|
13072
|
+
onComplete: () => {
|
|
13073
|
+
}
|
|
13074
|
+
}
|
|
13075
|
+
)
|
|
13076
|
+
);
|
|
13077
|
+
waitUntilExit().then(async () => {
|
|
13078
|
+
const created2 = await repository.createSpendRequest(createParams);
|
|
13079
|
+
resolve(created2);
|
|
13080
|
+
});
|
|
13081
|
+
});
|
|
13082
|
+
}
|
|
13083
|
+
const created = await repository.createSpendRequest(createParams);
|
|
13084
|
+
if (!requestApproval) {
|
|
13085
|
+
yield created;
|
|
13086
|
+
return;
|
|
13087
|
+
}
|
|
13088
|
+
yield {
|
|
13089
|
+
...created,
|
|
13090
|
+
instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${created.id} --interval 2 --max-attempts 150\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
|
|
13091
|
+
_next: {
|
|
13092
|
+
command: `spend-request retrieve ${created.id} --interval 2 --max-attempts 150`,
|
|
13093
|
+
until: "status changes from pending_approval"
|
|
13560
13094
|
}
|
|
13561
|
-
|
|
13562
|
-
}
|
|
13095
|
+
};
|
|
13096
|
+
}
|
|
13563
13097
|
});
|
|
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
|
-
);
|
|
13098
|
+
cli2.command("update", {
|
|
13099
|
+
description: "Update a spend request",
|
|
13100
|
+
args: z6.object({
|
|
13101
|
+
id: z6.string().describe("Spend request ID")
|
|
13102
|
+
}),
|
|
13103
|
+
options: updateOptions,
|
|
13104
|
+
outputPolicy: "agent-only",
|
|
13105
|
+
async run(c) {
|
|
13106
|
+
if (!storage.isAuthenticated()) {
|
|
13107
|
+
return c.error({
|
|
13108
|
+
code: "NOT_AUTHENTICATED",
|
|
13109
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13110
|
+
cta: {
|
|
13111
|
+
commands: [
|
|
13112
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13113
|
+
]
|
|
13586
13114
|
}
|
|
13587
13115
|
});
|
|
13588
|
-
}
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13116
|
+
}
|
|
13117
|
+
const id = c.args.id;
|
|
13118
|
+
const opts = c.options;
|
|
13119
|
+
const params = {};
|
|
13120
|
+
if (opts.paymentMethodId !== void 0)
|
|
13121
|
+
params.payment_details = opts.paymentMethodId;
|
|
13122
|
+
if (opts.amount !== void 0) params.amount = opts.amount;
|
|
13123
|
+
if (opts.merchantUrl !== void 0)
|
|
13124
|
+
params.merchant_url = opts.merchantUrl;
|
|
13125
|
+
if (opts.profileId !== void 0) params.profile_id = opts.profileId;
|
|
13126
|
+
if (opts.merchantId !== void 0) params.merchant_id = opts.merchantId;
|
|
13127
|
+
if (opts.currency !== void 0) params.currency = opts.currency;
|
|
13128
|
+
if (opts.lineItem?.length)
|
|
13129
|
+
params.line_items = opts.lineItem.map(
|
|
13130
|
+
(item) => typeof item === "string" ? parseLineItemFlag(item) : item
|
|
13131
|
+
);
|
|
13132
|
+
if (opts.total?.length)
|
|
13133
|
+
params.totals = opts.total.map(
|
|
13134
|
+
(item) => typeof item === "string" ? parseTotalFlag(item) : item
|
|
13135
|
+
);
|
|
13136
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13137
|
+
return new Promise((resolve) => {
|
|
13138
|
+
const { waitUntilExit } = render4(
|
|
13139
|
+
/* @__PURE__ */ jsx16(
|
|
13140
|
+
UpdateSpendRequest,
|
|
13141
|
+
{
|
|
13142
|
+
repository,
|
|
13143
|
+
id,
|
|
13144
|
+
params,
|
|
13145
|
+
onComplete: () => {
|
|
13146
|
+
}
|
|
13147
|
+
}
|
|
13148
|
+
)
|
|
13149
|
+
);
|
|
13150
|
+
waitUntilExit().then(async () => {
|
|
13151
|
+
resolve(await repository.updateSpendRequest(id, params));
|
|
13152
|
+
});
|
|
13153
|
+
});
|
|
13154
|
+
}
|
|
13155
|
+
return repository.updateSpendRequest(id, params);
|
|
13156
|
+
}
|
|
13157
|
+
});
|
|
13158
|
+
cli2.command("request-approval", {
|
|
13159
|
+
description: "Request approval for a spend request",
|
|
13160
|
+
args: z6.object({
|
|
13161
|
+
id: z6.string().describe("Spend request ID")
|
|
13162
|
+
}),
|
|
13163
|
+
outputPolicy: "agent-only",
|
|
13164
|
+
async *run(c) {
|
|
13165
|
+
if (!storage.isAuthenticated()) {
|
|
13166
|
+
return c.error({
|
|
13167
|
+
code: "NOT_AUTHENTICATED",
|
|
13168
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13169
|
+
cta: {
|
|
13170
|
+
commands: [
|
|
13171
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13172
|
+
]
|
|
13595
13173
|
}
|
|
13174
|
+
});
|
|
13175
|
+
}
|
|
13176
|
+
const id = c.args.id;
|
|
13177
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13178
|
+
return new Promise((resolve) => {
|
|
13179
|
+
const { waitUntilExit } = render4(
|
|
13180
|
+
/* @__PURE__ */ jsx16(
|
|
13181
|
+
RequestApproval,
|
|
13182
|
+
{
|
|
13183
|
+
repository,
|
|
13184
|
+
id,
|
|
13185
|
+
onComplete: () => {
|
|
13186
|
+
}
|
|
13187
|
+
}
|
|
13188
|
+
)
|
|
13189
|
+
);
|
|
13190
|
+
waitUntilExit().then(async () => {
|
|
13191
|
+
const approval2 = await repository.requestApproval(id);
|
|
13192
|
+
resolve(approval2);
|
|
13193
|
+
});
|
|
13194
|
+
});
|
|
13195
|
+
}
|
|
13196
|
+
const approval = await repository.requestApproval(id);
|
|
13197
|
+
yield {
|
|
13198
|
+
...approval,
|
|
13199
|
+
instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${id} --interval 2 --max-attempts 150\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
|
|
13200
|
+
_next: {
|
|
13201
|
+
command: `spend-request retrieve ${id} --interval 2 --max-attempts 150`,
|
|
13202
|
+
until: "status changes from pending_approval"
|
|
13596
13203
|
}
|
|
13597
|
-
|
|
13598
|
-
}
|
|
13204
|
+
};
|
|
13205
|
+
}
|
|
13599
13206
|
});
|
|
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
|
-
|
|
13207
|
+
cli2.command("retrieve", {
|
|
13208
|
+
description: "Retrieve a spend request",
|
|
13209
|
+
args: z6.object({
|
|
13210
|
+
id: z6.string().describe("Spend request ID")
|
|
13211
|
+
}),
|
|
13212
|
+
options: retrieveOptions,
|
|
13213
|
+
outputPolicy: "agent-only",
|
|
13214
|
+
async *run(c) {
|
|
13215
|
+
if (!storage.isAuthenticated()) {
|
|
13216
|
+
return c.error({
|
|
13217
|
+
code: "NOT_AUTHENTICATED",
|
|
13218
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13219
|
+
cta: {
|
|
13220
|
+
commands: [
|
|
13221
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13222
|
+
]
|
|
13223
|
+
}
|
|
13224
|
+
});
|
|
13225
|
+
}
|
|
13226
|
+
const id = c.args.id;
|
|
13227
|
+
const opts = c.options;
|
|
13228
|
+
const timeout = opts.timeout;
|
|
13229
|
+
const interval = opts.interval;
|
|
13230
|
+
const maxAttempts = opts.maxAttempts;
|
|
13231
|
+
const includeArr = opts.include;
|
|
13232
|
+
const include = includeArr?.length ? includeArr : void 0;
|
|
13233
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13234
|
+
return new Promise((resolve) => {
|
|
13235
|
+
const { waitUntilExit } = render4(
|
|
13236
|
+
/* @__PURE__ */ jsx16(
|
|
13237
|
+
RetrieveSpendRequest,
|
|
13238
|
+
{
|
|
13239
|
+
repository,
|
|
13240
|
+
id,
|
|
13241
|
+
timeout,
|
|
13242
|
+
include,
|
|
13243
|
+
onComplete: () => {
|
|
13244
|
+
}
|
|
13245
|
+
}
|
|
13246
|
+
)
|
|
13247
|
+
);
|
|
13248
|
+
waitUntilExit().then(async () => {
|
|
13249
|
+
const request = await repository.getSpendRequest(id, { include });
|
|
13250
|
+
resolve(request);
|
|
13251
|
+
});
|
|
13252
|
+
});
|
|
13253
|
+
}
|
|
13254
|
+
const terminalStatuses = /* @__PURE__ */ new Set([
|
|
13255
|
+
"approved",
|
|
13256
|
+
"denied",
|
|
13257
|
+
"expired",
|
|
13258
|
+
"succeeded",
|
|
13259
|
+
"failed"
|
|
13260
|
+
]);
|
|
13261
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
13262
|
+
let attempts = 0;
|
|
13263
|
+
while (true) {
|
|
13627
13264
|
const request = await repository.getSpendRequest(id, { include });
|
|
13628
13265
|
if (!request) {
|
|
13629
|
-
|
|
13266
|
+
return c.error({
|
|
13267
|
+
code: "NOT_FOUND",
|
|
13268
|
+
message: `Spend request ${id} not found`
|
|
13269
|
+
});
|
|
13630
13270
|
}
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
RetrieveSpendRequest,
|
|
13635
|
-
{
|
|
13636
|
-
repository,
|
|
13637
|
-
id,
|
|
13638
|
-
timeout,
|
|
13639
|
-
include,
|
|
13640
|
-
onComplete: () => {
|
|
13641
|
-
}
|
|
13271
|
+
if (terminalStatuses.has(request.status)) {
|
|
13272
|
+
yield request;
|
|
13273
|
+
return;
|
|
13642
13274
|
}
|
|
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
|
-
);
|
|
13275
|
+
attempts++;
|
|
13276
|
+
const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
|
|
13277
|
+
if (shouldStop) {
|
|
13278
|
+
yield request;
|
|
13279
|
+
return;
|
|
13664
13280
|
}
|
|
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), "");
|
|
13281
|
+
yield request;
|
|
13282
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
|
|
13674
13283
|
}
|
|
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
13284
|
}
|
|
13736
13285
|
});
|
|
13286
|
+
return cli2;
|
|
13737
13287
|
}
|
|
13738
13288
|
|
|
13739
13289
|
// src/auth/auth-resource.ts
|
|
@@ -13908,6 +13458,26 @@ ${JSON.stringify(redacted, null, 2)}`
|
|
|
13908
13458
|
}
|
|
13909
13459
|
);
|
|
13910
13460
|
}
|
|
13461
|
+
async revokeToken(token) {
|
|
13462
|
+
const { status, data, rawBody } = await this.postForm(
|
|
13463
|
+
`${this.config.authBaseUrl}/device/revoke`,
|
|
13464
|
+
{
|
|
13465
|
+
client_id: CLIENT_ID,
|
|
13466
|
+
token
|
|
13467
|
+
}
|
|
13468
|
+
);
|
|
13469
|
+
if (status < 200 || status >= 300) {
|
|
13470
|
+
throw new LinkApiError(
|
|
13471
|
+
formatOAuthError("Token revocation failed", status, data, rawBody),
|
|
13472
|
+
{
|
|
13473
|
+
status,
|
|
13474
|
+
code: data?.error,
|
|
13475
|
+
rawBody,
|
|
13476
|
+
details: data
|
|
13477
|
+
}
|
|
13478
|
+
);
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13911
13481
|
async refreshToken(refreshToken) {
|
|
13912
13482
|
const { status, data, rawBody } = await this.postForm(
|
|
13913
13483
|
`${this.config.authBaseUrl}/device/token`,
|
|
@@ -14016,46 +13586,28 @@ var ResourceFactory = class {
|
|
|
14016
13586
|
};
|
|
14017
13587
|
|
|
14018
13588
|
// src/cli.tsx
|
|
14019
|
-
var cliVersion = "0.
|
|
13589
|
+
var cliVersion = "0.2.0";
|
|
14020
13590
|
var buildNumber = "1";
|
|
14021
13591
|
var defaultHeaders = {
|
|
14022
13592
|
"User-Agent": `link-cli/${cliVersion} (build ${buildNumber})`,
|
|
14023
13593
|
"X-Build-Number": buildNumber
|
|
14024
13594
|
};
|
|
14025
|
-
var program = new Command();
|
|
14026
13595
|
var verbose = process.argv.includes("--verbose");
|
|
14027
13596
|
var factory = new ResourceFactory({ verbose, defaultHeaders });
|
|
14028
13597
|
var authRepo = factory.createAuthResource();
|
|
14029
13598
|
var spendRequestRepo = factory.createSpendRequestResource();
|
|
14030
|
-
|
|
14031
|
-
"Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users."
|
|
14032
|
-
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
write("Run 'link-cli --skill' for full instructions.\n");
|
|
14039
|
-
}
|
|
14040
|
-
}
|
|
14041
|
-
});
|
|
14042
|
-
var authCommand = registerAuthCommands(program, authRepo);
|
|
14043
|
-
var spendRequestCommand = registerSpendRequestCommands(
|
|
14044
|
-
program,
|
|
14045
|
-
spendRequestRepo
|
|
13599
|
+
var cli = Cli5.create("link-cli", {
|
|
13600
|
+
description: "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users.",
|
|
13601
|
+
version: `${cliVersion} (build ${buildNumber})`
|
|
13602
|
+
});
|
|
13603
|
+
cli.command(createAuthCli(authRepo));
|
|
13604
|
+
cli.command(createSpendRequestCli(spendRequestRepo));
|
|
13605
|
+
cli.command(
|
|
13606
|
+
createPaymentMethodsCli(() => factory.createPaymentMethodsResource())
|
|
14046
13607
|
);
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
configureRootHelp(
|
|
14054
|
-
program,
|
|
14055
|
-
authCommand,
|
|
14056
|
-
spendRequestCommand,
|
|
14057
|
-
paymentMethodsCommand,
|
|
14058
|
-
skillCommand,
|
|
14059
|
-
mppCommand
|
|
14060
|
-
);
|
|
14061
|
-
program.parse();
|
|
13608
|
+
cli.command(createMppCli(spendRequestRepo));
|
|
13609
|
+
cli.serve();
|
|
13610
|
+
var cli_default = cli;
|
|
13611
|
+
export {
|
|
13612
|
+
cli_default as default
|
|
13613
|
+
};
|