@getrouter/getrouter-cli 0.1.8 → 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.8";
11
+ var version = "0.1.10";
12
12
 
13
13
  //#endregion
14
14
  //#region src/generated/router/dashboard/v1/index.ts
@@ -369,12 +369,15 @@ const shouldRetryResponse = (error) => {
369
369
  if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") return isServerError(error.status);
370
370
  return error instanceof TypeError;
371
371
  };
372
- const requestJson = async ({ path: path$1, method, body, fetchImpl, maxRetries = 3, _retrySleep }) => {
372
+ const requestJson = async ({ path: path$1, method, body, fetchImpl, maxRetries = 3, includeAuth = true, _retrySleep }) => {
373
373
  return withRetry(async () => {
374
- const auth = readAuth();
374
+ const auth = includeAuth ? readAuth() : {
375
+ accessToken: void 0,
376
+ refreshToken: void 0
377
+ };
375
378
  const url = buildApiUrl(path$1);
376
- let res = await doFetch(url, method, buildHeaders(auth.accessToken), body, fetchImpl);
377
- if (res.status === 401 && auth.refreshToken) {
379
+ let res = await doFetch(url, method, includeAuth ? buildHeaders(auth.accessToken) : buildHeaders(), body, fetchImpl);
380
+ if (includeAuth && res.status === 401 && auth.refreshToken) {
378
381
  const refreshed = await refreshAccessToken({ fetchImpl });
379
382
  if (refreshed?.accessToken) res = await doFetch(url, method, buildHeaders(refreshed.accessToken), body, fetchImpl);
380
383
  }
@@ -389,7 +392,7 @@ const requestJson = async ({ path: path$1, method, body, fetchImpl, maxRetries =
389
392
 
390
393
  //#endregion
391
394
  //#region src/core/api/client.ts
392
- const createApiClients = ({ fetchImpl, clients }) => {
395
+ const createApiClients = ({ fetchImpl, clients, includeAuth = true }) => {
393
396
  const factories = clients ?? {
394
397
  createConsumerServiceClient,
395
398
  createAuthServiceClient,
@@ -402,7 +405,8 @@ const createApiClients = ({ fetchImpl, clients }) => {
402
405
  path: path$1,
403
406
  method,
404
407
  body: body ? JSON.parse(body) : void 0,
405
- fetchImpl
408
+ fetchImpl,
409
+ includeAuth
406
410
  });
407
411
  };
408
412
  return {
@@ -505,7 +509,7 @@ const pollAuthorize = async ({ authorize, code, timeoutMs = 300 * 1e3, initialDe
505
509
  //#region src/cmd/auth.ts
506
510
  const registerAuthCommands = (program) => {
507
511
  program.command("login").description("Login with device flow").action(async () => {
508
- const { authService } = createApiClients({});
512
+ const { authService } = createApiClients({ includeAuth: false });
509
513
  const authCode = generateAuthCode();
510
514
  const url = buildLoginUrl(authCode);
511
515
  console.log("🔐 To authenticate, visit:");
@@ -595,9 +599,19 @@ const fuzzySelect = async ({ message, choices }) => {
595
599
 
596
600
  //#endregion
597
601
  //#region src/core/interactive/keys.ts
598
- const sortByCreatedAtDesc = (consumers) => consumers.slice().sort((a, b) => {
599
- const aTime = Date.parse(a.createdAt ?? "") || 0;
600
- 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;
601
615
  });
602
616
  const normalizeName = (consumer) => {
603
617
  const name = consumer.name?.trim();
@@ -613,8 +627,8 @@ const buildNameCounts = (consumers) => {
613
627
  };
614
628
  const formatChoice = (consumer, nameCounts) => {
615
629
  const name = normalizeName(consumer);
616
- const createdAt = consumer.createdAt ?? "-";
617
- 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;
618
632
  };
619
633
  const promptKeyName = async (initial) => {
620
634
  const response = await prompts({
@@ -649,14 +663,18 @@ const selectConsumer = async (consumerService) => {
649
663
  pageToken
650
664
  }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0);
651
665
  if (consumers.length === 0) throw new Error("No available API keys");
652
- const sorted = sortByCreatedAtDesc(consumers);
666
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
653
667
  const nameCounts = buildNameCounts(sorted);
654
668
  return await fuzzySelect({
655
669
  message: "🔎 Search keys",
656
670
  choices: sorted.map((consumer) => ({
657
671
  title: formatChoice(consumer, nameCounts),
658
672
  value: consumer,
659
- keywords: [normalizeName(consumer), consumer.createdAt ?? ""].filter(Boolean)
673
+ keywords: [
674
+ normalizeName(consumer),
675
+ consumer.updatedAt ?? "",
676
+ consumer.createdAt ?? ""
677
+ ].filter(Boolean)
660
678
  }))
661
679
  }) ?? null;
662
680
  };
@@ -666,7 +684,7 @@ const selectConsumerList = async (consumerService, message) => {
666
684
  pageToken
667
685
  }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0);
668
686
  if (consumers.length === 0) throw new Error("No available API keys");
669
- const sorted = sortByCreatedAtDesc(consumers);
687
+ const sorted = sortConsumersByUpdatedAtDesc(consumers);
670
688
  const nameCounts = buildNameCounts(sorted);
671
689
  const response = await prompts({
672
690
  type: "select",
@@ -951,25 +969,16 @@ const getCodexModelChoices = async () => {
951
969
  value: model,
952
970
  keywords: [model, "codex"]
953
971
  }));
954
- if (remoteChoices.length > 0) {
955
- remoteChoices.sort((a, b) => a.title.localeCompare(b.title));
956
- return remoteChoices;
957
- }
972
+ if (remoteChoices.length > 0) return remoteChoices.reverse();
958
973
  } catch {}
959
974
  return MODEL_CHOICES;
960
975
  };
961
976
  const REASONING_CHOICES = [
962
977
  {
963
- id: "low",
964
- label: "Low",
965
- value: "low",
966
- description: "Fast responses with lighter reasoning"
967
- },
968
- {
969
- id: "medium",
970
- label: "Medium (default)",
971
- value: "medium",
972
- 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."
973
982
  },
974
983
  {
975
984
  id: "high",
@@ -978,10 +987,16 @@ const REASONING_CHOICES = [
978
987
  description: "Greater reasoning depth for complex problems"
979
988
  },
980
989
  {
981
- id: "extra_high",
982
- label: "Extra high",
983
- value: "xhigh",
984
- 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"
985
1000
  }
986
1001
  ];
987
1002
  const REASONING_FUZZY_CHOICES = REASONING_CHOICES.map((choice) => ({
@@ -1326,10 +1341,10 @@ const updateConsumer = async (consumerService, consumer, name, enabled) => {
1326
1341
  });
1327
1342
  };
1328
1343
  const listConsumers = async (consumerService, showApiKey) => {
1329
- outputConsumers(await fetchAllPages((pageToken) => consumerService.ListConsumers({
1344
+ outputConsumers(sortConsumersByUpdatedAtDesc(await fetchAllPages((pageToken) => consumerService.ListConsumers({
1330
1345
  pageSize: void 0,
1331
1346
  pageToken
1332
- }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0), showApiKey);
1347
+ }), (res) => res?.consumers ?? [], (res) => res?.nextPageToken || void 0)), showApiKey);
1333
1348
  };
1334
1349
  const resolveConsumerForUpdate = async (consumerService, id) => {
1335
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.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "CLI for getrouter.dev",
6
6
  "bin": {
package/src/cmd/auth.ts CHANGED
@@ -14,7 +14,7 @@ export const registerAuthCommands = (program: Command) => {
14
14
  .command("login")
15
15
  .description("Login with device flow")
16
16
  .action(async () => {
17
- const { authService } = createApiClients({});
17
+ const { authService } = createApiClients({ includeAuth: false });
18
18
  const authCode = generateAuthCode();
19
19
  const url = buildLoginUrl(authCode);
20
20
  console.log("🔐 To authenticate, visit:");
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 (
@@ -46,9 +46,11 @@ export type ApiClients = {
46
46
  export const createApiClients = ({
47
47
  fetchImpl,
48
48
  clients,
49
+ includeAuth = true,
49
50
  }: {
50
51
  fetchImpl?: typeof fetch;
51
52
  clients?: ClientFactories;
53
+ includeAuth?: boolean;
52
54
  }): ApiClients => {
53
55
  const factories =
54
56
  clients ??
@@ -66,6 +68,7 @@ export const createApiClients = ({
66
68
  method,
67
69
  body: body ? JSON.parse(body) : undefined,
68
70
  fetchImpl,
71
+ includeAuth,
69
72
  });
70
73
  };
71
74
 
@@ -10,6 +10,7 @@ type RequestInput = {
10
10
  body?: unknown;
11
11
  fetchImpl?: typeof fetch;
12
12
  maxRetries?: number;
13
+ includeAuth?: boolean;
13
14
  /** For testing: override the sleep function used for retry delays */
14
15
  _retrySleep?: (ms: number) => Promise<void>;
15
16
  };
@@ -63,18 +64,23 @@ export const requestJson = async <T = unknown>({
63
64
  body,
64
65
  fetchImpl,
65
66
  maxRetries = 3,
67
+ includeAuth = true,
66
68
  _retrySleep,
67
69
  }: RequestInput): Promise<T> => {
68
70
  return withRetry(
69
71
  async () => {
70
- const auth = readAuth();
72
+ const auth = includeAuth
73
+ ? readAuth()
74
+ : { accessToken: undefined, refreshToken: undefined };
71
75
  const url = buildApiUrl(path);
72
- const headers = buildHeaders(auth.accessToken);
76
+ const headers = includeAuth
77
+ ? buildHeaders(auth.accessToken)
78
+ : buildHeaders();
73
79
 
74
80
  let res = await doFetch(url, method, headers, body, fetchImpl);
75
81
 
76
82
  // On 401, attempt token refresh and retry once
77
- if (res.status === 401 && auth.refreshToken) {
83
+ if (includeAuth && res.status === 401 && auth.refreshToken) {
78
84
  const refreshed = await refreshAccessToken({ fetchImpl });
79
85
  if (refreshed?.accessToken) {
80
86
  const newHeaders = buildHeaders(refreshed.accessToken);
@@ -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
  );
@@ -51,6 +51,36 @@ describe("requestJson", () => {
51
51
  expect(headers.Cookie).toBe("access_token=t");
52
52
  });
53
53
 
54
+ it("skips auth headers when includeAuth is false", async () => {
55
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-"));
56
+ process.env.GETROUTER_CONFIG_DIR = dir;
57
+ fs.writeFileSync(
58
+ path.join(dir, "auth.json"),
59
+ JSON.stringify({ accessToken: "t" }),
60
+ );
61
+
62
+ const fetchSpy = vi.fn(
63
+ async (_input: RequestInfo | URL, _init?: RequestInit) =>
64
+ ({
65
+ ok: true,
66
+ json: async () => ({ ok: true }),
67
+ }) as Response,
68
+ );
69
+
70
+ await requestJson({
71
+ path: "/v1/test",
72
+ method: "GET",
73
+ fetchImpl: fetchSpy as unknown as typeof fetch,
74
+ includeAuth: false,
75
+ });
76
+
77
+ const call = fetchSpy.mock.calls[0] as Parameters<typeof fetch> | undefined;
78
+ const init = call?.[1];
79
+ const headers = (init?.headers ?? {}) as Record<string, string>;
80
+ expect(headers.Authorization).toBeUndefined();
81
+ expect(headers.Cookie).toBeUndefined();
82
+ });
83
+
54
84
  it("uses GETROUTER_AUTH_COOKIE when set", async () => {
55
85
  process.env.GETROUTER_AUTH_COOKIE = "router_auth";
56
86
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "getrouter-"));