@stripe/link-cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -27
- package/dist/cli.js +1081 -1547
- 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,168 +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
|
-
|
|
12480
|
-
|
|
12481
|
-
let content = '---\nname: create-payment-credential\ndescription: |\n Gets secure, one-time-use payment credentials (cards, tokens) from a Link wallet so agents can complete purchases on behalf of users. Use when the user says "get me a card", "buy something", "pay for X", "make a purchase", "I need to pay", "complete checkout", or asks to transact on any merchant site. Use when the user asks to connect or log in to or sign up for their Link account.\nallowed-tools:\n - Bash(link-cli:*)\n - Bash(npx:*)\n - Bash(npm:*)\nlicense: Complete terms in LICENSE\nmetadata:\n author: stripe\n url: link.com/agents\n openclaw:\n emoji: "\u{1F4B3}"\n homepage: https://link.com/agents\n requires:\n bins:\n - link-cli\n install:\n - kind: node\n package: "@stripe/link-cli"\n bins: [link-cli]\nuser-invocable: true\n---\n\n# Creating Payment Credentials\n\nUse the Link CLI to get secure, one-time-use payment credentials from a Link wallet to complete purchases.\n\n## Running commands\n\nAll commands support `--output-json` for machine-readable output. Use `--json` to pass structured input. Always run `link-cli <command> --help` before running the command to see full schema details, including all fields, types, and constraints.\n\nIMPORTANT: Run `auth login`, `spend-request create`, and `spend-request request-approval` with `run_in_background=true` (or `TaskOutput(task_id, block: false)`). These commands emit JSON to stdout before they exit, then keep running while they poll for user action.\n\nThe JSON stream contract for these long-running commands is:\n\n- `auth login --output-json`: first object contains `verification_url` and `passphrase`; final object contains authentication result after approval succeeds\n- `spend-request create --request-approval --output-json`: first object is the created spend request; final object is the terminal spend request after polling completes\n- `spend-request request-approval --output-json`: first object contains the approval link; final object is the terminal spend request after polling completes\n\nAlways keep reading stdout until the process exits. Do not assume the first JSON object is the full result. The user MUST visit the verification or approval URL to continue, and you should always show that full URL in clear text.\n\n## Core flow\n\nCopy this checklist and track progress:\n\n- Step 1: Authenticate with Link\n- Step 2: Evaluate merchant site (determine credential type)\n- Step 3: Get payment methods\n- Step 4: Create spend request with correct credential type\n- Step 5: Complete payment\n\n### Step 1: Authenticate with Link\n\nCheck auth status:\n\n```bash\nlink-cli auth status --output-json\n```\n\nIf not authenticated:\n\n```bash\nlink-cli auth login --client-name "<your-agent-name>" --output-json\n```\n\nReplace `<your-agent-name>` with the name of your agent or application (e.g. `"Personal Assistant", "Shopping Bot"`). This name appears in the user\'s Link app when they approve the connection. Use a clear, unique, identifiable name. Display the url and passphase to the user, with the guidance "Please visit the following URL to approve secure access to Link.\u201D\n\nDO NOT PROCEED until the user is authenticated with Link.\n\n### Step 2: Evaluate the merchant site BEFORE creating a spend request\n\n**CRITICAL \u2014 You MUST complete this step before calling `spend-request create`.** Do NOT default to `card` credential type. The merchant determines the credential type \u2014 you cannot know it without checking first. Skipping this step will produce a spend request with the wrong credential type.\n\nDetermine how the merchant accepts payment:\n\n1. **Navigate to the merchant page** \u2014 browse it, read the page content, and understand how the site accepts payment.\n2. **If the page has a credit card form, Stripe Elements, or traditional checkout UI** \u2014 use `card`.\n3. **If the page describes an API or programmatic payment flow** \u2014 make a request to the relevant endpoint. If it returns **HTTP 402** with a `www-authenticate` header, use `shared_payment_token`.\n\nWhat you find determines which credential type to use:\n\n| What you see | Credential type | What to request |\n|---|---|---|\n| Credit card form / Stripe Elements | `card` (default) | Card |\n| HTTP 402 with `method="stripe"` in `www-authenticate` | `shared_payment_token` | Shared payment token (SPT) |\n| HTTP 402 without `method="stripe"` in `www-authenticate` | not supported | Do not continue |\n\n**For 402 responses:** The `www-authenticate` header may contain **multiple** payment challenges (e.g. `tempo`, `stripe`) in a single header value. Do not try to decode the payload manually. Pass the **full raw `WWW-Authenticate` header value** to Link CLI and let `mpp decode` select and validate the `method="stripe"` challenge.\n\nTo derive `network_id`, use Link CLI\'s challenge decoder:\n\n```bash\nlink-cli mpp decode --challenge \'<raw WWW-Authenticate header>\' --output-json\n```\n\nThis validates the Stripe challenge, decodes the `request` payload, and returns both the extracted `network_id` and the decoded request JSON. Pass the full header exactly as received, even if it also contains non-Stripe or multiple `Payment` challenges.\n\n### Step 3: Get payment methods\n\nUse the default payment method, unless the user explicitly asks to select a different one.\n\n```bash\nlink-cli payment-methods list --output-json\n```\n\n### Step 4: Create the spend request with the right credential type\n\n```bash\nlink-cli spend-request create --json "{request}" --output-json\n```\n\nImportant: use the --json method to create the request.\n\nWait until the user has approved the spend request. If they deny, ask for clarification what to do next.\n\nRecommend the user approves with the [Link app](https://link.com/download). Show the download URL.\n\n**Test mode:** Add `"test": true` to the JSON input (or `--test` flag) to create testmode credentials instead of real ones. Useful for development and integration testing.\n\n### Step 5: Complete payment\n\n**Card:** The approved spend request includes a `card` object with `number`, `cvc`, `exp_month`, `exp_year`, `billing_address` (name, line1, line2, city, state, postal_code, country), and `valid_until` (unix timestamp \u2014 the card stops working after this time). Enter these details into the merchant\'s checkout form. If you need to fetch them again, run `link-cli spend-request retrieve <id> --output-json` and use the returned `card` field.\n\n**SPT with 402 flow:** The SPT is **one-time use** \u2014 if the payment fails, you need a new spend request and new SPT.\n\n```bash\nlink-cli mpp pay <url> --spend-request-id <id> [--method POST] [--data \'{"amount":100}\'] [--header \'Name: Value\'] --output-json\n```\n\n`mpp pay` handles the full 402 flow automatically: probes the URL, parses the `www-authenticate` header, builds the `Authorization: Payment` credential using the SPT, and retries.\n\n\n## Important\n\n- Treat the user\'s payment methods and credentials extremely carefully \u2014 card numbers and SPTs grant real spending power; leaking them outside a secure checkout could result in unauthorized charges the user cannot reverse.\n- Respect `/agents.txt` and `/llm.txt` and other directives on sites you browse \u2014 these files declare whether the site permits automated agent interactions; ignoring them may violate the merchant\'s terms.\n- Avoid suspicious merchants, checkout pages and websites \u2014 phishing pages that mimic legitimate merchants can steal credentials; if anything about the page feels off (mismatched domain, unusual redirect, unexpected login prompt), stop and ask the user to verify.\n- NEVER expose payment credentials (card numbers, SPTs) outside of a secure checkout form or the 402 payment flow \u2014 logging them, passing them to other tools, or including them in summaries creates unnecessary exposure vectors.\n- DO NOT use playwright or other automated browsers to authenticate with Link or approve a request on behalf of the user.\n\n## Errors\n\nAll errors go to stderr as `{"error": "..."}` with exit code 1.\n\n### Common errors and recovery\n\n| Error / Symptom | Cause | Recovery |\n|---|---|---|\n| `verification-failed` in error body from `mpp pay` | SPT was already consumed (one-time use) | Create a new spend request with `credential_type: "shared_payment_token"` \u2014 do not retry with the same spend request ID |\n| `context` validation error on `spend-request create` | `context` field is under 100 characters | Rewrite `context` as a full sentence explaining what is being purchased and why; the user reads this when approving |\n| API rejects `merchant_name` or `merchant_url` | These fields are forbidden when `credential_type` is `shared_payment_token` | Remove both fields from the request; SPT flows identify the merchant via `network_id` instead |\n| Command hangs indefinitely | `auth login` or `spend-request create` run synchronously | Always run these commands with `run_in_background=true` \u2014 they block until the user acts, so synchronous execution freezes the agent |\n| Spend request approved but payment fails immediately | Wrong credential type for the merchant (e.g. `card` on a 402-only endpoint) | Go back to Step 2, re-evaluate the merchant, create a new spend request with the correct `credential_type` |\n| Auth token expired mid-session (exit code 1 during approval polling) | Token refresh failure during background polling | Re-authenticate with `auth login`, then retrieve the existing spend request or resume polling. Only create a new spend request if the original one expired, was denied, or its shared payment token was already consumed |\n\n## Further docs\n\n- MPP/x402 protocol: https://mpp.dev/protocol.md, https://mpp.dev/protocol/http-402.md, https://mpp.dev/protocol/challenges.md\n- Link: https://link.com/agents\n- Link App (for account management): https://app.link.com\n- Link support (if the user needs help with Link): https://support.link.com/topics/about-link\n';
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
content = '---\nname: create-payment-credential\ndescription: |\n Gets secure, one-time-use payment credentials (cards, tokens) from a Link wallet so agents can complete purchases on behalf of users. Use when the user says "get me a card", "buy something", "pay for X", "make a purchase", "I need to pay", "complete checkout", or asks to transact on any merchant site. Use when the user asks to connect or log in to or sign up for their Link account.\nallowed-tools:\n - Bash(link-cli:*)\n - Bash(npx:*)\n - Bash(npm:*)\nlicense: Complete terms in LICENSE\nmetadata:\n author: stripe\n url: link.com/agents\n openclaw:\n emoji: "\u{1F4B3}"\n homepage: https://link.com/agents\n requires:\n bins:\n - link-cli\n install:\n - kind: node\n package: "@stripe/link-cli"\n bins: [link-cli]\nuser-invocable: true\n---\n\n# Creating Payment Credentials\n\nUse the Link CLI to get secure, one-time-use payment credentials from a Link wallet to complete purchases.\n\n## Running commands\n\nAll commands support `--output-json` for machine-readable output. Use `--json` to pass structured input. Always run `link-cli <command> --help` before running the command to see full schema details, including all fields, types, and constraints.\n\nIMPORTANT: Run `auth login`, `spend-request create`, and `spend-request request-approval` with `run_in_background=true` (or `TaskOutput(task_id, block: false)`). These commands emit JSON to stdout before they exit, then keep running while they poll for user action.\n\nThe JSON stream contract for these long-running commands is:\n\n- `auth login --output-json`: first object contains `verification_url` and `passphrase`; final object contains authentication result after approval succeeds\n- `spend-request create --request-approval --output-json`: first object is the created spend request; final object is the terminal spend request after polling completes\n- `spend-request request-approval --output-json`: first object contains the approval link; final object is the terminal spend request after polling completes\n\nAlways keep reading stdout until the process exits. Do not assume the first JSON object is the full result. The user MUST visit the verification or approval URL to continue, and you should always show that full URL in clear text.\n\n## Core flow\n\nCopy this checklist and track progress:\n\n- Step 1: Authenticate with Link\n- Step 2: Evaluate merchant site (determine credential type)\n- Step 3: Get payment methods\n- Step 4: Create spend request with correct credential type\n- Step 5: Complete payment\n\n### Step 1: Authenticate with Link\n\nCheck auth status:\n\n```bash\nlink-cli auth status --output-json\n```\n\nIf not authenticated:\n\n```bash\nlink-cli auth login --client-name "<your-agent-name>" --output-json\n```\n\nReplace `<your-agent-name>` with the name of your agent or application (e.g. `"Personal Assistant", "Shopping Bot"`). This name appears in the user\'s Link app when they approve the connection. Use a clear, unique, identifiable name. Display the url and passphase to the user, with the guidance "Please visit the following URL to approve secure access to Link.\u201D\n\nDO NOT PROCEED until the user is authenticated with Link.\n\n### Step 2: Evaluate the merchant site BEFORE creating a spend request\n\n**CRITICAL \u2014 You MUST complete this step before calling `spend-request create`.** Do NOT default to `card` credential type. The merchant determines the credential type \u2014 you cannot know it without checking first. Skipping this step will produce a spend request with the wrong credential type.\n\nDetermine how the merchant accepts payment:\n\n1. **Navigate to the merchant page** \u2014 browse it, read the page content, and understand how the site accepts payment.\n2. **If the page has a credit card form, Stripe Elements, or traditional checkout UI** \u2014 use `card`.\n3. **If the page describes an API or programmatic payment flow** \u2014 make a request to the relevant endpoint. If it returns **HTTP 402** with a `www-authenticate` header, use `shared_payment_token`.\n\nWhat you find determines which credential type to use:\n\n| What you see | Credential type | What to request |\n|---|---|---|\n| Credit card form / Stripe Elements | `card` (default) | Card |\n| HTTP 402 with `method="stripe"` in `www-authenticate` | `shared_payment_token` | Shared payment token (SPT) |\n| HTTP 402 without `method="stripe"` in `www-authenticate` | not supported | Do not continue |\n\n**For 402 responses:** The `www-authenticate` header may contain **multiple** payment challenges (e.g. `tempo`, `stripe`) in a single header value. Do not try to decode the payload manually. Pass the **full raw `WWW-Authenticate` header value** to Link CLI and let `mpp decode` select and validate the `method="stripe"` challenge.\n\nTo derive `network_id`, use Link CLI\'s challenge decoder:\n\n```bash\nlink-cli mpp decode --challenge \'<raw WWW-Authenticate header>\' --output-json\n```\n\nThis validates the Stripe challenge, decodes the `request` payload, and returns both the extracted `network_id` and the decoded request JSON. Pass the full header exactly as received, even if it also contains non-Stripe or multiple `Payment` challenges.\n\n### Step 3: Get payment methods\n\nUse the default payment method, unless the user explicitly asks to select a different one.\n\n```bash\nlink-cli payment-methods list --output-json\n```\n\n### Step 4: Create the spend request with the right credential type\n\n```bash\nlink-cli spend-request create --json "{request}" --output-json\n```\n\nImportant: use the --json method to create the request.\n\nWait until the user has approved the spend request. If they deny, ask for clarification what to do next.\n\nRecommend the user approves with the [Link app](https://link.com/download). Show the download URL.\n\n**Test mode:** Add `"test": true` to the JSON input (or `--test` flag) to create testmode credentials instead of real ones. Useful for development and integration testing.\n\n### Step 5: Complete payment\n\n**Card:** The approved spend request includes a `card` object with `number`, `cvc`, `exp_month`, `exp_year`, `billing_address` (name, line1, line2, city, state, postal_code, country), and `valid_until` (unix timestamp \u2014 the card stops working after this time). Enter these details into the merchant\'s checkout form. If you need to fetch them again, run `link-cli spend-request retrieve <id> --output-json` and use the returned `card` field.\n\n**SPT with 402 flow:** The SPT is **one-time use** \u2014 if the payment fails, you need a new spend request and new SPT.\n\n```bash\nlink-cli mpp pay <url> --spend-request-id <id> [--method POST] [--data \'{"amount":100}\'] [--header \'Name: Value\'] --output-json\n```\n\n`mpp pay` handles the full 402 flow automatically: probes the URL, parses the `www-authenticate` header, builds the `Authorization: Payment` credential using the SPT, and retries.\n\n\n## Important\n\n- Treat the user\'s payment methods and credentials extremely carefully \u2014 card numbers and SPTs grant real spending power; leaking them outside a secure checkout could result in unauthorized charges the user cannot reverse.\n- Respect `/agents.txt` and `/llm.txt` and other directives on sites you browse \u2014 these files declare whether the site permits automated agent interactions; ignoring them may violate the merchant\'s terms.\n- Avoid suspicious merchants, checkout pages and websites \u2014 phishing pages that mimic legitimate merchants can steal credentials; if anything about the page feels off (mismatched domain, unusual redirect, unexpected login prompt), stop and ask the user to verify.\n- NEVER expose payment credentials (card numbers, SPTs) outside of a secure checkout form or the 402 payment flow \u2014 logging them, passing them to other tools, or including them in summaries creates unnecessary exposure vectors.\n- DO NOT use playwright or other automated browsers to authenticate with Link or approve a request on behalf of the user.\n\n## Errors\n\nAll errors go to stderr as `{"error": "..."}` with exit code 1.\n\n### Common errors and recovery\n\n| Error / Symptom | Cause | Recovery |\n|---|---|---|\n| `verification-failed` in error body from `mpp pay` | SPT was already consumed (one-time use) | Create a new spend request with `credential_type: "shared_payment_token"` \u2014 do not retry with the same spend request ID |\n| `context` validation error on `spend-request create` | `context` field is under 100 characters | Rewrite `context` as a full sentence explaining what is being purchased and why; the user reads this when approving |\n| API rejects `merchant_name` or `merchant_url` | These fields are forbidden when `credential_type` is `shared_payment_token` | Remove both fields from the request; SPT flows identify the merchant via `network_id` instead |\n| Command hangs indefinitely | `auth login` or `spend-request create` run synchronously | Always run these commands with `run_in_background=true` \u2014 they block until the user acts, so synchronous execution freezes the agent |\n| Spend request approved but payment fails immediately | Wrong credential type for the merchant (e.g. `card` on a 402-only endpoint) | Go back to Step 2, re-evaluate the merchant, create a new spend request with the correct `credential_type` |\n| Auth token expired mid-session (exit code 1 during approval polling) | Token refresh failure during background polling | Re-authenticate with `auth login`, then retrieve the existing spend request or resume polling. Only create a new spend request if the original one expired, was denied, or its shared payment token was already consumed |\n\n## Further docs\n\n- MPP/x402 protocol: https://mpp.dev/protocol.md, https://mpp.dev/protocol/http-402.md, https://mpp.dev/protocol/challenges.md\n- Link: https://link.com/agents\n- Link App (for account management): https://app.link.com\n- Link support (if the user needs help with Link): https://support.link.com/topics/about-link\n'.replace(
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
if (options.install) {
|
|
12496
|
-
const baseDir = existsSync(join(process.cwd(), ".claude")) ? ".claude" : ".agents";
|
|
12497
|
-
const destDir = join(
|
|
12498
|
-
process.cwd(),
|
|
12499
|
-
baseDir,
|
|
12500
|
-
"skills",
|
|
12501
|
-
"create-payment-credential"
|
|
12502
|
-
);
|
|
12503
|
-
const destPath = join(destDir, "SKILL.md");
|
|
12504
|
-
mkdirSync(destDir, { recursive: true });
|
|
12505
|
-
writeFileSync2(destPath, content);
|
|
12506
|
-
process.stdout.write(`Skill installed to ${destPath}
|
|
12507
|
-
`);
|
|
12508
|
-
} else {
|
|
12509
|
-
process.stdout.write(content);
|
|
12131
|
+
cli2.command("add", {
|
|
12132
|
+
description: "Open the Link wallet to add a new payment method",
|
|
12133
|
+
outputPolicy: "agent-only",
|
|
12134
|
+
async run(c) {
|
|
12135
|
+
if (!storage.isAuthenticated()) {
|
|
12136
|
+
return c.error({
|
|
12137
|
+
code: "NOT_AUTHENTICATED",
|
|
12138
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
12139
|
+
cta: {
|
|
12140
|
+
commands: [
|
|
12141
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
12142
|
+
]
|
|
12143
|
+
}
|
|
12144
|
+
});
|
|
12145
|
+
}
|
|
12146
|
+
if (!c.agent && !c.formatExplicit) {
|
|
12147
|
+
return new Promise((resolve) => {
|
|
12148
|
+
const { waitUntilExit } = render3(/* @__PURE__ */ jsx9(AddPaymentMethod, {}));
|
|
12149
|
+
waitUntilExit().then(() => resolve({ url: WALLET_URL }));
|
|
12150
|
+
});
|
|
12151
|
+
}
|
|
12152
|
+
return { url: WALLET_URL };
|
|
12510
12153
|
}
|
|
12511
12154
|
});
|
|
12155
|
+
return cli2;
|
|
12512
12156
|
}
|
|
12513
12157
|
|
|
12514
|
-
// src/
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12158
|
+
// src/commands/spend-request/index.tsx
|
|
12159
|
+
import { Cli as Cli4, z as z6 } from "incur";
|
|
12160
|
+
import { render as render4 } from "ink";
|
|
12161
|
+
|
|
12162
|
+
// src/utils/line-item-parser.ts
|
|
12163
|
+
import { z as z4 } from "zod";
|
|
12164
|
+
var LineItemSchema = z4.object({
|
|
12165
|
+
name: z4.string(),
|
|
12166
|
+
url: z4.string().optional(),
|
|
12167
|
+
image_url: z4.string().optional(),
|
|
12168
|
+
description: z4.string().optional(),
|
|
12169
|
+
sku: z4.string().optional(),
|
|
12170
|
+
quantity: z4.coerce.number().optional(),
|
|
12171
|
+
unit_amount: z4.coerce.number().optional(),
|
|
12172
|
+
product_url: z4.string().optional()
|
|
12173
|
+
}).strict();
|
|
12174
|
+
var TotalSchema = z4.object({
|
|
12175
|
+
type: z4.string(),
|
|
12176
|
+
display_text: z4.string(),
|
|
12177
|
+
amount: z4.coerce.number()
|
|
12178
|
+
}).strict();
|
|
12179
|
+
function parseKvString(raw) {
|
|
12180
|
+
const result = {};
|
|
12181
|
+
for (const pair of raw.split(",")) {
|
|
12182
|
+
const idx = pair.indexOf(":");
|
|
12183
|
+
if (idx === -1) {
|
|
12184
|
+
throw new Error(`Invalid field (missing ':'): ${pair}`);
|
|
12530
12185
|
}
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12186
|
+
result[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim();
|
|
12187
|
+
}
|
|
12188
|
+
return result;
|
|
12189
|
+
}
|
|
12190
|
+
function formatZodError(err, prefix) {
|
|
12191
|
+
const messages = err.issues.map((issue) => {
|
|
12192
|
+
const key = issue.path[0]?.toString();
|
|
12193
|
+
return key ? `${prefix} ${key}: ${issue.message}` : `${prefix}: ${issue.message}`;
|
|
12194
|
+
});
|
|
12195
|
+
return new Error(messages.join("\n"));
|
|
12196
|
+
}
|
|
12197
|
+
function parseLineItemFlag(raw) {
|
|
12198
|
+
const obj = parseKvString(raw);
|
|
12199
|
+
try {
|
|
12200
|
+
return LineItemSchema.parse(obj);
|
|
12201
|
+
} catch (err) {
|
|
12202
|
+
if (err instanceof z4.ZodError) throw formatZodError(err, "Line item");
|
|
12203
|
+
throw err;
|
|
12204
|
+
}
|
|
12205
|
+
}
|
|
12206
|
+
function parseTotalFlag(raw) {
|
|
12207
|
+
const obj = parseKvString(raw);
|
|
12208
|
+
try {
|
|
12209
|
+
return TotalSchema.parse(obj);
|
|
12210
|
+
} catch (err) {
|
|
12211
|
+
if (err instanceof z4.ZodError) throw formatZodError(err, "Total");
|
|
12212
|
+
throw err;
|
|
12213
|
+
}
|
|
12536
12214
|
}
|
|
12537
12215
|
|
|
12538
12216
|
// src/commands/spend-request/create.tsx
|
|
12539
|
-
import { Box as
|
|
12217
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
12540
12218
|
import Spinner5 from "ink-spinner";
|
|
12541
|
-
import { useCallback, useEffect as
|
|
12219
|
+
import { useCallback, useEffect as useEffect6, useState as useState5 } from "react";
|
|
12542
12220
|
|
|
12543
12221
|
// src/commands/spend-request/app-download-qr-codes.tsx
|
|
12544
|
-
import { Box as
|
|
12222
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
12545
12223
|
import { useMemo } from "react";
|
|
12546
12224
|
|
|
12547
12225
|
// src/utils/render-qr-matrix.ts
|
|
@@ -12578,32 +12256,32 @@ function renderQrMatrix(url) {
|
|
|
12578
12256
|
}
|
|
12579
12257
|
|
|
12580
12258
|
// src/commands/spend-request/app-download-qr-codes.tsx
|
|
12581
|
-
import { jsx as
|
|
12259
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
12582
12260
|
var DOWNLOAD_URL = "https://link.com/download";
|
|
12583
12261
|
var AppDownloadQrCodes = () => {
|
|
12584
12262
|
const qrLines = useMemo(() => renderQrMatrix(DOWNLOAD_URL), []);
|
|
12585
|
-
return /* @__PURE__ */
|
|
12586
|
-
/* @__PURE__ */
|
|
12587
|
-
/* @__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: [
|
|
12588
12266
|
qrLines.map((line, i) => (
|
|
12589
12267
|
// biome-ignore lint/suspicious/noArrayIndexKey: stable static array
|
|
12590
|
-
/* @__PURE__ */
|
|
12268
|
+
/* @__PURE__ */ jsx10(Text7, { children: line }, i)
|
|
12591
12269
|
)),
|
|
12592
|
-
/* @__PURE__ */
|
|
12270
|
+
/* @__PURE__ */ jsx10(Text7, { dimColor: true, children: DOWNLOAD_URL })
|
|
12593
12271
|
] })
|
|
12594
12272
|
] });
|
|
12595
12273
|
};
|
|
12596
12274
|
|
|
12597
12275
|
// src/commands/spend-request/approval-waiting-view.tsx
|
|
12598
|
-
import { Box as
|
|
12276
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
12599
12277
|
import Spinner4 from "ink-spinner";
|
|
12600
|
-
import { jsx as
|
|
12278
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
12601
12279
|
var ApprovalWaitingView = ({
|
|
12602
12280
|
status,
|
|
12603
12281
|
approvalUrl
|
|
12604
|
-
}) => /* @__PURE__ */
|
|
12605
|
-
/* @__PURE__ */
|
|
12606
|
-
|
|
12282
|
+
}) => /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingY: 1, children: [
|
|
12283
|
+
/* @__PURE__ */ jsxs7(
|
|
12284
|
+
Box8,
|
|
12607
12285
|
{
|
|
12608
12286
|
flexDirection: "column",
|
|
12609
12287
|
borderStyle: "round",
|
|
@@ -12611,25 +12289,51 @@ var ApprovalWaitingView = ({
|
|
|
12611
12289
|
paddingX: 2,
|
|
12612
12290
|
paddingY: 1,
|
|
12613
12291
|
children: [
|
|
12614
|
-
/* @__PURE__ */
|
|
12292
|
+
/* @__PURE__ */ jsxs7(Text8, { children: [
|
|
12615
12293
|
"Approve at:",
|
|
12616
12294
|
" ",
|
|
12617
|
-
/* @__PURE__ */
|
|
12295
|
+
/* @__PURE__ */ jsx11(Text8, { bold: true, color: "cyan", children: approvalUrl })
|
|
12618
12296
|
] }),
|
|
12619
|
-
/* @__PURE__ */
|
|
12297
|
+
/* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Press Enter to open in browser" })
|
|
12620
12298
|
]
|
|
12621
12299
|
}
|
|
12622
12300
|
),
|
|
12623
|
-
/* @__PURE__ */
|
|
12624
|
-
/* @__PURE__ */
|
|
12625
|
-
/* @__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" }),
|
|
12626
12304
|
" Waiting for approval..."
|
|
12627
|
-
] }) : /* @__PURE__ */
|
|
12305
|
+
] }) : /* @__PURE__ */ jsx11(Text8, { dimColor: true, children: "Waiting..." }) })
|
|
12628
12306
|
] });
|
|
12629
12307
|
|
|
12630
12308
|
// src/commands/spend-request/use-approval-polling.ts
|
|
12631
12309
|
import { useInput as useInput3 } from "ink";
|
|
12632
|
-
import { useEffect as
|
|
12310
|
+
import { useEffect as useEffect5 } from "react";
|
|
12311
|
+
|
|
12312
|
+
// src/utils/poll-until-approved.ts
|
|
12313
|
+
function pollUntilApproved(repository, id, options = {}) {
|
|
12314
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
12315
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
12316
|
+
const startTime = Date.now();
|
|
12317
|
+
const poll = async () => {
|
|
12318
|
+
const elapsed = Date.now() - startTime;
|
|
12319
|
+
if (elapsed > timeoutMs) {
|
|
12320
|
+
throw new Error("Approval polling timed out");
|
|
12321
|
+
}
|
|
12322
|
+
const request = await repository.getSpendRequest(id);
|
|
12323
|
+
if (!request) {
|
|
12324
|
+
throw new Error(`Spend request ${id} not found`);
|
|
12325
|
+
}
|
|
12326
|
+
if (request.status !== "created" && request.status !== "pending_approval") {
|
|
12327
|
+
return request;
|
|
12328
|
+
}
|
|
12329
|
+
options.onProgress?.(Math.floor(elapsed / 1e3));
|
|
12330
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
12331
|
+
return poll();
|
|
12332
|
+
};
|
|
12333
|
+
return poll();
|
|
12334
|
+
}
|
|
12335
|
+
|
|
12336
|
+
// src/commands/spend-request/use-approval-polling.ts
|
|
12633
12337
|
function useApprovalPolling({
|
|
12634
12338
|
status,
|
|
12635
12339
|
setStatus,
|
|
@@ -12647,12 +12351,12 @@ function useApprovalPolling({
|
|
|
12647
12351
|
},
|
|
12648
12352
|
{ isActive: isWaiting }
|
|
12649
12353
|
);
|
|
12650
|
-
|
|
12354
|
+
useEffect5(() => {
|
|
12651
12355
|
if (status !== "waiting") return;
|
|
12652
12356
|
const timeout = setTimeout(() => setStatus("polling"), 1e3);
|
|
12653
12357
|
return () => clearTimeout(timeout);
|
|
12654
12358
|
}, [status, setStatus]);
|
|
12655
|
-
|
|
12359
|
+
useEffect5(() => {
|
|
12656
12360
|
if (status !== "polling" || !requestId) return;
|
|
12657
12361
|
let cancelled = false;
|
|
12658
12362
|
const poll = async () => {
|
|
@@ -12686,16 +12390,16 @@ function useApprovalPolling({
|
|
|
12686
12390
|
}
|
|
12687
12391
|
|
|
12688
12392
|
// src/commands/spend-request/create.tsx
|
|
12689
|
-
import { Fragment, jsx as
|
|
12393
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
12690
12394
|
var CreateSpendRequest = ({
|
|
12691
12395
|
repository,
|
|
12692
12396
|
params,
|
|
12693
12397
|
requestApproval = false,
|
|
12694
12398
|
onComplete
|
|
12695
12399
|
}) => {
|
|
12696
|
-
const [status, setStatus] =
|
|
12697
|
-
const [request, setRequest] =
|
|
12698
|
-
const [error, setError] =
|
|
12400
|
+
const [status, setStatus] = useState5("creating");
|
|
12401
|
+
const [request, setRequest] = useState5(null);
|
|
12402
|
+
const [error, setError] = useState5("");
|
|
12699
12403
|
const approvalUrl = request?.approval_url ?? "";
|
|
12700
12404
|
const onSuccess = useCallback(
|
|
12701
12405
|
(result) => setRequest(result),
|
|
@@ -12712,7 +12416,7 @@ var CreateSpendRequest = ({
|
|
|
12712
12416
|
onSuccess,
|
|
12713
12417
|
onError
|
|
12714
12418
|
});
|
|
12715
|
-
|
|
12419
|
+
useEffect6(() => {
|
|
12716
12420
|
const create = async () => {
|
|
12717
12421
|
try {
|
|
12718
12422
|
const result = await repository.createSpendRequest(params);
|
|
@@ -12732,57 +12436,57 @@ var CreateSpendRequest = ({
|
|
|
12732
12436
|
create();
|
|
12733
12437
|
}, [repository, params, requestApproval, onComplete]);
|
|
12734
12438
|
if (status === "creating") {
|
|
12735
|
-
return /* @__PURE__ */
|
|
12736
|
-
/* @__PURE__ */
|
|
12439
|
+
return /* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
|
|
12440
|
+
/* @__PURE__ */ jsx12(Spinner5, { type: "dots" }),
|
|
12737
12441
|
" Creating spend request..."
|
|
12738
12442
|
] }) });
|
|
12739
12443
|
}
|
|
12740
12444
|
if (status === "error") {
|
|
12741
|
-
return /* @__PURE__ */
|
|
12742
|
-
/* @__PURE__ */
|
|
12743
|
-
/* @__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 })
|
|
12744
12448
|
] });
|
|
12745
12449
|
}
|
|
12746
12450
|
if (status === "success") {
|
|
12747
|
-
return /* @__PURE__ */
|
|
12748
|
-
/* @__PURE__ */
|
|
12749
|
-
/* @__PURE__ */
|
|
12750
|
-
/* @__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: [
|
|
12751
12455
|
"ID: ",
|
|
12752
|
-
/* @__PURE__ */
|
|
12456
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id })
|
|
12753
12457
|
] }),
|
|
12754
|
-
/* @__PURE__ */
|
|
12458
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12755
12459
|
"Status: ",
|
|
12756
|
-
/* @__PURE__ */
|
|
12460
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.status })
|
|
12757
12461
|
] }),
|
|
12758
|
-
/* @__PURE__ */
|
|
12462
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12759
12463
|
"Amount:",
|
|
12760
12464
|
" ",
|
|
12761
|
-
/* @__PURE__ */
|
|
12465
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: (() => {
|
|
12762
12466
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
12763
12467
|
return t ? String(t.amount) : "N/A";
|
|
12764
12468
|
})() })
|
|
12765
12469
|
] }),
|
|
12766
|
-
/* @__PURE__ */
|
|
12470
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12767
12471
|
"Merchant: ",
|
|
12768
|
-
/* @__PURE__ */
|
|
12472
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.merchant_name })
|
|
12769
12473
|
] }),
|
|
12770
|
-
/* @__PURE__ */
|
|
12474
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
12771
12475
|
"Line Items:",
|
|
12772
12476
|
" ",
|
|
12773
|
-
/* @__PURE__ */
|
|
12477
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") || "N/A" })
|
|
12774
12478
|
] })
|
|
12775
12479
|
] }),
|
|
12776
|
-
/* @__PURE__ */
|
|
12480
|
+
/* @__PURE__ */ jsx12(AppDownloadQrCodes, {})
|
|
12777
12481
|
] });
|
|
12778
12482
|
}
|
|
12779
|
-
return /* @__PURE__ */
|
|
12780
|
-
/* @__PURE__ */
|
|
12483
|
+
return /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
12484
|
+
/* @__PURE__ */ jsx12(Box9, { children: /* @__PURE__ */ jsxs8(Text9, { color: "green", children: [
|
|
12781
12485
|
"\u2713 Spend request created (ID: ",
|
|
12782
|
-
/* @__PURE__ */
|
|
12486
|
+
/* @__PURE__ */ jsx12(Text9, { bold: true, children: request?.id }),
|
|
12783
12487
|
")"
|
|
12784
12488
|
] }) }),
|
|
12785
|
-
/* @__PURE__ */
|
|
12489
|
+
/* @__PURE__ */ jsx12(
|
|
12786
12490
|
ApprovalWaitingView,
|
|
12787
12491
|
{
|
|
12788
12492
|
status,
|
|
@@ -12793,19 +12497,19 @@ var CreateSpendRequest = ({
|
|
|
12793
12497
|
};
|
|
12794
12498
|
|
|
12795
12499
|
// src/commands/spend-request/request-approval.tsx
|
|
12796
|
-
import { Box as
|
|
12500
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
12797
12501
|
import Spinner6 from "ink-spinner";
|
|
12798
|
-
import { useCallback as useCallback2, useEffect as
|
|
12799
|
-
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";
|
|
12800
12504
|
var RequestApproval = ({
|
|
12801
12505
|
repository,
|
|
12802
12506
|
id,
|
|
12803
12507
|
onComplete
|
|
12804
12508
|
}) => {
|
|
12805
|
-
const [status, setStatus] =
|
|
12806
|
-
const [approvalUrl, setApprovalUrl] =
|
|
12807
|
-
const [result, setResult] =
|
|
12808
|
-
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("");
|
|
12809
12513
|
const onSuccess = useCallback2((r) => setResult(r), []);
|
|
12810
12514
|
const onError = useCallback2((msg) => setError(msg), []);
|
|
12811
12515
|
useApprovalPolling({
|
|
@@ -12818,7 +12522,7 @@ var RequestApproval = ({
|
|
|
12818
12522
|
onSuccess,
|
|
12819
12523
|
onError
|
|
12820
12524
|
});
|
|
12821
|
-
|
|
12525
|
+
useEffect7(() => {
|
|
12822
12526
|
const request = async () => {
|
|
12823
12527
|
try {
|
|
12824
12528
|
const res = await repository.requestApproval(id);
|
|
@@ -12832,45 +12536,45 @@ var RequestApproval = ({
|
|
|
12832
12536
|
request();
|
|
12833
12537
|
}, [repository, id]);
|
|
12834
12538
|
if (status === "requesting") {
|
|
12835
|
-
return /* @__PURE__ */
|
|
12836
|
-
/* @__PURE__ */
|
|
12539
|
+
return /* @__PURE__ */ jsx13(Box10, { children: /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
|
|
12540
|
+
/* @__PURE__ */ jsx13(Spinner6, { type: "dots" }),
|
|
12837
12541
|
" Requesting approval..."
|
|
12838
12542
|
] }) });
|
|
12839
12543
|
}
|
|
12840
12544
|
if (status === "error") {
|
|
12841
|
-
return /* @__PURE__ */
|
|
12842
|
-
/* @__PURE__ */
|
|
12843
|
-
/* @__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 })
|
|
12844
12548
|
] });
|
|
12845
12549
|
}
|
|
12846
12550
|
if (status === "success") {
|
|
12847
|
-
return /* @__PURE__ */
|
|
12848
|
-
/* @__PURE__ */
|
|
12849
|
-
/* @__PURE__ */
|
|
12850
|
-
/* @__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: [
|
|
12851
12555
|
"ID: ",
|
|
12852
|
-
/* @__PURE__ */
|
|
12556
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.id })
|
|
12853
12557
|
] }),
|
|
12854
|
-
/* @__PURE__ */
|
|
12558
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12855
12559
|
"Status: ",
|
|
12856
|
-
/* @__PURE__ */
|
|
12560
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.status })
|
|
12857
12561
|
] }),
|
|
12858
|
-
/* @__PURE__ */
|
|
12562
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12859
12563
|
"Amount:",
|
|
12860
12564
|
" ",
|
|
12861
|
-
/* @__PURE__ */
|
|
12565
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: (() => {
|
|
12862
12566
|
const t = result?.totals.find((t2) => t2.type === "total");
|
|
12863
12567
|
return t ? String(t.amount) : "N/A";
|
|
12864
12568
|
})() })
|
|
12865
12569
|
] }),
|
|
12866
|
-
/* @__PURE__ */
|
|
12570
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
12867
12571
|
"Merchant: ",
|
|
12868
|
-
/* @__PURE__ */
|
|
12572
|
+
/* @__PURE__ */ jsx13(Text10, { bold: true, children: result?.merchant_name })
|
|
12869
12573
|
] })
|
|
12870
12574
|
] })
|
|
12871
12575
|
] });
|
|
12872
12576
|
}
|
|
12873
|
-
return /* @__PURE__ */
|
|
12577
|
+
return /* @__PURE__ */ jsx13(
|
|
12874
12578
|
ApprovalWaitingView,
|
|
12875
12579
|
{
|
|
12876
12580
|
status,
|
|
@@ -12880,10 +12584,10 @@ var RequestApproval = ({
|
|
|
12880
12584
|
};
|
|
12881
12585
|
|
|
12882
12586
|
// src/commands/spend-request/retrieve.tsx
|
|
12883
|
-
import { Box as
|
|
12587
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
12884
12588
|
import Spinner7 from "ink-spinner";
|
|
12885
|
-
import { useEffect as
|
|
12886
|
-
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";
|
|
12887
12591
|
var RetrieveSpendRequest = ({
|
|
12888
12592
|
repository,
|
|
12889
12593
|
id,
|
|
@@ -12891,20 +12595,20 @@ var RetrieveSpendRequest = ({
|
|
|
12891
12595
|
include,
|
|
12892
12596
|
onComplete
|
|
12893
12597
|
}) => {
|
|
12894
|
-
const [phase, setPhase] =
|
|
12895
|
-
const [request, setRequest] =
|
|
12896
|
-
const [error, setError] =
|
|
12897
|
-
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);
|
|
12898
12602
|
const startTimeRef = useRef(Date.now());
|
|
12899
12603
|
const pollRef = useRef(null);
|
|
12900
12604
|
const timerRef = useRef(null);
|
|
12901
|
-
|
|
12605
|
+
useEffect8(() => {
|
|
12902
12606
|
return () => {
|
|
12903
12607
|
if (pollRef.current) clearInterval(pollRef.current);
|
|
12904
12608
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
12905
12609
|
};
|
|
12906
12610
|
}, []);
|
|
12907
|
-
|
|
12611
|
+
useEffect8(() => {
|
|
12908
12612
|
const fetch2 = async () => {
|
|
12909
12613
|
try {
|
|
12910
12614
|
const result = await repository.getSpendRequest(id, { include });
|
|
@@ -12933,7 +12637,7 @@ var RetrieveSpendRequest = ({
|
|
|
12933
12637
|
};
|
|
12934
12638
|
fetch2();
|
|
12935
12639
|
}, [repository, id, include, onComplete]);
|
|
12936
|
-
|
|
12640
|
+
useEffect8(() => {
|
|
12937
12641
|
if (phase !== "polling") return;
|
|
12938
12642
|
timerRef.current = setInterval(() => {
|
|
12939
12643
|
const secs = Math.floor((Date.now() - startTimeRef.current) / 1e3);
|
|
@@ -12972,170 +12676,170 @@ var RetrieveSpendRequest = ({
|
|
|
12972
12676
|
};
|
|
12973
12677
|
}, [phase, repository, id, include, timeout, onComplete]);
|
|
12974
12678
|
if (phase === "fetching") {
|
|
12975
|
-
return /* @__PURE__ */
|
|
12976
|
-
/* @__PURE__ */
|
|
12679
|
+
return /* @__PURE__ */ jsx14(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
|
|
12680
|
+
/* @__PURE__ */ jsx14(Spinner7, { type: "dots" }),
|
|
12977
12681
|
" Retrieving spend request ",
|
|
12978
12682
|
id,
|
|
12979
12683
|
"..."
|
|
12980
12684
|
] }) });
|
|
12981
12685
|
}
|
|
12982
12686
|
if (phase === "error") {
|
|
12983
|
-
return /* @__PURE__ */
|
|
12687
|
+
return /* @__PURE__ */ jsx14(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
|
|
12984
12688
|
"\u2717 ",
|
|
12985
12689
|
error
|
|
12986
12690
|
] }) });
|
|
12987
12691
|
}
|
|
12988
12692
|
if (phase === "timeout") {
|
|
12989
|
-
return /* @__PURE__ */
|
|
12990
|
-
/* @__PURE__ */
|
|
12693
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
12694
|
+
/* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
|
|
12991
12695
|
"\u2717 Timed out waiting for approval after ",
|
|
12992
12696
|
timeout,
|
|
12993
12697
|
"s"
|
|
12994
12698
|
] }),
|
|
12995
|
-
request && /* @__PURE__ */
|
|
12996
|
-
/* @__PURE__ */
|
|
12699
|
+
request && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
|
|
12700
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
12997
12701
|
"ID: ",
|
|
12998
|
-
/* @__PURE__ */
|
|
12702
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.id })
|
|
12999
12703
|
] }),
|
|
13000
|
-
/* @__PURE__ */
|
|
12704
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13001
12705
|
"Status: ",
|
|
13002
|
-
/* @__PURE__ */
|
|
12706
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.status })
|
|
13003
12707
|
] })
|
|
13004
12708
|
] })
|
|
13005
12709
|
] });
|
|
13006
12710
|
}
|
|
13007
12711
|
if (phase === "polling") {
|
|
13008
|
-
return /* @__PURE__ */
|
|
13009
|
-
/* @__PURE__ */
|
|
13010
|
-
/* @__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" }),
|
|
13011
12715
|
" Awaiting approval... (",
|
|
13012
12716
|
elapsed,
|
|
13013
12717
|
"s elapsed)"
|
|
13014
12718
|
] }) }),
|
|
13015
|
-
request?.approval_url && /* @__PURE__ */
|
|
12719
|
+
request?.approval_url && /* @__PURE__ */ jsx14(Box11, { marginTop: 1, paddingX: 2, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
13016
12720
|
"Approval URL: ",
|
|
13017
|
-
/* @__PURE__ */
|
|
12721
|
+
/* @__PURE__ */ jsx14(Text11, { color: "cyan", children: request.approval_url })
|
|
13018
12722
|
] }) })
|
|
13019
12723
|
] });
|
|
13020
12724
|
}
|
|
13021
12725
|
if (phase === "declined") {
|
|
13022
|
-
return /* @__PURE__ */
|
|
13023
|
-
/* @__PURE__ */
|
|
13024
|
-
/* @__PURE__ */
|
|
13025
|
-
/* @__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: [
|
|
13026
12730
|
"ID: ",
|
|
13027
|
-
/* @__PURE__ */
|
|
12731
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
|
|
13028
12732
|
] }),
|
|
13029
|
-
/* @__PURE__ */
|
|
12733
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13030
12734
|
"Status:",
|
|
13031
12735
|
" ",
|
|
13032
|
-
/* @__PURE__ */
|
|
12736
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, color: "red", children: request?.status })
|
|
13033
12737
|
] }),
|
|
13034
|
-
/* @__PURE__ */
|
|
12738
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13035
12739
|
"Amount:",
|
|
13036
12740
|
" ",
|
|
13037
|
-
/* @__PURE__ */
|
|
12741
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
|
|
13038
12742
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
13039
12743
|
return t ? String(t.amount) : "N/A";
|
|
13040
12744
|
})() })
|
|
13041
12745
|
] }),
|
|
13042
|
-
/* @__PURE__ */
|
|
12746
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13043
12747
|
"Merchant: ",
|
|
13044
|
-
/* @__PURE__ */
|
|
12748
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
|
|
13045
12749
|
] })
|
|
13046
12750
|
] })
|
|
13047
12751
|
] });
|
|
13048
12752
|
}
|
|
13049
|
-
return /* @__PURE__ */
|
|
13050
|
-
/* @__PURE__ */
|
|
13051
|
-
/* @__PURE__ */
|
|
13052
|
-
/* @__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: [
|
|
13053
12757
|
"ID: ",
|
|
13054
|
-
/* @__PURE__ */
|
|
12758
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.id })
|
|
13055
12759
|
] }),
|
|
13056
|
-
/* @__PURE__ */
|
|
12760
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13057
12761
|
"Status:",
|
|
13058
12762
|
" ",
|
|
13059
|
-
/* @__PURE__ */
|
|
12763
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, color: "green", children: request?.status })
|
|
13060
12764
|
] }),
|
|
13061
|
-
/* @__PURE__ */
|
|
12765
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13062
12766
|
"Amount:",
|
|
13063
12767
|
" ",
|
|
13064
|
-
/* @__PURE__ */
|
|
12768
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: (() => {
|
|
13065
12769
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
13066
12770
|
return t ? String(t.amount) : "N/A";
|
|
13067
12771
|
})() })
|
|
13068
12772
|
] }),
|
|
13069
|
-
/* @__PURE__ */
|
|
12773
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13070
12774
|
"Merchant: ",
|
|
13071
|
-
/* @__PURE__ */
|
|
12775
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.merchant_name })
|
|
13072
12776
|
] }),
|
|
13073
|
-
/* @__PURE__ */
|
|
12777
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13074
12778
|
"Line Items:",
|
|
13075
12779
|
" ",
|
|
13076
|
-
/* @__PURE__ */
|
|
12780
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
|
|
13077
12781
|
] }),
|
|
13078
|
-
request?.shared_payment_token && /* @__PURE__ */
|
|
13079
|
-
/* @__PURE__ */
|
|
12782
|
+
request?.shared_payment_token && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
12783
|
+
/* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
|
|
13080
12784
|
"\x1B]8;;https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens\x07",
|
|
13081
12785
|
"Shared Payment Token",
|
|
13082
12786
|
"\x1B]8;;\x07",
|
|
13083
12787
|
":"
|
|
13084
12788
|
] }),
|
|
13085
|
-
/* @__PURE__ */
|
|
12789
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13086
12790
|
" ",
|
|
13087
12791
|
"Token: ",
|
|
13088
|
-
/* @__PURE__ */
|
|
12792
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.shared_payment_token.id })
|
|
13089
12793
|
] })
|
|
13090
12794
|
] }),
|
|
13091
|
-
request?.card && /* @__PURE__ */
|
|
13092
|
-
/* @__PURE__ */
|
|
13093
|
-
/* @__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: [
|
|
13094
12798
|
" ",
|
|
13095
12799
|
"Number: ",
|
|
13096
|
-
/* @__PURE__ */
|
|
12800
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.number })
|
|
13097
12801
|
] }),
|
|
13098
|
-
/* @__PURE__ */
|
|
12802
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13099
12803
|
" ",
|
|
13100
12804
|
"Brand: ",
|
|
13101
|
-
/* @__PURE__ */
|
|
12805
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request?.card.brand })
|
|
13102
12806
|
] }),
|
|
13103
|
-
/* @__PURE__ */
|
|
12807
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13104
12808
|
" ",
|
|
13105
12809
|
"Expiry:",
|
|
13106
12810
|
" ",
|
|
13107
|
-
/* @__PURE__ */
|
|
12811
|
+
/* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
|
|
13108
12812
|
String(request?.card.exp_month).padStart(2, "0"),
|
|
13109
12813
|
"/",
|
|
13110
12814
|
request?.card.exp_year
|
|
13111
12815
|
] })
|
|
13112
12816
|
] }),
|
|
13113
|
-
request?.card.cvc && /* @__PURE__ */
|
|
12817
|
+
request?.card.cvc && /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13114
12818
|
" ",
|
|
13115
12819
|
"CVC: ",
|
|
13116
|
-
/* @__PURE__ */
|
|
12820
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: request.card.cvc })
|
|
13117
12821
|
] }),
|
|
13118
|
-
request?.card.valid_until && /* @__PURE__ */
|
|
12822
|
+
request?.card.valid_until && /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13119
12823
|
" ",
|
|
13120
12824
|
"Valid Until:",
|
|
13121
12825
|
" ",
|
|
13122
|
-
/* @__PURE__ */
|
|
12826
|
+
/* @__PURE__ */ jsx14(Text11, { bold: true, children: new Date(request.card.valid_until * 1e3).toISOString() })
|
|
13123
12827
|
] }),
|
|
13124
|
-
request?.card.billing_address && /* @__PURE__ */
|
|
13125
|
-
/* @__PURE__ */
|
|
13126
|
-
/* @__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: [
|
|
13127
12831
|
" ",
|
|
13128
12832
|
request.card.billing_address.name
|
|
13129
12833
|
] }),
|
|
13130
|
-
/* @__PURE__ */
|
|
12834
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13131
12835
|
" ",
|
|
13132
12836
|
request.card.billing_address.line1
|
|
13133
12837
|
] }),
|
|
13134
|
-
request.card.billing_address.line2 && /* @__PURE__ */
|
|
12838
|
+
request.card.billing_address.line2 && /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13135
12839
|
" ",
|
|
13136
12840
|
request.card.billing_address.line2
|
|
13137
12841
|
] }),
|
|
13138
|
-
/* @__PURE__ */
|
|
12842
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13139
12843
|
" ",
|
|
13140
12844
|
[
|
|
13141
12845
|
request.card.billing_address.city,
|
|
@@ -13143,7 +12847,7 @@ var RetrieveSpendRequest = ({
|
|
|
13143
12847
|
request.card.billing_address.postal_code
|
|
13144
12848
|
].filter(Boolean).join(", ")
|
|
13145
12849
|
] }),
|
|
13146
|
-
/* @__PURE__ */
|
|
12850
|
+
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
13147
12851
|
" ",
|
|
13148
12852
|
request.card.billing_address.country
|
|
13149
12853
|
] })
|
|
@@ -13154,226 +12858,69 @@ var RetrieveSpendRequest = ({
|
|
|
13154
12858
|
};
|
|
13155
12859
|
|
|
13156
12860
|
// src/commands/spend-request/schema.ts
|
|
13157
|
-
import { z as
|
|
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
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
},
|
|
13203
|
-
payment_details: {
|
|
13204
|
-
outputExample: '"csmrpd_abcde12345"',
|
|
13205
|
-
description: "Payment method ID"
|
|
13206
|
-
},
|
|
13207
|
-
amount: { outputExample: "1000", description: "Amount in cents" },
|
|
13208
|
-
merchant_name: { outputExample: '"Powdur"', description: "Merchant name" },
|
|
13209
|
-
line_items: { outputExample: "[...]", description: "Line items" },
|
|
13210
|
-
totals: { outputExample: "[...]", description: "Totals" },
|
|
13211
|
-
card: {
|
|
13212
|
-
outputExample: '{"number":"4242424242424242","exp_month":12,"exp_year":2027,"cvc":"123","billing_address":{"name":"Jane Doe","line1":"123 Main St","city":"San Francisco","state":"CA","postal_code":"94111","country":"US"},"valid_until":1750000000}',
|
|
13213
|
-
description: "Card credentials (present when credential_type is card and status is approved). Includes billing_address (name, line1, line2, city, state, postal_code, country) and valid_until (unix timestamp) when available."
|
|
13214
|
-
},
|
|
13215
|
-
shared_payment_token: {
|
|
13216
|
-
outputExample: '{"id":"spt_xxx","billing_address":{"name":"Jane Doe","line1":"123 Main St","city":"San Francisco","state":"CA","postal_code":"94111","country":"US"},"valid_until":"2026-04-21T20:46:58Z"}',
|
|
13217
|
-
description: 'Shared payment token object (present when credential_type is shared_payment_token and status is approved). Use the "id" field as the SPT value.'
|
|
13218
|
-
}
|
|
13219
|
-
};
|
|
13220
|
-
var CREATE_INPUT_SCHEMA = {
|
|
13221
|
-
payment_method_id: {
|
|
13222
|
-
schema: z6.string().min(1),
|
|
13223
|
-
flag: "--payment-method-id <id>",
|
|
13224
|
-
description: "Payment method ID",
|
|
13225
|
-
required: true
|
|
13226
|
-
},
|
|
13227
|
-
credential_type: {
|
|
13228
|
-
schema: z6.enum(["shared_payment_token", "card"]),
|
|
13229
|
-
flag: "--credential-type <type>",
|
|
13230
|
-
description: "Payment credential type",
|
|
13231
|
-
jsonDescription: '"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows \u2014 evaluate the merchant site before choosing',
|
|
13232
|
-
defaultValue: "card",
|
|
13233
|
-
required: true
|
|
13234
|
-
},
|
|
13235
|
-
network_id: {
|
|
13236
|
-
schema: z6.string().min(1),
|
|
13237
|
-
flag: "--network-id <id>",
|
|
13238
|
-
description: "Network ID (required for shared_payment_token)",
|
|
13239
|
-
jsonDescription: "Required for shared_payment_token \u2014 use `link-cli mpp decode --challenge <www-authenticate>` to validate the stripe challenge and extract this value"
|
|
13240
|
-
},
|
|
13241
|
-
amount: {
|
|
13242
|
-
schema: z6.coerce.number().int().positive().max(5e4),
|
|
13243
|
-
flag: "--amount <cents>",
|
|
13244
|
-
description: "Amount in cents",
|
|
13245
|
-
jsonDescription: "Total in cents, max 50000 ($500.00)",
|
|
13246
|
-
required: true
|
|
13247
|
-
},
|
|
13248
|
-
currency: {
|
|
13249
|
-
schema: z6.string().length(3),
|
|
13250
|
-
flag: "--currency <code>",
|
|
13251
|
-
description: "Currency code",
|
|
13252
|
-
defaultValue: "usd"
|
|
13253
|
-
},
|
|
13254
|
-
merchant_name: {
|
|
13255
|
-
schema: z6.string().min(3),
|
|
13256
|
-
flag: "--merchant-name <name>",
|
|
13257
|
-
description: "Merchant name",
|
|
13258
|
-
jsonDescription: "Required for card credential type; forbidden for shared_payment_token",
|
|
13259
|
-
alias: "-m"
|
|
13260
|
-
},
|
|
13261
|
-
merchant_url: {
|
|
13262
|
-
schema: z6.url(),
|
|
13263
|
-
flag: "--merchant-url <url>",
|
|
13264
|
-
description: "Merchant URL",
|
|
13265
|
-
jsonDescription: "Required for card credential type; forbidden for shared_payment_token"
|
|
13266
|
-
},
|
|
13267
|
-
context: {
|
|
13268
|
-
schema: z6.string().min(100),
|
|
13269
|
-
flag: "--context <context>",
|
|
13270
|
-
description: "Description of what is being purchased and why",
|
|
13271
|
-
jsonDescription: "Min 100 chars \u2014 write a full sentence describing the purchase and rationale; the user reads this when approving",
|
|
13272
|
-
required: true
|
|
13273
|
-
},
|
|
13274
|
-
line_items: {
|
|
13275
|
-
schema: z6.array(LineItemSchema),
|
|
13276
|
-
flag: "--line-item <item>",
|
|
13277
|
-
description: "Line item (repeatable)",
|
|
13278
|
-
flagParser: parseKvString
|
|
13279
|
-
},
|
|
13280
|
-
totals: {
|
|
13281
|
-
schema: z6.array(TotalSchema),
|
|
13282
|
-
flag: "--total <total>",
|
|
13283
|
-
description: "Total (repeatable)",
|
|
13284
|
-
flagParser: parseKvString
|
|
13285
|
-
},
|
|
13286
|
-
request_approval: {
|
|
13287
|
-
schema: z6.boolean(),
|
|
13288
|
-
flag: "--request-approval",
|
|
13289
|
-
description: "Request approval and wait for user to approve/deny",
|
|
13290
|
-
jsonDescription: "Polls until approved/denied/expired; blocks until the user acts",
|
|
13291
|
-
defaultValue: true
|
|
13292
|
-
},
|
|
13293
|
-
test: {
|
|
13294
|
-
schema: z6.boolean(),
|
|
13295
|
-
flag: "--test",
|
|
13296
|
-
description: "Use test mode (creates testmode credentials from test card data)",
|
|
13297
|
-
jsonDescription: "When true, creates testmode credentials instead of real ones \u2014 safe for development and testing",
|
|
13298
|
-
defaultValue: false
|
|
13299
|
-
}
|
|
13300
|
-
};
|
|
13301
|
-
var RETRIEVE_INPUT_SCHEMA = {
|
|
13302
|
-
timeout: {
|
|
13303
|
-
schema: z6.coerce.number(),
|
|
13304
|
-
flag: "--timeout <seconds>",
|
|
13305
|
-
description: "Polling timeout in seconds",
|
|
13306
|
-
defaultValue: 300
|
|
13307
|
-
},
|
|
13308
|
-
include: {
|
|
13309
|
-
schema: z6.array(z6.string()),
|
|
13310
|
-
flag: "--include <value>",
|
|
13311
|
-
description: "Include extra data (repeatable, e.g. --include card)"
|
|
13312
|
-
}
|
|
13313
|
-
};
|
|
13314
|
-
var UPDATE_INPUT_SCHEMA = {
|
|
13315
|
-
payment_method_id: {
|
|
13316
|
-
schema: z6.string().min(1),
|
|
13317
|
-
flag: "--payment-method-id <id>",
|
|
13318
|
-
description: "Payment method ID",
|
|
13319
|
-
required: true
|
|
13320
|
-
},
|
|
13321
|
-
amount: {
|
|
13322
|
-
schema: z6.coerce.number().int().positive(),
|
|
13323
|
-
flag: "--amount <cents>",
|
|
13324
|
-
description: "Amount in cents"
|
|
13325
|
-
},
|
|
13326
|
-
merchant_url: {
|
|
13327
|
-
schema: z6.string().min(1),
|
|
13328
|
-
flag: "--merchant-url <url>",
|
|
13329
|
-
description: "Merchant URL"
|
|
13330
|
-
},
|
|
13331
|
-
profile_id: {
|
|
13332
|
-
schema: z6.string().min(1),
|
|
13333
|
-
flag: "--profile-id <id>",
|
|
13334
|
-
description: "Profile ID"
|
|
13335
|
-
},
|
|
13336
|
-
merchant_id: {
|
|
13337
|
-
schema: z6.string().min(1),
|
|
13338
|
-
flag: "--merchant-id <id>",
|
|
13339
|
-
description: "Merchant ID"
|
|
13340
|
-
},
|
|
13341
|
-
currency: {
|
|
13342
|
-
schema: z6.string().min(1),
|
|
13343
|
-
flag: "--currency <code>",
|
|
13344
|
-
description: "Currency code"
|
|
13345
|
-
},
|
|
13346
|
-
line_items: {
|
|
13347
|
-
schema: z6.array(LineItemSchema),
|
|
13348
|
-
flag: "--line-item <item>",
|
|
13349
|
-
description: "Line item (repeatable)",
|
|
13350
|
-
flagParser: parseKvString
|
|
13351
|
-
},
|
|
13352
|
-
totals: {
|
|
13353
|
-
schema: z6.array(TotalSchema),
|
|
13354
|
-
flag: "--total <total>",
|
|
13355
|
-
description: "Total (repeatable)",
|
|
13356
|
-
flagParser: parseKvString
|
|
13357
|
-
}
|
|
13358
|
-
};
|
|
12861
|
+
import { z as z5 } from "incur";
|
|
12862
|
+
var createOptions = z5.object({
|
|
12863
|
+
paymentMethodId: z5.string().describe("Payment method ID"),
|
|
12864
|
+
credentialType: z5.enum(["shared_payment_token", "card"]).default("card").describe(
|
|
12865
|
+
'"card" for checkout forms/Stripe Elements; "shared_payment_token" for HTTP 402/machine payment flows'
|
|
12866
|
+
),
|
|
12867
|
+
networkId: z5.string().optional().describe(
|
|
12868
|
+
"Network ID (required for shared_payment_token) \u2014 use `link-cli mpp decode` to extract"
|
|
12869
|
+
),
|
|
12870
|
+
amount: z5.coerce.number().int().positive().max(5e4).describe("Amount in cents, max 50000 ($500.00)"),
|
|
12871
|
+
currency: z5.string().length(3).default("usd").describe("Currency code"),
|
|
12872
|
+
merchantName: z5.string().optional().describe(
|
|
12873
|
+
"Merchant name (required for card; forbidden for shared_payment_token)"
|
|
12874
|
+
),
|
|
12875
|
+
merchantUrl: z5.string().optional().describe(
|
|
12876
|
+
"Merchant URL (required for card; forbidden for shared_payment_token)"
|
|
12877
|
+
),
|
|
12878
|
+
context: z5.string().min(100).describe(
|
|
12879
|
+
"Min 100 chars \u2014 describe the purchase and rationale; the user reads this when approving"
|
|
12880
|
+
),
|
|
12881
|
+
lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
|
|
12882
|
+
total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)"),
|
|
12883
|
+
requestApproval: z5.boolean().default(true).describe("Request approval and poll until approved/denied/expired"),
|
|
12884
|
+
test: z5.boolean().default(false).describe(
|
|
12885
|
+
"Use test mode (creates testmode credentials from test card data)"
|
|
12886
|
+
)
|
|
12887
|
+
});
|
|
12888
|
+
var retrieveOptions = z5.object({
|
|
12889
|
+
timeout: z5.coerce.number().default(300).describe("Polling timeout in seconds"),
|
|
12890
|
+
interval: z5.coerce.number().default(0).describe(
|
|
12891
|
+
"Poll interval in seconds. When > 0, polls until status is terminal or timeout is reached, yielding status on each attempt."
|
|
12892
|
+
),
|
|
12893
|
+
maxAttempts: z5.coerce.number().default(0).describe("Max poll attempts. 0 = unlimited (use timeout instead)."),
|
|
12894
|
+
include: z5.array(z5.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)")
|
|
12895
|
+
});
|
|
12896
|
+
var updateOptions = z5.object({
|
|
12897
|
+
paymentMethodId: z5.string().optional().describe("Payment method ID"),
|
|
12898
|
+
amount: z5.coerce.number().optional().describe("Amount in cents"),
|
|
12899
|
+
merchantUrl: z5.string().optional().describe("Merchant URL"),
|
|
12900
|
+
profileId: z5.string().optional().describe("Profile ID"),
|
|
12901
|
+
merchantId: z5.string().optional().describe("Merchant ID"),
|
|
12902
|
+
currency: z5.string().optional().describe("Currency code"),
|
|
12903
|
+
lineItem: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Line item (repeatable, key:value format)"),
|
|
12904
|
+
total: z5.array(z5.union([z5.string(), z5.record(z5.string(), z5.unknown())])).default([]).describe("Total (repeatable, key:value format)")
|
|
12905
|
+
});
|
|
13359
12906
|
|
|
13360
12907
|
// src/commands/spend-request/update.tsx
|
|
13361
|
-
import { Box as
|
|
12908
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
13362
12909
|
import Spinner8 from "ink-spinner";
|
|
13363
|
-
import { useEffect as
|
|
13364
|
-
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";
|
|
13365
12912
|
var UpdateSpendRequest = ({
|
|
13366
12913
|
repository,
|
|
13367
12914
|
id,
|
|
13368
12915
|
params,
|
|
13369
12916
|
onComplete
|
|
13370
12917
|
}) => {
|
|
13371
|
-
const [status, setStatus] =
|
|
12918
|
+
const [status, setStatus] = useState8(
|
|
13372
12919
|
"loading"
|
|
13373
12920
|
);
|
|
13374
|
-
const [request, setRequest] =
|
|
13375
|
-
const [error, setError] =
|
|
13376
|
-
|
|
12921
|
+
const [request, setRequest] = useState8(null);
|
|
12922
|
+
const [error, setError] = useState8("");
|
|
12923
|
+
useEffect9(() => {
|
|
13377
12924
|
const update = async () => {
|
|
13378
12925
|
try {
|
|
13379
12926
|
const result = await repository.updateSpendRequest(id, params);
|
|
@@ -13389,369 +12936,354 @@ var UpdateSpendRequest = ({
|
|
|
13389
12936
|
update();
|
|
13390
12937
|
}, [repository, id, params, onComplete]);
|
|
13391
12938
|
if (status === "loading") {
|
|
13392
|
-
return /* @__PURE__ */
|
|
13393
|
-
/* @__PURE__ */
|
|
12939
|
+
return /* @__PURE__ */ jsx15(Box12, { children: /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
|
|
12940
|
+
/* @__PURE__ */ jsx15(Spinner8, { type: "dots" }),
|
|
13394
12941
|
" Updating spend request ",
|
|
13395
12942
|
id,
|
|
13396
12943
|
"..."
|
|
13397
12944
|
] }) });
|
|
13398
12945
|
}
|
|
13399
12946
|
if (status === "error") {
|
|
13400
|
-
return /* @__PURE__ */
|
|
13401
|
-
/* @__PURE__ */
|
|
13402
|
-
/* @__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 })
|
|
13403
12950
|
] });
|
|
13404
12951
|
}
|
|
13405
|
-
return /* @__PURE__ */
|
|
13406
|
-
/* @__PURE__ */
|
|
13407
|
-
/* @__PURE__ */
|
|
13408
|
-
/* @__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: [
|
|
13409
12956
|
"ID: ",
|
|
13410
|
-
/* @__PURE__ */
|
|
12957
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.id })
|
|
13411
12958
|
] }),
|
|
13412
|
-
/* @__PURE__ */
|
|
12959
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13413
12960
|
"Status: ",
|
|
13414
|
-
/* @__PURE__ */
|
|
12961
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.status })
|
|
13415
12962
|
] }),
|
|
13416
|
-
/* @__PURE__ */
|
|
12963
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13417
12964
|
"Amount:",
|
|
13418
12965
|
" ",
|
|
13419
|
-
/* @__PURE__ */
|
|
12966
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: (() => {
|
|
13420
12967
|
const t = request?.totals.find((t2) => t2.type === "total");
|
|
13421
12968
|
return t ? String(t.amount) : "N/A";
|
|
13422
12969
|
})() })
|
|
13423
12970
|
] }),
|
|
13424
|
-
/* @__PURE__ */
|
|
12971
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13425
12972
|
"Merchant: ",
|
|
13426
|
-
/* @__PURE__ */
|
|
12973
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.merchant_name })
|
|
13427
12974
|
] }),
|
|
13428
|
-
/* @__PURE__ */
|
|
12975
|
+
/* @__PURE__ */ jsxs11(Text12, { children: [
|
|
13429
12976
|
"Line Items:",
|
|
13430
12977
|
" ",
|
|
13431
|
-
/* @__PURE__ */
|
|
12978
|
+
/* @__PURE__ */ jsx15(Text12, { bold: true, children: request?.line_items.map((li) => li.name).join(", ") })
|
|
13432
12979
|
] })
|
|
13433
12980
|
] })
|
|
13434
12981
|
] });
|
|
13435
12982
|
};
|
|
13436
12983
|
|
|
13437
12984
|
// src/commands/spend-request/index.tsx
|
|
13438
|
-
import { jsx as
|
|
13439
|
-
function
|
|
13440
|
-
const
|
|
13441
|
-
|
|
13442
|
-
registerSchemaOptions(createCmd, CREATE_INPUT_SCHEMA);
|
|
13443
|
-
createCmd.option(
|
|
13444
|
-
"--json <json>",
|
|
13445
|
-
`JSON input (keys: ${Object.keys(CREATE_INPUT_SCHEMA).join(", ")})`
|
|
13446
|
-
).option(
|
|
13447
|
-
"--output-json",
|
|
13448
|
-
"Output result as JSON instead of interactive display"
|
|
13449
|
-
).addHelpText(
|
|
13450
|
-
"after",
|
|
13451
|
-
buildInputHelp(CREATE_INPUT_SCHEMA) + buildOutputHelp(SPEND_REQUEST_OUTPUT_SCHEMA)
|
|
13452
|
-
).action(async (options) => {
|
|
13453
|
-
requireAuth();
|
|
13454
|
-
let resolved = {};
|
|
13455
|
-
try {
|
|
13456
|
-
resolved = resolveInput(options, CREATE_INPUT_SCHEMA);
|
|
13457
|
-
} catch (err) {
|
|
13458
|
-
if (err instanceof ValidationError)
|
|
13459
|
-
outputErrors(err.errors, !!options.outputJson);
|
|
13460
|
-
outputError(err.message);
|
|
13461
|
-
}
|
|
13462
|
-
const requestApproval = !!resolved.request_approval;
|
|
13463
|
-
const credentialType = resolved.credential_type;
|
|
13464
|
-
const networkId = resolved.network_id;
|
|
13465
|
-
if (credentialType === "shared_payment_token" && !networkId) {
|
|
13466
|
-
outputError(
|
|
13467
|
-
"network-id is required when credential-type is shared_payment_token"
|
|
13468
|
-
);
|
|
13469
|
-
}
|
|
13470
|
-
if (networkId && credentialType !== "shared_payment_token") {
|
|
13471
|
-
outputError(
|
|
13472
|
-
"network-id can only be used when credential-type is shared_payment_token"
|
|
13473
|
-
);
|
|
13474
|
-
}
|
|
13475
|
-
if (credentialType !== "shared_payment_token" && !resolved.merchant_name) {
|
|
13476
|
-
outputError("merchant-name is required when credential-type is card");
|
|
13477
|
-
}
|
|
13478
|
-
if (credentialType !== "shared_payment_token" && !resolved.merchant_url) {
|
|
13479
|
-
outputError("merchant-url is required when credential-type is card");
|
|
13480
|
-
}
|
|
13481
|
-
const createParams = {
|
|
13482
|
-
payment_details: resolved.payment_method_id,
|
|
13483
|
-
credential_type: credentialType,
|
|
13484
|
-
network_id: networkId,
|
|
13485
|
-
amount: resolved.amount,
|
|
13486
|
-
currency: resolved.currency,
|
|
13487
|
-
merchant_name: resolved.merchant_name,
|
|
13488
|
-
merchant_url: resolved.merchant_url,
|
|
13489
|
-
context: resolved.context,
|
|
13490
|
-
line_items: resolved.line_items,
|
|
13491
|
-
totals: resolved.totals,
|
|
13492
|
-
request_approval: requestApproval || void 0,
|
|
13493
|
-
test: resolved.test ? true : void 0
|
|
13494
|
-
};
|
|
13495
|
-
await executeCommand({
|
|
13496
|
-
outputJson: !!options.outputJson,
|
|
13497
|
-
jsonFn: async () => {
|
|
13498
|
-
const created = await repository.createSpendRequest(createParams);
|
|
13499
|
-
if (requestApproval) {
|
|
13500
|
-
outputJson(created);
|
|
13501
|
-
return pollUntilApproved(repository, created.id, {
|
|
13502
|
-
onProgress: (elapsedSeconds) => {
|
|
13503
|
-
process.stderr.write(
|
|
13504
|
-
`${JSON.stringify({
|
|
13505
|
-
type: "waiting",
|
|
13506
|
-
command: "spend_request_approval",
|
|
13507
|
-
elapsed_seconds: elapsedSeconds,
|
|
13508
|
-
approval_url: created.approval_url ?? null,
|
|
13509
|
-
spend_request_id: created.id
|
|
13510
|
-
})}
|
|
13511
|
-
`
|
|
13512
|
-
);
|
|
13513
|
-
}
|
|
13514
|
-
});
|
|
13515
|
-
}
|
|
13516
|
-
return created;
|
|
13517
|
-
},
|
|
13518
|
-
renderFn: () => /* @__PURE__ */ jsx17(
|
|
13519
|
-
CreateSpendRequest,
|
|
13520
|
-
{
|
|
13521
|
-
repository,
|
|
13522
|
-
params: createParams,
|
|
13523
|
-
requestApproval,
|
|
13524
|
-
onComplete: () => {
|
|
13525
|
-
}
|
|
13526
|
-
}
|
|
13527
|
-
)
|
|
13528
|
-
});
|
|
12985
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
12986
|
+
function createSpendRequestCli(repository) {
|
|
12987
|
+
const cli2 = Cli4.create("spend-request", {
|
|
12988
|
+
description: "Spend request management commands"
|
|
13529
12989
|
});
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
"
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
if (resolved.line_items !== void 0)
|
|
13563
|
-
params.line_items = resolved.line_items;
|
|
13564
|
-
if (resolved.totals !== void 0) params.totals = resolved.totals;
|
|
13565
|
-
await executeCommand({
|
|
13566
|
-
outputJson: !!options.outputJson,
|
|
13567
|
-
jsonFn: async () => {
|
|
13568
|
-
return repository.updateSpendRequest(id, params);
|
|
13569
|
-
},
|
|
13570
|
-
renderFn: () => /* @__PURE__ */ jsx17(
|
|
13571
|
-
UpdateSpendRequest,
|
|
13572
|
-
{
|
|
13573
|
-
repository,
|
|
13574
|
-
id,
|
|
13575
|
-
params,
|
|
13576
|
-
onComplete: () => {
|
|
12990
|
+
cli2.command("create", {
|
|
12991
|
+
description: "Create a new spend request",
|
|
12992
|
+
options: createOptions,
|
|
12993
|
+
alias: { merchantName: "m" },
|
|
12994
|
+
outputPolicy: "agent-only",
|
|
12995
|
+
async *run(c) {
|
|
12996
|
+
if (!storage.isAuthenticated()) {
|
|
12997
|
+
return c.error({
|
|
12998
|
+
code: "NOT_AUTHENTICATED",
|
|
12999
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13000
|
+
cta: {
|
|
13001
|
+
commands: [
|
|
13002
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13003
|
+
]
|
|
13004
|
+
}
|
|
13005
|
+
});
|
|
13006
|
+
}
|
|
13007
|
+
const opts = c.options;
|
|
13008
|
+
const requestApproval = !!opts.requestApproval;
|
|
13009
|
+
const credentialType = opts.credentialType;
|
|
13010
|
+
const networkId = opts.networkId;
|
|
13011
|
+
if (credentialType === "shared_payment_token" && !networkId) {
|
|
13012
|
+
return c.error({
|
|
13013
|
+
code: "INVALID_INPUT",
|
|
13014
|
+
message: "network-id is required when credential-type is shared_payment_token",
|
|
13015
|
+
cta: {
|
|
13016
|
+
commands: [
|
|
13017
|
+
{
|
|
13018
|
+
command: "mpp decode",
|
|
13019
|
+
description: "Decode a WWW-Authenticate challenge to extract network-id"
|
|
13020
|
+
}
|
|
13021
|
+
]
|
|
13577
13022
|
}
|
|
13023
|
+
});
|
|
13024
|
+
}
|
|
13025
|
+
if (networkId && credentialType !== "shared_payment_token") {
|
|
13026
|
+
return c.error({
|
|
13027
|
+
code: "INVALID_INPUT",
|
|
13028
|
+
message: "network-id can only be used when credential-type is shared_payment_token"
|
|
13029
|
+
});
|
|
13030
|
+
}
|
|
13031
|
+
if (credentialType !== "shared_payment_token" && !opts.merchantName) {
|
|
13032
|
+
return c.error({
|
|
13033
|
+
code: "INVALID_INPUT",
|
|
13034
|
+
message: "merchant-name is required when credential-type is card"
|
|
13035
|
+
});
|
|
13036
|
+
}
|
|
13037
|
+
if (credentialType !== "shared_payment_token" && !opts.merchantUrl) {
|
|
13038
|
+
return c.error({
|
|
13039
|
+
code: "INVALID_INPUT",
|
|
13040
|
+
message: "merchant-url is required when credential-type is card"
|
|
13041
|
+
});
|
|
13042
|
+
}
|
|
13043
|
+
const lineItems = opts.lineItem?.length ? opts.lineItem.map(
|
|
13044
|
+
(item) => typeof item === "string" ? parseLineItemFlag(item) : item
|
|
13045
|
+
) : void 0;
|
|
13046
|
+
const totals = opts.total?.length ? opts.total.map(
|
|
13047
|
+
(item) => typeof item === "string" ? parseTotalFlag(item) : item
|
|
13048
|
+
) : void 0;
|
|
13049
|
+
const createParams = {
|
|
13050
|
+
payment_details: opts.paymentMethodId,
|
|
13051
|
+
credential_type: credentialType,
|
|
13052
|
+
network_id: networkId,
|
|
13053
|
+
amount: opts.amount,
|
|
13054
|
+
currency: opts.currency,
|
|
13055
|
+
merchant_name: opts.merchantName,
|
|
13056
|
+
merchant_url: opts.merchantUrl,
|
|
13057
|
+
context: opts.context,
|
|
13058
|
+
line_items: lineItems,
|
|
13059
|
+
totals,
|
|
13060
|
+
request_approval: requestApproval || void 0,
|
|
13061
|
+
test: opts.test ? true : void 0
|
|
13062
|
+
};
|
|
13063
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13064
|
+
return new Promise((resolve) => {
|
|
13065
|
+
const { waitUntilExit } = render4(
|
|
13066
|
+
/* @__PURE__ */ jsx16(
|
|
13067
|
+
CreateSpendRequest,
|
|
13068
|
+
{
|
|
13069
|
+
repository,
|
|
13070
|
+
params: createParams,
|
|
13071
|
+
requestApproval,
|
|
13072
|
+
onComplete: () => {
|
|
13073
|
+
}
|
|
13074
|
+
}
|
|
13075
|
+
)
|
|
13076
|
+
);
|
|
13077
|
+
waitUntilExit().then(async () => {
|
|
13078
|
+
const created2 = await repository.createSpendRequest(createParams);
|
|
13079
|
+
resolve(created2);
|
|
13080
|
+
});
|
|
13081
|
+
});
|
|
13082
|
+
}
|
|
13083
|
+
const created = await repository.createSpendRequest(createParams);
|
|
13084
|
+
if (!requestApproval) {
|
|
13085
|
+
yield created;
|
|
13086
|
+
return;
|
|
13087
|
+
}
|
|
13088
|
+
yield {
|
|
13089
|
+
...created,
|
|
13090
|
+
instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${created.id} --interval 2 --max-attempts 150\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
|
|
13091
|
+
_next: {
|
|
13092
|
+
command: `spend-request retrieve ${created.id} --interval 2 --max-attempts 150`,
|
|
13093
|
+
until: "status changes from pending_approval"
|
|
13578
13094
|
}
|
|
13579
|
-
|
|
13580
|
-
}
|
|
13095
|
+
};
|
|
13096
|
+
}
|
|
13581
13097
|
});
|
|
13582
|
-
|
|
13583
|
-
"
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
elapsed_seconds: elapsedSeconds,
|
|
13599
|
-
approval_url: approval.approval_link ?? null,
|
|
13600
|
-
spend_request_id: id
|
|
13601
|
-
})}
|
|
13602
|
-
`
|
|
13603
|
-
);
|
|
13098
|
+
cli2.command("update", {
|
|
13099
|
+
description: "Update a spend request",
|
|
13100
|
+
args: z6.object({
|
|
13101
|
+
id: z6.string().describe("Spend request ID")
|
|
13102
|
+
}),
|
|
13103
|
+
options: updateOptions,
|
|
13104
|
+
outputPolicy: "agent-only",
|
|
13105
|
+
async run(c) {
|
|
13106
|
+
if (!storage.isAuthenticated()) {
|
|
13107
|
+
return c.error({
|
|
13108
|
+
code: "NOT_AUTHENTICATED",
|
|
13109
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13110
|
+
cta: {
|
|
13111
|
+
commands: [
|
|
13112
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13113
|
+
]
|
|
13604
13114
|
}
|
|
13605
13115
|
});
|
|
13606
|
-
}
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13116
|
+
}
|
|
13117
|
+
const id = c.args.id;
|
|
13118
|
+
const opts = c.options;
|
|
13119
|
+
const params = {};
|
|
13120
|
+
if (opts.paymentMethodId !== void 0)
|
|
13121
|
+
params.payment_details = opts.paymentMethodId;
|
|
13122
|
+
if (opts.amount !== void 0) params.amount = opts.amount;
|
|
13123
|
+
if (opts.merchantUrl !== void 0)
|
|
13124
|
+
params.merchant_url = opts.merchantUrl;
|
|
13125
|
+
if (opts.profileId !== void 0) params.profile_id = opts.profileId;
|
|
13126
|
+
if (opts.merchantId !== void 0) params.merchant_id = opts.merchantId;
|
|
13127
|
+
if (opts.currency !== void 0) params.currency = opts.currency;
|
|
13128
|
+
if (opts.lineItem?.length)
|
|
13129
|
+
params.line_items = opts.lineItem.map(
|
|
13130
|
+
(item) => typeof item === "string" ? parseLineItemFlag(item) : item
|
|
13131
|
+
);
|
|
13132
|
+
if (opts.total?.length)
|
|
13133
|
+
params.totals = opts.total.map(
|
|
13134
|
+
(item) => typeof item === "string" ? parseTotalFlag(item) : item
|
|
13135
|
+
);
|
|
13136
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13137
|
+
return new Promise((resolve) => {
|
|
13138
|
+
const { waitUntilExit } = render4(
|
|
13139
|
+
/* @__PURE__ */ jsx16(
|
|
13140
|
+
UpdateSpendRequest,
|
|
13141
|
+
{
|
|
13142
|
+
repository,
|
|
13143
|
+
id,
|
|
13144
|
+
params,
|
|
13145
|
+
onComplete: () => {
|
|
13146
|
+
}
|
|
13147
|
+
}
|
|
13148
|
+
)
|
|
13149
|
+
);
|
|
13150
|
+
waitUntilExit().then(async () => {
|
|
13151
|
+
resolve(await repository.updateSpendRequest(id, params));
|
|
13152
|
+
});
|
|
13153
|
+
});
|
|
13154
|
+
}
|
|
13155
|
+
return repository.updateSpendRequest(id, params);
|
|
13156
|
+
}
|
|
13157
|
+
});
|
|
13158
|
+
cli2.command("request-approval", {
|
|
13159
|
+
description: "Request approval for a spend request",
|
|
13160
|
+
args: z6.object({
|
|
13161
|
+
id: z6.string().describe("Spend request ID")
|
|
13162
|
+
}),
|
|
13163
|
+
outputPolicy: "agent-only",
|
|
13164
|
+
async *run(c) {
|
|
13165
|
+
if (!storage.isAuthenticated()) {
|
|
13166
|
+
return c.error({
|
|
13167
|
+
code: "NOT_AUTHENTICATED",
|
|
13168
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13169
|
+
cta: {
|
|
13170
|
+
commands: [
|
|
13171
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13172
|
+
]
|
|
13613
13173
|
}
|
|
13174
|
+
});
|
|
13175
|
+
}
|
|
13176
|
+
const id = c.args.id;
|
|
13177
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13178
|
+
return new Promise((resolve) => {
|
|
13179
|
+
const { waitUntilExit } = render4(
|
|
13180
|
+
/* @__PURE__ */ jsx16(
|
|
13181
|
+
RequestApproval,
|
|
13182
|
+
{
|
|
13183
|
+
repository,
|
|
13184
|
+
id,
|
|
13185
|
+
onComplete: () => {
|
|
13186
|
+
}
|
|
13187
|
+
}
|
|
13188
|
+
)
|
|
13189
|
+
);
|
|
13190
|
+
waitUntilExit().then(async () => {
|
|
13191
|
+
const approval2 = await repository.requestApproval(id);
|
|
13192
|
+
resolve(approval2);
|
|
13193
|
+
});
|
|
13194
|
+
});
|
|
13195
|
+
}
|
|
13196
|
+
const approval = await repository.requestApproval(id);
|
|
13197
|
+
yield {
|
|
13198
|
+
...approval,
|
|
13199
|
+
instruction: `Present the approval_url to the user and ask them to approve in the Link app. Then call \`spend-request retrieve ${id} --interval 2 --max-attempts 150\` to poll until approved. Do not wait for the user to reply \u2014 start polling immediately.`,
|
|
13200
|
+
_next: {
|
|
13201
|
+
command: `spend-request retrieve ${id} --interval 2 --max-attempts 150`,
|
|
13202
|
+
until: "status changes from pending_approval"
|
|
13614
13203
|
}
|
|
13615
|
-
|
|
13616
|
-
}
|
|
13204
|
+
};
|
|
13205
|
+
}
|
|
13617
13206
|
});
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13624
|
-
"
|
|
13625
|
-
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13207
|
+
cli2.command("retrieve", {
|
|
13208
|
+
description: "Retrieve a spend request",
|
|
13209
|
+
args: z6.object({
|
|
13210
|
+
id: z6.string().describe("Spend request ID")
|
|
13211
|
+
}),
|
|
13212
|
+
options: retrieveOptions,
|
|
13213
|
+
outputPolicy: "agent-only",
|
|
13214
|
+
async *run(c) {
|
|
13215
|
+
if (!storage.isAuthenticated()) {
|
|
13216
|
+
return c.error({
|
|
13217
|
+
code: "NOT_AUTHENTICATED",
|
|
13218
|
+
message: 'Not authenticated. Run "link-cli auth login" first.',
|
|
13219
|
+
cta: {
|
|
13220
|
+
commands: [
|
|
13221
|
+
{ command: "auth login", description: "Log in to Link" }
|
|
13222
|
+
]
|
|
13223
|
+
}
|
|
13224
|
+
});
|
|
13225
|
+
}
|
|
13226
|
+
const id = c.args.id;
|
|
13227
|
+
const opts = c.options;
|
|
13228
|
+
const timeout = opts.timeout;
|
|
13229
|
+
const interval = opts.interval;
|
|
13230
|
+
const maxAttempts = opts.maxAttempts;
|
|
13231
|
+
const includeArr = opts.include;
|
|
13232
|
+
const include = includeArr?.length ? includeArr : void 0;
|
|
13233
|
+
if (!c.agent && !c.formatExplicit) {
|
|
13234
|
+
return new Promise((resolve) => {
|
|
13235
|
+
const { waitUntilExit } = render4(
|
|
13236
|
+
/* @__PURE__ */ jsx16(
|
|
13237
|
+
RetrieveSpendRequest,
|
|
13238
|
+
{
|
|
13239
|
+
repository,
|
|
13240
|
+
id,
|
|
13241
|
+
timeout,
|
|
13242
|
+
include,
|
|
13243
|
+
onComplete: () => {
|
|
13244
|
+
}
|
|
13245
|
+
}
|
|
13246
|
+
)
|
|
13247
|
+
);
|
|
13248
|
+
waitUntilExit().then(async () => {
|
|
13249
|
+
const request = await repository.getSpendRequest(id, { include });
|
|
13250
|
+
resolve(request);
|
|
13251
|
+
});
|
|
13252
|
+
});
|
|
13253
|
+
}
|
|
13254
|
+
const terminalStatuses = /* @__PURE__ */ new Set([
|
|
13255
|
+
"approved",
|
|
13256
|
+
"denied",
|
|
13257
|
+
"expired",
|
|
13258
|
+
"succeeded",
|
|
13259
|
+
"failed"
|
|
13260
|
+
]);
|
|
13261
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
13262
|
+
let attempts = 0;
|
|
13263
|
+
while (true) {
|
|
13645
13264
|
const request = await repository.getSpendRequest(id, { include });
|
|
13646
13265
|
if (!request) {
|
|
13647
|
-
|
|
13266
|
+
return c.error({
|
|
13267
|
+
code: "NOT_FOUND",
|
|
13268
|
+
message: `Spend request ${id} not found`
|
|
13269
|
+
});
|
|
13648
13270
|
}
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
RetrieveSpendRequest,
|
|
13653
|
-
{
|
|
13654
|
-
repository,
|
|
13655
|
-
id,
|
|
13656
|
-
timeout,
|
|
13657
|
-
include,
|
|
13658
|
-
onComplete: () => {
|
|
13659
|
-
}
|
|
13271
|
+
if (terminalStatuses.has(request.status)) {
|
|
13272
|
+
yield request;
|
|
13273
|
+
return;
|
|
13660
13274
|
}
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
// src/utils/configure-root-help.ts
|
|
13668
|
-
function configureRootHelp(program2, authCommand2, spendIntentCommand, paymentMethodsCommand2, skillCommand2, mppCommand2) {
|
|
13669
|
-
program2.configureHelp({
|
|
13670
|
-
formatHelp(cmd, helper) {
|
|
13671
|
-
const helpWidth = helper.helpWidth || 80;
|
|
13672
|
-
const itemIndent = 2;
|
|
13673
|
-
const itemSeparator = 2;
|
|
13674
|
-
function formatItem(term, termWidth, description) {
|
|
13675
|
-
if (description) {
|
|
13676
|
-
const fullText = `${term.padEnd(termWidth + itemSeparator)}${description}`;
|
|
13677
|
-
return helper.wrap(
|
|
13678
|
-
fullText,
|
|
13679
|
-
helpWidth - itemIndent,
|
|
13680
|
-
termWidth + itemSeparator
|
|
13681
|
-
);
|
|
13275
|
+
attempts++;
|
|
13276
|
+
const shouldStop = interval <= 0 || maxAttempts > 0 && attempts >= maxAttempts || Date.now() >= deadline;
|
|
13277
|
+
if (shouldStop) {
|
|
13278
|
+
yield request;
|
|
13279
|
+
return;
|
|
13682
13280
|
}
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
function formatList(items) {
|
|
13686
|
-
return items.join("\n").replace(/^/gm, " ".repeat(itemIndent));
|
|
13687
|
-
}
|
|
13688
|
-
const output = [`Usage: ${helper.commandUsage(cmd)}`, ""];
|
|
13689
|
-
const desc = helper.commandDescription(cmd);
|
|
13690
|
-
if (desc.length > 0) {
|
|
13691
|
-
output.push(helper.wrap(desc, helpWidth, 0), "");
|
|
13281
|
+
yield request;
|
|
13282
|
+
await new Promise((resolve) => setTimeout(resolve, interval * 1e3));
|
|
13692
13283
|
}
|
|
13693
|
-
output.push(
|
|
13694
|
-
"Getting started:",
|
|
13695
|
-
formatList([
|
|
13696
|
-
"As an agent, you MUST run `link-cli skill` to fully understand how to get setup.",
|
|
13697
|
-
"Optional: Run `link-cli skill --install` to install the skill for future use."
|
|
13698
|
-
]),
|
|
13699
|
-
""
|
|
13700
|
-
);
|
|
13701
|
-
const optTermWidth = helper.longestOptionTermLength(cmd, helper);
|
|
13702
|
-
const optionList = helper.visibleOptions(cmd).map(
|
|
13703
|
-
(option) => formatItem(
|
|
13704
|
-
helper.optionTerm(option),
|
|
13705
|
-
optTermWidth,
|
|
13706
|
-
helper.optionDescription(option)
|
|
13707
|
-
)
|
|
13708
|
-
);
|
|
13709
|
-
if (optionList.length > 0) {
|
|
13710
|
-
output.push("Options:", formatList(optionList), "");
|
|
13711
|
-
}
|
|
13712
|
-
const commandGroups = [
|
|
13713
|
-
{ heading: "Auth:", parent: authCommand2 },
|
|
13714
|
-
{ heading: "Spend Requests:", parent: spendIntentCommand },
|
|
13715
|
-
{ heading: "Payment Methods:", parent: paymentMethodsCommand2 },
|
|
13716
|
-
{ heading: "MPP:", parent: mppCommand2 }
|
|
13717
|
-
];
|
|
13718
|
-
const allLeafCmds = commandGroups.flatMap(
|
|
13719
|
-
({ parent }) => helper.visibleCommands(parent)
|
|
13720
|
-
);
|
|
13721
|
-
const maxTermWidth = allLeafCmds.reduce(
|
|
13722
|
-
(max, sub) => Math.max(
|
|
13723
|
-
max,
|
|
13724
|
-
// biome-ignore lint/style/noNonNullAssertion: sub is always a subcommand and always has a parent
|
|
13725
|
-
`${sub.parent.name()} ${helper.subcommandTerm(sub)}`.length
|
|
13726
|
-
),
|
|
13727
|
-
skillCommand2.name().length
|
|
13728
|
-
);
|
|
13729
|
-
for (const { heading, parent } of commandGroups) {
|
|
13730
|
-
const cmds = helper.visibleCommands(parent);
|
|
13731
|
-
if (cmds.length === 0) continue;
|
|
13732
|
-
const list = cmds.map(
|
|
13733
|
-
(sub) => formatItem(
|
|
13734
|
-
`${parent.name()} ${helper.subcommandTerm(sub)}`,
|
|
13735
|
-
maxTermWidth,
|
|
13736
|
-
helper.subcommandDescription(sub)
|
|
13737
|
-
)
|
|
13738
|
-
);
|
|
13739
|
-
output.push(heading, formatList(list), "");
|
|
13740
|
-
}
|
|
13741
|
-
output.push(
|
|
13742
|
-
"Other:",
|
|
13743
|
-
formatList([
|
|
13744
|
-
formatItem(
|
|
13745
|
-
skillCommand2.name(),
|
|
13746
|
-
maxTermWidth,
|
|
13747
|
-
skillCommand2.description()
|
|
13748
|
-
)
|
|
13749
|
-
]),
|
|
13750
|
-
""
|
|
13751
|
-
);
|
|
13752
|
-
return output.join("\n");
|
|
13753
13284
|
}
|
|
13754
13285
|
});
|
|
13286
|
+
return cli2;
|
|
13755
13287
|
}
|
|
13756
13288
|
|
|
13757
13289
|
// src/auth/auth-resource.ts
|
|
@@ -13926,6 +13458,26 @@ ${JSON.stringify(redacted, null, 2)}`
|
|
|
13926
13458
|
}
|
|
13927
13459
|
);
|
|
13928
13460
|
}
|
|
13461
|
+
async revokeToken(token) {
|
|
13462
|
+
const { status, data, rawBody } = await this.postForm(
|
|
13463
|
+
`${this.config.authBaseUrl}/device/revoke`,
|
|
13464
|
+
{
|
|
13465
|
+
client_id: CLIENT_ID,
|
|
13466
|
+
token
|
|
13467
|
+
}
|
|
13468
|
+
);
|
|
13469
|
+
if (status < 200 || status >= 300) {
|
|
13470
|
+
throw new LinkApiError(
|
|
13471
|
+
formatOAuthError("Token revocation failed", status, data, rawBody),
|
|
13472
|
+
{
|
|
13473
|
+
status,
|
|
13474
|
+
code: data?.error,
|
|
13475
|
+
rawBody,
|
|
13476
|
+
details: data
|
|
13477
|
+
}
|
|
13478
|
+
);
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13929
13481
|
async refreshToken(refreshToken) {
|
|
13930
13482
|
const { status, data, rawBody } = await this.postForm(
|
|
13931
13483
|
`${this.config.authBaseUrl}/device/token`,
|
|
@@ -14034,46 +13586,28 @@ var ResourceFactory = class {
|
|
|
14034
13586
|
};
|
|
14035
13587
|
|
|
14036
13588
|
// src/cli.tsx
|
|
14037
|
-
var cliVersion = "0.
|
|
14038
|
-
var buildNumber = "
|
|
13589
|
+
var cliVersion = "0.2.0";
|
|
13590
|
+
var buildNumber = "1";
|
|
14039
13591
|
var defaultHeaders = {
|
|
14040
13592
|
"User-Agent": `link-cli/${cliVersion} (build ${buildNumber})`,
|
|
14041
13593
|
"X-Build-Number": buildNumber
|
|
14042
13594
|
};
|
|
14043
|
-
var program = new Command();
|
|
14044
13595
|
var verbose = process.argv.includes("--verbose");
|
|
14045
13596
|
var factory = new ResourceFactory({ verbose, defaultHeaders });
|
|
14046
13597
|
var authRepo = factory.createAuthResource();
|
|
14047
13598
|
var spendRequestRepo = factory.createSpendRequestResource();
|
|
14048
|
-
|
|
14049
|
-
"Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users."
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
write("Run 'link-cli --skill' for full instructions.\n");
|
|
14057
|
-
}
|
|
14058
|
-
}
|
|
14059
|
-
});
|
|
14060
|
-
var authCommand = registerAuthCommands(program, authRepo);
|
|
14061
|
-
var spendRequestCommand = registerSpendRequestCommands(
|
|
14062
|
-
program,
|
|
14063
|
-
spendRequestRepo
|
|
13599
|
+
var cli = Cli5.create("link-cli", {
|
|
13600
|
+
description: "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users.",
|
|
13601
|
+
version: `${cliVersion} (build ${buildNumber})`
|
|
13602
|
+
});
|
|
13603
|
+
cli.command(createAuthCli(authRepo));
|
|
13604
|
+
cli.command(createSpendRequestCli(spendRequestRepo));
|
|
13605
|
+
cli.command(
|
|
13606
|
+
createPaymentMethodsCli(() => factory.createPaymentMethodsResource())
|
|
14064
13607
|
);
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
configureRootHelp(
|
|
14072
|
-
program,
|
|
14073
|
-
authCommand,
|
|
14074
|
-
spendRequestCommand,
|
|
14075
|
-
paymentMethodsCommand,
|
|
14076
|
-
skillCommand,
|
|
14077
|
-
mppCommand
|
|
14078
|
-
);
|
|
14079
|
-
program.parse();
|
|
13608
|
+
cli.command(createMppCli(spendRequestRepo));
|
|
13609
|
+
cli.serve();
|
|
13610
|
+
var cli_default = cli;
|
|
13611
|
+
export {
|
|
13612
|
+
cli_default as default
|
|
13613
|
+
};
|