@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 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 (uses test card `4242424242424242`), useful for development and integration testing without real payment methods.
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 { status: response.status, headers: responseHeaders, body };
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 url = err.details?.error?.verification_url;
16042
- if (url) setVerificationUrl(url);
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 url = err.details?.error?.verification_url;
16274
- if (url) setVerificationUrl(url);
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.10.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.10.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"
@@ -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);