@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9

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 (107) hide show
  1. package/README.md +88 -88
  2. package/dist/opencode-anthropic-auth-cli.mjs +804 -507
  3. package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
  4. package/package.json +67 -59
  5. package/src/__tests__/billing-edge-cases.test.ts +59 -59
  6. package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
  7. package/src/__tests__/cc-comparison.test.ts +87 -87
  8. package/src/__tests__/cc-credentials.test.ts +254 -250
  9. package/src/__tests__/cch-drift-checker.test.ts +51 -51
  10. package/src/__tests__/cch-native-style.test.ts +56 -56
  11. package/src/__tests__/debug-gating.test.ts +42 -42
  12. package/src/__tests__/decomposition-smoke.test.ts +68 -68
  13. package/src/__tests__/fingerprint-regression.test.ts +575 -566
  14. package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
  15. package/src/__tests__/helpers/conversation-history.ts +119 -119
  16. package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
  17. package/src/__tests__/helpers/deferred.ts +69 -69
  18. package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
  19. package/src/__tests__/helpers/in-memory-storage.ts +88 -88
  20. package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
  21. package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
  22. package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
  23. package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
  24. package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
  25. package/src/__tests__/helpers/sse.ts +209 -209
  26. package/src/__tests__/index.parallel.test.ts +605 -595
  27. package/src/__tests__/sanitization-regex.test.ts +112 -112
  28. package/src/__tests__/state-bounds.test.ts +90 -90
  29. package/src/account-identity.test.ts +197 -192
  30. package/src/account-identity.ts +69 -67
  31. package/src/account-state.test.ts +86 -86
  32. package/src/account-state.ts +25 -25
  33. package/src/accounts/matching.test.ts +335 -0
  34. package/src/accounts/matching.ts +167 -0
  35. package/src/accounts/persistence.test.ts +345 -0
  36. package/src/accounts/persistence.ts +432 -0
  37. package/src/accounts/repair.test.ts +276 -0
  38. package/src/accounts/repair.ts +407 -0
  39. package/src/accounts.dedup.test.ts +621 -621
  40. package/src/accounts.test.ts +933 -929
  41. package/src/accounts.ts +633 -989
  42. package/src/backoff.test.ts +345 -345
  43. package/src/backoff.ts +219 -219
  44. package/src/betas.ts +124 -124
  45. package/src/bun-fetch.test.ts +345 -342
  46. package/src/bun-fetch.ts +424 -424
  47. package/src/bun-proxy.test.ts +25 -25
  48. package/src/bun-proxy.ts +209 -209
  49. package/src/cc-credentials.ts +111 -111
  50. package/src/circuit-breaker.test.ts +184 -184
  51. package/src/circuit-breaker.ts +169 -169
  52. package/src/cli/commands/auth.ts +963 -0
  53. package/src/cli/commands/config.ts +547 -0
  54. package/src/cli/formatting.test.ts +406 -0
  55. package/src/cli/formatting.ts +219 -0
  56. package/src/cli.ts +255 -2022
  57. package/src/commands/handlers/betas.ts +100 -0
  58. package/src/commands/handlers/config.ts +99 -0
  59. package/src/commands/handlers/files.ts +375 -0
  60. package/src/commands/oauth-flow.ts +181 -166
  61. package/src/commands/prompts.ts +61 -61
  62. package/src/commands/router.test.ts +421 -0
  63. package/src/commands/router.ts +143 -635
  64. package/src/config.test.ts +482 -482
  65. package/src/config.ts +412 -404
  66. package/src/constants.ts +48 -48
  67. package/src/drift/cch-constants.ts +95 -95
  68. package/src/env.ts +111 -105
  69. package/src/headers/billing.ts +33 -33
  70. package/src/headers/builder.ts +130 -130
  71. package/src/headers/cch.ts +75 -75
  72. package/src/headers/stainless.ts +25 -25
  73. package/src/headers/user-agent.ts +23 -23
  74. package/src/index.ts +436 -828
  75. package/src/models.ts +27 -27
  76. package/src/oauth.test.ts +102 -102
  77. package/src/oauth.ts +178 -178
  78. package/src/parent-pid-watcher.test.ts +148 -148
  79. package/src/parent-pid-watcher.ts +69 -69
  80. package/src/plugin-helpers.ts +82 -82
  81. package/src/refresh-helpers.ts +145 -139
  82. package/src/refresh-lock.test.ts +94 -94
  83. package/src/refresh-lock.ts +93 -93
  84. package/src/request/body.history.test.ts +579 -571
  85. package/src/request/body.ts +255 -255
  86. package/src/request/metadata.ts +65 -65
  87. package/src/request/retry.test.ts +156 -156
  88. package/src/request/retry.ts +67 -67
  89. package/src/request/url.ts +21 -21
  90. package/src/request-orchestration-helpers.ts +648 -0
  91. package/src/response/index.ts +5 -5
  92. package/src/response/mcp.ts +58 -58
  93. package/src/response/streaming.test.ts +313 -311
  94. package/src/response/streaming.ts +412 -410
  95. package/src/rotation.test.ts +304 -301
  96. package/src/rotation.ts +205 -205
  97. package/src/storage.test.ts +547 -547
  98. package/src/storage.ts +315 -291
  99. package/src/system-prompt/builder.ts +38 -38
  100. package/src/system-prompt/index.ts +5 -5
  101. package/src/system-prompt/normalize.ts +60 -60
  102. package/src/system-prompt/sanitize.ts +30 -30
  103. package/src/thinking.ts +21 -20
  104. package/src/token-refresh.test.ts +265 -265
  105. package/src/token-refresh.ts +219 -214
  106. package/src/types.ts +30 -30
  107. package/dist/bun-proxy.mjs +0 -291
@@ -4,23 +4,23 @@ import { homedir } from "node:os";
4
4
  import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
5
5
 
6
6
  vi.mock("node:child_process", () => ({
7
- execSync: vi.fn(),
7
+ execSync: vi.fn(),
8
8
  }));
9
9
 
10
10
  vi.mock("node:fs", () => ({
11
- readFileSync: vi.fn(),
11
+ readFileSync: vi.fn(),
12
12
  }));
13
13
 
14
14
  vi.mock("node:os", () => ({
15
- homedir: vi.fn(() => "/mock-home"),
15
+ homedir: vi.fn(() => "/mock-home"),
16
16
  }));
17
17
 
18
18
  import type { CCCredential } from "../cc-credentials.js";
19
19
  import {
20
- parseCCCredentialData,
21
- readCCCredentials,
22
- readCCCredentialsFromFile,
23
- readCCCredentialsFromKeychain,
20
+ parseCCCredentialData,
21
+ readCCCredentials,
22
+ readCCCredentialsFromFile,
23
+ readCCCredentialsFromKeychain,
24
24
  } from "../cc-credentials.js";
25
25
 
26
26
  const mockExecSync = execSync as Mock;
@@ -29,298 +29,302 @@ const mockHomedir = homedir as Mock;
29
29
  const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
30
30
 
31
31
  function setPlatform(platform: NodeJS.Platform): void {
32
- Object.defineProperty(process, "platform", {
33
- value: platform,
34
- configurable: true,
35
- });
32
+ Object.defineProperty(process, "platform", {
33
+ value: platform,
34
+ configurable: true,
35
+ });
36
36
  }
37
37
 
38
38
  function restorePlatform(): void {
39
- if (originalPlatformDescriptor) {
40
- Object.defineProperty(process, "platform", originalPlatformDescriptor);
41
- }
39
+ if (originalPlatformDescriptor) {
40
+ Object.defineProperty(process, "platform", originalPlatformDescriptor);
41
+ }
42
42
  }
43
43
 
44
44
  function makeWrappedCredential(overrides: Record<string, unknown> = {}): string {
45
- return JSON.stringify({
46
- claudeAiOauth: {
47
- accessToken: "access-token",
48
- refreshToken: "refresh-token",
49
- expiresAt: 1_700_000_000_000,
50
- subscriptionType: "max",
51
- ...overrides,
52
- },
53
- });
45
+ return JSON.stringify({
46
+ claudeAiOauth: {
47
+ accessToken: "access-token",
48
+ refreshToken: "refresh-token",
49
+ expiresAt: 1_700_000_000_000,
50
+ subscriptionType: "max",
51
+ ...overrides,
52
+ },
53
+ });
54
54
  }
55
55
 
56
56
  function makeFlatCredential(overrides: Record<string, unknown> = {}): string {
57
- return JSON.stringify({
58
- accessToken: "flat-access",
59
- refreshToken: "flat-refresh",
60
- expiresAt: 1_800_000_000_000,
61
- subscriptionType: "pro",
62
- ...overrides,
63
- });
57
+ return JSON.stringify({
58
+ accessToken: "flat-access",
59
+ refreshToken: "flat-refresh",
60
+ expiresAt: 1_800_000_000_000,
61
+ subscriptionType: "pro",
62
+ ...overrides,
63
+ });
64
64
  }
65
65
 
66
66
  function makeSecurityError(status?: number, code?: string): Error {
67
- const error = new Error("security failed") as Error & { status?: number; code?: string };
68
- error.status = status;
69
- error.code = code;
70
- return error;
67
+ const error = new Error("security failed") as Error & { status?: number; code?: string };
68
+ error.status = status;
69
+ error.code = code;
70
+ return error;
71
71
  }
72
72
 
73
73
  describe("parseCCCredentialData", () => {
74
- beforeEach(() => {
75
- vi.resetAllMocks();
76
- mockHomedir.mockReturnValue("/mock-home");
77
- restorePlatform();
78
- });
79
-
80
- afterEach(() => {
81
- restorePlatform();
82
- });
83
-
84
- it("parses wrapped Claude Code credentials", () => {
85
- expect(parseCCCredentialData(makeWrappedCredential())).toEqual({
86
- accessToken: "access-token",
87
- refreshToken: "refresh-token",
88
- expiresAt: 1_700_000_000_000,
89
- subscriptionType: "max",
90
- source: "cc-file",
91
- label: "/mock-home/.claude/.credentials.json",
74
+ beforeEach(() => {
75
+ vi.resetAllMocks();
76
+ mockHomedir.mockReturnValue("/mock-home");
77
+ restorePlatform();
92
78
  });
93
- });
94
-
95
- it("parses flat credential format", () => {
96
- expect(parseCCCredentialData(makeFlatCredential())).toEqual({
97
- accessToken: "flat-access",
98
- refreshToken: "flat-refresh",
99
- expiresAt: 1_800_000_000_000,
100
- subscriptionType: "pro",
101
- source: "cc-file",
102
- label: "/mock-home/.claude/.credentials.json",
79
+
80
+ afterEach(() => {
81
+ restorePlatform();
103
82
  });
104
- });
105
83
 
106
- it("filters MCP-only payloads", () => {
107
- expect(parseCCCredentialData(JSON.stringify({ mcpOAuth: { accessToken: "mcp-only" } }))).toBeNull();
108
- });
84
+ it("parses wrapped Claude Code credentials", () => {
85
+ expect(parseCCCredentialData(makeWrappedCredential())).toEqual({
86
+ accessToken: "access-token",
87
+ refreshToken: "refresh-token",
88
+ expiresAt: 1_700_000_000_000,
89
+ subscriptionType: "max",
90
+ source: "cc-file",
91
+ label: "/mock-home/.claude/.credentials.json",
92
+ });
93
+ });
109
94
 
110
- it("returns null for malformed JSON", () => {
111
- expect(parseCCCredentialData("not-json")).toBeNull();
112
- });
95
+ it("parses flat credential format", () => {
96
+ expect(parseCCCredentialData(makeFlatCredential())).toEqual({
97
+ accessToken: "flat-access",
98
+ refreshToken: "flat-refresh",
99
+ expiresAt: 1_800_000_000_000,
100
+ subscriptionType: "pro",
101
+ source: "cc-file",
102
+ label: "/mock-home/.claude/.credentials.json",
103
+ });
104
+ });
105
+
106
+ it("filters MCP-only payloads", () => {
107
+ expect(parseCCCredentialData(JSON.stringify({ mcpOAuth: { accessToken: "mcp-only" } }))).toBeNull();
108
+ });
109
+
110
+ it("returns null for malformed JSON", () => {
111
+ expect(parseCCCredentialData("not-json")).toBeNull();
112
+ });
113
113
  });
114
114
 
115
115
  describe("readCCCredentialsFromKeychain", () => {
116
- beforeEach(() => {
117
- vi.resetAllMocks();
118
- mockHomedir.mockReturnValue("/mock-home");
119
- setPlatform("darwin");
120
- });
121
-
122
- afterEach(() => {
123
- restorePlatform();
124
- });
125
-
126
- it("reads multiple Claude Code services from macOS Keychain", () => {
127
- mockExecSync.mockImplementation((command: string) => {
128
- if (command === "security dump-keychain") {
129
- return [
130
- ' "svce"<blob>="Claude Code-credentials"',
131
- ' "svce"<blob>="Claude Code-credentials-abc123"',
132
- ' "svce"<blob>="ignored-service"',
133
- ].join("\n");
134
- }
135
-
136
- if (command === "security find-generic-password -s 'Claude Code-credentials' -w") {
137
- return makeWrappedCredential();
138
- }
139
-
140
- if (command === "security find-generic-password -s 'Claude Code-credentials-abc123' -w") {
141
- return makeFlatCredential({ accessToken: "access-2", refreshToken: "refresh-2", subscriptionType: "team" });
142
- }
143
-
144
- throw new Error(`unexpected command: ${command}`);
116
+ beforeEach(() => {
117
+ vi.resetAllMocks();
118
+ mockHomedir.mockReturnValue("/mock-home");
119
+ setPlatform("darwin");
145
120
  });
146
121
 
147
- expect(readCCCredentialsFromKeychain()).toEqual<CCCredential[]>([
148
- {
149
- accessToken: "access-token",
150
- refreshToken: "refresh-token",
151
- expiresAt: 1_700_000_000_000,
152
- subscriptionType: "max",
153
- source: "cc-keychain",
154
- label: "Claude Code-credentials",
155
- },
156
- {
157
- accessToken: "access-2",
158
- refreshToken: "refresh-2",
159
- expiresAt: 1_800_000_000_000,
160
- subscriptionType: "team",
161
- source: "cc-keychain",
162
- label: "Claude Code-credentials-abc123",
163
- },
164
- ]);
165
-
166
- expect(mockExecSync).toHaveBeenCalledWith("security dump-keychain", {
167
- encoding: "utf-8",
168
- timeout: 5000,
122
+ afterEach(() => {
123
+ restorePlatform();
169
124
  });
170
- });
171
-
172
- it.each([44, 36, 128])("returns null for handled security exit code %i", (status) => {
173
- mockExecSync.mockImplementation((command: string) => {
174
- if (command === "security dump-keychain") {
175
- throw makeSecurityError(status);
176
- }
177
- return "";
125
+
126
+ it("reads multiple Claude Code services from macOS Keychain", () => {
127
+ mockExecSync.mockImplementation((command: string) => {
128
+ if (command === "security dump-keychain") {
129
+ return [
130
+ ' "svce"<blob>="Claude Code-credentials"',
131
+ ' "svce"<blob>="Claude Code-credentials-abc123"',
132
+ ' "svce"<blob>="ignored-service"',
133
+ ].join("\n");
134
+ }
135
+
136
+ if (command === "security find-generic-password -s 'Claude Code-credentials' -w") {
137
+ return makeWrappedCredential();
138
+ }
139
+
140
+ if (command === "security find-generic-password -s 'Claude Code-credentials-abc123' -w") {
141
+ return makeFlatCredential({
142
+ accessToken: "access-2",
143
+ refreshToken: "refresh-2",
144
+ subscriptionType: "team",
145
+ });
146
+ }
147
+
148
+ throw new Error(`unexpected command: ${command}`);
149
+ });
150
+
151
+ expect(readCCCredentialsFromKeychain()).toEqual<CCCredential[]>([
152
+ {
153
+ accessToken: "access-token",
154
+ refreshToken: "refresh-token",
155
+ expiresAt: 1_700_000_000_000,
156
+ subscriptionType: "max",
157
+ source: "cc-keychain",
158
+ label: "Claude Code-credentials",
159
+ },
160
+ {
161
+ accessToken: "access-2",
162
+ refreshToken: "refresh-2",
163
+ expiresAt: 1_800_000_000_000,
164
+ subscriptionType: "team",
165
+ source: "cc-keychain",
166
+ label: "Claude Code-credentials-abc123",
167
+ },
168
+ ]);
169
+
170
+ expect(mockExecSync).toHaveBeenCalledWith("security dump-keychain", {
171
+ encoding: "utf-8",
172
+ timeout: 5000,
173
+ });
178
174
  });
179
175
 
180
- expect(readCCCredentialsFromKeychain()).toBeNull();
181
- });
176
+ it.each([44, 36, 128])("returns null for handled security exit code %i", (status) => {
177
+ mockExecSync.mockImplementation((command: string) => {
178
+ if (command === "security dump-keychain") {
179
+ throw makeSecurityError(status);
180
+ }
181
+ return "";
182
+ });
182
183
 
183
- it("returns null when security command times out", () => {
184
- mockExecSync.mockImplementation((command: string) => {
185
- if (command === "security dump-keychain") {
186
- throw makeSecurityError(undefined, "ETIMEDOUT");
187
- }
188
- return "";
184
+ expect(readCCCredentialsFromKeychain()).toBeNull();
189
185
  });
190
186
 
191
- expect(readCCCredentialsFromKeychain()).toBeNull();
192
- });
187
+ it("returns null when security command times out", () => {
188
+ mockExecSync.mockImplementation((command: string) => {
189
+ if (command === "security dump-keychain") {
190
+ throw makeSecurityError(undefined, "ETIMEDOUT");
191
+ }
192
+ return "";
193
+ });
194
+
195
+ expect(readCCCredentialsFromKeychain()).toBeNull();
196
+ });
193
197
 
194
- it("returns null when a service payload is missing usable Claude credentials", () => {
195
- mockExecSync.mockImplementation((command: string) => {
196
- if (command === "security dump-keychain") {
197
- return ' "svce"<blob>="Claude Code-credentials"';
198
- }
198
+ it("returns null when a service payload is missing usable Claude credentials", () => {
199
+ mockExecSync.mockImplementation((command: string) => {
200
+ if (command === "security dump-keychain") {
201
+ return ' "svce"<blob>="Claude Code-credentials"';
202
+ }
199
203
 
200
- if (command === "security find-generic-password -s 'Claude Code-credentials' -w") {
201
- return JSON.stringify({ mcpOAuth: { accessToken: "mcp-only" } });
202
- }
204
+ if (command === "security find-generic-password -s 'Claude Code-credentials' -w") {
205
+ return JSON.stringify({ mcpOAuth: { accessToken: "mcp-only" } });
206
+ }
203
207
 
204
- throw new Error(`unexpected command: ${command}`);
205
- });
208
+ throw new Error(`unexpected command: ${command}`);
209
+ });
206
210
 
207
- expect(readCCCredentialsFromKeychain()).toBeNull();
208
- });
211
+ expect(readCCCredentialsFromKeychain()).toBeNull();
212
+ });
209
213
  });
210
214
 
211
215
  describe("readCCCredentialsFromFile", () => {
212
- beforeEach(() => {
213
- vi.resetAllMocks();
214
- mockHomedir.mockReturnValue("/mock-home");
215
- restorePlatform();
216
- });
217
-
218
- afterEach(() => {
219
- restorePlatform();
220
- });
221
-
222
- it("reads wrapped credentials from ~/.claude/.credentials.json", () => {
223
- mockReadFileSync.mockReturnValue(makeWrappedCredential());
224
-
225
- expect(readCCCredentialsFromFile()).toEqual<CCCredential>({
226
- accessToken: "access-token",
227
- refreshToken: "refresh-token",
228
- expiresAt: 1_700_000_000_000,
229
- subscriptionType: "max",
230
- source: "cc-file",
231
- label: "/mock-home/.claude/.credentials.json",
216
+ beforeEach(() => {
217
+ vi.resetAllMocks();
218
+ mockHomedir.mockReturnValue("/mock-home");
219
+ restorePlatform();
232
220
  });
233
- });
234
-
235
- it("reads flat credentials from ~/.claude/.credentials.json", () => {
236
- mockReadFileSync.mockReturnValue(makeFlatCredential());
237
-
238
- expect(readCCCredentialsFromFile()).toEqual<CCCredential>({
239
- accessToken: "flat-access",
240
- refreshToken: "flat-refresh",
241
- expiresAt: 1_800_000_000_000,
242
- subscriptionType: "pro",
243
- source: "cc-file",
244
- label: "/mock-home/.claude/.credentials.json",
221
+
222
+ afterEach(() => {
223
+ restorePlatform();
245
224
  });
246
- });
247
225
 
248
- it("returns null when the credentials file is missing", () => {
249
- const error = new Error("missing") as Error & { code?: string };
250
- error.code = "ENOENT";
251
- mockReadFileSync.mockImplementation(() => {
252
- throw error;
226
+ it("reads wrapped credentials from ~/.claude/.credentials.json", () => {
227
+ mockReadFileSync.mockReturnValue(makeWrappedCredential());
228
+
229
+ expect(readCCCredentialsFromFile()).toEqual<CCCredential>({
230
+ accessToken: "access-token",
231
+ refreshToken: "refresh-token",
232
+ expiresAt: 1_700_000_000_000,
233
+ subscriptionType: "max",
234
+ source: "cc-file",
235
+ label: "/mock-home/.claude/.credentials.json",
236
+ });
253
237
  });
254
238
 
255
- expect(readCCCredentialsFromFile()).toBeNull();
256
- });
239
+ it("reads flat credentials from ~/.claude/.credentials.json", () => {
240
+ mockReadFileSync.mockReturnValue(makeFlatCredential());
241
+
242
+ expect(readCCCredentialsFromFile()).toEqual<CCCredential>({
243
+ accessToken: "flat-access",
244
+ refreshToken: "flat-refresh",
245
+ expiresAt: 1_800_000_000_000,
246
+ subscriptionType: "pro",
247
+ source: "cc-file",
248
+ label: "/mock-home/.claude/.credentials.json",
249
+ });
250
+ });
257
251
 
258
- it("returns null when the file is malformed", () => {
259
- mockReadFileSync.mockReturnValue("not-json");
252
+ it("returns null when the credentials file is missing", () => {
253
+ const error = new Error("missing") as Error & { code?: string };
254
+ error.code = "ENOENT";
255
+ mockReadFileSync.mockImplementation(() => {
256
+ throw error;
257
+ });
258
+
259
+ expect(readCCCredentialsFromFile()).toBeNull();
260
+ });
260
261
 
261
- expect(readCCCredentialsFromFile()).toBeNull();
262
- });
262
+ it("returns null when the file is malformed", () => {
263
+ mockReadFileSync.mockReturnValue("not-json");
264
+
265
+ expect(readCCCredentialsFromFile()).toBeNull();
266
+ });
263
267
  });
264
268
 
265
269
  describe("readCCCredentials", () => {
266
- beforeEach(() => {
267
- vi.resetAllMocks();
268
- mockHomedir.mockReturnValue("/mock-home");
269
- });
270
+ beforeEach(() => {
271
+ vi.resetAllMocks();
272
+ mockHomedir.mockReturnValue("/mock-home");
273
+ });
270
274
 
271
- afterEach(() => {
272
- restorePlatform();
273
- });
275
+ afterEach(() => {
276
+ restorePlatform();
277
+ });
274
278
 
275
- it("skips Keychain on non-macOS platforms and still reads the file", () => {
276
- setPlatform("linux");
277
- mockReadFileSync.mockReturnValue(makeFlatCredential());
279
+ it("skips Keychain on non-macOS platforms and still reads the file", () => {
280
+ setPlatform("linux");
281
+ mockReadFileSync.mockReturnValue(makeFlatCredential());
282
+
283
+ expect(readCCCredentials()).toEqual<CCCredential[]>([
284
+ {
285
+ accessToken: "flat-access",
286
+ refreshToken: "flat-refresh",
287
+ expiresAt: 1_800_000_000_000,
288
+ subscriptionType: "pro",
289
+ source: "cc-file",
290
+ label: "/mock-home/.claude/.credentials.json",
291
+ },
292
+ ]);
293
+ expect(mockExecSync).not.toHaveBeenCalled();
294
+ });
278
295
 
279
- expect(readCCCredentials()).toEqual<CCCredential[]>([
280
- {
281
- accessToken: "flat-access",
282
- refreshToken: "flat-refresh",
283
- expiresAt: 1_800_000_000_000,
284
- subscriptionType: "pro",
285
- source: "cc-file",
286
- label: "/mock-home/.claude/.credentials.json",
287
- },
288
- ]);
289
- expect(mockExecSync).not.toHaveBeenCalled();
290
- });
291
-
292
- it("combines Keychain and file credentials on macOS", () => {
293
- setPlatform("darwin");
294
- mockExecSync.mockImplementation((command: string) => {
295
- if (command === "security dump-keychain") {
296
- return ' "svce"<blob>="Claude Code-credentials"';
297
- }
298
-
299
- if (command === "security find-generic-password -s 'Claude Code-credentials' -w") {
300
- return makeWrappedCredential();
301
- }
302
-
303
- throw new Error(`unexpected command: ${command}`);
296
+ it("combines Keychain and file credentials on macOS", () => {
297
+ setPlatform("darwin");
298
+ mockExecSync.mockImplementation((command: string) => {
299
+ if (command === "security dump-keychain") {
300
+ return ' "svce"<blob>="Claude Code-credentials"';
301
+ }
302
+
303
+ if (command === "security find-generic-password -s 'Claude Code-credentials' -w") {
304
+ return makeWrappedCredential();
305
+ }
306
+
307
+ throw new Error(`unexpected command: ${command}`);
308
+ });
309
+ mockReadFileSync.mockReturnValue(makeFlatCredential());
310
+
311
+ expect(readCCCredentials()).toEqual<CCCredential[]>([
312
+ {
313
+ accessToken: "access-token",
314
+ refreshToken: "refresh-token",
315
+ expiresAt: 1_700_000_000_000,
316
+ subscriptionType: "max",
317
+ source: "cc-keychain",
318
+ label: "Claude Code-credentials",
319
+ },
320
+ {
321
+ accessToken: "flat-access",
322
+ refreshToken: "flat-refresh",
323
+ expiresAt: 1_800_000_000_000,
324
+ subscriptionType: "pro",
325
+ source: "cc-file",
326
+ label: "/mock-home/.claude/.credentials.json",
327
+ },
328
+ ]);
304
329
  });
305
- mockReadFileSync.mockReturnValue(makeFlatCredential());
306
-
307
- expect(readCCCredentials()).toEqual<CCCredential[]>([
308
- {
309
- accessToken: "access-token",
310
- refreshToken: "refresh-token",
311
- expiresAt: 1_700_000_000_000,
312
- subscriptionType: "max",
313
- source: "cc-keychain",
314
- label: "Claude Code-credentials",
315
- },
316
- {
317
- accessToken: "flat-access",
318
- refreshToken: "flat-refresh",
319
- expiresAt: 1_800_000_000_000,
320
- subscriptionType: "pro",
321
- source: "cc-file",
322
- label: "/mock-home/.claude/.credentials.json",
323
- },
324
- ]);
325
- });
326
330
  });