@stripe/link-cli 0.4.1 → 0.4.3
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 +87 -37
- package/dist/cli.js +315 -61
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Link CLI
|
|
2
2
|
|
|
3
|
-
Link CLI lets agents get secure, one-time-use payment credentials from a Link wallet
|
|
3
|
+
Link CLI lets agents get secure, one-time-use payment credentials from a Link wallet to complete purchases on your behalf — without storing your real card details.
|
|
4
|
+
|
|
5
|
+
The CLI can produce one of two credential types:
|
|
6
|
+
|
|
7
|
+
- A virtual card (PAN) for use with a standard web checkout form. The issued card works anywhere, and is not restricted to Link-enabled sellers or sellers that use Stripe.
|
|
8
|
+
- A [Shared Payment Token](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens) (SPT) when the seller accepts programmatic payments through [Machine Payment Protocols](https://mpp.dev) (MPP)
|
|
9
|
+
|
|
10
|
+
For now, this is only available to US Link accounts.
|
|
4
11
|
|
|
5
12
|
## Installation
|
|
6
13
|
|
|
@@ -14,11 +21,31 @@ Or run directly with `npx`:
|
|
|
14
21
|
npx @stripe/link-cli
|
|
15
22
|
```
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
### Use with agents
|
|
25
|
+
|
|
26
|
+
Install the skill:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx skills add stripe/link-cli
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
By default when called from an agent (non-TTY), all commands use `toon` output — a compact, LLM-friendly text format. All commands accept `--format [format]` for structured output. Other formats: `json`, `yaml`, `md`, `jsonl`.
|
|
33
|
+
|
|
34
|
+
List available commands:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
link-cli --llms-full
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Get a command's full schema with `--schema`. Example:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
link-cli spend-request create --schema
|
|
44
|
+
```
|
|
18
45
|
|
|
19
|
-
|
|
46
|
+
#### MCP Server
|
|
20
47
|
|
|
21
|
-
Link CLI can
|
|
48
|
+
Link CLI can run as a local MCP server. Add the following to your MCP client config (`.mcp.json`, etc.)
|
|
22
49
|
|
|
23
50
|
```json
|
|
24
51
|
{
|
|
@@ -33,6 +60,12 @@ Link CLI can also run as a local MCP server. Add the following to your MCP clien
|
|
|
33
60
|
|
|
34
61
|
## Quickstart
|
|
35
62
|
|
|
63
|
+
Run a guided onboarding and demo flow:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
link-cli onboard
|
|
67
|
+
```
|
|
68
|
+
|
|
36
69
|
### Login
|
|
37
70
|
|
|
38
71
|
The `link-cli` requires a Link account. You can log in to your existing one or [register online](https://app.link.com).
|
|
@@ -41,7 +74,7 @@ The `link-cli` requires a Link account. You can log in to your existing one or [
|
|
|
41
74
|
link-cli auth login
|
|
42
75
|
```
|
|
43
76
|
|
|
44
|
-
You
|
|
77
|
+
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.
|
|
45
78
|
|
|
46
79
|
### List payment methods
|
|
47
80
|
|
|
@@ -49,11 +82,11 @@ You'll receive a verification URL and a short phrase. Visit the URL, log in to y
|
|
|
49
82
|
link-cli payment-methods list
|
|
50
83
|
```
|
|
51
84
|
|
|
52
|
-
Returns the cards and bank accounts saved to your Link account. Use the `id` field as `payment_method_id` in the next step. If you have no payment methods,
|
|
85
|
+
Returns the cards and bank accounts saved to your Link account. Use the `id` field as `payment_method_id` in the next step. If you have no payment methods, [add new ones in Link](https://app.link.com/wallet).
|
|
53
86
|
|
|
54
87
|
### Create a spend request
|
|
55
88
|
|
|
56
|
-
|
|
89
|
+
Create a spend request with a payment method, merchant details, line items, and amounts:
|
|
57
90
|
|
|
58
91
|
```bash
|
|
59
92
|
link-cli spend-request create \
|
|
@@ -67,27 +100,52 @@ link-cli spend-request create \
|
|
|
67
100
|
--request-approval
|
|
68
101
|
```
|
|
69
102
|
|
|
70
|
-
The `--request-approval` flag triggers a push notification
|
|
103
|
+
The `--request-approval` flag triggers a push notification to the user for approval, then polls until the request is approved or denied.
|
|
104
|
+
|
|
105
|
+
Easily approve requests with the [Link app](https://link.com/download).
|
|
106
|
+
|
|
107
|
+
#### Line items and totals
|
|
108
|
+
|
|
109
|
+
`--line-item` and `--total` use repeatable `key:value` format.
|
|
110
|
+
|
|
111
|
+
**`--line-item` keys:** `name` (required), `quantity`, `unit_amount`, `description`, `sku`, `url`, `image_url`, `product_url`
|
|
71
112
|
|
|
72
|
-
|
|
113
|
+
```bash
|
|
114
|
+
--line-item "name:Running Shoes,unit_amount:12000,quantity:1,description:Trail runners"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**`--total` keys:** `type` (required; one of: `subtotal`, `tax`, `total`), `display_text` (required), `amount` (required)
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
--total "type:subtotal,display_text:Subtotal,amount:12000" \
|
|
121
|
+
--total "type:total,display_text:Total,amount:12000"
|
|
122
|
+
```
|
|
73
123
|
|
|
74
124
|
#### Credential types
|
|
75
125
|
|
|
76
|
-
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,
|
|
126
|
+
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"`.
|
|
77
127
|
|
|
78
128
|
### Execute payment
|
|
79
129
|
|
|
80
130
|
The approved spend request includes a `card` object with `number`, `cvc`, `exp_month`, `exp_year`, `billing_address`, and `valid_until`. Enter these into the merchant's checkout form.
|
|
81
131
|
|
|
82
132
|
```bash
|
|
83
|
-
link-cli spend-request retrieve lsrq_001
|
|
133
|
+
link-cli spend-request retrieve lsrq_001
|
|
134
|
+
```
|
|
135
|
+
By default, retrieving a spend request doesn't include card details. Pass `--include card` to see unmasked card details.
|
|
136
|
+
|
|
137
|
+
To avoid leaking card credentials into agent transcripts or logs, use `--output-file` to write the full card to a secure local file while stdout shows only redacted data (brand, last4, expiry):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
link-cli spend-request retrieve lsrq_001 --include card --output-file /tmp/link-card.json --format json
|
|
84
141
|
```
|
|
85
|
-
|
|
142
|
+
|
|
143
|
+
The file is created with `0600` permissions. If the file already exists, the command fails unless `--force` is passed. When `--output-file` is set, the JSON output replaces the `card` object with redacted fields and adds a `card_output_file` path.
|
|
86
144
|
|
|
87
145
|
For agent polling, pass `--interval` and optionally `--max-attempts`:
|
|
88
146
|
|
|
89
147
|
```bash
|
|
90
|
-
link-cli spend-request retrieve lsrq_001 --interval 2 --max-attempts 150
|
|
148
|
+
link-cli spend-request retrieve lsrq_001 --interval 2 --max-attempts 150
|
|
91
149
|
```
|
|
92
150
|
|
|
93
151
|
Polling exits successfully only after the request reaches a terminal status such as `approved`, `denied`, or `expired`. If polling reaches `--timeout` or exhausts `--max-attempts` while the request is still non-terminal, the command exits non-zero with `code: "POLLING_TIMEOUT"` so callers do not treat a still-pending request as complete.
|
|
@@ -98,8 +156,7 @@ If the merchant supports MPP, use `link-cli mpp pay` instead:
|
|
|
98
156
|
link-cli mpp pay https://climate.stripe.dev/api/contribute \
|
|
99
157
|
--spend-request-id lsrq_001 \
|
|
100
158
|
--method POST \
|
|
101
|
-
--data '{"amount":100}'
|
|
102
|
-
--format json
|
|
159
|
+
--data '{"amount":100}'
|
|
103
160
|
```
|
|
104
161
|
|
|
105
162
|
## Advanced
|
|
@@ -107,14 +164,14 @@ link-cli mpp pay https://climate.stripe.dev/api/contribute \
|
|
|
107
164
|
### Authentication
|
|
108
165
|
|
|
109
166
|
```bash
|
|
110
|
-
link-cli auth login --client-name "Claude Code"
|
|
111
|
-
link-cli auth status
|
|
112
|
-
link-cli auth logout
|
|
167
|
+
link-cli auth login --client-name "Claude Code" # identify the connecting agent
|
|
168
|
+
link-cli auth status # check auth status
|
|
169
|
+
link-cli auth logout # disconnect
|
|
113
170
|
```
|
|
114
171
|
|
|
115
|
-
When `--client-name
|
|
172
|
+
When you provide `--client-name`, the Link app displays it when you approve the connection — for example, `Claude Code on my-macbook` instead of `link-cli on my-macbook`.
|
|
116
173
|
|
|
117
|
-
`auth status
|
|
174
|
+
`auth status` includes an `update` field when a newer version is available:
|
|
118
175
|
|
|
119
176
|
```json
|
|
120
177
|
{
|
|
@@ -127,7 +184,7 @@ When `--client-name` is provided, the name is shown in the Link app when the use
|
|
|
127
184
|
}
|
|
128
185
|
```
|
|
129
186
|
|
|
130
|
-
Set `NO_UPDATE_NOTIFIER=1` to suppress update checks (
|
|
187
|
+
Set `NO_UPDATE_NOTIFIER=1` to suppress update checks (for example, in CI).
|
|
131
188
|
|
|
132
189
|
### Spend request lifecycle
|
|
133
190
|
|
|
@@ -136,44 +193,37 @@ A spend request moves through: **create** → **request approval** → **approve
|
|
|
136
193
|
**Required fields for create:** `payment_method_id`, `merchant_name`, `merchant_url`, `context`, `amount`
|
|
137
194
|
|
|
138
195
|
**Constraints:** `context` must be at least 100 characters; `amount` must not exceed 50000 (cents); `currency` must be a 3-letter ISO code.
|
|
139
|
-
**Test mode:** Pass `--test` to create testmode credentials (uses test card `4242424242424242`)
|
|
196
|
+
**Test mode:** Pass `--test` to create testmode credentials (uses test card `4242424242424242`), useful for development and integration testing without real payment methods.
|
|
140
197
|
|
|
141
198
|
```bash
|
|
142
199
|
# Update before approval
|
|
143
200
|
link-cli spend-request update lsrq_001 \
|
|
144
|
-
--merchant-url https://press.stripe.com/working-in-public
|
|
145
|
-
--format json
|
|
201
|
+
--merchant-url https://press.stripe.com/working-in-public
|
|
146
202
|
|
|
147
203
|
# Request approval separately (alternative to create --request-approval)
|
|
148
|
-
link-cli spend-request request-approval lsrq_001
|
|
204
|
+
link-cli spend-request request-approval lsrq_001
|
|
149
205
|
|
|
150
|
-
# Retrieve at any time (includes card credentials
|
|
151
|
-
link-cli spend-request retrieve lsrq_001
|
|
206
|
+
# Retrieve at any time (includes card credentials after approval)
|
|
207
|
+
link-cli spend-request retrieve lsrq_001
|
|
152
208
|
```
|
|
153
209
|
|
|
154
|
-
### Output formats
|
|
155
|
-
|
|
156
|
-
All commands accept `--format json` for structured JSON output. Other formats: `yaml`, `md`, `jsonl`, `toon` (default). Errors are returned as JSON with `code` and `message` fields, with exit code 1.
|
|
157
|
-
|
|
158
210
|
### MPP
|
|
159
211
|
|
|
160
|
-
Use `mpp pay` to complete purchases on merchants that use the [Machine Payments Protocol](https://mpp.dev). The spend request must use `credential_type: "shared_payment_token"` and
|
|
212
|
+
Use `mpp pay` to complete purchases on merchants that use the [Machine Payments Protocol](https://mpp.dev). The spend request must use `credential_type: "shared_payment_token"` and you must approve it before paying. The SPT is one-time-use — if payment fails, create a new spend request.
|
|
161
213
|
|
|
162
214
|
```bash
|
|
163
215
|
link-cli mpp pay https://climate.stripe.dev/api/contribute \
|
|
164
216
|
--spend-request-id lsrq_001 \
|
|
165
217
|
--method POST \
|
|
166
218
|
--data '{"amount":100}' \
|
|
167
|
-
--header "X-Custom: value"
|
|
168
|
-
--format json
|
|
219
|
+
--header "X-Custom: value"
|
|
169
220
|
```
|
|
170
221
|
|
|
171
222
|
Use `mpp decode` to validate a raw `WWW-Authenticate` header and extract the `network_id` needed for `shared_payment_token` spend requests:
|
|
172
223
|
|
|
173
224
|
```bash
|
|
174
225
|
link-cli mpp decode \
|
|
175
|
-
--challenge 'Payment id="ch_001", realm="merchant.example", method="stripe", intent="charge", request="..."'
|
|
176
|
-
--format json
|
|
226
|
+
--challenge 'Payment id="ch_001", realm="merchant.example", method="stripe", intent="charge", request="..."'
|
|
177
227
|
```
|
|
178
228
|
|
|
179
229
|
### Environment variables
|
|
@@ -186,7 +236,7 @@ link-cli mpp decode \
|
|
|
186
236
|
|
|
187
237
|
## Onboard
|
|
188
238
|
|
|
189
|
-
Run the guided setup flow — authenticates, checks payment methods, shows the app download QR, and
|
|
239
|
+
Run the guided setup flow — authenticates, checks payment methods, shows the app download QR, and runs both demo flows:
|
|
190
240
|
|
|
191
241
|
```bash
|
|
192
242
|
link-cli onboard
|
package/dist/cli.js
CHANGED
|
@@ -3218,8 +3218,8 @@ var require_utils = __commonJS({
|
|
|
3218
3218
|
}
|
|
3219
3219
|
return ind;
|
|
3220
3220
|
}
|
|
3221
|
-
function removeDotSegments(
|
|
3222
|
-
let input =
|
|
3221
|
+
function removeDotSegments(path6) {
|
|
3222
|
+
let input = path6;
|
|
3223
3223
|
const output = [];
|
|
3224
3224
|
let nextSlash = -1;
|
|
3225
3225
|
let len = 0;
|
|
@@ -3418,8 +3418,8 @@ var require_schemes = __commonJS({
|
|
|
3418
3418
|
wsComponent.secure = void 0;
|
|
3419
3419
|
}
|
|
3420
3420
|
if (wsComponent.resourceName) {
|
|
3421
|
-
const [
|
|
3422
|
-
wsComponent.path =
|
|
3421
|
+
const [path6, query] = wsComponent.resourceName.split("?");
|
|
3422
|
+
wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
|
|
3423
3423
|
wsComponent.query = query;
|
|
3424
3424
|
wsComponent.resourceName = void 0;
|
|
3425
3425
|
}
|
|
@@ -7605,12 +7605,12 @@ var require_dist = __commonJS({
|
|
|
7605
7605
|
throw new Error(`Unknown format "${name}"`);
|
|
7606
7606
|
return f;
|
|
7607
7607
|
};
|
|
7608
|
-
function addFormats(ajv, list,
|
|
7608
|
+
function addFormats(ajv, list, fs5, exportName) {
|
|
7609
7609
|
var _a;
|
|
7610
7610
|
var _b;
|
|
7611
7611
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
7612
7612
|
for (const f of list)
|
|
7613
|
-
ajv.addFormat(f,
|
|
7613
|
+
ajv.addFormat(f, fs5[f]);
|
|
7614
7614
|
}
|
|
7615
7615
|
module.exports = exports = formatsPlugin;
|
|
7616
7616
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -9550,7 +9550,6 @@ var require_semver2 = __commonJS({
|
|
|
9550
9550
|
|
|
9551
9551
|
// src/cli.tsx
|
|
9552
9552
|
import { Cli as Cli7 } from "incur";
|
|
9553
|
-
import updateNotifier from "update-notifier";
|
|
9554
9553
|
|
|
9555
9554
|
// ../sdk/dist/index.js
|
|
9556
9555
|
import fs3 from "fs";
|
|
@@ -9574,12 +9573,12 @@ var disallowedKeys = /* @__PURE__ */ new Set([
|
|
|
9574
9573
|
"constructor"
|
|
9575
9574
|
]);
|
|
9576
9575
|
var digits = new Set("0123456789");
|
|
9577
|
-
function getPathSegments(
|
|
9576
|
+
function getPathSegments(path6) {
|
|
9578
9577
|
const parts = [];
|
|
9579
9578
|
let currentSegment = "";
|
|
9580
9579
|
let currentPart = "start";
|
|
9581
9580
|
let isIgnoring = false;
|
|
9582
|
-
for (const character of
|
|
9581
|
+
for (const character of path6) {
|
|
9583
9582
|
switch (character) {
|
|
9584
9583
|
case "\\": {
|
|
9585
9584
|
if (currentPart === "index") {
|
|
@@ -9701,11 +9700,11 @@ function assertNotStringIndex(object, key) {
|
|
|
9701
9700
|
throw new Error("Cannot use string index");
|
|
9702
9701
|
}
|
|
9703
9702
|
}
|
|
9704
|
-
function getProperty(object,
|
|
9705
|
-
if (!isObject(object) || typeof
|
|
9703
|
+
function getProperty(object, path6, value) {
|
|
9704
|
+
if (!isObject(object) || typeof path6 !== "string") {
|
|
9706
9705
|
return value === void 0 ? object : value;
|
|
9707
9706
|
}
|
|
9708
|
-
const pathArray = getPathSegments(
|
|
9707
|
+
const pathArray = getPathSegments(path6);
|
|
9709
9708
|
if (pathArray.length === 0) {
|
|
9710
9709
|
return value;
|
|
9711
9710
|
}
|
|
@@ -9725,12 +9724,12 @@ function getProperty(object, path5, value) {
|
|
|
9725
9724
|
}
|
|
9726
9725
|
return object === void 0 ? value : object;
|
|
9727
9726
|
}
|
|
9728
|
-
function setProperty(object,
|
|
9729
|
-
if (!isObject(object) || typeof
|
|
9727
|
+
function setProperty(object, path6, value) {
|
|
9728
|
+
if (!isObject(object) || typeof path6 !== "string") {
|
|
9730
9729
|
return object;
|
|
9731
9730
|
}
|
|
9732
9731
|
const root = object;
|
|
9733
|
-
const pathArray = getPathSegments(
|
|
9732
|
+
const pathArray = getPathSegments(path6);
|
|
9734
9733
|
for (let index = 0; index < pathArray.length; index++) {
|
|
9735
9734
|
const key = pathArray[index];
|
|
9736
9735
|
assertNotStringIndex(object, key);
|
|
@@ -9743,11 +9742,11 @@ function setProperty(object, path5, value) {
|
|
|
9743
9742
|
}
|
|
9744
9743
|
return root;
|
|
9745
9744
|
}
|
|
9746
|
-
function deleteProperty(object,
|
|
9747
|
-
if (!isObject(object) || typeof
|
|
9745
|
+
function deleteProperty(object, path6) {
|
|
9746
|
+
if (!isObject(object) || typeof path6 !== "string") {
|
|
9748
9747
|
return false;
|
|
9749
9748
|
}
|
|
9750
|
-
const pathArray = getPathSegments(
|
|
9749
|
+
const pathArray = getPathSegments(path6);
|
|
9751
9750
|
for (let index = 0; index < pathArray.length; index++) {
|
|
9752
9751
|
const key = pathArray[index];
|
|
9753
9752
|
assertNotStringIndex(object, key);
|
|
@@ -9761,11 +9760,11 @@ function deleteProperty(object, path5) {
|
|
|
9761
9760
|
}
|
|
9762
9761
|
}
|
|
9763
9762
|
}
|
|
9764
|
-
function hasProperty(object,
|
|
9765
|
-
if (!isObject(object) || typeof
|
|
9763
|
+
function hasProperty(object, path6) {
|
|
9764
|
+
if (!isObject(object) || typeof path6 !== "string") {
|
|
9766
9765
|
return false;
|
|
9767
9766
|
}
|
|
9768
|
-
const pathArray = getPathSegments(
|
|
9767
|
+
const pathArray = getPathSegments(path6);
|
|
9769
9768
|
if (pathArray.length === 0) {
|
|
9770
9769
|
return false;
|
|
9771
9770
|
}
|
|
@@ -10861,12 +10860,19 @@ function withComputedExpiry(auth) {
|
|
|
10861
10860
|
expires_at: auth.expires_at ?? Date.now() + auth.expires_in * 1e3
|
|
10862
10861
|
};
|
|
10863
10862
|
}
|
|
10863
|
+
var CONFIG_FILE_MODE = 384;
|
|
10864
10864
|
var Storage = class {
|
|
10865
10865
|
config;
|
|
10866
|
+
options;
|
|
10867
|
+
constructor(options = {}) {
|
|
10868
|
+
this.options = options;
|
|
10869
|
+
}
|
|
10866
10870
|
getConfig() {
|
|
10867
10871
|
if (!this.config) {
|
|
10868
10872
|
this.config = new Conf({
|
|
10869
10873
|
projectName: "link-cli",
|
|
10874
|
+
configFileMode: CONFIG_FILE_MODE,
|
|
10875
|
+
...this.options.cwd ? { cwd: this.options.cwd } : {},
|
|
10870
10876
|
defaults: {
|
|
10871
10877
|
auth: null,
|
|
10872
10878
|
pendingDeviceAuth: null
|
|
@@ -11488,7 +11494,7 @@ var AuthStatus = ({ onComplete }) => {
|
|
|
11488
11494
|
|
|
11489
11495
|
// src/commands/auth/index.tsx
|
|
11490
11496
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
11491
|
-
function createAuthCli(authResource,
|
|
11497
|
+
function createAuthCli(authResource, getUpdateInfo2) {
|
|
11492
11498
|
const cli2 = Cli.create("auth", {
|
|
11493
11499
|
description: "Authentication commands"
|
|
11494
11500
|
});
|
|
@@ -11574,6 +11580,18 @@ function createAuthCli(authResource, updateInfo) {
|
|
|
11574
11580
|
options: statusOptions,
|
|
11575
11581
|
outputPolicy: "agent-only",
|
|
11576
11582
|
async *run(c) {
|
|
11583
|
+
const opts = c.options;
|
|
11584
|
+
const interval = opts.interval;
|
|
11585
|
+
const maxAttempts = opts.maxAttempts;
|
|
11586
|
+
const deadline = Date.now() + opts.timeout * 1e3;
|
|
11587
|
+
const updateInfo = await getUpdateInfo2?.({
|
|
11588
|
+
polling: interval > 0
|
|
11589
|
+
});
|
|
11590
|
+
const update = updateInfo ? {
|
|
11591
|
+
current_version: updateInfo.current,
|
|
11592
|
+
latest_version: updateInfo.latest,
|
|
11593
|
+
update_command: "npm install -g @stripe/link-cli"
|
|
11594
|
+
} : void 0;
|
|
11577
11595
|
if (!c.agent && !c.formatExplicit) {
|
|
11578
11596
|
return new Promise((resolve) => {
|
|
11579
11597
|
const { waitUntilExit } = render(
|
|
@@ -11588,15 +11606,12 @@ function createAuthCli(authResource, updateInfo) {
|
|
|
11588
11606
|
access_token: `${auth.access_token.substring(0, 20)}...`,
|
|
11589
11607
|
token_type: auth.token_type
|
|
11590
11608
|
} : {},
|
|
11591
|
-
credentials_path: storage.getPath()
|
|
11609
|
+
credentials_path: storage.getPath(),
|
|
11610
|
+
...update && { update }
|
|
11592
11611
|
});
|
|
11593
11612
|
});
|
|
11594
11613
|
});
|
|
11595
11614
|
}
|
|
11596
|
-
const opts = c.options;
|
|
11597
|
-
const interval = opts.interval;
|
|
11598
|
-
const maxAttempts = opts.maxAttempts;
|
|
11599
|
-
const deadline = Date.now() + opts.timeout * 1e3;
|
|
11600
11615
|
let attempts = 0;
|
|
11601
11616
|
let previousAttemptData;
|
|
11602
11617
|
while (true) {
|
|
@@ -11609,11 +11624,6 @@ function createAuthCli(authResource, updateInfo) {
|
|
|
11609
11624
|
}
|
|
11610
11625
|
}
|
|
11611
11626
|
const auth = storage.getAuth();
|
|
11612
|
-
const update = updateInfo ? {
|
|
11613
|
-
current_version: updateInfo.current,
|
|
11614
|
-
latest_version: updateInfo.latest,
|
|
11615
|
-
update_command: "npm install -g @stripe/link-cli"
|
|
11616
|
-
} : void 0;
|
|
11617
11627
|
if (auth) {
|
|
11618
11628
|
yield {
|
|
11619
11629
|
authenticated: true,
|
|
@@ -12293,16 +12303,16 @@ import { useEffect as useEffect6, useRef as useRef2, useState as useState6 } fro
|
|
|
12293
12303
|
|
|
12294
12304
|
// src/commands/mpp/decode.ts
|
|
12295
12305
|
import { Challenge } from "mppx";
|
|
12296
|
-
function getString(value,
|
|
12306
|
+
function getString(value, path6, required = true) {
|
|
12297
12307
|
if (value == null) {
|
|
12298
12308
|
if (required) {
|
|
12299
|
-
throw new Error(`Invalid stripe challenge request: ${
|
|
12309
|
+
throw new Error(`Invalid stripe challenge request: ${path6}: missing`);
|
|
12300
12310
|
}
|
|
12301
12311
|
return void 0;
|
|
12302
12312
|
}
|
|
12303
12313
|
if (typeof value !== "string") {
|
|
12304
12314
|
throw new Error(
|
|
12305
|
-
`Invalid stripe challenge request: ${
|
|
12315
|
+
`Invalid stripe challenge request: ${path6}: expected string, received ${typeof value}`
|
|
12306
12316
|
);
|
|
12307
12317
|
}
|
|
12308
12318
|
return value;
|
|
@@ -13562,6 +13572,28 @@ function createPaymentMethodsCli(createResource) {
|
|
|
13562
13572
|
import { Cli as Cli6, z as z7 } from "incur";
|
|
13563
13573
|
import { render as render6 } from "ink";
|
|
13564
13574
|
|
|
13575
|
+
// src/utils/credential-output.ts
|
|
13576
|
+
import fs4 from "fs/promises";
|
|
13577
|
+
import path5 from "path";
|
|
13578
|
+
async function writeCredentialFile(filePath, data, force) {
|
|
13579
|
+
const resolved = path5.resolve(filePath);
|
|
13580
|
+
if (!force) {
|
|
13581
|
+
try {
|
|
13582
|
+
await fs4.access(resolved);
|
|
13583
|
+
throw new Error(
|
|
13584
|
+
`OUTPUT_FILE_EXISTS: ${resolved} already exists. Use --force to overwrite.`
|
|
13585
|
+
);
|
|
13586
|
+
} catch (err) {
|
|
13587
|
+
if (err.code !== "ENOENT") throw err;
|
|
13588
|
+
}
|
|
13589
|
+
}
|
|
13590
|
+
await fs4.writeFile(resolved, JSON.stringify(data, null, 2), {
|
|
13591
|
+
mode: 384
|
|
13592
|
+
});
|
|
13593
|
+
await fs4.chmod(resolved, 384);
|
|
13594
|
+
return resolved;
|
|
13595
|
+
}
|
|
13596
|
+
|
|
13565
13597
|
// src/utils/line-item-parser.ts
|
|
13566
13598
|
import { z as z5 } from "zod";
|
|
13567
13599
|
var LineItemSchema = z5.object({
|
|
@@ -13590,8 +13622,13 @@ function parseKvString(raw) {
|
|
|
13590
13622
|
}
|
|
13591
13623
|
return result;
|
|
13592
13624
|
}
|
|
13593
|
-
function formatZodError(err, prefix) {
|
|
13625
|
+
function formatZodError(err, prefix, schema) {
|
|
13626
|
+
const allowed = Object.keys(schema.shape);
|
|
13594
13627
|
const messages = err.issues.map((issue) => {
|
|
13628
|
+
if (issue.code === "unrecognized_keys") {
|
|
13629
|
+
const keys = issue.keys.map((k) => `"${k}"`).join(", ");
|
|
13630
|
+
return `${prefix}: unrecognized key ${keys}. Allowed keys: ${allowed.join(", ")}`;
|
|
13631
|
+
}
|
|
13595
13632
|
const key = issue.path[0]?.toString();
|
|
13596
13633
|
return key ? `${prefix} ${key}: ${issue.message}` : `${prefix}: ${issue.message}`;
|
|
13597
13634
|
});
|
|
@@ -13602,7 +13639,8 @@ function parseLineItemFlag(raw) {
|
|
|
13602
13639
|
try {
|
|
13603
13640
|
return LineItemSchema.parse(obj);
|
|
13604
13641
|
} catch (err) {
|
|
13605
|
-
if (err instanceof z5.ZodError)
|
|
13642
|
+
if (err instanceof z5.ZodError)
|
|
13643
|
+
throw formatZodError(err, "Line item", LineItemSchema);
|
|
13606
13644
|
throw err;
|
|
13607
13645
|
}
|
|
13608
13646
|
}
|
|
@@ -13611,7 +13649,8 @@ function parseTotalFlag(raw) {
|
|
|
13611
13649
|
try {
|
|
13612
13650
|
return TotalSchema.parse(obj);
|
|
13613
13651
|
} catch (err) {
|
|
13614
|
-
if (err instanceof z5.ZodError)
|
|
13652
|
+
if (err instanceof z5.ZodError)
|
|
13653
|
+
throw formatZodError(err, "Total", TotalSchema);
|
|
13615
13654
|
throw err;
|
|
13616
13655
|
}
|
|
13617
13656
|
}
|
|
@@ -13718,11 +13757,15 @@ var CreateSpendRequest = ({
|
|
|
13718
13757
|
repository,
|
|
13719
13758
|
params,
|
|
13720
13759
|
requestApproval = false,
|
|
13760
|
+
outputFile,
|
|
13761
|
+
force,
|
|
13721
13762
|
onComplete
|
|
13722
13763
|
}) => {
|
|
13723
13764
|
const [status, setStatus] = useState10("creating");
|
|
13724
13765
|
const [request, setRequest] = useState10(null);
|
|
13725
13766
|
const [error, setError] = useState10("");
|
|
13767
|
+
const [outputFilePath, setOutputFilePath] = useState10(null);
|
|
13768
|
+
const [fileError, setFileError] = useState10("");
|
|
13726
13769
|
const approvalUrl = request?.approval_url ?? "";
|
|
13727
13770
|
const onSuccess = useCallback2(
|
|
13728
13771
|
(result) => setRequest(result),
|
|
@@ -13758,6 +13801,18 @@ var CreateSpendRequest = ({
|
|
|
13758
13801
|
};
|
|
13759
13802
|
create();
|
|
13760
13803
|
}, [repository, params, requestApproval, onComplete]);
|
|
13804
|
+
useEffect10(() => {
|
|
13805
|
+
if (status !== "success" || !outputFile || !request?.card) return;
|
|
13806
|
+
const fileData = {
|
|
13807
|
+
spend_request_id: request.id,
|
|
13808
|
+
merchant_name: request.merchant_name,
|
|
13809
|
+
merchant_url: request.merchant_url,
|
|
13810
|
+
context: request.context,
|
|
13811
|
+
created_at: request.created_at,
|
|
13812
|
+
card: request.card
|
|
13813
|
+
};
|
|
13814
|
+
writeCredentialFile(outputFile, fileData, force ?? false).then((path6) => setOutputFilePath(path6)).catch((err) => setFileError(err.message));
|
|
13815
|
+
}, [status, outputFile, force, request]);
|
|
13761
13816
|
if (status === "creating") {
|
|
13762
13817
|
return /* @__PURE__ */ jsx20(Box14, { children: /* @__PURE__ */ jsxs13(Text15, { color: "cyan", children: [
|
|
13763
13818
|
/* @__PURE__ */ jsx20(Spinner5, { type: "dots" }),
|
|
@@ -13801,6 +13856,49 @@ var CreateSpendRequest = ({
|
|
|
13801
13856
|
/* @__PURE__ */ jsx20(Text15, { bold: true, children: request.shared_payment_token.id })
|
|
13802
13857
|
] })
|
|
13803
13858
|
] }),
|
|
13859
|
+
request?.card && !outputFile && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginTop: 1, children: [
|
|
13860
|
+
/* @__PURE__ */ jsx20(Text15, { bold: true, children: "Card Details:" }),
|
|
13861
|
+
/* @__PURE__ */ jsxs13(Text15, { children: [
|
|
13862
|
+
" ",
|
|
13863
|
+
"Number: ",
|
|
13864
|
+
/* @__PURE__ */ jsx20(Text15, { bold: true, children: request.card.number })
|
|
13865
|
+
] }),
|
|
13866
|
+
/* @__PURE__ */ jsxs13(Text15, { children: [
|
|
13867
|
+
" ",
|
|
13868
|
+
"Brand: ",
|
|
13869
|
+
/* @__PURE__ */ jsx20(Text15, { bold: true, children: request.card.brand })
|
|
13870
|
+
] }),
|
|
13871
|
+
/* @__PURE__ */ jsxs13(Text15, { children: [
|
|
13872
|
+
" ",
|
|
13873
|
+
"Expiry:",
|
|
13874
|
+
" ",
|
|
13875
|
+
/* @__PURE__ */ jsxs13(Text15, { bold: true, children: [
|
|
13876
|
+
String(request.card.exp_month).padStart(2, "0"),
|
|
13877
|
+
"/",
|
|
13878
|
+
request.card.exp_year
|
|
13879
|
+
] })
|
|
13880
|
+
] }),
|
|
13881
|
+
request.card.cvc && /* @__PURE__ */ jsxs13(Text15, { children: [
|
|
13882
|
+
" ",
|
|
13883
|
+
"CVC: ",
|
|
13884
|
+
/* @__PURE__ */ jsx20(Text15, { bold: true, children: request.card.cvc })
|
|
13885
|
+
] }),
|
|
13886
|
+
request.card.valid_until && /* @__PURE__ */ jsxs13(Text15, { children: [
|
|
13887
|
+
" ",
|
|
13888
|
+
"Valid Until: ",
|
|
13889
|
+
/* @__PURE__ */ jsx20(Text15, { bold: true, children: request.card.valid_until })
|
|
13890
|
+
] })
|
|
13891
|
+
] }),
|
|
13892
|
+
request?.card && outputFile && /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginTop: 1, children: [
|
|
13893
|
+
outputFilePath && /* @__PURE__ */ jsxs13(Text15, { color: "green", children: [
|
|
13894
|
+
"Card credentials written to ",
|
|
13895
|
+
/* @__PURE__ */ jsx20(Text15, { bold: true, children: outputFilePath })
|
|
13896
|
+
] }),
|
|
13897
|
+
fileError && /* @__PURE__ */ jsxs13(Text15, { color: "red", children: [
|
|
13898
|
+
"Failed to write card file: ",
|
|
13899
|
+
fileError
|
|
13900
|
+
] })
|
|
13901
|
+
] }),
|
|
13804
13902
|
/* @__PURE__ */ jsx20(AppDownloadQrCodes, {})
|
|
13805
13903
|
] });
|
|
13806
13904
|
}
|
|
@@ -13918,12 +14016,16 @@ var RetrieveSpendRequest = ({
|
|
|
13918
14016
|
id,
|
|
13919
14017
|
timeout = 300,
|
|
13920
14018
|
include,
|
|
14019
|
+
outputFile,
|
|
14020
|
+
force,
|
|
13921
14021
|
onComplete
|
|
13922
14022
|
}) => {
|
|
13923
14023
|
const [phase, setPhase] = useState12("fetching");
|
|
13924
14024
|
const [request, setRequest] = useState12(null);
|
|
13925
14025
|
const [error, setError] = useState12("");
|
|
13926
14026
|
const [elapsed, setElapsed] = useState12(0);
|
|
14027
|
+
const [outputFilePath, setOutputFilePath] = useState12(null);
|
|
14028
|
+
const [fileError, setFileError] = useState12("");
|
|
13927
14029
|
const startTimeRef = useRef4(Date.now());
|
|
13928
14030
|
const pollRef = useRef4(null);
|
|
13929
14031
|
const timerRef = useRef4(null);
|
|
@@ -13934,6 +14036,18 @@ var RetrieveSpendRequest = ({
|
|
|
13934
14036
|
if (timerRef.current) clearInterval(timerRef.current);
|
|
13935
14037
|
};
|
|
13936
14038
|
}, []);
|
|
14039
|
+
useEffect12(() => {
|
|
14040
|
+
if (phase !== "success" || !outputFile || !request?.card) return;
|
|
14041
|
+
const fileData = {
|
|
14042
|
+
spend_request_id: request.id,
|
|
14043
|
+
merchant_name: request.merchant_name,
|
|
14044
|
+
merchant_url: request.merchant_url,
|
|
14045
|
+
context: request.context,
|
|
14046
|
+
created_at: request.created_at,
|
|
14047
|
+
card: request.card
|
|
14048
|
+
};
|
|
14049
|
+
writeCredentialFile(outputFile, fileData, force ?? false).then((path6) => setOutputFilePath(path6)).catch((err) => setFileError(err.message));
|
|
14050
|
+
}, [phase, outputFile, force, request]);
|
|
13937
14051
|
useEffect12(() => {
|
|
13938
14052
|
const fetch2 = async () => {
|
|
13939
14053
|
try {
|
|
@@ -14114,7 +14228,7 @@ var RetrieveSpendRequest = ({
|
|
|
14114
14228
|
/* @__PURE__ */ jsx22(Text17, { bold: true, children: request.shared_payment_token.id })
|
|
14115
14229
|
] })
|
|
14116
14230
|
] }),
|
|
14117
|
-
request?.card && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
|
|
14231
|
+
request?.card && !outputFile && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
|
|
14118
14232
|
/* @__PURE__ */ jsx22(Text17, { bold: true, children: "Card Details:" }),
|
|
14119
14233
|
/* @__PURE__ */ jsxs15(Text17, { children: [
|
|
14120
14234
|
" ",
|
|
@@ -14173,6 +14287,16 @@ var RetrieveSpendRequest = ({
|
|
|
14173
14287
|
request.card.billing_address.country
|
|
14174
14288
|
] })
|
|
14175
14289
|
] })
|
|
14290
|
+
] }),
|
|
14291
|
+
request?.card && outputFile && /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", marginTop: 1, children: [
|
|
14292
|
+
outputFilePath && /* @__PURE__ */ jsxs15(Text17, { color: "green", children: [
|
|
14293
|
+
"Card credentials written to ",
|
|
14294
|
+
/* @__PURE__ */ jsx22(Text17, { bold: true, children: outputFilePath })
|
|
14295
|
+
] }),
|
|
14296
|
+
fileError && /* @__PURE__ */ jsxs15(Text17, { color: "red", children: [
|
|
14297
|
+
"Failed to write card file: ",
|
|
14298
|
+
fileError
|
|
14299
|
+
] })
|
|
14176
14300
|
] })
|
|
14177
14301
|
] })
|
|
14178
14302
|
] });
|
|
@@ -14199,12 +14323,20 @@ var createOptions = z6.object({
|
|
|
14199
14323
|
context: z6.string().min(100).describe(
|
|
14200
14324
|
"Min 100 chars \u2014 describe the purchase and rationale; the user reads this when approving"
|
|
14201
14325
|
),
|
|
14202
|
-
lineItem: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
|
|
14203
|
-
|
|
14326
|
+
lineItem: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
|
|
14327
|
+
'Line item (repeatable, key:value format). Keys: name (required), quantity, unit_amount, description, sku, url, image_url, product_url. Example: "name:Shoes,unit_amount:5000,quantity:2"'
|
|
14328
|
+
),
|
|
14329
|
+
total: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
|
|
14330
|
+
'Total (repeatable, key:value format). Keys: type (required; one of: subtotal, tax, total), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
|
|
14331
|
+
),
|
|
14204
14332
|
requestApproval: z6.boolean().default(true).describe("Request approval and poll until approved/denied/expired"),
|
|
14205
14333
|
test: z6.boolean().default(false).describe(
|
|
14206
14334
|
"Use test mode (creates testmode credentials from test card data)"
|
|
14207
|
-
)
|
|
14335
|
+
),
|
|
14336
|
+
outputFile: z6.string().optional().describe(
|
|
14337
|
+
"Write full card credentials to this file path; stdout shows redacted card data only"
|
|
14338
|
+
),
|
|
14339
|
+
force: z6.boolean().default(false).describe("Overwrite output file if it already exists")
|
|
14208
14340
|
});
|
|
14209
14341
|
var retrieveOptions = z6.object({
|
|
14210
14342
|
timeout: z6.coerce.number().default(300).describe(
|
|
@@ -14216,7 +14348,11 @@ var retrieveOptions = z6.object({
|
|
|
14216
14348
|
maxAttempts: z6.coerce.number().default(0).describe(
|
|
14217
14349
|
"Max poll attempts. 0 = unlimited. Exhaustion during active polling exits non-zero with POLLING_TIMEOUT."
|
|
14218
14350
|
),
|
|
14219
|
-
include: z6.array(z6.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)")
|
|
14351
|
+
include: z6.array(z6.string()).default([]).describe("Include extra data (repeatable, e.g. --include card)"),
|
|
14352
|
+
outputFile: z6.string().optional().describe(
|
|
14353
|
+
"Write full card credentials to this file path; stdout shows redacted card data only"
|
|
14354
|
+
),
|
|
14355
|
+
force: z6.boolean().default(false).describe("Overwrite output file if it already exists")
|
|
14220
14356
|
});
|
|
14221
14357
|
var updateOptions = z6.object({
|
|
14222
14358
|
paymentMethodId: z6.string().optional().describe("Payment method ID"),
|
|
@@ -14225,8 +14361,12 @@ var updateOptions = z6.object({
|
|
|
14225
14361
|
profileId: z6.string().optional().describe("Profile ID"),
|
|
14226
14362
|
merchantId: z6.string().optional().describe("Merchant ID"),
|
|
14227
14363
|
currency: z6.string().optional().describe("Currency code"),
|
|
14228
|
-
lineItem: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
|
|
14229
|
-
|
|
14364
|
+
lineItem: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
|
|
14365
|
+
'Line item (repeatable, key:value format). Keys: name (required), quantity, unit_amount, description, sku, url, image_url, product_url. Example: "name:Shoes,unit_amount:5000,quantity:2"'
|
|
14366
|
+
),
|
|
14367
|
+
total: z6.array(z6.union([z6.string(), z6.record(z6.string(), z6.unknown())])).default([]).describe(
|
|
14368
|
+
'Total (repeatable, key:value format). Keys: type (required; one of: subtotal, tax, total), display_text (required), amount (required). Example: "type:total,display_text:Total,amount:5000"'
|
|
14369
|
+
)
|
|
14230
14370
|
});
|
|
14231
14371
|
|
|
14232
14372
|
// src/commands/spend-request/update.tsx
|
|
@@ -14308,6 +14448,23 @@ var UpdateSpendRequest = ({
|
|
|
14308
14448
|
|
|
14309
14449
|
// src/commands/spend-request/index.tsx
|
|
14310
14450
|
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
14451
|
+
async function applyOutputFile(request, outputFile, force) {
|
|
14452
|
+
if (!outputFile || !request.card) return request;
|
|
14453
|
+
const fileData = {
|
|
14454
|
+
spend_request_id: request.id,
|
|
14455
|
+
merchant_name: request.merchant_name,
|
|
14456
|
+
merchant_url: request.merchant_url,
|
|
14457
|
+
context: request.context,
|
|
14458
|
+
created_at: request.created_at,
|
|
14459
|
+
card: request.card
|
|
14460
|
+
};
|
|
14461
|
+
const resolvedPath = await writeCredentialFile(outputFile, fileData, force);
|
|
14462
|
+
const { card: _, ...withoutCard } = request;
|
|
14463
|
+
return {
|
|
14464
|
+
...withoutCard,
|
|
14465
|
+
card_output_file: resolvedPath
|
|
14466
|
+
};
|
|
14467
|
+
}
|
|
14311
14468
|
function createSpendRequestCli(repository) {
|
|
14312
14469
|
const cli2 = Cli6.create("spend-request", {
|
|
14313
14470
|
description: "Spend request management commands"
|
|
@@ -14385,6 +14542,8 @@ function createSpendRequestCli(repository) {
|
|
|
14385
14542
|
request_approval: requestApproval || void 0,
|
|
14386
14543
|
test: opts.test ? true : void 0
|
|
14387
14544
|
};
|
|
14545
|
+
const outputFile = opts.outputFile;
|
|
14546
|
+
const forceOverwrite = opts.force;
|
|
14388
14547
|
if (!c.agent && !c.formatExplicit) {
|
|
14389
14548
|
return new Promise((resolve) => {
|
|
14390
14549
|
let capturedResult = null;
|
|
@@ -14395,6 +14554,8 @@ function createSpendRequestCli(repository) {
|
|
|
14395
14554
|
repository,
|
|
14396
14555
|
params: createParams,
|
|
14397
14556
|
requestApproval,
|
|
14557
|
+
outputFile,
|
|
14558
|
+
force: forceOverwrite,
|
|
14398
14559
|
onComplete: (result) => {
|
|
14399
14560
|
capturedResult = result;
|
|
14400
14561
|
}
|
|
@@ -14408,7 +14569,15 @@ function createSpendRequestCli(repository) {
|
|
|
14408
14569
|
}
|
|
14409
14570
|
const created = await repository.createSpendRequest(createParams);
|
|
14410
14571
|
if (!requestApproval) {
|
|
14411
|
-
|
|
14572
|
+
try {
|
|
14573
|
+
yield await applyOutputFile(created, outputFile, forceOverwrite);
|
|
14574
|
+
} catch (err) {
|
|
14575
|
+
const message = err.message;
|
|
14576
|
+
if (message.startsWith("OUTPUT_FILE_EXISTS")) {
|
|
14577
|
+
return c.error({ code: "OUTPUT_FILE_EXISTS", message });
|
|
14578
|
+
}
|
|
14579
|
+
return c.error({ code: "OUTPUT_FILE_WRITE_ERROR", message });
|
|
14580
|
+
}
|
|
14412
14581
|
return;
|
|
14413
14582
|
}
|
|
14414
14583
|
yield {
|
|
@@ -14559,6 +14728,8 @@ function createSpendRequestCli(repository) {
|
|
|
14559
14728
|
const maxAttempts = opts.maxAttempts;
|
|
14560
14729
|
const includeArr = opts.include;
|
|
14561
14730
|
const include = includeArr?.length ? includeArr : void 0;
|
|
14731
|
+
const outputFile = opts.outputFile;
|
|
14732
|
+
const forceOverwrite = opts.force;
|
|
14562
14733
|
if (!c.agent && !c.formatExplicit) {
|
|
14563
14734
|
return new Promise((resolve) => {
|
|
14564
14735
|
let capturedResult = null;
|
|
@@ -14570,6 +14741,8 @@ function createSpendRequestCli(repository) {
|
|
|
14570
14741
|
id,
|
|
14571
14742
|
timeout,
|
|
14572
14743
|
include,
|
|
14744
|
+
outputFile,
|
|
14745
|
+
force: forceOverwrite,
|
|
14573
14746
|
onComplete: (result) => {
|
|
14574
14747
|
capturedResult = result;
|
|
14575
14748
|
}
|
|
@@ -14599,15 +14772,20 @@ function createSpendRequestCli(repository) {
|
|
|
14599
14772
|
message: `Spend request ${id} not found`
|
|
14600
14773
|
});
|
|
14601
14774
|
}
|
|
14602
|
-
|
|
14603
|
-
|
|
14775
|
+
const shouldEmitFinal = terminalStatuses.has(request.status) || interval <= 0;
|
|
14776
|
+
if (shouldEmitFinal) {
|
|
14777
|
+
try {
|
|
14778
|
+
yield await applyOutputFile(request, outputFile, forceOverwrite);
|
|
14779
|
+
} catch (err) {
|
|
14780
|
+
const message = err.message;
|
|
14781
|
+
if (message.startsWith("OUTPUT_FILE_EXISTS")) {
|
|
14782
|
+
return c.error({ code: "OUTPUT_FILE_EXISTS", message });
|
|
14783
|
+
}
|
|
14784
|
+
return c.error({ code: "OUTPUT_FILE_WRITE_ERROR", message });
|
|
14785
|
+
}
|
|
14604
14786
|
return;
|
|
14605
14787
|
}
|
|
14606
14788
|
attempts++;
|
|
14607
|
-
if (interval <= 0) {
|
|
14608
|
-
yield request;
|
|
14609
|
-
return;
|
|
14610
|
-
}
|
|
14611
14789
|
const maxAttemptsExhausted = maxAttempts > 0 && attempts >= maxAttempts;
|
|
14612
14790
|
const timeoutReached = Date.now() >= deadline;
|
|
14613
14791
|
if (maxAttemptsExhausted || timeoutReached) {
|
|
@@ -14929,8 +15107,78 @@ var ResourceFactory = class {
|
|
|
14929
15107
|
}
|
|
14930
15108
|
};
|
|
14931
15109
|
|
|
15110
|
+
// src/utils/update-info.ts
|
|
15111
|
+
import process8 from "process";
|
|
15112
|
+
import updateNotifier from "update-notifier";
|
|
15113
|
+
var UPDATE_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
15114
|
+
var FAILURE_CACHE_TTL_MS = 60 * 1e3;
|
|
15115
|
+
var cachedUpdateInfo;
|
|
15116
|
+
var inflightUpdateInfo;
|
|
15117
|
+
function createAgentUpdateInfoProvider(packageName, cliVersion2) {
|
|
15118
|
+
return async ({ polling }) => {
|
|
15119
|
+
if (polling) {
|
|
15120
|
+
return cachedUpdateInfo?.value;
|
|
15121
|
+
}
|
|
15122
|
+
const now = Date.now();
|
|
15123
|
+
if (cachedUpdateInfo && cachedUpdateInfo.expiresAt > now) {
|
|
15124
|
+
return cachedUpdateInfo.value;
|
|
15125
|
+
}
|
|
15126
|
+
if (!inflightUpdateInfo) {
|
|
15127
|
+
inflightUpdateInfo = fetchLatestVersion(packageName, cliVersion2).finally(
|
|
15128
|
+
() => {
|
|
15129
|
+
inflightUpdateInfo = void 0;
|
|
15130
|
+
}
|
|
15131
|
+
);
|
|
15132
|
+
}
|
|
15133
|
+
return inflightUpdateInfo;
|
|
15134
|
+
};
|
|
15135
|
+
}
|
|
15136
|
+
function createInteractiveUpdateInfoProvider(updateInfo) {
|
|
15137
|
+
return async () => updateInfo;
|
|
15138
|
+
}
|
|
15139
|
+
function renderInteractiveUpdateNotice(updateInfo) {
|
|
15140
|
+
return [
|
|
15141
|
+
"",
|
|
15142
|
+
`Update available for @stripe/link-cli: ${updateInfo.current} -> ${updateInfo.latest}`,
|
|
15143
|
+
"Run: npm install -g @stripe/link-cli",
|
|
15144
|
+
""
|
|
15145
|
+
].join("\n");
|
|
15146
|
+
}
|
|
15147
|
+
async function fetchLatestVersion(packageName, cliVersion2) {
|
|
15148
|
+
const previousDisableFlag = process8.env.NO_UPDATE_NOTIFIER;
|
|
15149
|
+
try {
|
|
15150
|
+
process8.env.NO_UPDATE_NOTIFIER = "1";
|
|
15151
|
+
const notifier = updateNotifier({
|
|
15152
|
+
pkg: {
|
|
15153
|
+
name: packageName,
|
|
15154
|
+
version: cliVersion2
|
|
15155
|
+
}
|
|
15156
|
+
});
|
|
15157
|
+
const payload = await notifier.fetchInfo();
|
|
15158
|
+
const latest = payload.latest?.trim();
|
|
15159
|
+
const value = latest && latest.length > 0 && latest !== cliVersion2 ? { current: cliVersion2, latest } : void 0;
|
|
15160
|
+
cacheUpdateInfo(value);
|
|
15161
|
+
return value;
|
|
15162
|
+
} catch {
|
|
15163
|
+
cacheUpdateInfo(void 0, FAILURE_CACHE_TTL_MS);
|
|
15164
|
+
return void 0;
|
|
15165
|
+
} finally {
|
|
15166
|
+
if (previousDisableFlag === void 0) {
|
|
15167
|
+
process8.env.NO_UPDATE_NOTIFIER = void 0;
|
|
15168
|
+
} else {
|
|
15169
|
+
process8.env.NO_UPDATE_NOTIFIER = previousDisableFlag;
|
|
15170
|
+
}
|
|
15171
|
+
}
|
|
15172
|
+
}
|
|
15173
|
+
function cacheUpdateInfo(value, ttlMs = UPDATE_CACHE_TTL_MS) {
|
|
15174
|
+
cachedUpdateInfo = {
|
|
15175
|
+
expiresAt: Date.now() + ttlMs,
|
|
15176
|
+
value
|
|
15177
|
+
};
|
|
15178
|
+
}
|
|
15179
|
+
|
|
14932
15180
|
// src/cli.tsx
|
|
14933
|
-
var cliVersion = "0.4.
|
|
15181
|
+
var cliVersion = "0.4.3";
|
|
14934
15182
|
var buildNumber = "1";
|
|
14935
15183
|
var cliName = "@stripe/link-cli";
|
|
14936
15184
|
var defaultHeaders = {
|
|
@@ -14941,14 +15189,24 @@ var verbose = process.argv.includes("--verbose");
|
|
|
14941
15189
|
var factory = new ResourceFactory({ verbose, defaultHeaders });
|
|
14942
15190
|
var authRepo = factory.createAuthResource();
|
|
14943
15191
|
var spendRequestRepo = factory.createSpendRequestResource();
|
|
14944
|
-
var notifier = updateNotifier({
|
|
14945
|
-
pkg: { name: cliName, version: cliVersion }
|
|
14946
|
-
});
|
|
14947
15192
|
var cli = Cli7.create("link-cli", {
|
|
14948
15193
|
description: "Create a secure, one-time payment credential from a Link wallet to let agents complete purchases on behalf of users.",
|
|
14949
15194
|
version: `${cliVersion} (build ${buildNumber})`
|
|
14950
15195
|
});
|
|
14951
|
-
|
|
15196
|
+
var isAgent = process.argv.includes("--format") || process.argv.includes("--mcp");
|
|
15197
|
+
var agentUpdateInfoProvider = createAgentUpdateInfoProvider(
|
|
15198
|
+
cliName,
|
|
15199
|
+
cliVersion
|
|
15200
|
+
);
|
|
15201
|
+
var getUpdateInfo = agentUpdateInfoProvider;
|
|
15202
|
+
if (!isAgent && process.stdout.isTTY) {
|
|
15203
|
+
const updateInfo = await agentUpdateInfoProvider({ polling: false });
|
|
15204
|
+
getUpdateInfo = createInteractiveUpdateInfoProvider(updateInfo);
|
|
15205
|
+
if (updateInfo) {
|
|
15206
|
+
process.stderr.write(renderInteractiveUpdateNotice(updateInfo));
|
|
15207
|
+
}
|
|
15208
|
+
}
|
|
15209
|
+
cli.command(createAuthCli(authRepo, getUpdateInfo));
|
|
14952
15210
|
cli.command(createSpendRequestCli(spendRequestRepo));
|
|
14953
15211
|
cli.command(
|
|
14954
15212
|
createPaymentMethodsCli(() => factory.createPaymentMethodsResource())
|
|
@@ -14968,10 +15226,6 @@ cli.command(
|
|
|
14968
15226
|
() => factory.createPaymentMethodsResource()
|
|
14969
15227
|
)
|
|
14970
15228
|
);
|
|
14971
|
-
var isAgent = process.argv.includes("--format") || process.argv.includes("--mcp");
|
|
14972
|
-
if (!isAgent) {
|
|
14973
|
-
notifier.notify({ defer: false });
|
|
14974
|
-
}
|
|
14975
15229
|
cli.serve();
|
|
14976
15230
|
var cli_default = cli;
|
|
14977
15231
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stripe/link-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"link-cli": "./dist/cli.js"
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"ink": "^5.2.1",
|
|
26
26
|
"ink-spinner": "^5.0.0",
|
|
27
27
|
"mppx": "^0.5.7",
|
|
28
|
+
"viem": "^2.47.5",
|
|
28
29
|
"qrcode": "^1.5.4",
|
|
29
30
|
"react": "^18.3.1",
|
|
30
31
|
"update-notifier": "^7.3.1",
|
|
@@ -39,8 +40,8 @@
|
|
|
39
40
|
"tsx": "^4.21.0",
|
|
40
41
|
"typescript": "^5.9.3",
|
|
41
42
|
"vitest": "^4.1.2",
|
|
42
|
-
"@stripe/link-
|
|
43
|
-
"@stripe/link-
|
|
43
|
+
"@stripe/link-typescript-config": "0.0.0",
|
|
44
|
+
"@stripe/link-sdk": "1.0.0"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "tsup",
|