@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.
- package/.github/workflows/ci.yml +19 -0
- package/AGENTS.md +78 -0
- package/README.ja.md +116 -0
- package/README.md +116 -0
- package/README.zh-cn.md +116 -0
- package/biome.json +10 -0
- package/bun.lock +397 -0
- package/dist/bin.mjs +1422 -0
- package/docs/plans/2026-01-01-getrouter-cli-config-command-plan.md +231 -0
- package/docs/plans/2026-01-01-getrouter-cli-config-core-plan.md +307 -0
- package/docs/plans/2026-01-01-getrouter-cli-design.md +106 -0
- package/docs/plans/2026-01-01-getrouter-cli-scaffold-plan.md +327 -0
- package/docs/plans/2026-01-02-getrouter-cli-auth-design.md +68 -0
- package/docs/plans/2026-01-02-getrouter-cli-auth-device-design.md +73 -0
- package/docs/plans/2026-01-02-getrouter-cli-auth-device-plan.md +411 -0
- package/docs/plans/2026-01-02-getrouter-cli-auth-plan.md +435 -0
- package/docs/plans/2026-01-02-getrouter-cli-http-client-plan.md +235 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-create-update-output-design.md +24 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-create-update-output-plan.md +141 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-delete-output-design.md +22 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-delete-output-plan.md +122 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-get-output-design.md +23 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-get-output-plan.md +141 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-interactive-design.md +28 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-interactive-plan.md +247 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-output-design.md +31 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-output-plan.md +187 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-subscription-design.md +52 -0
- package/docs/plans/2026-01-02-getrouter-cli-keys-subscription-plan.md +306 -0
- package/docs/plans/2026-01-02-getrouter-cli-setup-env-design.md +67 -0
- package/docs/plans/2026-01-02-getrouter-cli-setup-env-plan.md +441 -0
- package/docs/plans/2026-01-02-getrouter-cli-subscription-output-design.md +34 -0
- package/docs/plans/2026-01-02-getrouter-cli-subscription-output-plan.md +157 -0
- package/docs/plans/2026-01-03-bun-migration-plan.md +103 -0
- package/docs/plans/2026-01-03-cli-emoji-output.md +45 -0
- package/docs/plans/2026-01-03-cli-english-output.md +123 -0
- package/docs/plans/2026-01-03-cli-simplify-design.md +62 -0
- package/docs/plans/2026-01-03-cli-simplify-implementation.md +468 -0
- package/docs/plans/2026-01-03-readme-command-descriptions.md +116 -0
- package/docs/plans/2026-01-03-tsdown-migration-plan.md +75 -0
- package/docs/plans/2026-01-04-cli-docs-cleanup-design.md +49 -0
- package/docs/plans/2026-01-04-cli-docs-cleanup-plan.md +126 -0
- package/docs/plans/2026-01-04-codex-multistep-design.md +76 -0
- package/docs/plans/2026-01-04-codex-multistep-plan.md +240 -0
- package/docs/plans/2026-01-04-env-hook-design.md +48 -0
- package/docs/plans/2026-01-04-env-hook-plan.md +173 -0
- package/docs/plans/2026-01-04-models-keys-fuzzy-design.md +75 -0
- package/docs/plans/2026-01-04-models-keys-fuzzy-implementation.md +704 -0
- package/package.json +37 -0
- package/src/.gitkeep +0 -0
- package/src/bin.ts +4 -0
- package/src/cli.ts +12 -0
- package/src/cmd/auth.ts +44 -0
- package/src/cmd/claude.ts +10 -0
- package/src/cmd/codex.ts +119 -0
- package/src/cmd/config-helpers.ts +16 -0
- package/src/cmd/config.ts +31 -0
- package/src/cmd/env.ts +103 -0
- package/src/cmd/index.ts +20 -0
- package/src/cmd/keys.ts +207 -0
- package/src/cmd/models.ts +48 -0
- package/src/cmd/status.ts +106 -0
- package/src/cmd/usages.ts +29 -0
- package/src/core/api/client.ts +79 -0
- package/src/core/auth/device.ts +105 -0
- package/src/core/auth/index.ts +37 -0
- package/src/core/config/fs.ts +13 -0
- package/src/core/config/index.ts +37 -0
- package/src/core/config/paths.ts +5 -0
- package/src/core/config/redact.ts +18 -0
- package/src/core/config/types.ts +23 -0
- package/src/core/http/errors.ts +32 -0
- package/src/core/http/request.ts +41 -0
- package/src/core/http/url.ts +12 -0
- package/src/core/interactive/clipboard.ts +61 -0
- package/src/core/interactive/codex.ts +75 -0
- package/src/core/interactive/fuzzy.ts +64 -0
- package/src/core/interactive/keys.ts +164 -0
- package/src/core/output/table.ts +34 -0
- package/src/core/output/usages.ts +75 -0
- package/src/core/paths.ts +4 -0
- package/src/core/setup/codex.ts +129 -0
- package/src/core/setup/env.ts +220 -0
- package/src/core/usages/aggregate.ts +69 -0
- package/src/generated/router/dashboard/v1/index.ts +1104 -0
- package/src/index.ts +1 -0
- package/tests/.gitkeep +0 -0
- package/tests/auth/device.test.ts +75 -0
- package/tests/auth/status.test.ts +64 -0
- package/tests/cli.test.ts +31 -0
- package/tests/cmd/auth.test.ts +90 -0
- package/tests/cmd/claude.test.ts +132 -0
- package/tests/cmd/codex.test.ts +147 -0
- package/tests/cmd/config-helpers.test.ts +18 -0
- package/tests/cmd/config.test.ts +56 -0
- package/tests/cmd/keys.test.ts +163 -0
- package/tests/cmd/models.test.ts +63 -0
- package/tests/cmd/status.test.ts +82 -0
- package/tests/cmd/usages.test.ts +42 -0
- package/tests/config/fs.test.ts +14 -0
- package/tests/config/index.test.ts +63 -0
- package/tests/config/paths.test.ts +10 -0
- package/tests/config/redact.test.ts +17 -0
- package/tests/config/types.test.ts +10 -0
- package/tests/core/api/client.test.ts +92 -0
- package/tests/core/interactive/clipboard.test.ts +44 -0
- package/tests/core/interactive/codex.test.ts +17 -0
- package/tests/core/interactive/fuzzy.test.ts +30 -0
- package/tests/core/setup/codex.test.ts +38 -0
- package/tests/core/setup/env.test.ts +84 -0
- package/tests/core/usages/aggregate.test.ts +55 -0
- package/tests/http/errors.test.ts +15 -0
- package/tests/http/request.test.ts +82 -0
- package/tests/http/url.test.ts +17 -0
- package/tests/output/table.test.ts +29 -0
- package/tests/output/usages.test.ts +71 -0
- package/tests/paths.test.ts +9 -0
- package/tsconfig.json +13 -0
- package/tsdown.config.ts +5 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Keys Create/Update Table Output Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Change `keys create` and `keys update` default output to a single-row table matching `keys list/get`, while keeping JSON output unchanged and preserving the create reminder line.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Reuse the existing consumer table helper in `cmd/keys` and route non-JSON create/update output through that table renderer. Keep `--json` behavior intact and preserve the post-create reminder line.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Commander, Vitest.
|
|
10
|
+
|
|
11
|
+
**Skills:** @superpowers:test-driven-development, @superpowers:systematic-debugging (if failures)
|
|
12
|
+
|
|
13
|
+
### Task 1: Add failing tests for create/update table output
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `tests/cmd/keys.test.ts`
|
|
17
|
+
|
|
18
|
+
**Step 1: Write the failing tests**
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
it("create prints table header and reminder in default mode", async () => {
|
|
22
|
+
(createApiClients as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
23
|
+
consumerService: {
|
|
24
|
+
CreateConsumer: vi.fn().mockResolvedValue(mockConsumer),
|
|
25
|
+
UpdateConsumer: vi.fn().mockResolvedValue(mockConsumer),
|
|
26
|
+
},
|
|
27
|
+
subscriptionService: {} as any,
|
|
28
|
+
});
|
|
29
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
30
|
+
const program = createProgram();
|
|
31
|
+
await program.parseAsync([
|
|
32
|
+
"node",
|
|
33
|
+
"getrouter",
|
|
34
|
+
"keys",
|
|
35
|
+
"create",
|
|
36
|
+
"--name",
|
|
37
|
+
"dev",
|
|
38
|
+
]);
|
|
39
|
+
const output = log.mock.calls.map((c) => c[0]).join("\n");
|
|
40
|
+
expect(output).toContain("ID");
|
|
41
|
+
expect(output).toContain("NAME");
|
|
42
|
+
expect(output).toContain("API_KEY");
|
|
43
|
+
expect(output).toContain("请妥善保存 API Key。");
|
|
44
|
+
log.mockRestore();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("update prints table header in default mode", async () => {
|
|
48
|
+
(createApiClients as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
49
|
+
consumerService: {
|
|
50
|
+
UpdateConsumer: vi.fn().mockResolvedValue(mockConsumer),
|
|
51
|
+
},
|
|
52
|
+
subscriptionService: {} as any,
|
|
53
|
+
});
|
|
54
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
55
|
+
const program = createProgram();
|
|
56
|
+
await program.parseAsync([
|
|
57
|
+
"node",
|
|
58
|
+
"getrouter",
|
|
59
|
+
"keys",
|
|
60
|
+
"update",
|
|
61
|
+
"c1",
|
|
62
|
+
"--name",
|
|
63
|
+
"dev",
|
|
64
|
+
]);
|
|
65
|
+
const output = log.mock.calls.map((c) => c[0]).join("\n");
|
|
66
|
+
expect(output).toContain("ID");
|
|
67
|
+
expect(output).toContain("NAME");
|
|
68
|
+
expect(output).toContain("API_KEY");
|
|
69
|
+
log.mockRestore();
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Step 2: Run test to verify it fails**
|
|
74
|
+
|
|
75
|
+
Run: `npm test -- tests/cmd/keys.test.ts`
|
|
76
|
+
|
|
77
|
+
Expected: FAIL because create/update currently output key=value lines.
|
|
78
|
+
|
|
79
|
+
**Step 3: Commit the failing tests**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git add tests/cmd/keys.test.ts
|
|
83
|
+
git commit -m "test: cover keys create/update table output"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Task 2: Render create/update output using table helper
|
|
87
|
+
|
|
88
|
+
**Files:**
|
|
89
|
+
- Modify: `src/cmd/keys.ts`
|
|
90
|
+
- Test: `tests/cmd/keys.test.ts`
|
|
91
|
+
|
|
92
|
+
**Step 1: Route non-JSON create/update to table output**
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// In create
|
|
96
|
+
if (shouldJson(options)) {
|
|
97
|
+
outputConsumer(output, true);
|
|
98
|
+
} else {
|
|
99
|
+
outputConsumerTable(output);
|
|
100
|
+
console.log("请妥善保存 API Key。");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// In update
|
|
104
|
+
if (shouldJson(options)) {
|
|
105
|
+
outputConsumer(output, true);
|
|
106
|
+
} else {
|
|
107
|
+
outputConsumerTable(output);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Step 2: Run tests to verify pass**
|
|
112
|
+
|
|
113
|
+
Run: `npm test -- tests/cmd/keys.test.ts`
|
|
114
|
+
|
|
115
|
+
Expected: PASS
|
|
116
|
+
|
|
117
|
+
**Step 3: Commit**
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git add src/cmd/keys.ts tests/cmd/keys.test.ts
|
|
121
|
+
git commit -m "feat: render keys create/update output as table"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Task 3: Full test run
|
|
125
|
+
|
|
126
|
+
**Files:**
|
|
127
|
+
- None
|
|
128
|
+
|
|
129
|
+
**Step 1: Run full test suite**
|
|
130
|
+
|
|
131
|
+
Run: `npm test`
|
|
132
|
+
|
|
133
|
+
Expected: PASS
|
|
134
|
+
|
|
135
|
+
**Step 2: Commit (if needed)**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git status -sb
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If clean, no commit needed.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# getrouter CLI Keys delete 表格输出设计
|
|
2
|
+
|
|
3
|
+
日期:2026-01-02
|
|
4
|
+
状态:已确认
|
|
5
|
+
|
|
6
|
+
## 目标
|
|
7
|
+
- `keys delete` 默认输出改为表格,与其它 `keys` 命令风格一致。
|
|
8
|
+
- `--json` 行为不变。
|
|
9
|
+
|
|
10
|
+
## 输出格式
|
|
11
|
+
- 列顺序:`ID | NAME | ENABLED | LAST_ACCESS | CREATED_AT | API_KEY`
|
|
12
|
+
- 单行表格:表头 + 1 行数据。
|
|
13
|
+
- 仅填充 `ID`,其余字段为空(表格显示为 `-`)。
|
|
14
|
+
|
|
15
|
+
## 行为细节
|
|
16
|
+
- 默认人类可读:表格输出。
|
|
17
|
+
- `--json`:输出 `{ deleted: true, id }`。
|
|
18
|
+
- 不再输出 `deleted=true` / `id=...` 的 key=value 形式。
|
|
19
|
+
|
|
20
|
+
## 测试策略
|
|
21
|
+
- `keys delete` 默认输出包含表头并包含 `ID` 值。
|
|
22
|
+
- `--json` 输出结构不变。
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Keys Delete Table Output Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Change `keys delete` default output to the standard keys table while keeping JSON output unchanged.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Reuse the existing keys table helpers in `cmd/keys` and render a single-row table with only `id` populated. Preserve `{ deleted: true, id }` for `--json` output.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Commander, Vitest.
|
|
10
|
+
|
|
11
|
+
**Skills:** @superpowers:test-driven-development, @superpowers:systematic-debugging (if failures)
|
|
12
|
+
|
|
13
|
+
### Task 1: Add failing tests for delete table output
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `tests/cmd/keys.test.ts`
|
|
17
|
+
|
|
18
|
+
**Step 1: Write the failing tests**
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
it("delete prints table header and id in default mode", async () => {
|
|
22
|
+
(createApiClients as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
23
|
+
consumerService: {
|
|
24
|
+
DeleteConsumer: vi.fn().mockResolvedValue({}),
|
|
25
|
+
},
|
|
26
|
+
subscriptionService: {} as any,
|
|
27
|
+
});
|
|
28
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
29
|
+
const program = createProgram();
|
|
30
|
+
await program.parseAsync(["node", "getrouter", "keys", "delete", "c1"]);
|
|
31
|
+
const output = log.mock.calls.map((c) => c[0]).join("\n");
|
|
32
|
+
expect(output).toContain("ID");
|
|
33
|
+
expect(output).toContain("API_KEY");
|
|
34
|
+
expect(output).toContain("c1");
|
|
35
|
+
log.mockRestore();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("delete outputs json when --json is set", async () => {
|
|
39
|
+
(createApiClients as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
40
|
+
consumerService: {
|
|
41
|
+
DeleteConsumer: vi.fn().mockResolvedValue({}),
|
|
42
|
+
},
|
|
43
|
+
subscriptionService: {} as any,
|
|
44
|
+
});
|
|
45
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
46
|
+
const program = createProgram();
|
|
47
|
+
await program.parseAsync([
|
|
48
|
+
"node",
|
|
49
|
+
"getrouter",
|
|
50
|
+
"keys",
|
|
51
|
+
"delete",
|
|
52
|
+
"c1",
|
|
53
|
+
"--json",
|
|
54
|
+
]);
|
|
55
|
+
const payload = JSON.parse(log.mock.calls[0][0]);
|
|
56
|
+
expect(payload.deleted).toBe(true);
|
|
57
|
+
expect(payload.id).toBe("c1");
|
|
58
|
+
log.mockRestore();
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Step 2: Run test to verify it fails**
|
|
63
|
+
|
|
64
|
+
Run: `npm test -- tests/cmd/keys.test.ts`
|
|
65
|
+
|
|
66
|
+
Expected: FAIL because delete currently prints key=value lines.
|
|
67
|
+
|
|
68
|
+
**Step 3: Commit the failing tests**
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git add tests/cmd/keys.test.ts
|
|
72
|
+
git commit -m "test: cover keys delete table output"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Task 2: Render delete output using table helper
|
|
76
|
+
|
|
77
|
+
**Files:**
|
|
78
|
+
- Modify: `src/cmd/keys.ts`
|
|
79
|
+
- Test: `tests/cmd/keys.test.ts`
|
|
80
|
+
|
|
81
|
+
**Step 1: Route non-JSON delete output to table renderer**
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// In delete
|
|
85
|
+
if (shouldJson(options)) {
|
|
86
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
87
|
+
} else {
|
|
88
|
+
outputConsumerTable({ id });
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Step 2: Run tests to verify pass**
|
|
93
|
+
|
|
94
|
+
Run: `npm test -- tests/cmd/keys.test.ts`
|
|
95
|
+
|
|
96
|
+
Expected: PASS
|
|
97
|
+
|
|
98
|
+
**Step 3: Commit**
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
git add src/cmd/keys.ts tests/cmd/keys.test.ts
|
|
102
|
+
git commit -m "feat: render keys delete output as table"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Task 3: Full test run
|
|
106
|
+
|
|
107
|
+
**Files:**
|
|
108
|
+
- None
|
|
109
|
+
|
|
110
|
+
**Step 1: Run full test suite**
|
|
111
|
+
|
|
112
|
+
Run: `npm test`
|
|
113
|
+
|
|
114
|
+
Expected: PASS
|
|
115
|
+
|
|
116
|
+
**Step 2: Commit (if needed)**
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
git status -sb
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If clean, no commit needed.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# getrouter CLI Keys get 表格输出设计
|
|
2
|
+
|
|
3
|
+
日期:2026-01-02
|
|
4
|
+
状态:已确认
|
|
5
|
+
|
|
6
|
+
## 目标
|
|
7
|
+
- `keys get <id>` 默认输出改为单行表格,与 `keys list` 同列顺序。
|
|
8
|
+
- `--json` 行为不变;`apiKey` 默认脱敏。
|
|
9
|
+
|
|
10
|
+
## 输出格式
|
|
11
|
+
- 列顺序:`ID | NAME | ENABLED | LAST_ACCESS | CREATED_AT | API_KEY`
|
|
12
|
+
- 单行表格:表头 + 1 行数据。
|
|
13
|
+
- 空值显示 `-`;超长字段按 `renderTable` 规则截断。
|
|
14
|
+
|
|
15
|
+
## 行为细节
|
|
16
|
+
- 默认人类可读:表格输出。
|
|
17
|
+
- `--json`:输出完整对象(脱敏或明文由 `--show-secret` 决定)。
|
|
18
|
+
- 其它命令输出暂不改变(create/update 保持 key=value)。
|
|
19
|
+
|
|
20
|
+
## 测试策略
|
|
21
|
+
- `keys get` 默认输出包含表头与列顺序。
|
|
22
|
+
- `apiKey` 仍为脱敏值(前 4 后 4)。
|
|
23
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# getrouter CLI Keys get Table Output Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Change `keys get <id>` default output to a single-row table matching `keys list` while keeping JSON output unchanged.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Reuse the existing table renderer by adding a shared row/headers helper for consumers, then route `keys get` non-JSON output through the table path without altering create/update behavior.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Commander, Vitest.
|
|
10
|
+
|
|
11
|
+
**Skills:** @superpowers:test-driven-development, @superpowers:systematic-debugging (if failures)
|
|
12
|
+
|
|
13
|
+
### Task 1: Add failing test for `keys get` default table output
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `tests/cmd/keys.test.ts`
|
|
17
|
+
|
|
18
|
+
**Step 1: Write the failing test**
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
it("get prints table header and redacts apiKey in default mode", async () => {
|
|
22
|
+
(createApiClients as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
|
|
23
|
+
consumerService: {
|
|
24
|
+
GetConsumer: vi.fn().mockResolvedValue(mockConsumer),
|
|
25
|
+
},
|
|
26
|
+
subscriptionService: {} as any,
|
|
27
|
+
});
|
|
28
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
29
|
+
const program = createProgram();
|
|
30
|
+
await program.parseAsync(["node", "getrouter", "keys", "get", "c1"]);
|
|
31
|
+
const output = log.mock.calls.map((c) => c[0]).join("\n");
|
|
32
|
+
expect(output).toContain("ID");
|
|
33
|
+
expect(output).toContain("NAME");
|
|
34
|
+
expect(output).toContain("ENABLED");
|
|
35
|
+
expect(output).toContain("LAST_ACCESS");
|
|
36
|
+
expect(output).toContain("CREATED_AT");
|
|
37
|
+
expect(output).toContain("API_KEY");
|
|
38
|
+
expect(output).toContain("abcd...WXYZ");
|
|
39
|
+
log.mockRestore();
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Step 2: Run test to verify it fails**
|
|
44
|
+
|
|
45
|
+
Run: `npm test -- tests/cmd/keys.test.ts`
|
|
46
|
+
|
|
47
|
+
Expected: FAIL because `keys get` currently prints key=value lines instead of a table.
|
|
48
|
+
|
|
49
|
+
**Step 3: Commit the failing test**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git add tests/cmd/keys.test.ts
|
|
53
|
+
git commit -m "test: cover keys get table output"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Task 2: Render `keys get` with table output
|
|
57
|
+
|
|
58
|
+
**Files:**
|
|
59
|
+
- Modify: `src/cmd/keys.ts`
|
|
60
|
+
- Test: `tests/cmd/keys.test.ts`
|
|
61
|
+
|
|
62
|
+
**Step 1: Implement shared headers/row helper**
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const consumerHeaders = [
|
|
66
|
+
"ID",
|
|
67
|
+
"NAME",
|
|
68
|
+
"ENABLED",
|
|
69
|
+
"LAST_ACCESS",
|
|
70
|
+
"CREATED_AT",
|
|
71
|
+
"API_KEY",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const consumerRow = (consumer: any) => [
|
|
75
|
+
String(consumer.id ?? ""),
|
|
76
|
+
String(consumer.name ?? ""),
|
|
77
|
+
String(consumer.enabled ?? ""),
|
|
78
|
+
String(consumer.lastAccess ?? ""),
|
|
79
|
+
String(consumer.createdAt ?? ""),
|
|
80
|
+
String(consumer.apiKey ?? ""),
|
|
81
|
+
];
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Step 2: Use table output for `keys get` only**
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const outputConsumerTable = (consumer: any) =>
|
|
88
|
+
console.log(renderTable(consumerHeaders, [consumerRow(consumer)]));
|
|
89
|
+
|
|
90
|
+
// In keys get
|
|
91
|
+
if (shouldJson(options)) {
|
|
92
|
+
outputConsumer(output, true);
|
|
93
|
+
} else {
|
|
94
|
+
outputConsumerTable(output);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Step 3: Keep list output using the same helpers**
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const outputConsumers = (consumers: any[], json: boolean) => {
|
|
102
|
+
if (json) {
|
|
103
|
+
console.log(JSON.stringify({ consumers }, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const rows = consumers.map(consumerRow);
|
|
107
|
+
console.log(renderTable(consumerHeaders, rows));
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Step 4: Run tests to verify pass**
|
|
112
|
+
|
|
113
|
+
Run: `npm test -- tests/cmd/keys.test.ts`
|
|
114
|
+
|
|
115
|
+
Expected: PASS
|
|
116
|
+
|
|
117
|
+
**Step 5: Commit**
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git add src/cmd/keys.ts tests/cmd/keys.test.ts
|
|
121
|
+
git commit -m "feat: render keys get output as table"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Task 3: Full test run
|
|
125
|
+
|
|
126
|
+
**Files:**
|
|
127
|
+
- None
|
|
128
|
+
|
|
129
|
+
**Step 1: Run full test suite**
|
|
130
|
+
|
|
131
|
+
Run: `npm test`
|
|
132
|
+
|
|
133
|
+
Expected: PASS
|
|
134
|
+
|
|
135
|
+
**Step 2: Commit (if needed)**
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git status -sb
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If clean, no commit needed.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# getrouter CLI Keys 交互式选择设计
|
|
2
|
+
|
|
3
|
+
日期:2026-01-02
|
|
4
|
+
状态:已确认
|
|
5
|
+
|
|
6
|
+
## 目标
|
|
7
|
+
- `keys get/update/delete` 在未传 `id` 时进入交互式选择(上下键选择)。
|
|
8
|
+
- 非交互环境未传 `id` 时报错提示缺少 `id`。
|
|
9
|
+
|
|
10
|
+
## 交互行为
|
|
11
|
+
- 触发条件:`keys get/update/delete` 未传 `id`。
|
|
12
|
+
- 非交互(stdin 非 TTY):直接报错“缺少 id”(提示可传 `id`)。
|
|
13
|
+
- `keys delete`:选择后二次确认(Y/N)再执行删除。
|
|
14
|
+
- `keys get/update`:选择后直接执行。
|
|
15
|
+
- `--json` 输出保持不变。
|
|
16
|
+
|
|
17
|
+
## 列表显示与排序
|
|
18
|
+
- 显示字段:`NAME (ID)` + `ENABLED` + `CREATED_AT`。
|
|
19
|
+
- 排序:按 `CREATED_AT` 倒序(最新在前)。
|
|
20
|
+
|
|
21
|
+
## 错误处理
|
|
22
|
+
- 无可选 key:提示“没有可用的 API key”。
|
|
23
|
+
- 401/403:沿用现有鉴权提示。
|
|
24
|
+
|
|
25
|
+
## 测试策略
|
|
26
|
+
- 未传 `id` 时进入选择流程(mock 选择返回)。
|
|
27
|
+
- 非交互且未传 `id` 时抛错。
|
|
28
|
+
- `keys delete` 需确认后才调用删除。
|