@getrouter/getrouter-cli 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ja.md CHANGED
@@ -51,6 +51,7 @@ getrouter login
51
51
  ```
52
52
 
53
53
  表示された URL をブラウザで開くと、CLI はトークンを受け取るまでポーリングします。
54
+ すでにログイン済みでも `getrouter login` を再実行すると、ローカルの auth.json は新しいトークンで上書きされます。
54
55
 
55
56
  ## よく使うコマンド
56
57
 
@@ -75,6 +76,12 @@ getrouter login
75
76
  getrouter codex
76
77
  ```
77
78
 
79
+ Codex の設定/認証から GetRouter の項目を削除する場合:
80
+
81
+ ```bash
82
+ getrouter codex uninstall
83
+ ```
84
+
78
85
  書き込まれるファイル(codex):
79
86
 
80
87
  - `~/.codex/config.toml`(model + reasoning + provider 設定)
package/README.md CHANGED
@@ -51,6 +51,7 @@ getrouter login
51
51
  ```
52
52
 
53
53
  Follow the printed URL in your browser, then the CLI will poll until it receives tokens.
54
+ Re-running `getrouter login` will overwrite the local auth state with new tokens.
54
55
 
55
56
  ## Common Commands
56
57
 
@@ -75,6 +76,12 @@ Notes:
75
76
  getrouter codex
76
77
  ```
77
78
 
79
+ To remove GetRouter entries from Codex config/auth:
80
+
81
+ ```bash
82
+ getrouter codex uninstall
83
+ ```
84
+
78
85
  Files written (codex):
79
86
 
80
87
  - `~/.codex/config.toml` (model + reasoning + provider settings)
package/README.zh-cn.md CHANGED
@@ -51,6 +51,7 @@ getrouter login
51
51
  ```
52
52
 
53
53
  按提示打开浏览器完成确认,CLI 会轮询直到拿到 token。
54
+ 即使已登录,也可再次执行 `getrouter login`,会用新 token 覆盖本地 auth.json。
54
55
 
55
56
  ## 常用命令
56
57
 
@@ -75,6 +76,12 @@ getrouter login
75
76
  getrouter codex
76
77
  ```
77
78
 
79
+ 如需移除 Codex 配置/认证中的 GetRouter 条目:
80
+
81
+ ```bash
82
+ getrouter codex uninstall
83
+ ```
84
+
78
85
  写入文件(codex):
79
86
 
80
87
  - `~/.codex/config.toml`(model + reasoning + provider 设置)
package/dist/bin.mjs CHANGED
@@ -8,7 +8,7 @@ import { randomInt } from "node:crypto";
8
8
  import prompts from "prompts";
9
9
 
10
10
  //#region package.json
11
- var version = "0.1.9";
11
+ var version = "0.1.10";
12
12
 
13
13
  //#endregion
14
14
  //#region src/generated/router/dashboard/v1/index.ts
@@ -599,9 +599,19 @@ const fuzzySelect = async ({ message, choices }) => {
599
599
 
600
600
  //#endregion
601
601
  //#region src/core/interactive/keys.ts
602
- const sortByCreatedAtDesc = (consumers) => consumers.slice().sort((a, b) => {
603
- const aTime = Date.parse(a.createdAt ?? "") || 0;
604
- return (Date.parse(b.createdAt ?? "") || 0) - aTime;
602
+ const parseTimestamp = (value) => {
603
+ const parsed = Date.parse(value ?? "");
604
+ return Number.isNaN(parsed) ? 0 : parsed;
605
+ };
606
+ const getUpdatedAtTime = (consumer) => {
607
+ const updatedAt = parseTimestamp(consumer.updatedAt);
608
+ if (updatedAt) return updatedAt;
609
+ return parseTimestamp(consumer.createdAt);
610
+ };
611
+ const getDisplayTimestamp = (consumer) => consumer.updatedAt ?? consumer.createdAt ?? "-";
612
+ const sortConsumersByUpdatedAtDesc = (consumers) => consumers.slice().sort((a, b) => {
613
+ const aTime = getUpdatedAtTime(a);
614
+ return getUpdatedAtTime(b) - aTime;
605
615
  });
606
616
  const normalizeName = (consumer) => {
607
617
  const name = consumer.name?.trim();
@@ -617,8 +627,8 @@ const buildNameCounts = (consumers) => {
617
627
  };
618
628
  const formatChoice = (consumer, nameCounts) => {
619
629
  const name = normalizeName(consumer);
620
- const createdAt = consumer.createdAt ?? "-";
621
- return (nameCounts.get(name) ?? 0) > 1 || name === "(unnamed)" ? `${name} (${createdAt})` : name;
630
+ const displayTimestamp = getDisplayTimestamp(consumer);
631
+ return (nameCounts.get(name) ?? 0) > 1 || name === "(unnamed)" ? `${name} (${displayTimestamp})` : name;
622
632
  };
623
633
  const promptKeyName = async (initial) => {
624
634
  const response = await prompts({
@@ -653,14 +663,18 @@ const selectConsumer = async (consumerService) => {
653
663
  pageToken
654
664
  }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0);
655
665
  if (consumers.length === 0) throw new Error("No available API keys");
656
- const sorted = sortByCreatedAtDesc(consumers);
666
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
657
667
  const nameCounts = buildNameCounts(sorted);
658
668
  return await fuzzySelect({
659
669
  message: "🔎 Search keys",
660
670
  choices: sorted.map((consumer) => ({
661
671
  title: formatChoice(consumer, nameCounts),
662
672
  value: consumer,
663
- keywords: [normalizeName(consumer), consumer.createdAt ?? ""].filter(Boolean)
673
+ keywords: [
674
+ normalizeName(consumer),
675
+ consumer.updatedAt ?? "",
676
+ consumer.createdAt ?? ""
677
+ ].filter(Boolean)
664
678
  }))
665
679
  }) ?? null;
666
680
  };
@@ -670,7 +684,7 @@ const selectConsumerList = async (consumerService, message) => {
670
684
  pageToken
671
685
  }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0);
672
686
  if (consumers.length === 0) throw new Error("No available API keys");
673
- const sorted = sortByCreatedAtDesc(consumers);
687
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
674
688
  const nameCounts = buildNameCounts(sorted);
675
689
  const response = await prompts({
676
690
  type: "select",
@@ -955,25 +969,16 @@ const getCodexModelChoices = async () => {
955
969
  value: model,
956
970
  keywords: [model, "codex"]
957
971
  }));
958
- if (remoteChoices.length > 0) {
959
- remoteChoices.sort((a, b) => a.title.localeCompare(b.title));
960
- return remoteChoices;
961
- }
972
+ if (remoteChoices.length > 0) return remoteChoices.reverse();
962
973
  } catch {}
963
974
  return MODEL_CHOICES;
964
975
  };
965
976
  const REASONING_CHOICES = [
966
977
  {
967
- id: "low",
968
- label: "Low",
969
- value: "low",
970
- description: "Fast responses with lighter reasoning"
971
- },
972
- {
973
- id: "medium",
974
- label: "Medium (default)",
975
- value: "medium",
976
- description: "Balances speed and reasoning depth for everyday tasks"
978
+ id: "extra_high",
979
+ label: "Extra high",
980
+ value: "xhigh",
981
+ description: "Extra high reasoning depth for complex problems. Warning: Extra high reasoning effort can quickly consume Plus plan rate limits."
977
982
  },
978
983
  {
979
984
  id: "high",
@@ -982,10 +987,16 @@ const REASONING_CHOICES = [
982
987
  description: "Greater reasoning depth for complex problems"
983
988
  },
984
989
  {
985
- id: "extra_high",
986
- label: "Extra high",
987
- value: "xhigh",
988
- description: "Extra high reasoning depth for complex problems. Warning: Extra high reasoning effort can quickly consume Plus plan rate limits."
990
+ id: "medium",
991
+ label: "Medium (default)",
992
+ value: "medium",
993
+ description: "Balances speed and reasoning depth for everyday tasks"
994
+ },
995
+ {
996
+ id: "low",
997
+ label: "Low",
998
+ value: "low",
999
+ description: "Fast responses with lighter reasoning"
989
1000
  }
990
1001
  ];
991
1002
  const REASONING_FUZZY_CHOICES = REASONING_CHOICES.map((choice) => ({
@@ -1330,10 +1341,10 @@ const updateConsumer = async (consumerService, consumer, name, enabled) => {
1330
1341
  });
1331
1342
  };
1332
1343
  const listConsumers = async (consumerService, showApiKey) => {
1333
- outputConsumers(await fetchAllPages((pageToken) => consumerService.ListConsumers({
1344
+ outputConsumers(sortConsumersByUpdatedAtDesc(await fetchAllPages((pageToken) => consumerService.ListConsumers({
1334
1345
  pageSize: void 0,
1335
1346
  pageToken
1336
- }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0), showApiKey);
1347
+ }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0)), showApiKey);
1337
1348
  };
1338
1349
  const resolveConsumerForUpdate = async (consumerService, id) => {
1339
1350
  if (id) return consumerService.GetConsumer({ id });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getrouter/getrouter-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "CLI for getrouter.dev",
6
6
  "bin": {
package/src/cmd/keys.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  promptKeyEnabled,
8
8
  promptKeyName,
9
9
  selectConsumerList,
10
+ sortConsumersByUpdatedAtDesc,
10
11
  } from "../core/interactive/keys";
11
12
  import { renderTable } from "../core/output/table";
12
13
  import type {
@@ -103,7 +104,8 @@ const listConsumers = async (
103
104
  (res) => res?.consumers ?? [],
104
105
  (res) => res?.nextPageToken || undefined,
105
106
  );
106
- outputConsumers(consumers, showApiKey);
107
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
108
+ outputConsumers(sorted, showApiKey);
107
109
  };
108
110
 
109
111
  const resolveConsumerForUpdate = async (
@@ -48,8 +48,7 @@ export const getCodexModelChoices = async (): Promise<
48
48
  }));
49
49
 
50
50
  if (remoteChoices.length > 0) {
51
- remoteChoices.sort((a, b) => a.title.localeCompare(b.title));
52
- return remoteChoices;
51
+ return remoteChoices.reverse();
53
52
  }
54
53
  } catch {}
55
54
 
@@ -58,16 +57,11 @@ export const getCodexModelChoices = async (): Promise<
58
57
 
59
58
  export const REASONING_CHOICES: ReasoningChoice[] = [
60
59
  {
61
- id: "low",
62
- label: "Low",
63
- value: "low",
64
- description: "Fast responses with lighter reasoning",
65
- },
66
- {
67
- id: "medium",
68
- label: "Medium (default)",
69
- value: "medium",
70
- description: "Balances speed and reasoning depth for everyday tasks",
60
+ id: "extra_high",
61
+ label: "Extra high",
62
+ value: "xhigh",
63
+ description:
64
+ "Extra high reasoning depth for complex problems. Warning: Extra high reasoning effort can quickly consume Plus plan rate limits.",
71
65
  },
72
66
  {
73
67
  id: "high",
@@ -76,11 +70,16 @@ export const REASONING_CHOICES: ReasoningChoice[] = [
76
70
  description: "Greater reasoning depth for complex problems",
77
71
  },
78
72
  {
79
- id: "extra_high",
80
- label: "Extra high",
81
- value: "xhigh",
82
- description:
83
- "Extra high reasoning depth for complex problems. Warning: Extra high reasoning effort can quickly consume Plus plan rate limits.",
73
+ id: "medium",
74
+ label: "Medium (default)",
75
+ value: "medium",
76
+ description: "Balances speed and reasoning depth for everyday tasks",
77
+ },
78
+ {
79
+ id: "low",
80
+ label: "Low",
81
+ value: "low",
82
+ description: "Fast responses with lighter reasoning",
84
83
  },
85
84
  ];
86
85
 
@@ -17,10 +17,24 @@ export type KeyMenuAction =
17
17
  | "delete"
18
18
  | "exit";
19
19
 
20
- const sortByCreatedAtDesc = (consumers: Consumer[]) =>
20
+ const parseTimestamp = (value?: string) => {
21
+ const parsed = Date.parse(value ?? "");
22
+ return Number.isNaN(parsed) ? 0 : parsed;
23
+ };
24
+
25
+ const getUpdatedAtTime = (consumer: Consumer) => {
26
+ const updatedAt = parseTimestamp(consumer.updatedAt);
27
+ if (updatedAt) return updatedAt;
28
+ return parseTimestamp(consumer.createdAt);
29
+ };
30
+
31
+ const getDisplayTimestamp = (consumer: Consumer) =>
32
+ consumer.updatedAt ?? consumer.createdAt ?? "-";
33
+
34
+ export const sortConsumersByUpdatedAtDesc = (consumers: Consumer[]) =>
21
35
  consumers.slice().sort((a, b) => {
22
- const aTime = Date.parse(a.createdAt ?? "") || 0;
23
- const bTime = Date.parse(b.createdAt ?? "") || 0;
36
+ const aTime = getUpdatedAtTime(a);
37
+ const bTime = getUpdatedAtTime(b);
24
38
  return bTime - aTime;
25
39
  });
26
40
 
@@ -40,9 +54,9 @@ const buildNameCounts = (consumers: Consumer[]) => {
40
54
 
41
55
  const formatChoice = (consumer: Consumer, nameCounts: Map<string, number>) => {
42
56
  const name = normalizeName(consumer);
43
- const createdAt = consumer.createdAt ?? "-";
57
+ const displayTimestamp = getDisplayTimestamp(consumer);
44
58
  const needsDetail = (nameCounts.get(name) ?? 0) > 1 || name === "(unnamed)";
45
- return needsDetail ? `${name} (${createdAt})` : name;
59
+ return needsDetail ? `${name} (${displayTimestamp})` : name;
46
60
  };
47
61
 
48
62
  export const selectKeyAction = async (): Promise<KeyMenuAction> => {
@@ -127,16 +141,18 @@ export const selectConsumer = async (
127
141
  if (consumers.length === 0) {
128
142
  throw new Error("No available API keys");
129
143
  }
130
- const sorted = sortByCreatedAtDesc(consumers);
144
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
131
145
  const nameCounts = buildNameCounts(sorted);
132
146
  const selected = await fuzzySelect({
133
147
  message: "🔎 Search keys",
134
148
  choices: sorted.map((consumer) => ({
135
149
  title: formatChoice(consumer, nameCounts),
136
150
  value: consumer,
137
- keywords: [normalizeName(consumer), consumer.createdAt ?? ""].filter(
138
- Boolean,
139
- ),
151
+ keywords: [
152
+ normalizeName(consumer),
153
+ consumer.updatedAt ?? "",
154
+ consumer.createdAt ?? "",
155
+ ].filter(Boolean),
140
156
  })),
141
157
  });
142
158
  return selected ?? null;
@@ -158,7 +174,7 @@ export const selectConsumerList = async (
158
174
  if (consumers.length === 0) {
159
175
  throw new Error("No available API keys");
160
176
  }
161
- const sorted = sortByCreatedAtDesc(consumers);
177
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
162
178
  const nameCounts = buildNameCounts(sorted);
163
179
  const response = await prompts({
164
180
  type: "select",
@@ -19,6 +19,7 @@ const mockConsumer = {
19
19
  apiKey: "abcd1234WXYZ",
20
20
  lastAccess: "2026-01-02T00:00:00Z",
21
21
  createdAt: "2026-01-01T00:00:00Z",
22
+ updatedAt: "2026-01-02T00:00:00Z",
22
23
  };
23
24
 
24
25
  const emptyAuthService = {} as AuthService;
@@ -113,6 +114,39 @@ describe("keys command", () => {
113
114
  log.mockRestore();
114
115
  });
115
116
 
117
+ it("sorts keys by updatedAt desc in list output", async () => {
118
+ setStdinTTY(false);
119
+ const consumers = [
120
+ {
121
+ ...mockConsumer,
122
+ id: "c1",
123
+ name: "older-key",
124
+ updatedAt: "2026-01-02T00:00:00Z",
125
+ },
126
+ {
127
+ ...mockConsumer,
128
+ id: "c2",
129
+ name: "newer-key",
130
+ updatedAt: "2026-01-03T00:00:00Z",
131
+ },
132
+ ];
133
+ (createApiClients as unknown as ReturnType<typeof vi.fn>).mockReturnValue({
134
+ consumerService: {
135
+ ListConsumers: vi.fn().mockResolvedValue({ consumers }),
136
+ } as unknown as ConsumerService,
137
+ subscriptionService: emptySubscriptionService,
138
+ authService: emptyAuthService,
139
+ });
140
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
141
+ const program = createProgram();
142
+ await program.parseAsync(["node", "getrouter", "keys", "list"]);
143
+ const output = log.mock.calls.map((c) => c[0]).join("\n");
144
+ expect(output.indexOf("newer-key")).toBeLessThan(
145
+ output.indexOf("older-key"),
146
+ );
147
+ log.mockRestore();
148
+ });
149
+
116
150
  it("rejects removed get subcommand", async () => {
117
151
  setStdinTTY(false);
118
152
  const program = createProgram();
@@ -25,15 +25,14 @@ describe("codex interactive helpers", () => {
25
25
  ok: true,
26
26
  status: 200,
27
27
  json: vi.fn().mockResolvedValue({
28
- models: ["new-codex-model-xyz"],
28
+ models: ["older-codex-model", "newer-codex-model"],
29
29
  }),
30
30
  });
31
31
  vi.stubGlobal("fetch", fetchMock);
32
32
 
33
33
  const choices = await getCodexModelChoices();
34
- expect(
35
- choices.some((choice) => choice.value === "new-codex-model-xyz"),
36
- ).toBe(true);
34
+ expect(choices[0]?.value).toBe("newer-codex-model");
35
+ expect(choices[1]?.value).toBe("older-codex-model");
37
36
  expect(String(fetchMock.mock.calls[0]?.[0] ?? "")).toContain(
38
37
  "/v1/dashboard/providers/models?tag=codex",
39
38
  );