@recur-tw/cli 0.1.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/AGENT.md +268 -0
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +2552 -0
- package/dist/index.d.mts +196 -0
- package/dist/index.mjs +1095 -0
- package/package.json +71 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recur-cli
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
description: Manage subscriptions, products, customers, webhooks, and checkout sessions on Recur — Taiwan's subscription payment platform.
|
|
5
|
+
requires:
|
|
6
|
+
bins: ["recur"]
|
|
7
|
+
env: ["RECUR_SECRET_KEY"]
|
|
8
|
+
capabilities:
|
|
9
|
+
- products
|
|
10
|
+
- customers
|
|
11
|
+
- subscriptions
|
|
12
|
+
- orders
|
|
13
|
+
- invoices
|
|
14
|
+
- webhooks
|
|
15
|
+
- checkout-sessions
|
|
16
|
+
- schema-introspection
|
|
17
|
+
- dry-run-validation
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Recur CLI — Agent Instructions
|
|
21
|
+
|
|
22
|
+
> For AI agents using `@recur-tw/cli`. Humans: see README.md.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. Auth (once)
|
|
28
|
+
export RECUR_SECRET_KEY=sk_test_xxx
|
|
29
|
+
|
|
30
|
+
# 2. Discover API shape
|
|
31
|
+
recur schema --output json
|
|
32
|
+
|
|
33
|
+
# 3. Drill into a specific action
|
|
34
|
+
recur schema products.create --output json
|
|
35
|
+
|
|
36
|
+
# 4. Dry-run first (validates locally, no API call)
|
|
37
|
+
recur products create --json '{"name":"Test","price":299}' --dry-run --output json
|
|
38
|
+
|
|
39
|
+
# 5. Execute
|
|
40
|
+
recur products create --json '{"name":"Test","price":299}' --output json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Rules (Non-Negotiable)
|
|
44
|
+
|
|
45
|
+
1. **`--output json` always.** Table format is for humans. When piped, JSON is auto-detected.
|
|
46
|
+
2. **`--dry-run` before mutations.** create, update, archive, cancel, delete — always dry-run first.
|
|
47
|
+
3. **`--fields id,name,price` on list calls.** Minimizes tokens. Only request fields you need.
|
|
48
|
+
4. **`--json` for write payloads.** Maps 1:1 to API body. Prefer over individual flags.
|
|
49
|
+
5. **Confirm with user before executing writes** without `--dry-run`.
|
|
50
|
+
6. **Prices are TWD integers.** `299` = NT$299. Never divide by 100.
|
|
51
|
+
7. **`--immediately` is a boolean flag.** Use `--immediately` or omit. Never `--immediately false`.
|
|
52
|
+
8. **IDs are CUID.** e.g. `k672i1kd6zgrw5b6w39xwpx3`. No prefix like `cus_` or `prod_`.
|
|
53
|
+
|
|
54
|
+
## Authentication
|
|
55
|
+
|
|
56
|
+
Priority: `--key` flag > `RECUR_SECRET_KEY` env > `~/.recur/credentials.json`
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Best for agents: env var (no filesystem writes)
|
|
60
|
+
export RECUR_SECRET_KEY=sk_test_xxx
|
|
61
|
+
|
|
62
|
+
# Or per-command
|
|
63
|
+
recur --key sk_test_xxx products list --output json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Schema Introspection
|
|
67
|
+
|
|
68
|
+
The CLI is self-describing. Use `recur schema` to discover everything at runtime.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# All resources (compact overview)
|
|
72
|
+
recur schema --output json
|
|
73
|
+
# Returns: [{resource, description, actions: [{name, method, path, description}]}]
|
|
74
|
+
|
|
75
|
+
# Specific action (full detail)
|
|
76
|
+
recur schema products.create --output json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Schema Fields
|
|
80
|
+
|
|
81
|
+
| Field | What it tells you |
|
|
82
|
+
|-------|-------------------|
|
|
83
|
+
| `bodySchema[field].required` | Must be provided |
|
|
84
|
+
| `bodySchema[field].enum` | Valid values (array) |
|
|
85
|
+
| `bodySchema[field].cliFlag` | CLI flag when different from API field (e.g. `--product-id` for `productId`) |
|
|
86
|
+
| `responseFields[].name` | Field name for `--fields` |
|
|
87
|
+
| `responseFields[].type` | `string`, `number`, `boolean`, `datetime`, `object`, `array` |
|
|
88
|
+
| `responseFields[].description` | What this field contains |
|
|
89
|
+
| `responseFields[].nullable` | `true` if field can be null |
|
|
90
|
+
| `supportsDryRun` | Whether `--dry-run` is available |
|
|
91
|
+
| `pagination.cursor` | Cursor param name (e.g. `starting_after`) |
|
|
92
|
+
| `pagination.defaultLimit` | Default page size |
|
|
93
|
+
| `pagination.maxLimit` | Max page size |
|
|
94
|
+
|
|
95
|
+
### Example Schema Output
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"resource": "products",
|
|
100
|
+
"action": "create",
|
|
101
|
+
"method": "POST",
|
|
102
|
+
"path": "/v1/products",
|
|
103
|
+
"bodySchema": {
|
|
104
|
+
"name": { "type": "string", "description": "Product name", "required": true },
|
|
105
|
+
"price": { "type": "number", "description": "Price in TWD (integer)", "required": true },
|
|
106
|
+
"interval": { "type": "string", "description": "Billing interval", "enum": ["monthly", "yearly"] }
|
|
107
|
+
},
|
|
108
|
+
"responseFields": [
|
|
109
|
+
{ "name": "id", "type": "string", "description": "Product ID (CUID)" },
|
|
110
|
+
{ "name": "price", "type": "number", "description": "Price in TWD (integer)" },
|
|
111
|
+
{ "name": "active", "type": "boolean" }
|
|
112
|
+
],
|
|
113
|
+
"supportsDryRun": true
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Output Parsing
|
|
118
|
+
|
|
119
|
+
| Stream | Contains | Parse with |
|
|
120
|
+
|--------|----------|------------|
|
|
121
|
+
| **stdout** | Data only (JSON/CSV/NDJSON/table) | `jq`, JSON.parse |
|
|
122
|
+
| **stderr** | Warnings, errors, `[dry-run]` labels | Ignore or log |
|
|
123
|
+
| **Exit 0** | Success | Check code |
|
|
124
|
+
| **Exit 1** | Error | Read stderr |
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Capture data, discard warnings
|
|
128
|
+
result=$(recur products list --fields id,name --output json 2>/dev/null)
|
|
129
|
+
|
|
130
|
+
# NDJSON for streaming (one JSON object per line)
|
|
131
|
+
recur customers list --output ndjson --fields id,email 2>/dev/null
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Error Shape
|
|
135
|
+
|
|
136
|
+
Errors print to stderr as `Error: [code] message`. The API error body follows:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"error": {
|
|
141
|
+
"code": "not_found",
|
|
142
|
+
"message": "Product not found",
|
|
143
|
+
"doc_url": "https://docs.recur.tw/api#errors"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Dry-Run Behavior
|
|
149
|
+
|
|
150
|
+
- Validates payload locally (required fields, enum values, numeric types)
|
|
151
|
+
- Prints validated payload to stdout as JSON
|
|
152
|
+
- Labels `[dry-run]` to stderr
|
|
153
|
+
- **No API call is made**
|
|
154
|
+
- Exit 1 if validation fails, exit 0 if valid
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Dry-run output:
|
|
158
|
+
# stderr: [dry-run] Would create product:
|
|
159
|
+
# stdout: {"name":"Test","price":299,"type":"SUBSCRIPTION"}
|
|
160
|
+
recur products create --json '{"name":"Test","price":299,"type":"SUBSCRIPTION"}' --dry-run --output json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Agent Workflow Patterns
|
|
164
|
+
|
|
165
|
+
### Pattern 1: Lookup → Act → Verify
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# 1. Find the customer
|
|
169
|
+
email=$(recur customers list --fields id,email --output json 2>/dev/null | jq -r '.[] | select(.email=="user@example.com") | .id')
|
|
170
|
+
|
|
171
|
+
# 2. Dry-run the cancel
|
|
172
|
+
recur subscriptions cancel "$sub_id" --dry-run --output json
|
|
173
|
+
|
|
174
|
+
# 3. Confirm with user, then execute
|
|
175
|
+
recur subscriptions cancel "$sub_id" --output json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Pattern 2: Minimal Reads
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Bad: fetches all fields (wastes tokens)
|
|
182
|
+
recur products list --output json
|
|
183
|
+
|
|
184
|
+
# Good: only what you need
|
|
185
|
+
recur products list --fields id,name,price --output json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Pattern 3: Schema-Driven Discovery
|
|
189
|
+
|
|
190
|
+
When you don't know the API shape, introspect first:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# What resources exist?
|
|
194
|
+
recur schema --output json 2>/dev/null | jq '.[].resource'
|
|
195
|
+
|
|
196
|
+
# What can I do with subscriptions?
|
|
197
|
+
recur schema subscriptions.cancel --output json 2>/dev/null | jq '{bodySchema, supportsDryRun}'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Error Recovery
|
|
201
|
+
|
|
202
|
+
All errors include machine-parseable hints:
|
|
203
|
+
|
|
204
|
+
| Error | CLI behavior |
|
|
205
|
+
|-------|-------------|
|
|
206
|
+
| Unknown resource | Lists available resources |
|
|
207
|
+
| Unknown action | Lists available actions for that resource |
|
|
208
|
+
| Missing required field | Lists missing field names |
|
|
209
|
+
| Invalid enum value | Lists valid values |
|
|
210
|
+
| Invalid ID | Describes what's wrong (path traversal, control chars) |
|
|
211
|
+
| HTTP 404 | Suggests checking ID or slug |
|
|
212
|
+
|
|
213
|
+
## Available Resources
|
|
214
|
+
|
|
215
|
+
| Resource | Actions | Writes | Paginated |
|
|
216
|
+
|----------|---------|--------|-----------|
|
|
217
|
+
| products | list, get, create, update, archive | --dry-run, --json | No |
|
|
218
|
+
| customers | list, get, update | --dry-run, --json | Yes |
|
|
219
|
+
| subscriptions | list, get, cancel | --dry-run, --json | Yes |
|
|
220
|
+
| orders | list, get | Read-only | Yes |
|
|
221
|
+
| invoices | list, get | Read-only | Yes |
|
|
222
|
+
| webhooks | list, create, test, delete, listen | --dry-run, --json | No |
|
|
223
|
+
| checkouts | create, get | --dry-run, --json | No |
|
|
224
|
+
|
|
225
|
+
## Context Budget Tips
|
|
226
|
+
|
|
227
|
+
1. **`--fields`** — Only request fields you need. `--fields id,status` instead of full object.
|
|
228
|
+
2. **`--limit`** — Set explicit limits. Default is 10-20 but say `--limit 5` if you only need a few.
|
|
229
|
+
3. **Pipe to `jq`** — Extract what you need: `recur products list --output json 2>/dev/null | jq '.[0].id'`
|
|
230
|
+
4. **Schema caching** — Call `recur schema` once per session, not per command.
|
|
231
|
+
5. **NDJSON** — For large lists, process line-by-line instead of loading entire array.
|
|
232
|
+
|
|
233
|
+
## Local Webhook Development
|
|
234
|
+
|
|
235
|
+
`recur webhooks listen` forwards live webhook events to a local URL via SSE relay. Use this during development to test webhook handlers without deploying to a public URL.
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Forward all events to local server
|
|
239
|
+
recur webhooks listen http://localhost:3000/api/webhooks --output json
|
|
240
|
+
|
|
241
|
+
# Filter specific event types (client-side filtering)
|
|
242
|
+
recur webhooks listen http://localhost:3000/api/webhooks --events checkout.completed,order.paid
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### How it works
|
|
246
|
+
|
|
247
|
+
1. CLI connects to Recur's SSE relay with your Secret Key
|
|
248
|
+
2. Relay assigns a per-session signing secret (`whsec_*`)
|
|
249
|
+
3. When Recur dispatches a webhook event, the relay pushes it to the CLI via SSE
|
|
250
|
+
4. CLI POSTs the event to your local URL with these headers:
|
|
251
|
+
- `X-Recur-Signature` — HMAC-SHA256 signature (verify with the signing secret)
|
|
252
|
+
- `X-Recur-Event-Type` — e.g. `checkout.completed`
|
|
253
|
+
- `X-Recur-Event-Id` — event ID
|
|
254
|
+
- `X-Recur-Timestamp` — ISO 8601 timestamp
|
|
255
|
+
|
|
256
|
+
### Agent usage notes
|
|
257
|
+
|
|
258
|
+
- **Not a daemon.** `listen` is a long-running foreground process — run it in a separate terminal or background process.
|
|
259
|
+
- **Signing secret changes per session.** Don't cache it across sessions.
|
|
260
|
+
- **Auto-reconnects** on disconnect (up to 10 consecutive failures).
|
|
261
|
+
- **`--events` is client-side filtering.** All events are still sent from the relay; the CLI drops non-matching ones locally.
|
|
262
|
+
- **Exit codes:** 0 = graceful shutdown (Ctrl+C / SIGTERM), 1 = max failures exceeded.
|
|
263
|
+
|
|
264
|
+
## Security
|
|
265
|
+
|
|
266
|
+
- All API responses are sanitized: control characters (ASCII < 0x20) are stripped from string values to prevent prompt injection.
|
|
267
|
+
- Resource IDs are validated against path traversal (`..`, `/`), query injection (`?`, `#`), and control characters.
|
|
268
|
+
- API keys are validated format before sending (`sk_test_*` or `sk_live_*`).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present Recur (recur.tw)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# @recur-tw/cli
|
|
2
|
+
|
|
3
|
+
Recur 訂閱金流平台的命令列工具。管理產品、客戶、訂閱、訂單等資源,同時支援人類使用與 AI Agent 整合。
|
|
4
|
+
|
|
5
|
+
## 安裝
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @recur-tw/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
或使用 npx 直接執行:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @recur-tw/cli products list
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 快速開始
|
|
18
|
+
|
|
19
|
+
### 1. 設定 API Key
|
|
20
|
+
|
|
21
|
+
前往 [Recur Dashboard](https://app.recur.tw) 取得 Secret Key(`sk_test_*` 或 `sk_live_*`),然後執行:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
recur --key sk_test_xxx login
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
API Key 會安全存儲在 `~/.recur/credentials.json`(檔案權限 `0600`)。
|
|
28
|
+
|
|
29
|
+
也可以透過環境變數設定:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export RECUR_SECRET_KEY=sk_test_xxx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. 開始使用
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 列出所有產品
|
|
39
|
+
recur products list
|
|
40
|
+
|
|
41
|
+
# 查看訂閱狀態
|
|
42
|
+
recur subscriptions list --status active
|
|
43
|
+
|
|
44
|
+
# 查看客戶資料
|
|
45
|
+
recur customers list --email user@example.com
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 指令一覽
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
recur <資源> <動作> [選項]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 全域選項
|
|
55
|
+
|
|
56
|
+
| 選項 | 說明 |
|
|
57
|
+
|------|------|
|
|
58
|
+
| `--key <sk_*>` | 指定 API Secret Key |
|
|
59
|
+
| `--profile <name>` | 使用指定的 profile |
|
|
60
|
+
| `--base-url <url>` | API 位址覆寫 |
|
|
61
|
+
| `--output <format>` | 輸出格式:`json`、`table`(預設)、`csv` |
|
|
62
|
+
| `--fields <fields>` | 逗號分隔的欄位篩選 |
|
|
63
|
+
| `--dry-run` | 僅本地驗證,不發送 API 請求 |
|
|
64
|
+
| `--json <payload>` | 直接傳入 JSON 作為 API request body |
|
|
65
|
+
|
|
66
|
+
### 驗證管理
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
recur login # 互動式設定 API Key
|
|
70
|
+
recur --key sk_test_xxx login # 直接設定
|
|
71
|
+
recur whoami # 顯示目前 profile
|
|
72
|
+
recur profiles # 列出所有 profile
|
|
73
|
+
recur use <profile> # 切換 profile
|
|
74
|
+
recur logout [profile] # 移除 profile
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### 多環境管理
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 儲存 sandbox profile
|
|
81
|
+
recur --key sk_test_xxx login --name sandbox
|
|
82
|
+
|
|
83
|
+
# 儲存 production profile
|
|
84
|
+
recur --key sk_live_xxx login --name production
|
|
85
|
+
|
|
86
|
+
# 切換環境
|
|
87
|
+
recur use production
|
|
88
|
+
recur products list # 使用 production key
|
|
89
|
+
|
|
90
|
+
# 或單次指定
|
|
91
|
+
recur --profile sandbox products list
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 產品 (Products)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
recur products list # 列出所有產品
|
|
98
|
+
recur products list --status active # 篩選狀態
|
|
99
|
+
recur products get <id> # 以 ID 查詢
|
|
100
|
+
recur products get <slug> # 以 slug 查詢
|
|
101
|
+
recur products create --name "方案" --price 299 --interval monthly --type SUBSCRIPTION
|
|
102
|
+
recur products create --json '{"name":"方案","price":299,"interval":"monthly"}'
|
|
103
|
+
recur products update <id> --name "新名稱"
|
|
104
|
+
recur products archive <id>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 客戶 (Customers)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
recur customers list # 列出所有客戶
|
|
111
|
+
recur customers list --email user@example.com # 以 email 篩選
|
|
112
|
+
recur customers list --limit 50 --offset 100 # 分頁
|
|
113
|
+
recur customers get <id> # 查詢客戶詳情
|
|
114
|
+
recur customers update <id> --name "新名稱"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 訂閱 (Subscriptions)
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
recur subscriptions list # 列出所有訂閱
|
|
121
|
+
recur subscriptions list --status active # 篩選狀態
|
|
122
|
+
recur subscriptions list --customer-id cus_xxx # 以客戶篩選
|
|
123
|
+
recur subscriptions list --email user@example.com # 以 email 篩選
|
|
124
|
+
recur subscriptions get <id> # 查詢訂閱詳情
|
|
125
|
+
recur subscriptions cancel <id> # 期末取消
|
|
126
|
+
recur subscriptions cancel <id> --immediately # 立即取消
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 訂單 (Orders)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
recur orders list # 列出所有訂單
|
|
133
|
+
recur orders list --status paid # 篩選狀態
|
|
134
|
+
recur orders list --customer-id cus_xxx # 以客戶篩選
|
|
135
|
+
recur orders get <id> # 查詢訂單詳情
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 帳單 (Invoices)
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
recur invoices list # 列出所有帳單
|
|
142
|
+
recur invoices list --subscription-id sub_xxx # 以訂閱篩選
|
|
143
|
+
recur invoices list --customer-id cus_xxx # 以客戶篩選
|
|
144
|
+
recur invoices list --status paid # 篩選狀態
|
|
145
|
+
recur invoices get <id> # 查詢帳單詳情
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Webhook
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
recur webhooks list # 列出所有 webhook
|
|
152
|
+
recur webhooks create --url https://example.com/hook # 建立 webhook
|
|
153
|
+
recur webhooks create --json '{"url":"...","events":["checkout.completed"]}'
|
|
154
|
+
recur webhooks test <id> # 發送測試事件
|
|
155
|
+
recur webhooks test <id> --event subscription.canceled # 指定事件類型
|
|
156
|
+
recur webhooks delete <id> # 刪除 webhook
|
|
157
|
+
recur webhooks listen http://localhost:3000/api/webhooks # 即時轉發到本地
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 本地 Webhook 開發(`webhooks listen`)
|
|
161
|
+
|
|
162
|
+
類似 `stripe listen`,`recur webhooks listen` 讓你在本地開發時即時接收 webhook 事件,不需要公開 URL。
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# 基本用法:轉發所有事件到本地伺服器
|
|
166
|
+
recur webhooks listen http://localhost:3000/api/webhooks
|
|
167
|
+
|
|
168
|
+
# 只轉發特定事件類型
|
|
169
|
+
recur webhooks listen http://localhost:3000/api/webhooks --events checkout.completed,order.paid
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### 運作原理
|
|
173
|
+
|
|
174
|
+
1. CLI 透過 SSE 連線到 Recur 的 relay 服務
|
|
175
|
+
2. 收到一個專屬的 signing secret(`whsec_*`)
|
|
176
|
+
3. 當 Recur 平台觸發 webhook 事件時,relay 即時推送到 CLI
|
|
177
|
+
4. CLI 將事件 POST 到你指定的本地 URL,附帶以下 headers:
|
|
178
|
+
- `X-Recur-Signature` — HMAC-SHA256 簽名(用 signing secret 驗證)
|
|
179
|
+
- `X-Recur-Event-Type` — 事件類型
|
|
180
|
+
- `X-Recur-Event-Id` — 事件 ID
|
|
181
|
+
- `X-Recur-Timestamp` — 事件時間戳
|
|
182
|
+
|
|
183
|
+
#### 輸出範例
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Ready! Listening for webhook events...
|
|
187
|
+
|
|
188
|
+
Signing secret: whsec_abc123def456...
|
|
189
|
+
Forwarding to: http://localhost:3000/api/webhooks
|
|
190
|
+
Environment: sandbox
|
|
191
|
+
|
|
192
|
+
12:03:45 -> checkout.completed [evt_abc123] 200 (23ms)
|
|
193
|
+
12:03:46 -> subscription.activated [evt_def456] 200 (18ms)
|
|
194
|
+
12:05:12 -> invoice.paid [evt_ghi789] 500 Internal Server Error (45ms)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### 選項
|
|
198
|
+
|
|
199
|
+
| 選項 | 說明 |
|
|
200
|
+
|------|------|
|
|
201
|
+
| `--events <types>` | 逗號分隔的事件類型篩選(CLI 端過濾) |
|
|
202
|
+
| `--relay-url <url>` | 覆寫 relay 服務 URL(開發用) |
|
|
203
|
+
|
|
204
|
+
#### 注意事項
|
|
205
|
+
|
|
206
|
+
- 需要 Secret Key(`sk_test_*` 或 `sk_live_*`)
|
|
207
|
+
- 斷線時自動重連(最多 10 次連續失敗後退出)
|
|
208
|
+
- `Ctrl+C` 優雅關閉
|
|
209
|
+
- Signing secret 每次連線都會重新產生
|
|
210
|
+
|
|
211
|
+
### Checkout Sessions
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
recur checkouts create --product-id prod_xxx --customer-email user@example.com
|
|
215
|
+
recur checkouts create --json '{"productId":"prod_xxx","successUrl":"https://..."}'
|
|
216
|
+
recur checkouts get <id> # 查詢 session 狀態
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## 輸出格式
|
|
220
|
+
|
|
221
|
+
### Table(預設,適合人類閱讀)
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
$ recur products list --fields name,price,type
|
|
225
|
+
|
|
226
|
+
name price type
|
|
227
|
+
─────────────── ───── ────────────
|
|
228
|
+
大師方案 - 月繳 799 SUBSCRIPTION
|
|
229
|
+
超級方案 - 月繳 499 SUBSCRIPTION
|
|
230
|
+
|
|
231
|
+
2 results
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### JSON(適合程式解析與 AI Agent)
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
$ recur products list --output json
|
|
238
|
+
|
|
239
|
+
[
|
|
240
|
+
{ "id": "prod_xxx", "name": "大師方案", "price": 799 },
|
|
241
|
+
...
|
|
242
|
+
]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
搭配 `jq` 使用:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
recur products list --output json | jq '.[].name'
|
|
249
|
+
recur subscriptions list --output json | jq '[.[] | select(.status == "ACTIVE")]'
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### CSV(適合匯出與試算表)
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
$ recur customers list --output csv --fields email,name > customers.csv
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 欄位篩選
|
|
259
|
+
|
|
260
|
+
用 `--fields` 減少輸出量,適合腳本和 AI Agent 控制 context window:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
recur products list --fields id,name,price
|
|
264
|
+
recur customers list --fields email,name --output json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## AI Agent 整合
|
|
268
|
+
|
|
269
|
+
此 CLI 遵循 [Agent-Optimized CLI](https://justin.poehnelt.com/posts/rewrite-your-cli-for-ai-agents/) 設計原則:
|
|
270
|
+
|
|
271
|
+
### Schema 自省
|
|
272
|
+
|
|
273
|
+
Agent 可以透過 `recur schema` 動態查詢 API 規格,不需要讀文件:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# 列出所有可用資源
|
|
277
|
+
recur schema
|
|
278
|
+
|
|
279
|
+
# 查看特定資源的所有動作
|
|
280
|
+
recur schema products
|
|
281
|
+
|
|
282
|
+
# 查看特定動作的完整參數定義
|
|
283
|
+
recur schema products.create
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
輸出包含 `method`、`path`、`params`、`bodySchema`、`responseFields`、`supportsDryRun` 等結構化資訊。
|
|
287
|
+
|
|
288
|
+
### Raw JSON Payload
|
|
289
|
+
|
|
290
|
+
`--json` 讓 Agent 直接傳入 API body,不需要逐一對應 flag:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
recur products create --json '{"name":"Pro Plan","price":599,"interval":"monthly","type":"SUBSCRIPTION"}'
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Dry Run
|
|
297
|
+
|
|
298
|
+
所有寫入操作支援 `--dry-run`,讓 Agent 先驗證再執行:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
recur products create --dry-run --json '{"name":"Test","price":299}'
|
|
302
|
+
# [dry-run] Would create product:
|
|
303
|
+
# { "name": "Test", "price": 299 }
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Input 驗證(防止 AI 幻覺)
|
|
307
|
+
|
|
308
|
+
CLI 內建多層輸入驗證,防止 Agent 送出錯誤的 ID 或惡意輸入:
|
|
309
|
+
|
|
310
|
+
- Resource ID 前綴驗證(`prod_*`、`cus_*`、`sub_*` 等)
|
|
311
|
+
- 路徑穿越偵測(`../`、`/`、`\`)
|
|
312
|
+
- 特殊字元阻擋(`?`、`#`、`%`)
|
|
313
|
+
- 控制字元過濾
|
|
314
|
+
|
|
315
|
+
## 驗證方式
|
|
316
|
+
|
|
317
|
+
API Key 的解析優先順序:
|
|
318
|
+
|
|
319
|
+
1. `--key` 參數(最高優先)
|
|
320
|
+
2. `RECUR_SECRET_KEY` 環境變數
|
|
321
|
+
3. `~/.recur/credentials.json` 中的 active profile
|
|
322
|
+
|
|
323
|
+
### API Key 類型
|
|
324
|
+
|
|
325
|
+
| 類型 | 格式 | 用途 |
|
|
326
|
+
|------|------|------|
|
|
327
|
+
| Secret Key (Sandbox) | `sk_test_*` | 測試環境,完整權限 |
|
|
328
|
+
| Secret Key (Production) | `sk_live_*` | 正式環境,完整權限 |
|
|
329
|
+
|
|
330
|
+
CLI 僅接受 Secret Key。Publishable Key(`pk_*`)僅供前端 SDK 使用。
|
|
331
|
+
|
|
332
|
+
### 安全性
|
|
333
|
+
|
|
334
|
+
- API Key 以 `0600` 權限存儲在 `~/.recur/credentials.json`
|
|
335
|
+
- CLI 不直接連接資料庫,所有操作經由 API server 驗證
|
|
336
|
+
- 每個 API Key 綁定一個 Organization,資料完全隔離
|
|
337
|
+
- Key 以 SHA-256 hash 存儲在 server 端
|
|
338
|
+
|
|
339
|
+
## 程式化使用
|
|
340
|
+
|
|
341
|
+
`@recur-tw/cli` 也提供 library API,可在 Node.js 中直接使用:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { RecurClient, resolveSecretKey, resolveBaseUrl } from '@recur-tw/cli'
|
|
345
|
+
|
|
346
|
+
const client = new RecurClient({
|
|
347
|
+
baseUrl: resolveBaseUrl({}),
|
|
348
|
+
secretKey: resolveSecretKey({}),
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
const products = await client.get('/v1/products')
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## 系統需求
|
|
355
|
+
|
|
356
|
+
- Node.js >= 18
|
|
357
|
+
- 作業系統:macOS、Linux、Windows
|
|
358
|
+
|
|
359
|
+
## 授權
|
|
360
|
+
|
|
361
|
+
MIT
|
|
362
|
+
|
|
363
|
+
## 相關連結
|
|
364
|
+
|
|
365
|
+
- [Recur 官網](https://recur.tw)
|
|
366
|
+
- [API 文件](https://recur.tw/docs/api)
|
|
367
|
+
- [SDK (recur-tw)](https://www.npmjs.com/package/recur-tw)
|
|
368
|
+
- [Dashboard](https://app.recur.tw)
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|