@stripe/link-cli 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +24 -1
  2. package/dist/cli.js +187 -27
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -111,6 +111,29 @@ link-cli auth login
111
111
 
112
112
  You receive a verification URL and a short phrase. Visit the URL, log in to your Link account, and enter the phrase to approve the connection.
113
113
 
114
+ ### Retrieve user info
115
+
116
+ ```bash
117
+ link-cli user-info retrieve --format json
118
+ ```
119
+
120
+ In addition to identity fields, the response can include Agent Wallet spend limits and step-up status:
121
+
122
+ ```json
123
+ {
124
+ "email": "jane@example.com",
125
+ "name": "Jane Doe",
126
+ "agent_wallet_spend_limits": {
127
+ "per_transaction": { "limit": 50000 },
128
+ "daily": { "limit": 500000, "used": 120000, "remaining": 380000 },
129
+ "thirty_day": { "limit": null, "used": 600000, "remaining": null }
130
+ },
131
+ "agent_wallet_step_up": { "status": "not_required" }
132
+ }
133
+ ```
134
+
135
+ Finite spend-limit values are cents because this response does not include a currency. A `null` limit or remaining amount means unlimited. Either Agent Wallet object can be absent when backend enrichment is unavailable; absence does not imply a limit or step-up status.
136
+
114
137
  ### List payment methods
115
138
 
116
139
  ```bash
@@ -496,4 +519,4 @@ pnpm turbo run build
496
519
  pnpm --filter @stripe/link-cli --filter @stripe/link-sdk publish --dry-run --no-git-checks
497
520
  ```
498
521
 
499
- CI runs the same publish dry-run for every pull request.
522
+ CI runs the same publish dry-run for every pull request.
package/dist/cli.js CHANGED
@@ -802,11 +802,13 @@ var SpendRequestResource = class extends BaseResource {
802
802
  );
803
803
  }
804
804
  async update(id, params) {
805
+ const { approve, ...body } = params;
806
+ const url = approve ? `${this.endpoint}/${id}/update_delegated` : `${this.endpoint}/${id}`;
805
807
  const { status, data, rawBody } = await this.apiFetch({
806
808
  method: "POST",
807
- url: `${this.endpoint}/${id}`,
809
+ url,
808
810
  headers: { "Content-Type": "application/json" },
809
- body: JSON.stringify(params)
811
+ body: JSON.stringify(body)
810
812
  });
811
813
  if (status < 200 || status >= 300) {
812
814
  this.throwApiError("update spend request", status, data, rawBody);
@@ -932,19 +934,54 @@ var TransactionsResource = class extends BaseResource {
932
934
  );
933
935
  }
934
936
  };
937
+ var rollingSpendLimitSchema = z8.object({
938
+ limit: z8.number().int().nullable(),
939
+ used: z8.number().int(),
940
+ remaining: z8.number().int().nullable()
941
+ });
942
+ var agentWalletSpendLimitsSchema = z8.object({
943
+ per_transaction: z8.object({
944
+ limit: z8.number().int().nullable()
945
+ }),
946
+ daily: rollingSpendLimitSchema,
947
+ thirty_day: rollingSpendLimitSchema
948
+ });
949
+ var agentWalletStepUpSchema = z8.object({
950
+ status: z8.enum([
951
+ "not_required",
952
+ "ssn_verification",
953
+ "identity_verification",
954
+ "contact_support",
955
+ "complete"
956
+ ])
957
+ });
935
958
  var userInfoSchema = z8.looseObject({
936
959
  email: z8.string().nullable().optional(),
937
960
  name: z8.string().nullable().optional(),
938
961
  first_name: z8.string().nullable().optional(),
939
962
  last_name: z8.string().nullable().optional(),
940
- phone: z8.string().nullable().optional()
941
- }).transform(({ email, name, first_name, last_name, phone }) => ({
942
- email: email ?? null,
943
- name: name ?? null,
944
- first_name: first_name ?? null,
945
- last_name: last_name ?? null,
946
- phone: phone ?? null
947
- }));
963
+ phone: z8.string().nullable().optional(),
964
+ agent_wallet_spend_limits: agentWalletSpendLimitsSchema.optional(),
965
+ agent_wallet_step_up: agentWalletStepUpSchema.optional()
966
+ }).transform(
967
+ ({
968
+ email,
969
+ name,
970
+ first_name,
971
+ last_name,
972
+ phone,
973
+ agent_wallet_spend_limits,
974
+ agent_wallet_step_up
975
+ }) => ({
976
+ email: email ?? null,
977
+ name: name ?? null,
978
+ first_name: first_name ?? null,
979
+ last_name: last_name ?? null,
980
+ phone: phone ?? null,
981
+ ...agent_wallet_spend_limits === void 0 ? {} : { agent_wallet_spend_limits },
982
+ ...agent_wallet_step_up === void 0 ? {} : { agent_wallet_step_up }
983
+ })
984
+ );
948
985
  var UserInfoResource = class extends BaseResource {
949
986
  constructor(options) {
950
987
  super(options, "/userinfo");
@@ -2801,7 +2838,7 @@ function buildHeaders(data, headers) {
2801
2838
  if (key) result[key] = value;
2802
2839
  }
2803
2840
  if (!Object.keys(result).some((key) => key.toLowerCase() === "user-agent")) {
2804
- result["User-Agent"] = `link-cli/${"0.16.0"}`;
2841
+ result["User-Agent"] = `link-cli/${"0.17.1"}`;
2805
2842
  }
2806
2843
  return result;
2807
2844
  }
@@ -4939,6 +4976,7 @@ var CreateSpendRequest = ({
4939
4976
  repository,
4940
4977
  params,
4941
4978
  requestApproval = false,
4979
+ approve = false,
4942
4980
  outputFile,
4943
4981
  force,
4944
4982
  onComplete
@@ -5077,7 +5115,7 @@ var CreateSpendRequest = ({
5077
5115
  result.status_details?.requires_action?.next_action ?? null
5078
5116
  );
5079
5117
  setStatus("requires_action");
5080
- } else if (requestApproval) {
5118
+ } else if (requestApproval && result.status !== "approved") {
5081
5119
  setStatus("waiting");
5082
5120
  } else {
5083
5121
  setStatus("success");
@@ -5352,7 +5390,7 @@ var CreateSpendRequest = ({
5352
5390
  fileError
5353
5391
  ] })
5354
5392
  ] }),
5355
- /* @__PURE__ */ jsx27(AppDownloadQrCodes, {})
5393
+ !approve && /* @__PURE__ */ jsx27(AppDownloadQrCodes, {})
5356
5394
  ] });
5357
5395
  }
5358
5396
  return /* @__PURE__ */ jsxs18(Fragment4, { children: [
@@ -6190,13 +6228,39 @@ var updateOptions = z19.object({
6190
6228
  ),
6191
6229
  total: z19.array(z19.union([z19.string(), z19.record(z19.string(), z19.unknown())])).default([]).describe(
6192
6230
  'Total (repeatable, key:value format). Keys: type (required; one of: subtotal, tax, total, items_base_amount, items_discount, discount, fulfillment, shipping, fee, gift_wrap, tip, store_credit), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
6193
- )
6231
+ ),
6232
+ approve: z19.boolean().default(false).describe("Use the delegated approval flow for this update")
6194
6233
  });
6195
6234
 
6196
6235
  // src/commands/spend-request/update.tsx
6197
- import { Box as Box22, Text as Text24 } from "ink";
6236
+ import { Box as Box22, Text as Text24, useInput as useInput10 } from "ink";
6198
6237
  import Spinner14 from "ink-spinner";
6199
- import { useCallback as useCallback11 } from "react";
6238
+ import { useCallback as useCallback11, useState as useState12 } from "react";
6239
+
6240
+ // src/utils/poll-until-spend-request-update.ts
6241
+ async function pollUntilSpendRequestUpdate(repository, id, pendingAmount, options = {}) {
6242
+ const pollIntervalMs = options.pollIntervalMs ?? 2e3;
6243
+ const timeoutMs = options.timeoutMs ?? 3e5;
6244
+ const startTime = Date.now();
6245
+ while (true) {
6246
+ if (Date.now() - startTime > timeoutMs) {
6247
+ throw new Error("Spend request update polling timed out");
6248
+ }
6249
+ const request = await repository.retrieve(id);
6250
+ if (!request) {
6251
+ throw new Error(`Spend request ${id} not found`);
6252
+ }
6253
+ if (request.amount === pendingAmount) {
6254
+ return { request, outcome: "approved" };
6255
+ }
6256
+ if (!request.approval_url) {
6257
+ return { request, outcome: "denied" };
6258
+ }
6259
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
6260
+ }
6261
+ }
6262
+
6263
+ // src/commands/spend-request/update.tsx
6200
6264
  import { jsx as jsx31, jsxs as jsxs22 } from "react/jsx-runtime";
6201
6265
  var UpdateSpendRequest = ({
6202
6266
  repository,
@@ -6204,12 +6268,45 @@ var UpdateSpendRequest = ({
6204
6268
  params,
6205
6269
  onComplete
6206
6270
  }) => {
6207
- const action = useCallback11(
6208
- () => repository.update(id, params),
6209
- [repository, id, params]
6271
+ const [pendingApproval, setPendingApproval] = useState12(
6272
+ null
6273
+ );
6274
+ const action = useCallback11(async () => {
6275
+ const request2 = await repository.update(id, params);
6276
+ if (params.amount !== void 0 && request2.status === "approved" && request2.approval_url) {
6277
+ setPendingApproval(request2);
6278
+ return pollUntilSpendRequestUpdate(repository, id, params.amount);
6279
+ }
6280
+ return { request: request2, outcome: "updated" };
6281
+ }, [repository, id, params]);
6282
+ const handleComplete = useCallback11(
6283
+ (result2) => onComplete(result2?.request ?? null),
6284
+ [onComplete]
6285
+ );
6286
+ const {
6287
+ status,
6288
+ data: result,
6289
+ error
6290
+ } = useAsyncAction(action, handleComplete);
6291
+ const request = result?.request ?? pendingApproval;
6292
+ useInput10(
6293
+ (_input, key) => {
6294
+ if (key.return && pendingApproval?.approval_url) {
6295
+ openUrl(pendingApproval.approval_url);
6296
+ }
6297
+ },
6298
+ { isActive: status === "loading" && pendingApproval !== null }
6210
6299
  );
6211
- const { status, data: request, error } = useAsyncAction(action, onComplete);
6212
6300
  if (status === "loading") {
6301
+ if (pendingApproval?.approval_url) {
6302
+ return /* @__PURE__ */ jsx31(
6303
+ ApprovalWaitingView,
6304
+ {
6305
+ status: "polling",
6306
+ approvalUrl: pendingApproval.approval_url
6307
+ }
6308
+ );
6309
+ }
6213
6310
  return /* @__PURE__ */ jsx31(Box22, { children: /* @__PURE__ */ jsxs22(Text24, { color: "cyan", children: [
6214
6311
  /* @__PURE__ */ jsx31(Spinner14, { type: "dots" }),
6215
6312
  " Updating spend request ",
@@ -6223,6 +6320,21 @@ var UpdateSpendRequest = ({
6223
6320
  /* @__PURE__ */ jsx31(Text24, { color: "red", children: error })
6224
6321
  ] });
6225
6322
  }
6323
+ if (result?.outcome === "denied") {
6324
+ return /* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", children: [
6325
+ /* @__PURE__ */ jsx31(Text24, { color: "yellow", children: "\u2717 Spend request update denied" }),
6326
+ /* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
6327
+ /* @__PURE__ */ jsxs22(Text24, { children: [
6328
+ "ID: ",
6329
+ /* @__PURE__ */ jsx31(Text24, { bold: true, children: request?.id })
6330
+ ] }),
6331
+ /* @__PURE__ */ jsxs22(Text24, { children: [
6332
+ "Amount: ",
6333
+ /* @__PURE__ */ jsx31(Text24, { bold: true, children: request?.amount ?? "N/A" })
6334
+ ] })
6335
+ ] })
6336
+ ] });
6337
+ }
6226
6338
  return /* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", children: [
6227
6339
  /* @__PURE__ */ jsx31(Text24, { color: "green", children: "\u2713 Spend request updated" }),
6228
6340
  /* @__PURE__ */ jsxs22(Box22, { flexDirection: "column", marginTop: 1, paddingX: 2, children: [
@@ -6237,10 +6349,7 @@ var UpdateSpendRequest = ({
6237
6349
  /* @__PURE__ */ jsxs22(Text24, { children: [
6238
6350
  "Amount:",
6239
6351
  " ",
6240
- /* @__PURE__ */ jsx31(Text24, { bold: true, children: (() => {
6241
- const t = request?.totals?.find((t2) => t2.type === "total");
6242
- return t ? String(t.amount) : "N/A";
6243
- })() })
6352
+ /* @__PURE__ */ jsx31(Text24, { bold: true, children: request?.amount !== void 0 ? String(request.amount) : "N/A" })
6244
6353
  ] }),
6245
6354
  /* @__PURE__ */ jsxs22(Text24, { children: [
6246
6355
  "Merchant: ",
@@ -6452,6 +6561,7 @@ function createSpendRequestCli(repository, authStorage2, envAccessToken2) {
6452
6561
  repository,
6453
6562
  params: createParams,
6454
6563
  requestApproval,
6564
+ approve: opts.approve ? true : void 0,
6455
6565
  outputFile,
6456
6566
  force: forceOverwrite,
6457
6567
  onComplete: (result) => {
@@ -6558,6 +6668,9 @@ function createSpendRequestCli(repository, authStorage2, envAccessToken2) {
6558
6668
  params.totals = opts.total.map(
6559
6669
  (item) => typeof item === "string" ? parseTotalFlag(item) : item
6560
6670
  );
6671
+ if (opts.approve !== void 0) {
6672
+ params.approve = opts.approve;
6673
+ }
6561
6674
  if (!c.agent && !c.formatExplicit) {
6562
6675
  let capturedResult = null;
6563
6676
  return renderInteractive(
@@ -6938,6 +7051,9 @@ import { Box as Box24, Text as Text26 } from "ink";
6938
7051
  import Spinner16 from "ink-spinner";
6939
7052
  import { useCallback as useCallback13 } from "react";
6940
7053
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
7054
+ function formatLimit(value) {
7055
+ return value === null ? "Unlimited" : `${value} cents`;
7056
+ }
6941
7057
  var UserInfoRetrieve = ({
6942
7058
  resource,
6943
7059
  onComplete
@@ -6970,7 +7086,51 @@ var UserInfoRetrieve = ({
6970
7086
  /* @__PURE__ */ jsxs24(Text26, { children: [
6971
7087
  /* @__PURE__ */ jsx35(Text26, { dimColor: true, children: "Phone: " }),
6972
7088
  userInfo?.phone ?? /* @__PURE__ */ jsx35(Text26, { dimColor: true, children: "Not set" })
6973
- ] })
7089
+ ] }),
7090
+ userInfo?.agent_wallet_spend_limits && /* @__PURE__ */ jsxs24(Box24, { flexDirection: "column", marginTop: 1, children: [
7091
+ /* @__PURE__ */ jsx35(Text26, { bold: true, children: "Agent Wallet Spend Limits" }),
7092
+ /* @__PURE__ */ jsxs24(Box24, { flexDirection: "column", paddingLeft: 2, children: [
7093
+ /* @__PURE__ */ jsxs24(Text26, { children: [
7094
+ /* @__PURE__ */ jsx35(Text26, { dimColor: true, children: "Per-transaction: " }),
7095
+ formatLimit(
7096
+ userInfo.agent_wallet_spend_limits.per_transaction.limit
7097
+ )
7098
+ ] }),
7099
+ /* @__PURE__ */ jsxs24(Text26, { children: [
7100
+ /* @__PURE__ */ jsx35(Text26, { dimColor: true, children: "Daily: " }),
7101
+ "limit",
7102
+ " ",
7103
+ formatLimit(userInfo.agent_wallet_spend_limits.daily.limit),
7104
+ ", used ",
7105
+ userInfo.agent_wallet_spend_limits.daily.used,
7106
+ " cents, remaining",
7107
+ " ",
7108
+ formatLimit(
7109
+ userInfo.agent_wallet_spend_limits.daily.remaining
7110
+ )
7111
+ ] }),
7112
+ /* @__PURE__ */ jsxs24(Text26, { children: [
7113
+ /* @__PURE__ */ jsx35(Text26, { dimColor: true, children: "30-day: " }),
7114
+ "limit",
7115
+ " ",
7116
+ formatLimit(
7117
+ userInfo.agent_wallet_spend_limits.thirty_day.limit
7118
+ ),
7119
+ ", used ",
7120
+ userInfo.agent_wallet_spend_limits.thirty_day.used,
7121
+ " ",
7122
+ "cents, remaining",
7123
+ " ",
7124
+ formatLimit(
7125
+ userInfo.agent_wallet_spend_limits.thirty_day.remaining
7126
+ )
7127
+ ] })
7128
+ ] })
7129
+ ] }),
7130
+ userInfo?.agent_wallet_step_up && /* @__PURE__ */ jsx35(Box24, { marginTop: 1, children: /* @__PURE__ */ jsxs24(Text26, { children: [
7131
+ /* @__PURE__ */ jsx35(Text26, { dimColor: true, children: "Agent Wallet step-up status: " }),
7132
+ userInfo.agent_wallet_step_up.status
7133
+ ] }) })
6974
7134
  ] })
6975
7135
  ] });
6976
7136
  };
@@ -6982,7 +7142,7 @@ function createUserInfoCli(createResource, authStorage2, envAccessToken2) {
6982
7142
  description: "User information commands"
6983
7143
  });
6984
7144
  cli2.command("retrieve", {
6985
- description: "Retrieve user info (email, name, phone)",
7145
+ description: "Retrieve user info, including optional Agent Wallet spend limits and step-up status",
6986
7146
  outputPolicy: "agent-only",
6987
7147
  middleware: [requireAuth(authStorage2, envAccessToken2)],
6988
7148
  async run(c) {
@@ -7599,7 +7759,7 @@ function cacheUpdateInfo(value, ttlMs = UPDATE_CACHE_TTL_MS) {
7599
7759
  }
7600
7760
 
7601
7761
  // src/cli.tsx
7602
- var cliVersion = "0.16.0";
7762
+ var cliVersion = "0.17.1";
7603
7763
  var cliName = "@stripe/link-cli";
7604
7764
  var defaultHeaders = {
7605
7765
  "User-Agent": `link-cli/${cliVersion}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stripe/link-cli",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "link-cli": "./dist/cli.js"
@@ -45,7 +45,7 @@
45
45
  "tsx": "^4.23.5",
46
46
  "typescript": "^5.9.3",
47
47
  "vitest": "^4.1.10",
48
- "@stripe/link-sdk": "0.2.1",
48
+ "@stripe/link-sdk": "0.3.1",
49
49
  "@stripe/link-typescript-config": "0.0.0"
50
50
  },
51
51
  "scripts": {