@stripe/link-cli 0.10.0 → 0.11.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 +18 -2
- package/dist/cli.js +96 -12
- package/package.json +3 -1
- package/postinstall.mjs +50 -0
package/README.md
CHANGED
|
@@ -154,6 +154,18 @@ link-cli spend-request create ... \
|
|
|
154
154
|
|
|
155
155
|
In MCP/agent mode, pass as a structured object.
|
|
156
156
|
|
|
157
|
+
#### Metadata
|
|
158
|
+
|
|
159
|
+
Attach arbitrary string data to a spend request with the repeatable `--metadata` flag (`key:value` format). Max 50 keys, key ≤ 40 chars, value ≤ 500 chars.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
link-cli spend-request create ... \
|
|
163
|
+
--metadata "order_id:ord_123" \
|
|
164
|
+
--metadata "team:growth"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
In MCP/agent mode, pass `metadata` as a structured `{ key: value }` object.
|
|
168
|
+
|
|
157
169
|
#### Credential types
|
|
158
170
|
|
|
159
171
|
By default, a spend request provisions a virtual card. For merchants that support the [Machine Payments Protocol](https://mpp.dev) (HTTP 402) and the Stripe payment method, instead pass `--credential-type "shared_payment_token"`.
|
|
@@ -207,11 +219,13 @@ When you provide `--client-name`, the Link app displays it when you approve the
|
|
|
207
219
|
|
|
208
220
|
With `--interval`, the login command yields the verification code immediately and then polls inline until authenticated or timed out — no separate `auth status` call needed. This is recommended for agents that cannot relay the code while a separate polling command blocks their I/O channel.
|
|
209
221
|
|
|
210
|
-
`auth status` includes an `update` field when a newer version is available:
|
|
222
|
+
`auth status` reports the `scope` and `authorization_details` the current session was granted (echoed by the token endpoint at login/refresh and stored in the credential file), and includes an `update` field when a newer version is available:
|
|
211
223
|
|
|
212
224
|
```json
|
|
213
225
|
{
|
|
214
226
|
"authenticated": true,
|
|
227
|
+
"scope": "userinfo:read payment_methods.agentic",
|
|
228
|
+
"authorization_details": [{ "type": "source", "actions": ["read"] }],
|
|
215
229
|
"update": {
|
|
216
230
|
"current_version": "0.1.2",
|
|
217
231
|
"latest_version": "0.2.0",
|
|
@@ -220,6 +234,8 @@ With `--interval`, the login command yields the verification code immediately an
|
|
|
220
234
|
}
|
|
221
235
|
```
|
|
222
236
|
|
|
237
|
+
`scope` and `authorization_details` are only present when the token endpoint returned them.
|
|
238
|
+
|
|
223
239
|
Set `NO_UPDATE_NOTIFIER=1` to suppress update checks (for example, in CI).
|
|
224
240
|
|
|
225
241
|
All commands accept `--auth <path>` to store auth credentials in a specific file instead of the default location. `auth login` writes to this file; all other commands read from it. Useful for running multiple sessions with separate identities.
|
|
@@ -231,7 +247,7 @@ A spend request moves through: **create** → **request approval** → **approve
|
|
|
231
247
|
**Required fields for create:** `merchant_name`, `merchant_url`, `context`, `amount`. `payment_method_id` is optional — if omitted, your default payment method will be used, or the first eligible one if no default is set.
|
|
232
248
|
|
|
233
249
|
**Constraints:** `context` must be at least 100 characters; `amount` must not exceed 500000 (cents); `currency` must be a 3-letter ISO code. The user has 10 minutes from when approval is requested to approve. Approved credentials (card or SPT) are valid for 12 hours from spend request creation.
|
|
234
|
-
**Test mode:** Pass `--test` to create testmode credentials (
|
|
250
|
+
**Test mode:** Pass `--test` to create a testmode SpendRequest. A testmode SpendRequest will return test payment credentials (e.g test card `4000009990001984`) rather than a real payment credential. Testmode SpendRequests will not charge the underlying payment method of the SpendRequest. This is useful for development and integration testing without real payment methods.
|
|
235
251
|
|
|
236
252
|
```bash
|
|
237
253
|
# Update before approval
|
package/dist/cli.js
CHANGED
|
@@ -12656,7 +12656,11 @@ function resolveAuthInfo(envAccessToken2, authStorage2) {
|
|
|
12656
12656
|
source: "storage",
|
|
12657
12657
|
tokenPreview: `${auth.access_token.substring(0, 20)}...`,
|
|
12658
12658
|
tokenType: auth.token_type,
|
|
12659
|
-
credentialsPath
|
|
12659
|
+
credentialsPath,
|
|
12660
|
+
...auth.scope && { scope: auth.scope },
|
|
12661
|
+
...auth.authorization_details && {
|
|
12662
|
+
authorizationDetails: auth.authorization_details
|
|
12663
|
+
}
|
|
12660
12664
|
};
|
|
12661
12665
|
}
|
|
12662
12666
|
return { authenticated: false, source: "storage", credentialsPath };
|
|
@@ -12690,6 +12694,15 @@ var AuthStatus = ({
|
|
|
12690
12694
|
"Token type: ",
|
|
12691
12695
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: info.tokenType })
|
|
12692
12696
|
] }),
|
|
12697
|
+
info.source === "storage" && info.scope && /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
12698
|
+
"Scope: ",
|
|
12699
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: info.scope })
|
|
12700
|
+
] }),
|
|
12701
|
+
info.source === "storage" && info.authorizationDetails && /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
12702
|
+
"Authorization details:",
|
|
12703
|
+
" ",
|
|
12704
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: JSON.stringify(info.authorizationDetails) })
|
|
12705
|
+
] }),
|
|
12693
12706
|
info.source === "env" ? /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
12694
12707
|
"Source: ",
|
|
12695
12708
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "LINK_ACCESS_TOKEN" })
|
|
@@ -12730,6 +12743,10 @@ async function* pollAuthStatus(authResource, storage2, opts, update) {
|
|
|
12730
12743
|
access_token: `${auth.access_token.substring(0, 20)}...`,
|
|
12731
12744
|
token_type: auth.token_type,
|
|
12732
12745
|
credentials_path: storage2.getPath(),
|
|
12746
|
+
...auth.scope && { scope: auth.scope },
|
|
12747
|
+
...auth.authorization_details && {
|
|
12748
|
+
authorization_details: auth.authorization_details
|
|
12749
|
+
},
|
|
12733
12750
|
...update && { update }
|
|
12734
12751
|
};
|
|
12735
12752
|
}
|
|
@@ -12939,7 +12956,11 @@ function createAuthCli(authResource, getUpdateInfo2, authStorage2, envAccessToke
|
|
|
12939
12956
|
access_token: info.tokenPreview,
|
|
12940
12957
|
token_type: info.tokenType,
|
|
12941
12958
|
...info.source === "storage" && {
|
|
12942
|
-
credentials_path: info.credentialsPath
|
|
12959
|
+
credentials_path: info.credentialsPath,
|
|
12960
|
+
...info.scope && { scope: info.scope },
|
|
12961
|
+
...info.authorizationDetails && {
|
|
12962
|
+
authorization_details: info.authorizationDetails
|
|
12963
|
+
}
|
|
12943
12964
|
},
|
|
12944
12965
|
...update && { update }
|
|
12945
12966
|
};
|
|
@@ -13894,7 +13915,11 @@ function buildHeaders(data, headers) {
|
|
|
13894
13915
|
async function readPayResult(response) {
|
|
13895
13916
|
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
13896
13917
|
const body = await response.text();
|
|
13897
|
-
return {
|
|
13918
|
+
return sanitizeDeep({
|
|
13919
|
+
status: response.status,
|
|
13920
|
+
headers: responseHeaders,
|
|
13921
|
+
body
|
|
13922
|
+
});
|
|
13898
13923
|
}
|
|
13899
13924
|
function createStripePaymentClient(spt) {
|
|
13900
13925
|
const stripeCharge = Method.toClient(StripeMethods.charge, {
|
|
@@ -15998,6 +16023,7 @@ var CreateSpendRequest = ({
|
|
|
15998
16023
|
const [request, setRequest] = useState9(null);
|
|
15999
16024
|
const [error, setError] = useState9("");
|
|
16000
16025
|
const [verificationUrl, setVerificationUrl] = useState9("");
|
|
16026
|
+
const [supportUrl, setSupportUrl] = useState9("");
|
|
16001
16027
|
const [outputFilePath, setOutputFilePath] = useState9(null);
|
|
16002
16028
|
const [fileError, setFileError] = useState9("");
|
|
16003
16029
|
const approvalUrl = request?.approval_url ?? "";
|
|
@@ -16038,8 +16064,11 @@ var CreateSpendRequest = ({
|
|
|
16038
16064
|
} catch (err) {
|
|
16039
16065
|
setError(err.message);
|
|
16040
16066
|
if (err instanceof LinkApiError) {
|
|
16041
|
-
const
|
|
16042
|
-
if (
|
|
16067
|
+
const errDetail = err.details;
|
|
16068
|
+
if (errDetail?.error?.verification_url)
|
|
16069
|
+
setVerificationUrl(errDetail.error.verification_url);
|
|
16070
|
+
if (errDetail?.error?.support_url)
|
|
16071
|
+
setSupportUrl(errDetail.error.support_url);
|
|
16043
16072
|
}
|
|
16044
16073
|
setStatus("error");
|
|
16045
16074
|
setTimeout(() => completeAndExit(null), DISPLAY_DELAY_MS);
|
|
@@ -16072,6 +16101,10 @@ var CreateSpendRequest = ({
|
|
|
16072
16101
|
verificationUrl && /* @__PURE__ */ jsxs18(Text20, { color: "red", children: [
|
|
16073
16102
|
"Complete additional verification at: ",
|
|
16074
16103
|
verificationUrl
|
|
16104
|
+
] }),
|
|
16105
|
+
supportUrl && /* @__PURE__ */ jsxs18(Text20, { color: "red", children: [
|
|
16106
|
+
"Identity verification failed. Contact support at: ",
|
|
16107
|
+
supportUrl
|
|
16075
16108
|
] })
|
|
16076
16109
|
] });
|
|
16077
16110
|
}
|
|
@@ -16241,6 +16274,7 @@ var RequestApproval = ({
|
|
|
16241
16274
|
const [result, setResult] = useState10(null);
|
|
16242
16275
|
const [error, setError] = useState10("");
|
|
16243
16276
|
const [verificationUrl, setVerificationUrl] = useState10("");
|
|
16277
|
+
const [supportUrl, setSupportUrl] = useState10("");
|
|
16244
16278
|
const { exit } = useApp6();
|
|
16245
16279
|
const completeAndExit = useCallback10(
|
|
16246
16280
|
(result2) => {
|
|
@@ -16270,8 +16304,11 @@ var RequestApproval = ({
|
|
|
16270
16304
|
} catch (err) {
|
|
16271
16305
|
setError(err.message);
|
|
16272
16306
|
if (err instanceof LinkApiError) {
|
|
16273
|
-
const
|
|
16274
|
-
if (
|
|
16307
|
+
const errDetail = err.details;
|
|
16308
|
+
if (errDetail?.error?.verification_url)
|
|
16309
|
+
setVerificationUrl(errDetail.error.verification_url);
|
|
16310
|
+
if (errDetail?.error?.support_url)
|
|
16311
|
+
setSupportUrl(errDetail.error.support_url);
|
|
16275
16312
|
}
|
|
16276
16313
|
setStatus("error");
|
|
16277
16314
|
setTimeout(() => {
|
|
@@ -16295,6 +16332,10 @@ var RequestApproval = ({
|
|
|
16295
16332
|
verificationUrl && /* @__PURE__ */ jsxs20(Text22, { color: "red", children: [
|
|
16296
16333
|
"Complete additional verification at: ",
|
|
16297
16334
|
verificationUrl
|
|
16335
|
+
] }),
|
|
16336
|
+
supportUrl && /* @__PURE__ */ jsxs20(Text22, { color: "red", children: [
|
|
16337
|
+
"Identity verification failed. Contact support at: ",
|
|
16338
|
+
supportUrl
|
|
16298
16339
|
] })
|
|
16299
16340
|
] });
|
|
16300
16341
|
}
|
|
@@ -16580,7 +16621,16 @@ var RetrieveSpendRequest = ({
|
|
|
16580
16621
|
/* @__PURE__ */ jsx30(Text23, { bold: true, children: psd.refund_details.state })
|
|
16581
16622
|
] })
|
|
16582
16623
|
] })
|
|
16583
|
-
] })
|
|
16624
|
+
] }),
|
|
16625
|
+
request?.link_transaction_id && /* @__PURE__ */ jsx30(Box21, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text23, { children: [
|
|
16626
|
+
"Transaction ID: ",
|
|
16627
|
+
/* @__PURE__ */ jsx30(Text23, { bold: true, children: request.link_transaction_id })
|
|
16628
|
+
] }) }),
|
|
16629
|
+
request?.status === "succeeded" && request?.activity_url && /* @__PURE__ */ jsx30(Box21, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text23, { children: [
|
|
16630
|
+
"Activity URL:",
|
|
16631
|
+
" ",
|
|
16632
|
+
/* @__PURE__ */ jsx30(Text23, { bold: true, color: "cyan", children: request.activity_url })
|
|
16633
|
+
] }) })
|
|
16584
16634
|
] })
|
|
16585
16635
|
] });
|
|
16586
16636
|
}
|
|
@@ -16795,6 +16845,9 @@ var createOptions = z10.object({
|
|
|
16795
16845
|
force: z10.boolean().default(false).describe("Overwrite output file if it already exists"),
|
|
16796
16846
|
approvalDetail: z10.union([z10.string(), z10.record(z10.string(), z10.unknown())]).optional().describe(
|
|
16797
16847
|
"Approval details object (MCP/agent: pass as object; CLI: pass as JSON string). Required fields: approved_at (unix timestamp), approval_method (click|programmatic|voice), app_name, external_user_id. Optional: ip_address, user_agent, device_type (mobile|web), agent_log_id, external_user_name, external_session_id, authentication_method (biometric_face|biometric_fingerprint|passkey)."
|
|
16848
|
+
),
|
|
16849
|
+
metadata: z10.array(z10.union([z10.string(), z10.record(z10.string(), z10.string())])).default([]).describe(
|
|
16850
|
+
'Metadata key:value pair (repeatable). Attaches arbitrary string data to the spend request. Max 50 keys, key <= 40 chars, value <= 500 chars. Example: "order_id:ord_123"'
|
|
16798
16851
|
)
|
|
16799
16852
|
});
|
|
16800
16853
|
var listOptions3 = z10.object({
|
|
@@ -16990,6 +17043,16 @@ function createSpendRequestCli(repository, authStorage2, envAccessToken2) {
|
|
|
16990
17043
|
(item) => typeof item === "string" ? parseTotalFlag(item) : item
|
|
16991
17044
|
) : void 0;
|
|
16992
17045
|
const approvalDetails = opts.approvalDetail !== void 0 ? typeof opts.approvalDetail === "string" ? JSON.parse(opts.approvalDetail) : opts.approvalDetail : void 0;
|
|
17046
|
+
let metadata;
|
|
17047
|
+
if (opts.metadata?.length) {
|
|
17048
|
+
metadata = {};
|
|
17049
|
+
for (const item of opts.metadata) {
|
|
17050
|
+
Object.assign(
|
|
17051
|
+
metadata,
|
|
17052
|
+
typeof item === "string" ? parseKvString(item) : item
|
|
17053
|
+
);
|
|
17054
|
+
}
|
|
17055
|
+
}
|
|
16993
17056
|
const createParams = {
|
|
16994
17057
|
payment_details: opts.paymentMethodId,
|
|
16995
17058
|
credential_type: credentialType,
|
|
@@ -17004,7 +17067,8 @@ function createSpendRequestCli(repository, authStorage2, envAccessToken2) {
|
|
|
17004
17067
|
request_approval: requestApproval || void 0,
|
|
17005
17068
|
test: opts.test ? true : void 0,
|
|
17006
17069
|
approve: opts.approve ? true : void 0,
|
|
17007
|
-
approval_details: approvalDetails
|
|
17070
|
+
approval_details: approvalDetails,
|
|
17071
|
+
metadata
|
|
17008
17072
|
};
|
|
17009
17073
|
const outputFile = opts.outputFile;
|
|
17010
17074
|
const forceOverwrite = opts.force;
|
|
@@ -17044,6 +17108,12 @@ function createSpendRequestCli(repository, authStorage2, envAccessToken2) {
|
|
|
17044
17108
|
message: `${err.message} Verification URL: ${apiErr.error.verification_url}`
|
|
17045
17109
|
});
|
|
17046
17110
|
}
|
|
17111
|
+
if (apiErr?.error?.support_url) {
|
|
17112
|
+
return c.error({
|
|
17113
|
+
code: err.code,
|
|
17114
|
+
message: `${err.message} Support URL: ${apiErr.error.support_url}`
|
|
17115
|
+
});
|
|
17116
|
+
}
|
|
17047
17117
|
}
|
|
17048
17118
|
throw err;
|
|
17049
17119
|
}
|
|
@@ -17163,6 +17233,12 @@ function createSpendRequestCli(repository, authStorage2, envAccessToken2) {
|
|
|
17163
17233
|
message: `${err.message} Verification URL: ${apiErr.error.verification_url}`
|
|
17164
17234
|
});
|
|
17165
17235
|
}
|
|
17236
|
+
if (apiErr?.error?.support_url) {
|
|
17237
|
+
return c.error({
|
|
17238
|
+
code: err.code,
|
|
17239
|
+
message: `${err.message} Support URL: ${apiErr.error.support_url}`
|
|
17240
|
+
});
|
|
17241
|
+
}
|
|
17166
17242
|
}
|
|
17167
17243
|
throw err;
|
|
17168
17244
|
}
|
|
@@ -17714,7 +17790,11 @@ ${serializeRedactedFormBody(params)}`
|
|
|
17714
17790
|
access_token: resp.access_token,
|
|
17715
17791
|
refresh_token: resp.refresh_token,
|
|
17716
17792
|
expires_in: resp.expires_in,
|
|
17717
|
-
token_type: resp.token_type
|
|
17793
|
+
token_type: resp.token_type,
|
|
17794
|
+
...resp.scope && { scope: resp.scope },
|
|
17795
|
+
...resp.authorization_details && {
|
|
17796
|
+
authorization_details: resp.authorization_details
|
|
17797
|
+
}
|
|
17718
17798
|
};
|
|
17719
17799
|
}
|
|
17720
17800
|
if (status === 400) {
|
|
@@ -17794,7 +17874,11 @@ ${serializeRedactedFormBody(params)}`
|
|
|
17794
17874
|
access_token: resp.access_token,
|
|
17795
17875
|
refresh_token: resp.refresh_token,
|
|
17796
17876
|
expires_in: resp.expires_in,
|
|
17797
|
-
token_type: resp.token_type
|
|
17877
|
+
token_type: resp.token_type,
|
|
17878
|
+
...resp.scope && { scope: resp.scope },
|
|
17879
|
+
...resp.authorization_details && {
|
|
17880
|
+
authorization_details: resp.authorization_details
|
|
17881
|
+
}
|
|
17798
17882
|
};
|
|
17799
17883
|
}
|
|
17800
17884
|
};
|
|
@@ -18115,7 +18199,7 @@ function cacheUpdateInfo(value, ttlMs = UPDATE_CACHE_TTL_MS) {
|
|
|
18115
18199
|
}
|
|
18116
18200
|
|
|
18117
18201
|
// src/cli.tsx
|
|
18118
|
-
var cliVersion = "0.
|
|
18202
|
+
var cliVersion = "0.11.0";
|
|
18119
18203
|
var cliName = "@stripe/link-cli";
|
|
18120
18204
|
var defaultHeaders = {
|
|
18121
18205
|
"User-Agent": `link-cli/${cliVersion}`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stripe/link-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"link-cli": "./dist/cli.js"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"!dist/sea",
|
|
11
|
+
"postinstall.mjs",
|
|
11
12
|
"README.md",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|
|
@@ -49,6 +50,7 @@
|
|
|
49
50
|
"scripts": {
|
|
50
51
|
"build": "tsup",
|
|
51
52
|
"build:sea": "tsup --config tsup.sea.config.ts",
|
|
53
|
+
"postinstall": "node postinstall.mjs",
|
|
52
54
|
"typecheck": "tsc",
|
|
53
55
|
"test": "vitest run",
|
|
54
56
|
"dev": "tsx src/cli.tsx"
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Refresh the create-payment-credential skill whenever the CLI is (re)installed
|
|
4
|
+
// or upgraded via npm. Delegates to the openclaw `skills` CLI so the skill file
|
|
5
|
+
// stays in sync with the installed CLI version. Must never fail the install.
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import process from 'node:process';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const REPO = 'stripe/link-cli';
|
|
12
|
+
|
|
13
|
+
function run() {
|
|
14
|
+
// Skip when running from the source monorepo (dev `pnpm install`). Installed
|
|
15
|
+
// copies live under node_modules; the dev tree does not.
|
|
16
|
+
if (!fileURLToPath(import.meta.url).includes('node_modules')) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (process.env.CI || process.env.LINK_CLI_SKIP_SKILL_INSTALL) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process.stdout.write(
|
|
25
|
+
'link-cli: refreshing the create-payment-credential skill…\n',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const result = spawnSync(
|
|
29
|
+
'npx',
|
|
30
|
+
['--yes', 'skills', 'add', REPO, '-g', '-y'],
|
|
31
|
+
{
|
|
32
|
+
stdio: 'inherit',
|
|
33
|
+
timeout: 60_000,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (result.error || result.status !== 0) {
|
|
38
|
+
process.stdout.write(
|
|
39
|
+
`link-cli: skipped skill refresh; run 'npx skills add ${REPO}' manually.\n`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
run();
|
|
46
|
+
} catch {
|
|
47
|
+
// Never fail the install.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.exit(0);
|