@getrouter/getrouter-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.
Files changed (120) hide show
  1. package/.github/workflows/ci.yml +19 -0
  2. package/AGENTS.md +78 -0
  3. package/README.ja.md +116 -0
  4. package/README.md +116 -0
  5. package/README.zh-cn.md +116 -0
  6. package/biome.json +10 -0
  7. package/bun.lock +397 -0
  8. package/dist/bin.mjs +1422 -0
  9. package/docs/plans/2026-01-01-getrouter-cli-config-command-plan.md +231 -0
  10. package/docs/plans/2026-01-01-getrouter-cli-config-core-plan.md +307 -0
  11. package/docs/plans/2026-01-01-getrouter-cli-design.md +106 -0
  12. package/docs/plans/2026-01-01-getrouter-cli-scaffold-plan.md +327 -0
  13. package/docs/plans/2026-01-02-getrouter-cli-auth-design.md +68 -0
  14. package/docs/plans/2026-01-02-getrouter-cli-auth-device-design.md +73 -0
  15. package/docs/plans/2026-01-02-getrouter-cli-auth-device-plan.md +411 -0
  16. package/docs/plans/2026-01-02-getrouter-cli-auth-plan.md +435 -0
  17. package/docs/plans/2026-01-02-getrouter-cli-http-client-plan.md +235 -0
  18. package/docs/plans/2026-01-02-getrouter-cli-keys-create-update-output-design.md +24 -0
  19. package/docs/plans/2026-01-02-getrouter-cli-keys-create-update-output-plan.md +141 -0
  20. package/docs/plans/2026-01-02-getrouter-cli-keys-delete-output-design.md +22 -0
  21. package/docs/plans/2026-01-02-getrouter-cli-keys-delete-output-plan.md +122 -0
  22. package/docs/plans/2026-01-02-getrouter-cli-keys-get-output-design.md +23 -0
  23. package/docs/plans/2026-01-02-getrouter-cli-keys-get-output-plan.md +141 -0
  24. package/docs/plans/2026-01-02-getrouter-cli-keys-interactive-design.md +28 -0
  25. package/docs/plans/2026-01-02-getrouter-cli-keys-interactive-plan.md +247 -0
  26. package/docs/plans/2026-01-02-getrouter-cli-keys-output-design.md +31 -0
  27. package/docs/plans/2026-01-02-getrouter-cli-keys-output-plan.md +187 -0
  28. package/docs/plans/2026-01-02-getrouter-cli-keys-subscription-design.md +52 -0
  29. package/docs/plans/2026-01-02-getrouter-cli-keys-subscription-plan.md +306 -0
  30. package/docs/plans/2026-01-02-getrouter-cli-setup-env-design.md +67 -0
  31. package/docs/plans/2026-01-02-getrouter-cli-setup-env-plan.md +441 -0
  32. package/docs/plans/2026-01-02-getrouter-cli-subscription-output-design.md +34 -0
  33. package/docs/plans/2026-01-02-getrouter-cli-subscription-output-plan.md +157 -0
  34. package/docs/plans/2026-01-03-bun-migration-plan.md +103 -0
  35. package/docs/plans/2026-01-03-cli-emoji-output.md +45 -0
  36. package/docs/plans/2026-01-03-cli-english-output.md +123 -0
  37. package/docs/plans/2026-01-03-cli-simplify-design.md +62 -0
  38. package/docs/plans/2026-01-03-cli-simplify-implementation.md +468 -0
  39. package/docs/plans/2026-01-03-readme-command-descriptions.md +116 -0
  40. package/docs/plans/2026-01-03-tsdown-migration-plan.md +75 -0
  41. package/docs/plans/2026-01-04-cli-docs-cleanup-design.md +49 -0
  42. package/docs/plans/2026-01-04-cli-docs-cleanup-plan.md +126 -0
  43. package/docs/plans/2026-01-04-codex-multistep-design.md +76 -0
  44. package/docs/plans/2026-01-04-codex-multistep-plan.md +240 -0
  45. package/docs/plans/2026-01-04-env-hook-design.md +48 -0
  46. package/docs/plans/2026-01-04-env-hook-plan.md +173 -0
  47. package/docs/plans/2026-01-04-models-keys-fuzzy-design.md +75 -0
  48. package/docs/plans/2026-01-04-models-keys-fuzzy-implementation.md +704 -0
  49. package/package.json +37 -0
  50. package/src/.gitkeep +0 -0
  51. package/src/bin.ts +4 -0
  52. package/src/cli.ts +12 -0
  53. package/src/cmd/auth.ts +44 -0
  54. package/src/cmd/claude.ts +10 -0
  55. package/src/cmd/codex.ts +119 -0
  56. package/src/cmd/config-helpers.ts +16 -0
  57. package/src/cmd/config.ts +31 -0
  58. package/src/cmd/env.ts +103 -0
  59. package/src/cmd/index.ts +20 -0
  60. package/src/cmd/keys.ts +207 -0
  61. package/src/cmd/models.ts +48 -0
  62. package/src/cmd/status.ts +106 -0
  63. package/src/cmd/usages.ts +29 -0
  64. package/src/core/api/client.ts +79 -0
  65. package/src/core/auth/device.ts +105 -0
  66. package/src/core/auth/index.ts +37 -0
  67. package/src/core/config/fs.ts +13 -0
  68. package/src/core/config/index.ts +37 -0
  69. package/src/core/config/paths.ts +5 -0
  70. package/src/core/config/redact.ts +18 -0
  71. package/src/core/config/types.ts +23 -0
  72. package/src/core/http/errors.ts +32 -0
  73. package/src/core/http/request.ts +41 -0
  74. package/src/core/http/url.ts +12 -0
  75. package/src/core/interactive/clipboard.ts +61 -0
  76. package/src/core/interactive/codex.ts +75 -0
  77. package/src/core/interactive/fuzzy.ts +64 -0
  78. package/src/core/interactive/keys.ts +164 -0
  79. package/src/core/output/table.ts +34 -0
  80. package/src/core/output/usages.ts +75 -0
  81. package/src/core/paths.ts +4 -0
  82. package/src/core/setup/codex.ts +129 -0
  83. package/src/core/setup/env.ts +220 -0
  84. package/src/core/usages/aggregate.ts +69 -0
  85. package/src/generated/router/dashboard/v1/index.ts +1104 -0
  86. package/src/index.ts +1 -0
  87. package/tests/.gitkeep +0 -0
  88. package/tests/auth/device.test.ts +75 -0
  89. package/tests/auth/status.test.ts +64 -0
  90. package/tests/cli.test.ts +31 -0
  91. package/tests/cmd/auth.test.ts +90 -0
  92. package/tests/cmd/claude.test.ts +132 -0
  93. package/tests/cmd/codex.test.ts +147 -0
  94. package/tests/cmd/config-helpers.test.ts +18 -0
  95. package/tests/cmd/config.test.ts +56 -0
  96. package/tests/cmd/keys.test.ts +163 -0
  97. package/tests/cmd/models.test.ts +63 -0
  98. package/tests/cmd/status.test.ts +82 -0
  99. package/tests/cmd/usages.test.ts +42 -0
  100. package/tests/config/fs.test.ts +14 -0
  101. package/tests/config/index.test.ts +63 -0
  102. package/tests/config/paths.test.ts +10 -0
  103. package/tests/config/redact.test.ts +17 -0
  104. package/tests/config/types.test.ts +10 -0
  105. package/tests/core/api/client.test.ts +92 -0
  106. package/tests/core/interactive/clipboard.test.ts +44 -0
  107. package/tests/core/interactive/codex.test.ts +17 -0
  108. package/tests/core/interactive/fuzzy.test.ts +30 -0
  109. package/tests/core/setup/codex.test.ts +38 -0
  110. package/tests/core/setup/env.test.ts +84 -0
  111. package/tests/core/usages/aggregate.test.ts +55 -0
  112. package/tests/http/errors.test.ts +15 -0
  113. package/tests/http/request.test.ts +82 -0
  114. package/tests/http/url.test.ts +17 -0
  115. package/tests/output/table.test.ts +29 -0
  116. package/tests/output/usages.test.ts +71 -0
  117. package/tests/paths.test.ts +9 -0
  118. package/tsconfig.json +13 -0
  119. package/tsdown.config.ts +5 -0
  120. package/vitest.config.ts +7 -0
@@ -0,0 +1,306 @@
1
+ # Keys & Subscription Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Implement keys (consumer) and subscription commands backed by the dashboard generated TypeScript client, with default redaction and JSON output support.
6
+
7
+ **Architecture:** Add a `core/api` adapter that wires generated clients to `core/http.requestJson`, then update `cmd/keys` and `cmd/subscription` to call that adapter and format outputs with redaction and `--json`/`--show-secret` flags.
8
+
9
+ **Tech Stack:** TypeScript, Node.js, commander, vitest.
10
+
11
+ ### Task 1: Add dashboard client adapter
12
+
13
+ **Files:**
14
+ - Create: `src/core/api/client.ts`
15
+ - Create: `tests/core/api/client.test.ts`
16
+
17
+ **Step 1: Write the failing test**
18
+
19
+ Create `tests/core/api/client.test.ts`:
20
+
21
+ ```ts
22
+ import { describe, it, expect, vi } from "vitest";
23
+ import { createApiClients } from "../../../src/core/api/client";
24
+
25
+ // Minimal fake fetch to capture request details
26
+ const makeFetch = () =>
27
+ vi.fn(async (input: RequestInfo, init?: RequestInit) => {
28
+ const url = typeof input === "string" ? input : input.toString();
29
+ return {
30
+ ok: true,
31
+ status: 200,
32
+ statusText: "OK",
33
+ json: async () => ({ url, init }),
34
+ } as Response;
35
+ });
36
+
37
+ describe("api client adapter", () => {
38
+ it("uses requestJson with generated paths", async () => {
39
+ const fetchImpl = makeFetch();
40
+ const { consumerService } = createApiClients({ fetchImpl });
41
+ const res = await consumerService.ListConsumers({
42
+ pageSize: 0,
43
+ pageToken: "",
44
+ });
45
+ expect((res as any).url).toContain("/v1/dashboard/consumers");
46
+ expect((res as any).init.method).toBe("GET");
47
+ });
48
+ });
49
+ ```
50
+
51
+ **Step 2: Run test to verify it fails**
52
+
53
+ Run: `npm test -- tests/core/api/client.test.ts`
54
+ Expected: FAIL (module missing)
55
+
56
+ **Step 3: Write minimal implementation**
57
+
58
+ Create `src/core/api/client.ts`:
59
+
60
+ ```ts
61
+ import { requestJson } from "../http/request";
62
+ import {
63
+ createConsumerServiceClient,
64
+ createSubscriptionServiceClient,
65
+ } from "../../generated/router/dashboard/v1";
66
+
67
+ export const createApiClients = ({ fetchImpl }: { fetchImpl?: typeof fetch }) => {
68
+ const handler = async (
69
+ { path, method, body }: { path: string; method: string; body: string | null },
70
+ ) => {
71
+ return requestJson({
72
+ path,
73
+ method,
74
+ body: body ? JSON.parse(body) : undefined,
75
+ fetchImpl,
76
+ });
77
+ };
78
+
79
+ return {
80
+ consumerService: createConsumerServiceClient(handler as any),
81
+ subscriptionService: createSubscriptionServiceClient(handler as any),
82
+ };
83
+ };
84
+ ```
85
+
86
+ **Note:** replace import path with the actual generated client path when wired into CLI (see Task 2).
87
+
88
+ **Step 4: Run test to verify it passes**
89
+
90
+ Run: `npm test -- tests/core/api/client.test.ts`
91
+ Expected: PASS
92
+
93
+ **Step 5: Commit**
94
+
95
+ ```bash
96
+ git add src/core/api/client.ts tests/core/api/client.test.ts
97
+ git commit -m "feat: add api client adapter"
98
+ ```
99
+
100
+ ### Task 2: Wire generated dashboard client into repo
101
+
102
+ **Files:**
103
+ - Create: `src/generated/router/dashboard/v1/index.ts`
104
+ - Modify: `package.json` (if needed for path aliases)
105
+
106
+ **Step 1: Write the failing test**
107
+
108
+ Update `tests/core/api/client.test.ts` import path to the actual generated file (if not already) so it fails without the file.
109
+
110
+ **Step 2: Run test to verify it fails**
111
+
112
+ Run: `npm test -- tests/core/api/client.test.ts`
113
+ Expected: FAIL (missing generated module)
114
+
115
+ **Step 3: Write minimal implementation**
116
+
117
+ Copy the generated file from dashboard repo:
118
+
119
+ ```bash
120
+ cp /Users/xus/code/github/getrouter/router/frontend/dashboard/src/services/router/dashboard/v1/index.ts \
121
+ src/generated/router/dashboard/v1/index.ts
122
+ ```
123
+
124
+ **Step 4: Run test to verify it passes**
125
+
126
+ Run: `npm test -- tests/core/api/client.test.ts`
127
+ Expected: PASS
128
+
129
+ **Step 5: Commit**
130
+
131
+ ```bash
132
+ git add src/generated/router/dashboard/v1/index.ts
133
+ git commit -m "chore: import generated dashboard client"
134
+ ```
135
+
136
+ ### Task 3: Implement keys commands
137
+
138
+ **Files:**
139
+ - Modify: `src/cmd/keys.ts`
140
+ - Create: `tests/cmd/keys.test.ts`
141
+
142
+ **Step 1: Write the failing tests**
143
+
144
+ Create `tests/cmd/keys.test.ts`:
145
+
146
+ ```ts
147
+ import { describe, it, expect, vi } from "vitest";
148
+ import { createProgram } from "../../src/cli";
149
+ import * as api from "../../src/core/api/client";
150
+
151
+ vi.mock("../../src/core/api/client");
152
+
153
+ const mockConsumer = {
154
+ id: "c1",
155
+ name: "dev",
156
+ enabled: true,
157
+ apiKey: "abcd1234WXYZ",
158
+ lastAccess: "2026-01-02T00:00:00Z",
159
+ createdAt: "2026-01-01T00:00:00Z",
160
+ };
161
+
162
+ describe("keys commands", () => {
163
+ it("list outputs redacted apiKey", async () => {
164
+ vi.spyOn(api, "createApiClients").mockReturnValue({
165
+ consumerService: {
166
+ ListConsumers: vi.fn().mockResolvedValue({ consumers: [mockConsumer] }),
167
+ },
168
+ subscriptionService: {} as any,
169
+ });
170
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
171
+ const program = createProgram();
172
+ await program.parseAsync(["node", "getrouter", "keys", "list", "--json"]);
173
+ const payload = JSON.parse(log.mock.calls[0][0]);
174
+ expect(payload.consumers[0].apiKey).toBe("abcd...WXYZ");
175
+ log.mockRestore();
176
+ });
177
+
178
+ it("update requires at least one field", async () => {
179
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
180
+ const program = createProgram();
181
+ await expect(
182
+ program.parseAsync(["node", "getrouter", "keys", "update", "c1"])
183
+ ).rejects.toThrow();
184
+ log.mockRestore();
185
+ });
186
+ });
187
+ ```
188
+
189
+ **Step 2: Run test to verify it fails**
190
+
191
+ Run: `npm test -- tests/cmd/keys.test.ts`
192
+ Expected: FAIL (commands not implemented)
193
+
194
+ **Step 3: Write minimal implementation**
195
+
196
+ Update `src/cmd/keys.ts` to:
197
+ - create `createApiClients()` per command
198
+ - map commands to `consumerService` methods
199
+ - apply redaction unless `--show-secret`
200
+ - `keys update` builds `updateMask` from provided fields
201
+ - `keys create` accepts `--name` and `--enabled`
202
+
203
+ Suggested outline:
204
+
205
+ ```ts
206
+ import { Command } from "commander";
207
+ import { readConfig } from "../core/config";
208
+ import { redactSecrets } from "../core/config/redact";
209
+ import { createApiClients } from "../core/api/client";
210
+
211
+ const shouldJson = (jsonFlag?: boolean) =>
212
+ typeof jsonFlag === "boolean" ? jsonFlag : readConfig().json;
213
+
214
+ const output = (payload: unknown, json: boolean) => {
215
+ if (json) return console.log(JSON.stringify(payload, null, 2));
216
+ console.log(JSON.stringify(payload, null, 2));
217
+ };
218
+ ```
219
+
220
+ Ensure list output wraps as `{ consumers }` in JSON mode.
221
+
222
+ **Step 4: Run test to verify it passes**
223
+
224
+ Run: `npm test -- tests/cmd/keys.test.ts`
225
+ Expected: PASS
226
+
227
+ **Step 5: Commit**
228
+
229
+ ```bash
230
+ git add src/cmd/keys.ts tests/cmd/keys.test.ts
231
+ git commit -m "feat: implement keys commands"
232
+ ```
233
+
234
+ ### Task 4: Implement subscription show command
235
+
236
+ **Files:**
237
+ - Modify: `src/cmd/subscription.ts`
238
+ - Create: `tests/cmd/subscription.test.ts`
239
+
240
+ **Step 1: Write the failing tests**
241
+
242
+ Create `tests/cmd/subscription.test.ts`:
243
+
244
+ ```ts
245
+ import { describe, it, expect, vi } from "vitest";
246
+ import { createProgram } from "../../src/cli";
247
+ import * as api from "../../src/core/api/client";
248
+
249
+ vi.mock("../../src/core/api/client");
250
+
251
+ const mockSubscription = {
252
+ status: "ACTIVE",
253
+ startAt: "2026-01-01T00:00:00Z",
254
+ endAt: "2026-02-01T00:00:00Z",
255
+ plan: { name: "Pro", requestPerMinute: 20, tokenPerMinute: "150K" },
256
+ };
257
+
258
+ describe("subscription command", () => {
259
+ it("shows current subscription", async () => {
260
+ vi.spyOn(api, "createApiClients").mockReturnValue({
261
+ subscriptionService: {
262
+ CurrentSubscription: vi.fn().mockResolvedValue(mockSubscription),
263
+ },
264
+ consumerService: {} as any,
265
+ });
266
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
267
+ const program = createProgram();
268
+ await program.parseAsync(["node", "getrouter", "subscription", "show", "--json"]);
269
+ const payload = JSON.parse(log.mock.calls[0][0]);
270
+ expect(payload.status).toBe("ACTIVE");
271
+ log.mockRestore();
272
+ });
273
+ });
274
+ ```
275
+
276
+ **Step 2: Run test to verify it fails**
277
+
278
+ Run: `npm test -- tests/cmd/subscription.test.ts`
279
+ Expected: FAIL (not implemented)
280
+
281
+ **Step 3: Write minimal implementation**
282
+
283
+ Update `src/cmd/subscription.ts` to call `subscriptionService.CurrentSubscription({})` and format output. For default output, print key fields; for `--json` output print full object.
284
+
285
+ **Step 4: Run test to verify it passes**
286
+
287
+ Run: `npm test -- tests/cmd/subscription.test.ts`
288
+ Expected: PASS
289
+
290
+ **Step 5: Commit**
291
+
292
+ ```bash
293
+ git add src/cmd/subscription.ts tests/cmd/subscription.test.ts
294
+ git commit -m "feat: implement subscription show"
295
+ ```
296
+
297
+ ---
298
+
299
+ Plan complete and saved to `docs/plans/2026-01-02-getrouter-cli-keys-subscription-plan.md`.
300
+
301
+ Two execution options:
302
+
303
+ 1. Subagent-Driven (this session) — use superpowers:subagent-driven-development
304
+ 2. Parallel Session (separate) — open a new session with executing-plans in this worktree
305
+
306
+ Which approach?
@@ -0,0 +1,67 @@
1
+ # getrouter CLI setup env 设计
2
+
3
+ 日期:2026-01-02
4
+
5
+ ## 目标
6
+
7
+ 为 CLI 提供“环境自动配置”能力,生成适用于 Codex/Claude 等 vibecoding 工具的环境变量配置,并支持可选安装到 shell rc。
8
+
9
+ ## 核心流程(默认)
10
+
11
+ - 命令:`getrouter setup env`
12
+ - 默认行为:
13
+ 1. 选择一个 API key(优先 `--key <id>`,否则进入交互选择;非 TTY 且无 key 时报错)。
14
+ 2. 生成并写入 `~/.getrouter/env.sh`(或 `env.ps1`)。
15
+ 3. 打印 `source ~/.getrouter/env.sh`(或 PowerShell 使用 `$PROFILE` 的指引)。
16
+
17
+ ## 输出内容
18
+
19
+ 统一写入以下变量(同一个 key 用于 OpenAI 与 Anthropic):
20
+ - `OPENAI_BASE_URL=https://api.getrouter.dev/v1`
21
+ - `OPENAI_API_KEY=<consumer api key>`
22
+ - `ANTHROPIC_BASE_URL=https://api.getrouter.dev/v1`
23
+ - `ANTHROPIC_API_KEY=<consumer api key>`
24
+
25
+ ## 可选参数
26
+
27
+ - `--key <id>`:直接使用指定 key,跳过交互。
28
+ - `--print`:只输出 env 内容到 stdout,不落盘。
29
+ - `--install`:追加 `source ~/.getrouter/env.sh` 到 shell rc(需确认并避免重复)。
30
+ - `--shell <zsh|bash|fish|pwsh>`:指定 rc 类型(不传则按 `SHELL` 推断)。
31
+ - `--json`:输出结构化结果(路径、选中 key、是否安装)。
32
+
33
+ ## 安全与兼容性
34
+
35
+ - 写入的 env 文件权限为 0600(与 `auth.json` 一致)。
36
+ - `--install` 前需确认,防止擅自修改用户配置。
37
+ - 自动检测 rc 文件,若不存在则提示用户手动操作。
38
+ - PowerShell 使用 `$env:VAR="..."` 语法。
39
+
40
+ ## 错误处理
41
+
42
+ - 未登录或无法获取 key:提示 `getrouter auth login` 或 `getrouter keys create`。
43
+ - 非 TTY 且未提供 `--key`:报错提示缺少 key。
44
+ - 远端请求失败:直接报错并退出。
45
+
46
+ ## 交互示例
47
+
48
+ ```
49
+ $ getrouter setup env
50
+ To configure your shell, run:
51
+ source ~/.getrouter/env.sh
52
+ ```
53
+
54
+ `--print` 示例:
55
+ ```
56
+ $ getrouter setup env --print
57
+ export OPENAI_BASE_URL=...
58
+ export OPENAI_API_KEY=...
59
+ ...
60
+ ```
61
+
62
+ ## 影响范围
63
+
64
+ - 新增 `setup` 命令模块
65
+ - 新增 env 生成/写入逻辑
66
+ - 复用 keys 交互选择逻辑
67
+