@getrouter/getrouter-cli 0.1.0 → 0.1.2

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/dist/bin.mjs CHANGED
@@ -7,6 +7,10 @@ import { execSync, spawn } from "node:child_process";
7
7
  import { randomInt } from "node:crypto";
8
8
  import prompts from "prompts";
9
9
 
10
+ //#region package.json
11
+ var version = "0.1.2";
12
+
13
+ //#endregion
10
14
  //#region src/generated/router/dashboard/v1/index.ts
11
15
  function createSubscriptionServiceClient(handler) {
12
16
  return { CurrentSubscription(request) {
@@ -232,7 +236,6 @@ const readConfig = () => ({
232
236
  ...defaultConfig(),
233
237
  ...readJsonFile(getConfigPath()) ?? {}
234
238
  });
235
- const writeConfig = (cfg) => writeJsonFile(getConfigPath(), cfg);
236
239
  const readAuth = () => ({
237
240
  ...defaultAuthState(),
238
241
  ...readJsonFile(getAuthPath()) ?? {}
@@ -243,6 +246,39 @@ const writeAuth = (auth) => {
243
246
  if (process.platform !== "win32") fs.chmodSync(authPath, 384);
244
247
  };
245
248
 
249
+ //#endregion
250
+ //#region src/core/http/url.ts
251
+ const getApiBase = () => {
252
+ return (readConfig().apiBase || "").replace(/\/+$/, "");
253
+ };
254
+ const buildApiUrl = (path$1) => {
255
+ const base = getApiBase();
256
+ const normalized = path$1.replace(/^\/+/, "");
257
+ return base ? `${base}/${normalized}` : `/${normalized}`;
258
+ };
259
+
260
+ //#endregion
261
+ //#region src/core/auth/refresh.ts
262
+ const EXPIRY_BUFFER_MS = 60 * 1e3;
263
+ const refreshAccessToken = async ({ fetchImpl }) => {
264
+ const auth = readAuth();
265
+ if (!auth.refreshToken) return null;
266
+ const res = await (fetchImpl ?? fetch)(buildApiUrl("v1/dashboard/auth/token"), {
267
+ method: "POST",
268
+ headers: { "Content-Type": "application/json" },
269
+ body: JSON.stringify({ refreshToken: auth.refreshToken })
270
+ });
271
+ if (!res.ok) return null;
272
+ const token = await res.json();
273
+ if (token.accessToken && token.refreshToken) writeAuth({
274
+ accessToken: token.accessToken,
275
+ refreshToken: token.refreshToken,
276
+ expiresAt: token.expiresAt ?? "",
277
+ tokenType: "Bearer"
278
+ });
279
+ return token;
280
+ };
281
+
246
282
  //#endregion
247
283
  //#region src/core/http/errors.ts
248
284
  const createApiError = (payload, fallbackMessage, status) => {
@@ -256,33 +292,71 @@ const createApiError = (payload, fallbackMessage, status) => {
256
292
  };
257
293
 
258
294
  //#endregion
259
- //#region src/core/http/url.ts
260
- const getApiBase = () => {
261
- return (readConfig().apiBase || "").replace(/\/+$/, "");
295
+ //#region src/core/http/retry.ts
296
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
297
+ const isRetryableError = (error) => {
298
+ if (error instanceof TypeError) return true;
299
+ if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") {
300
+ const status = error.status;
301
+ return status >= 500 || status === 408 || status === 429;
302
+ }
303
+ return false;
262
304
  };
263
- const buildApiUrl = (path$1) => {
264
- const base = getApiBase();
265
- const normalized = path$1.replace(/^\/+/, "");
266
- return base ? `${base}/${normalized}` : `/${normalized}`;
305
+ const withRetry = async (fn, options = {}) => {
306
+ const { maxRetries = 3, initialDelayMs = 1e3, maxDelayMs = 1e4, shouldRetry = isRetryableError, onRetry, sleep = defaultSleep } = options;
307
+ let lastError;
308
+ let delay = initialDelayMs;
309
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
310
+ return await fn();
311
+ } catch (error) {
312
+ lastError = error;
313
+ if (attempt >= maxRetries || !shouldRetry(error, attempt)) throw error;
314
+ onRetry?.(error, attempt + 1, delay);
315
+ await sleep(delay);
316
+ delay = Math.min(delay * 2, maxDelayMs);
317
+ }
318
+ throw lastError;
267
319
  };
320
+ const isServerError = (status) => status >= 500 || status === 408 || status === 429;
268
321
 
269
322
  //#endregion
270
323
  //#region src/core/http/request.ts
271
324
  const getAuthCookieName = () => process.env.GETROUTER_AUTH_COOKIE || process.env.KRATOS_AUTH_COOKIE || "access_token";
272
- const requestJson = async ({ path: path$1, method, body, fetchImpl }) => {
325
+ const buildHeaders = (accessToken) => {
273
326
  const headers = { "Content-Type": "application/json" };
274
- const auth = readAuth();
275
- if (auth.accessToken) {
276
- headers.Authorization = `Bearer ${auth.accessToken}`;
277
- headers.Cookie = `${getAuthCookieName()}=${auth.accessToken}`;
327
+ if (accessToken) {
328
+ headers.Authorization = `Bearer ${accessToken}`;
329
+ headers.Cookie = `${getAuthCookieName()}=${accessToken}`;
278
330
  }
279
- const res = await (fetchImpl ?? fetch)(buildApiUrl(path$1), {
331
+ return headers;
332
+ };
333
+ const doFetch = async (url, method, headers, body, fetchImpl) => {
334
+ return (fetchImpl ?? fetch)(url, {
280
335
  method,
281
336
  headers,
282
337
  body: body == null ? void 0 : JSON.stringify(body)
283
338
  });
284
- if (!res.ok) throw createApiError(await res.json().catch(() => null), res.statusText, res.status);
285
- return await res.json();
339
+ };
340
+ const shouldRetryResponse = (error) => {
341
+ if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") return isServerError(error.status);
342
+ return error instanceof TypeError;
343
+ };
344
+ const requestJson = async ({ path: path$1, method, body, fetchImpl, maxRetries = 3, _retrySleep }) => {
345
+ return withRetry(async () => {
346
+ const auth = readAuth();
347
+ const url = buildApiUrl(path$1);
348
+ let res = await doFetch(url, method, buildHeaders(auth.accessToken), body, fetchImpl);
349
+ if (res.status === 401 && auth.refreshToken) {
350
+ const refreshed = await refreshAccessToken({ fetchImpl });
351
+ if (refreshed?.accessToken) res = await doFetch(url, method, buildHeaders(refreshed.accessToken), body, fetchImpl);
352
+ }
353
+ if (!res.ok) throw createApiError(await res.json().catch(() => null), res.statusText, res.status);
354
+ return await res.json();
355
+ }, {
356
+ maxRetries,
357
+ shouldRetry: shouldRetryResponse,
358
+ sleep: _retrySleep
359
+ });
286
360
  };
287
361
 
288
362
  //#endregion
@@ -421,6 +495,28 @@ const registerAuthCommands = (program) => {
421
495
  });
422
496
  };
423
497
 
498
+ //#endregion
499
+ //#region src/core/api/pagination.ts
500
+ /**
501
+ * Fetches all pages from a paginated API endpoint.
502
+ *
503
+ * @param fetchPage - Function that fetches a single page given a pageToken
504
+ * @param getItems - Function that extracts items from the response
505
+ * @param getNextToken - Function that extracts the next page token from the response
506
+ * @returns Array of all items across all pages
507
+ */
508
+ const fetchAllPages = async (fetchPage, getItems, getNextToken) => {
509
+ const allItems = [];
510
+ let pageToken;
511
+ do {
512
+ const response = await fetchPage(pageToken);
513
+ const items = getItems(response);
514
+ allItems.push(...items);
515
+ pageToken = getNextToken(response);
516
+ } while (pageToken);
517
+ return allItems;
518
+ };
519
+
424
520
  //#endregion
425
521
  //#region src/core/interactive/fuzzy.ts
426
522
  const normalize = (value) => value.toLowerCase();
@@ -505,10 +601,10 @@ const promptKeyEnabled = async (initial) => {
505
601
  return typeof response.enabled === "boolean" ? response.enabled : initial;
506
602
  };
507
603
  const selectConsumer = async (consumerService) => {
508
- const consumers = (await consumerService.ListConsumers({
604
+ const consumers = await fetchAllPages((pageToken) => consumerService.ListConsumers({
509
605
  pageSize: void 0,
510
- pageToken: void 0
511
- }))?.consumers ?? [];
606
+ pageToken
607
+ }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0);
512
608
  if (consumers.length === 0) throw new Error("No available API keys");
513
609
  const sorted = sortByCreatedAtDesc(consumers);
514
610
  const nameCounts = buildNameCounts(sorted);
@@ -522,10 +618,10 @@ const selectConsumer = async (consumerService) => {
522
618
  }) ?? null;
523
619
  };
524
620
  const selectConsumerList = async (consumerService, message) => {
525
- const consumers = (await consumerService.ListConsumers({
621
+ const consumers = await fetchAllPages((pageToken) => consumerService.ListConsumers({
526
622
  pageSize: void 0,
527
- pageToken: void 0
528
- }))?.consumers ?? [];
623
+ pageToken
624
+ }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0);
529
625
  if (consumers.length === 0) throw new Error("No available API keys");
530
626
  const sorted = sortByCreatedAtDesc(consumers);
531
627
  const nameCounts = buildNameCounts(sorted);
@@ -945,8 +1041,7 @@ const registerCodexCommand = (program) => {
945
1041
  const apiKey = (await consumerService.GetConsumer({ id: selected.id }))?.apiKey ?? "";
946
1042
  if (!apiKey) throw new Error("API key not found. Please create one or choose another.");
947
1043
  const reasoningValue = mapReasoningValue(reasoningId);
948
- const keyName = selected.name ?? "-";
949
- const keyId = selected.id ?? "-";
1044
+ const keyName = selected.name?.trim() || "(unnamed)";
950
1045
  if (!(await prompts({
951
1046
  type: "confirm",
952
1047
  name: "confirm",
@@ -955,7 +1050,7 @@ const registerCodexCommand = (program) => {
955
1050
  `Model: ${model}`,
956
1051
  `Reasoning: ${formatReasoningLabel(reasoningId)} (${reasoningValue})`,
957
1052
  "Provider: getrouter",
958
- `Key: ${keyName} (${keyId})`
1053
+ `Key: ${keyName}`
959
1054
  ].join("\n"),
960
1055
  initial: true
961
1056
  })).confirm) return;
@@ -975,65 +1070,6 @@ const registerCodexCommand = (program) => {
975
1070
  });
976
1071
  };
977
1072
 
978
- //#endregion
979
- //#region src/cmd/config-helpers.ts
980
- const normalizeApiBase = (value) => value.trim().replace(/\/+$/, "");
981
- const parseConfigValue = (key, raw) => {
982
- if (key === "apiBase") {
983
- const normalized = normalizeApiBase(raw);
984
- if (!/^https?:\/\//.test(normalized)) throw new Error("apiBase must start with http:// or https://");
985
- return normalized;
986
- }
987
- const lowered = raw.toLowerCase();
988
- if (["true", "1"].includes(lowered)) return true;
989
- if (["false", "0"].includes(lowered)) return false;
990
- throw new Error("json must be true/false or 1/0");
991
- };
992
-
993
- //#endregion
994
- //#region src/cmd/config.ts
995
- const VALID_KEYS = new Set(["apiBase", "json"]);
996
- const registerConfigCommands = (program) => {
997
- program.command("config").description("Manage CLI config").argument("[key]").argument("[value]").action((key, value) => {
998
- const cfg = readConfig();
999
- if (!key) {
1000
- console.log(`apiBase=${cfg.apiBase}`);
1001
- console.log(`json=${cfg.json}`);
1002
- return;
1003
- }
1004
- if (!value) throw new Error("Missing config value");
1005
- if (!VALID_KEYS.has(key)) throw new Error("Unknown config key");
1006
- const parsed = parseConfigValue(key, value);
1007
- const next = {
1008
- ...cfg,
1009
- [key]: parsed
1010
- };
1011
- writeConfig(next);
1012
- console.log(`${key}=${next[key]}`);
1013
- });
1014
- };
1015
-
1016
- //#endregion
1017
- //#region src/core/config/redact.ts
1018
- const SECRET_KEYS = new Set([
1019
- "accessToken",
1020
- "refreshToken",
1021
- "apiKey"
1022
- ]);
1023
- const mask = (value) => {
1024
- if (!value) return "";
1025
- if (value.length <= 8) return "****";
1026
- return `${value.slice(0, 4)}...${value.slice(-4)}`;
1027
- };
1028
- const redactSecrets = (obj) => {
1029
- const out = { ...obj };
1030
- for (const key of Object.keys(out)) {
1031
- const value = out[key];
1032
- if (SECRET_KEYS.has(key) && typeof value === "string") out[key] = mask(value);
1033
- }
1034
- return out;
1035
- };
1036
-
1037
1073
  //#endregion
1038
1074
  //#region src/core/output/table.ts
1039
1075
  const truncate = (value, max) => {
@@ -1072,13 +1108,12 @@ const consumerRow = (consumer) => [
1072
1108
  String(consumer.apiKey ?? "")
1073
1109
  ];
1074
1110
  const outputConsumerTable = (consumer) => {
1075
- console.log(renderTable(consumerHeaders, [consumerRow(consumer)]));
1111
+ console.log(renderTable(consumerHeaders, [consumerRow(consumer)], { maxColWidth: 64 }));
1076
1112
  };
1077
1113
  const outputConsumers = (consumers) => {
1078
1114
  const rows = consumers.map(consumerRow);
1079
- console.log(renderTable(consumerHeaders, rows));
1115
+ console.log(renderTable(consumerHeaders, rows, { maxColWidth: 64 }));
1080
1116
  };
1081
- const redactConsumer = (consumer) => redactSecrets(consumer);
1082
1117
  const requireInteractive = (message) => {
1083
1118
  if (!process.stdin.isTTY) throw new Error(message);
1084
1119
  };
@@ -1097,10 +1132,10 @@ const updateConsumer = async (consumerService, consumer, name, enabled) => {
1097
1132
  });
1098
1133
  };
1099
1134
  const listConsumers = async (consumerService) => {
1100
- outputConsumers(((await consumerService.ListConsumers({
1135
+ outputConsumers(await fetchAllPages((pageToken) => consumerService.ListConsumers({
1101
1136
  pageSize: void 0,
1102
- pageToken: void 0
1103
- }))?.consumers ?? []).map(redactConsumer));
1137
+ pageToken
1138
+ }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0));
1104
1139
  };
1105
1140
  const resolveConsumerForUpdate = async (consumerService, id) => {
1106
1141
  if (id) return consumerService.GetConsumer({ id });
@@ -1125,7 +1160,7 @@ const updateConsumerById = async (consumerService, id) => {
1125
1160
  requireInteractiveForAction("update");
1126
1161
  const selected = await resolveConsumerForUpdate(consumerService, id);
1127
1162
  if (!selected?.id) return;
1128
- outputConsumerTable(redactConsumer(await updateConsumer(consumerService, selected, await promptKeyName(selected.name), await promptKeyEnabled(selected.enabled ?? true))));
1163
+ outputConsumerTable(await updateConsumer(consumerService, selected, await promptKeyName(selected.name), await promptKeyEnabled(selected.enabled ?? true)));
1129
1164
  };
1130
1165
  const deleteConsumerById = async (consumerService, id) => {
1131
1166
  requireInteractiveForAction("delete");
@@ -1133,7 +1168,7 @@ const deleteConsumerById = async (consumerService, id) => {
1133
1168
  if (!selected?.id) return;
1134
1169
  if (!await confirmDelete(selected)) return;
1135
1170
  await consumerService.DeleteConsumer({ id: selected.id });
1136
- outputConsumerTable(redactConsumer(selected));
1171
+ outputConsumerTable(selected);
1137
1172
  };
1138
1173
  const registerKeysCommands = (program) => {
1139
1174
  const keys = program.command("keys").description("Manage API keys");
@@ -1163,12 +1198,14 @@ const registerKeysCommands = (program) => {
1163
1198
  //#endregion
1164
1199
  //#region src/cmd/models.ts
1165
1200
  const modelHeaders = [
1201
+ "ID",
1166
1202
  "NAME",
1167
1203
  "AUTHOR",
1168
1204
  "ENABLED",
1169
1205
  "UPDATED_AT"
1170
1206
  ];
1171
1207
  const modelRow = (model) => [
1208
+ String(model.id ?? ""),
1172
1209
  String(model.name ?? ""),
1173
1210
  String(model.author ?? ""),
1174
1211
  String(model.enabled ?? ""),
@@ -1261,8 +1298,7 @@ const registerStatusCommand = (program) => {
1261
1298
 
1262
1299
  //#endregion
1263
1300
  //#region src/core/output/usages.ts
1264
- const INPUT_BLOCK = "█";
1265
- const OUTPUT_BLOCK = "▒";
1301
+ const TOTAL_BLOCK = "█";
1266
1302
  const DEFAULT_WIDTH = 24;
1267
1303
  const formatTokens = (value) => {
1268
1304
  const abs = Math.abs(value);
@@ -1293,46 +1329,25 @@ const renderUsageChart = (rows, width = DEFAULT_WIDTH) => {
1293
1329
  const header = "📊 Usage (last 7 days) · Tokens";
1294
1330
  if (rows.length === 0) return `${header}\n\nNo usage data available.`;
1295
1331
  const normalized = rows.map((row) => {
1296
- const input = Number(row.inputTokens);
1297
- const output = Number(row.outputTokens);
1298
- const safeInput = Number.isFinite(input) ? input : 0;
1299
- const safeOutput = Number.isFinite(output) ? output : 0;
1332
+ const total = Number(row.totalTokens);
1333
+ const safeTotal = Number.isFinite(total) ? total : 0;
1300
1334
  return {
1301
1335
  day: row.day,
1302
- input: safeInput,
1303
- output: safeOutput,
1304
- total: safeInput + safeOutput
1336
+ total: safeTotal
1305
1337
  };
1306
1338
  });
1307
1339
  const totals = normalized.map((row) => row.total);
1308
1340
  const maxTotal = Math.max(0, ...totals);
1309
- const lines = normalized.map((row) => {
1310
- const total = row.total;
1311
- if (maxTotal === 0 || total === 0) return `${row.day} ${"".padEnd(width, " ")} I:0 O:0`;
1312
- const scaled = Math.max(1, Math.round(total / maxTotal * width));
1313
- let inputBars = Math.round(row.input / total * scaled);
1314
- let outputBars = Math.max(0, scaled - inputBars);
1315
- if (row.input > 0 && row.output > 0) {
1316
- if (inputBars === 0) {
1317
- inputBars = 1;
1318
- outputBars = Math.max(0, scaled - 1);
1319
- } else if (outputBars === 0) {
1320
- outputBars = 1;
1321
- inputBars = Math.max(0, scaled - 1);
1322
- }
1323
- }
1324
- const bar = `${INPUT_BLOCK.repeat(inputBars)}${OUTPUT_BLOCK.repeat(outputBars)}`;
1325
- const inputLabel = formatTokens(row.input);
1326
- const outputLabel = formatTokens(row.output);
1327
- return `${row.day} ${bar.padEnd(width, " ")} I:${inputLabel} O:${outputLabel}`;
1328
- });
1329
- const legend = `Legend: ${INPUT_BLOCK} input ${OUTPUT_BLOCK} output`;
1330
1341
  return [
1331
1342
  header,
1332
1343
  "",
1333
- ...lines,
1334
- "",
1335
- legend
1344
+ ...normalized.map((row) => {
1345
+ if (maxTotal === 0 || row.total === 0) return `${row.day} ${"".padEnd(width, " ")} 0`;
1346
+ const scaled = Math.max(1, Math.round(row.total / maxTotal * width));
1347
+ const bar = TOTAL_BLOCK.repeat(scaled);
1348
+ const totalLabel = formatTokens(row.total);
1349
+ return `${row.day} ${bar.padEnd(width, " ")} ${totalLabel}`;
1350
+ })
1336
1351
  ].join("\n");
1337
1352
  };
1338
1353
 
@@ -1398,7 +1413,6 @@ const registerCommands = (program) => {
1398
1413
  registerAuthCommands(program);
1399
1414
  registerCodexCommand(program);
1400
1415
  registerClaudeCommand(program);
1401
- registerConfigCommands(program);
1402
1416
  registerKeysCommands(program);
1403
1417
  registerModelsCommands(program);
1404
1418
  registerStatusCommand(program);
@@ -1409,7 +1423,7 @@ const registerCommands = (program) => {
1409
1423
  //#region src/cli.ts
1410
1424
  const createProgram = () => {
1411
1425
  const program = new Command();
1412
- program.name("getrouter").description("CLI for getrouter.dev").version("0.1.0");
1426
+ program.name("getrouter").description("CLI for getrouter.dev").version(version);
1413
1427
  registerCommands(program);
1414
1428
  return program;
1415
1429
  };
@@ -0,0 +1,32 @@
1
+ # Remove Config Command Design
2
+
3
+ ## Goals
4
+ - Remove the `getrouter config` command and its subcommand behavior from the CLI.
5
+ - Keep config file usage via `~/.getrouter/config.json` for runtime reads/writes.
6
+ - Update tests and docs to reflect removal.
7
+
8
+ ## Non-Goals
9
+ - Do not change config file format or core config read/write logic.
10
+ - Do not add new CLI alternatives for config editing.
11
+
12
+ ## Scope
13
+ - Delete `src/cmd/config.ts` and `src/cmd/config-helpers.ts`.
14
+ - Remove `registerConfigCommands` from command registration.
15
+ - Remove/replace tests for config commands and config helpers.
16
+ - Update CLI entrypoint file list test to match remaining commands.
17
+ - Update README docs to remove `getrouter config` references.
18
+
19
+ ## Behavior Changes
20
+ - `getrouter config` should no longer be a recognized command.
21
+ - Users should edit `~/.getrouter/config.json` directly to change configuration.
22
+
23
+ ## Testing Plan
24
+ - Add a test that asserts `getrouter config` is rejected by the CLI.
25
+ - Remove tests that exercise config subcommands or config helper parsing.
26
+ - Ensure remaining test suite passes unchanged.
27
+
28
+ ## Files
29
+ - Remove: `src/cmd/config.ts`, `src/cmd/config-helpers.ts`
30
+ - Modify: `src/cmd/index.ts`, `tests/cli.test.ts`
31
+ - Add/Modify tests: `tests/cli.test.ts` (new assertion), delete `tests/cmd/config*.test.ts`
32
+ - Docs: `README.md`, `README.zh-cn.md`, `README.ja.md`
@@ -0,0 +1,129 @@
1
+ # Remove Config Command Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Remove the `getrouter config` CLI command and keep configuration changes handled via `~/.getrouter/config.json`.
6
+
7
+ **Architecture:** Delete config command entrypoints and helper parsing, update CLI registration and tests, and remove documentation references.
8
+
9
+ **Tech Stack:** TypeScript, Commander.js, Vitest.
10
+
11
+ ### Task 1: Add failing tests for config command removal
12
+
13
+ **Files:**
14
+ - Modify: `tests/cli.test.ts`
15
+
16
+ **Step 1: Write the failing test**
17
+
18
+ ```ts
19
+ it("rejects removed config command", async () => {
20
+ const program = createProgram();
21
+ program.exitOverride();
22
+ await expect(
23
+ program.parseAsync(["node", "getrouter", "config"]),
24
+ ).rejects.toBeTruthy();
25
+ });
26
+ ```
27
+
28
+ **Step 2: Run test to verify it fails**
29
+
30
+ Run: `bun run test -- tests/cli.test.ts`
31
+ Expected: FAIL (config command still registered).
32
+
33
+ **Step 3: Commit**
34
+
35
+ ```bash
36
+ git add tests/cli.test.ts
37
+ git commit -m "test: remove config command"
38
+ ```
39
+
40
+ ### Task 2: Remove config command implementation and helpers
41
+
42
+ **Files:**
43
+ - Delete: `src/cmd/config.ts`
44
+ - Delete: `src/cmd/config-helpers.ts`
45
+ - Modify: `src/cmd/index.ts`
46
+ - Modify: `tests/cli.test.ts`
47
+
48
+ **Step 1: Remove command registration**
49
+
50
+ - Delete import and registration of `registerConfigCommands`.
51
+ - Update the CLI file list test to remove `config.ts` and `config-helpers.ts`.
52
+
53
+ **Step 2: Run tests to verify they pass**
54
+
55
+ Run: `bun run test -- tests/cli.test.ts`
56
+ Expected: PASS
57
+
58
+ **Step 3: Commit**
59
+
60
+ ```bash
61
+ git add src/cmd/index.ts tests/cli.test.ts
62
+ git rm src/cmd/config.ts src/cmd/config-helpers.ts
63
+ git commit -m "feat: remove config command"
64
+ ```
65
+
66
+ ### Task 3: Remove config tests
67
+
68
+ **Files:**
69
+ - Delete: `tests/cmd/config.test.ts`
70
+ - Delete: `tests/cmd/config-helpers.test.ts`
71
+
72
+ **Step 1: Remove tests**
73
+
74
+ ```bash
75
+ git rm tests/cmd/config.test.ts tests/cmd/config-helpers.test.ts
76
+ ```
77
+
78
+ **Step 2: Run test to verify it passes**
79
+
80
+ Run: `bun run test -- tests/cmd/config.test.ts`
81
+ Expected: FAIL (file removed) and overall suite should pass after full test run.
82
+
83
+ **Step 3: Commit**
84
+
85
+ ```bash
86
+ git add -A
87
+ git commit -m "test: remove config command coverage"
88
+ ```
89
+
90
+ ### Task 4: Update documentation
91
+
92
+ **Files:**
93
+ - Modify: `README.md`
94
+ - Modify: `README.zh-cn.md`
95
+ - Modify: `README.ja.md`
96
+
97
+ **Step 1: Remove config command references**
98
+
99
+ - Remove any `getrouter config` examples.
100
+ - Add a short note that config is edited in `~/.getrouter/config.json`.
101
+
102
+ **Step 2: Commit**
103
+
104
+ ```bash
105
+ git add README.md README.zh-cn.md README.ja.md
106
+ git commit -m "docs: remove config command"
107
+ ```
108
+
109
+ ### Task 5: Full verification
110
+
111
+ **Step 1: Run tests**
112
+
113
+ Run: `bun run test`
114
+ Expected: PASS
115
+
116
+ **Step 2: Run typecheck**
117
+
118
+ Run: `bun run typecheck`
119
+ Expected: PASS
120
+
121
+ **Step 3: Run lint**
122
+
123
+ Run: `bun run lint`
124
+ Expected: PASS
125
+
126
+ **Step 4: Run format**
127
+
128
+ Run: `bun run format`
129
+ Expected: PASS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getrouter/getrouter-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "CLI for getrouter.dev",
6
6
  "bin": {
@@ -22,16 +22,16 @@
22
22
  },
23
23
  "packageManager": "bun@1.3.5",
24
24
  "dependencies": {
25
- "commander": "^12.1.0",
25
+ "commander": "^14.0.2",
26
26
  "prompts": "^2.4.2"
27
27
  },
28
28
  "devDependencies": {
29
- "@biomejs/biome": "^2.3.10",
30
- "@types/node": "^20.12.12",
29
+ "@biomejs/biome": "^2.3.11",
30
+ "@types/node": "^25.0.3",
31
31
  "@types/prompts": "^2.4.9",
32
- "tsdown": "^0.19.0-beta.2",
32
+ "tsdown": "^0.18.4",
33
33
  "tsx": "^4.19.2",
34
34
  "typescript": "^5.7.3",
35
- "vitest": "^2.1.8"
35
+ "vitest": "^4.0.16"
36
36
  }
37
37
  }
package/src/cli.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Command } from "commander";
2
+ import { version } from "../package.json";
2
3
  import { registerCommands } from "./cmd";
3
4
 
4
5
  export const createProgram = () => {
@@ -6,7 +7,7 @@ export const createProgram = () => {
6
7
  program
7
8
  .name("getrouter")
8
9
  .description("CLI for getrouter.dev")
9
- .version("0.1.0");
10
+ .version(version);
10
11
  registerCommands(program);
11
12
  return program;
12
13
  };
package/src/cmd/index.ts CHANGED
@@ -2,7 +2,6 @@ import type { Command } from "commander";
2
2
  import { registerAuthCommands } from "./auth";
3
3
  import { registerClaudeCommand } from "./claude";
4
4
  import { registerCodexCommand } from "./codex";
5
- import { registerConfigCommands } from "./config";
6
5
  import { registerKeysCommands } from "./keys";
7
6
  import { registerModelsCommands } from "./models";
8
7
  import { registerStatusCommand } from "./status";
@@ -12,7 +11,6 @@ export const registerCommands = (program: Command) => {
12
11
  registerAuthCommands(program);
13
12
  registerCodexCommand(program);
14
13
  registerClaudeCommand(program);
15
- registerConfigCommands(program);
16
14
  registerKeysCommands(program);
17
15
  registerModelsCommands(program);
18
16
  registerStatusCommand(program);