berget 2.2.7 → 2.2.8

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 (130) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +1 -1
  3. package/.prettierrc +5 -3
  4. package/dist/index.js +24 -25
  5. package/dist/package.json +5 -3
  6. package/dist/src/agents/app.js +8 -8
  7. package/dist/src/agents/backend.js +3 -3
  8. package/dist/src/agents/devops.js +8 -8
  9. package/dist/src/agents/frontend.js +3 -3
  10. package/dist/src/agents/fullstack.js +3 -3
  11. package/dist/src/agents/index.js +18 -18
  12. package/dist/src/agents/quality.js +8 -8
  13. package/dist/src/agents/security.js +8 -8
  14. package/dist/src/client.js +115 -127
  15. package/dist/src/commands/api-keys.js +195 -202
  16. package/dist/src/commands/auth.js +16 -25
  17. package/dist/src/commands/autocomplete.js +8 -8
  18. package/dist/src/commands/billing.js +10 -19
  19. package/dist/src/commands/chat.js +139 -170
  20. package/dist/src/commands/clusters.js +21 -30
  21. package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
  22. package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
  23. package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
  24. package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
  25. package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
  26. package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
  27. package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
  28. package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
  29. package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
  30. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
  31. package/dist/src/commands/code/auth-sync.js +215 -228
  32. package/dist/src/commands/code/errors.js +15 -12
  33. package/dist/src/commands/code/setup.js +390 -425
  34. package/dist/src/commands/code.js +279 -294
  35. package/dist/src/commands/index.js +5 -5
  36. package/dist/src/commands/models.js +16 -25
  37. package/dist/src/commands/users.js +9 -18
  38. package/dist/src/constants/command-structure.js +138 -138
  39. package/dist/src/services/api-key-service.js +132 -152
  40. package/dist/src/services/auth-service.js +81 -95
  41. package/dist/src/services/browser-auth.js +121 -131
  42. package/dist/src/services/chat-service.js +369 -386
  43. package/dist/src/services/cluster-service.js +47 -62
  44. package/dist/src/services/collaborator-service.js +9 -21
  45. package/dist/src/services/flux-service.js +13 -25
  46. package/dist/src/services/helm-service.js +9 -21
  47. package/dist/src/services/kubectl-service.js +15 -29
  48. package/dist/src/utils/config-checker.js +7 -7
  49. package/dist/src/utils/config-loader.js +109 -109
  50. package/dist/src/utils/default-api-key.js +129 -139
  51. package/dist/src/utils/env-manager.js +55 -66
  52. package/dist/src/utils/error-handler.js +62 -62
  53. package/dist/src/utils/logger.js +74 -67
  54. package/dist/src/utils/markdown-renderer.js +28 -28
  55. package/dist/src/utils/opencode-validator.js +67 -69
  56. package/dist/src/utils/token-manager.js +67 -65
  57. package/dist/tests/commands/chat.test.js +30 -39
  58. package/dist/tests/commands/code.test.js +186 -195
  59. package/dist/tests/utils/config-loader.test.js +107 -107
  60. package/dist/tests/utils/env-manager.test.js +81 -90
  61. package/dist/tests/utils/opencode-validator.test.js +42 -41
  62. package/dist/vitest.config.js +1 -1
  63. package/eslint.config.mjs +50 -30
  64. package/index.ts +30 -31
  65. package/package.json +5 -3
  66. package/src/agents/app.ts +9 -9
  67. package/src/agents/backend.ts +4 -4
  68. package/src/agents/devops.ts +9 -9
  69. package/src/agents/frontend.ts +4 -4
  70. package/src/agents/fullstack.ts +4 -4
  71. package/src/agents/index.ts +27 -25
  72. package/src/agents/quality.ts +9 -9
  73. package/src/agents/security.ts +9 -9
  74. package/src/agents/types.ts +10 -10
  75. package/src/client.ts +85 -77
  76. package/src/commands/api-keys.ts +190 -185
  77. package/src/commands/auth.ts +15 -14
  78. package/src/commands/autocomplete.ts +10 -10
  79. package/src/commands/billing.ts +13 -12
  80. package/src/commands/chat.ts +145 -142
  81. package/src/commands/clusters.ts +20 -19
  82. package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
  83. package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
  84. package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
  85. package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
  86. package/src/commands/code/__tests__/fake-file-store.ts +15 -15
  87. package/src/commands/code/__tests__/fake-prompter.ts +86 -85
  88. package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
  89. package/src/commands/code/adapters/clack-prompter.ts +32 -30
  90. package/src/commands/code/adapters/fs-file-store.ts +18 -17
  91. package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
  92. package/src/commands/code/auth-sync.ts +210 -210
  93. package/src/commands/code/errors.ts +11 -11
  94. package/src/commands/code/ports/auth-services.ts +7 -7
  95. package/src/commands/code/ports/command-runner.ts +2 -2
  96. package/src/commands/code/ports/file-store.ts +3 -3
  97. package/src/commands/code/ports/prompter.ts +13 -13
  98. package/src/commands/code/setup.ts +408 -406
  99. package/src/commands/code.ts +288 -287
  100. package/src/commands/index.ts +11 -10
  101. package/src/commands/models.ts +19 -18
  102. package/src/commands/users.ts +11 -10
  103. package/src/constants/command-structure.ts +159 -159
  104. package/src/services/api-key-service.ts +85 -85
  105. package/src/services/auth-service.ts +55 -54
  106. package/src/services/browser-auth.ts +62 -62
  107. package/src/services/chat-service.ts +169 -170
  108. package/src/services/cluster-service.ts +28 -28
  109. package/src/services/collaborator-service.ts +6 -6
  110. package/src/services/flux-service.ts +17 -17
  111. package/src/services/helm-service.ts +11 -11
  112. package/src/services/kubectl-service.ts +12 -12
  113. package/src/types/api.d.ts +1933 -1933
  114. package/src/types/json.d.ts +1 -1
  115. package/src/utils/config-checker.ts +6 -6
  116. package/src/utils/config-loader.ts +130 -129
  117. package/src/utils/default-api-key.ts +81 -80
  118. package/src/utils/env-manager.ts +37 -37
  119. package/src/utils/error-handler.ts +64 -64
  120. package/src/utils/logger.ts +72 -66
  121. package/src/utils/markdown-renderer.ts +28 -28
  122. package/src/utils/opencode-validator.ts +72 -71
  123. package/src/utils/token-manager.ts +69 -68
  124. package/tests/commands/chat.test.ts +32 -31
  125. package/tests/commands/code.test.ts +182 -181
  126. package/tests/utils/config-loader.test.ts +111 -110
  127. package/tests/utils/env-manager.test.ts +83 -79
  128. package/tests/utils/opencode-validator.test.ts +43 -42
  129. package/tsconfig.json +2 -1
  130. package/vitest.config.ts +2 -2
@@ -1,348 +1,351 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  const vitest_1 = require("vitest");
13
4
  const auth_sync_1 = require("../auth-sync");
5
+ const fake_api_key_service_1 = require("./fake-api-key-service");
6
+ const fake_auth_service_1 = require("./fake-auth-service");
14
7
  const fake_file_store_1 = require("./fake-file-store");
15
8
  const fake_prompter_1 = require("./fake-prompter");
16
- const fake_auth_service_1 = require("./fake-auth-service");
17
- const fake_api_key_service_1 = require("./fake-api-key-service");
18
9
  function base64urlEncode(data) {
19
- return Buffer.from(data).toString("base64url");
10
+ return Buffer.from(data).toString('base64url');
20
11
  }
21
12
  function makeJwt(payload) {
22
- const header = base64urlEncode(JSON.stringify({ alg: "none", typ: "JWT" }));
13
+ const header = base64urlEncode(JSON.stringify({ alg: 'none', typ: 'JWT' }));
23
14
  const body = base64urlEncode(JSON.stringify(payload));
24
15
  return `${header}.${body}.signature`;
25
16
  }
26
- const HOME = "/home/user";
27
- const fakeCliAuth = (overrides = {}) => (Object.assign({ access_token: makeJwt({
28
- realm_access: { roles: ["default-roles-berget"] },
17
+ const HOME = '/home/user';
18
+ const fakeCliAuth = (overrides = {}) => ({
19
+ access_token: makeJwt({
29
20
  exp: 9999999999999, // JWT exp in seconds (different from expires_at in ms)
30
- }), refresh_token: "refreshtoken", expires_at: 9999999999999 }, overrides));
31
- (0, vitest_1.describe)("readCliAuth", () => {
32
- (0, vitest_1.it)("returns null when auth file does not exist", () => __awaiter(void 0, void 0, void 0, function* () {
21
+ realm_access: { roles: ['default-roles-berget'] },
22
+ }),
23
+ expires_at: 9999999999999,
24
+ refresh_token: 'refreshtoken',
25
+ ...overrides,
26
+ });
27
+ (0, vitest_1.describe)('readCliAuth', () => {
28
+ (0, vitest_1.it)('returns null when auth file does not exist', async () => {
33
29
  const files = new fake_file_store_1.FakeFileStore();
34
- const result = yield (0, auth_sync_1.readCliAuth)(files, HOME);
30
+ const result = await (0, auth_sync_1.readCliAuth)(files, HOME);
35
31
  (0, vitest_1.expect)(result).toBeNull();
36
- }));
37
- (0, vitest_1.it)("parses valid auth file", () => __awaiter(void 0, void 0, void 0, function* () {
32
+ });
33
+ (0, vitest_1.it)('parses valid auth file', async () => {
38
34
  const files = new fake_file_store_1.FakeFileStore();
39
35
  const auth = fakeCliAuth();
40
- files.seed(HOME + "/.berget/auth.json", JSON.stringify(auth));
41
- const result = yield (0, auth_sync_1.readCliAuth)(files, HOME);
36
+ files.seed(HOME + '/.berget/auth.json', JSON.stringify(auth));
37
+ const result = await (0, auth_sync_1.readCliAuth)(files, HOME);
42
38
  // The JWT's exp claim should be extracted and converted to milliseconds
43
- const jwtPayload = JSON.parse(Buffer.from(auth.access_token.split(".")[1], "base64url").toString());
39
+ const jwtPayload = JSON.parse(Buffer.from(auth.access_token.split('.')[1], 'base64url').toString());
44
40
  const expectedAuth = {
45
41
  access_token: auth.access_token,
46
- refresh_token: auth.refresh_token,
47
42
  expires_at: jwtPayload.exp * 1000,
43
+ refresh_token: auth.refresh_token,
48
44
  };
49
45
  (0, vitest_1.expect)(result).toEqual(expectedAuth);
50
- }));
51
- (0, vitest_1.it)("returns null for malformed JSON", () => __awaiter(void 0, void 0, void 0, function* () {
46
+ });
47
+ (0, vitest_1.it)('returns null for malformed JSON', async () => {
52
48
  const files = new fake_file_store_1.FakeFileStore();
53
- files.seed(HOME + "/.berget/auth.json", "not json");
54
- const result = yield (0, auth_sync_1.readCliAuth)(files, HOME);
49
+ files.seed(HOME + '/.berget/auth.json', 'not json');
50
+ const result = await (0, auth_sync_1.readCliAuth)(files, HOME);
55
51
  (0, vitest_1.expect)(result).toBeNull();
56
- }));
57
- (0, vitest_1.it)("returns null when fields are missing", () => __awaiter(void 0, void 0, void 0, function* () {
52
+ });
53
+ (0, vitest_1.it)('returns null when fields are missing', async () => {
58
54
  const files = new fake_file_store_1.FakeFileStore();
59
- files.seed(HOME + "/.berget/auth.json", JSON.stringify({ access_token: "only" }));
60
- const result = yield (0, auth_sync_1.readCliAuth)(files, HOME);
55
+ files.seed(HOME + '/.berget/auth.json', JSON.stringify({ access_token: 'only' }));
56
+ const result = await (0, auth_sync_1.readCliAuth)(files, HOME);
61
57
  (0, vitest_1.expect)(result).toBeNull();
62
- }));
58
+ });
63
59
  });
64
- (0, vitest_1.describe)("isToolAuthenticated", () => {
65
- (0, vitest_1.it)("returns false when auth file does not exist", () => __awaiter(void 0, void 0, void 0, function* () {
60
+ (0, vitest_1.describe)('isToolAuthenticated', () => {
61
+ (0, vitest_1.it)('returns false when auth file does not exist', async () => {
66
62
  const files = new fake_file_store_1.FakeFileStore();
67
- const result = yield (0, auth_sync_1.isToolAuthenticated)(files, HOME, "opencode");
63
+ const result = await (0, auth_sync_1.isToolAuthenticated)(files, HOME, 'opencode');
68
64
  (0, vitest_1.expect)(result).toBe(false);
69
- }));
70
- (0, vitest_1.it)("returns true when berget entry exists", () => __awaiter(void 0, void 0, void 0, function* () {
65
+ });
66
+ (0, vitest_1.it)('returns true when berget entry exists', async () => {
71
67
  const files = new fake_file_store_1.FakeFileStore();
72
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ berget: { type: "oauth", access: "tok" } }));
73
- const result = yield (0, auth_sync_1.isToolAuthenticated)(files, HOME, "opencode");
68
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ berget: { access: 'tok', type: 'oauth' } }));
69
+ const result = await (0, auth_sync_1.isToolAuthenticated)(files, HOME, 'opencode');
74
70
  (0, vitest_1.expect)(result).toBe(true);
75
- }));
76
- (0, vitest_1.it)("returns false when berget entry is missing", () => __awaiter(void 0, void 0, void 0, function* () {
71
+ });
72
+ (0, vitest_1.it)('returns false when berget entry is missing', async () => {
77
73
  const files = new fake_file_store_1.FakeFileStore();
78
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ openai: { type: "api" } }));
79
- const result = yield (0, auth_sync_1.isToolAuthenticated)(files, HOME, "opencode");
74
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ openai: { type: 'api' } }));
75
+ const result = await (0, auth_sync_1.isToolAuthenticated)(files, HOME, 'opencode');
80
76
  (0, vitest_1.expect)(result).toBe(false);
81
- }));
82
- (0, vitest_1.it)("checks correct path for pi", () => __awaiter(void 0, void 0, void 0, function* () {
77
+ });
78
+ (0, vitest_1.it)('checks correct path for pi', async () => {
83
79
  const files = new fake_file_store_1.FakeFileStore();
84
- files.seed(HOME + "/.pi/agent/auth.json", JSON.stringify({ berget: { type: "oauth" } }));
85
- const result = yield (0, auth_sync_1.isToolAuthenticated)(files, HOME, "pi");
80
+ files.seed(HOME + '/.pi/agent/auth.json', JSON.stringify({ berget: { type: 'oauth' } }));
81
+ const result = await (0, auth_sync_1.isToolAuthenticated)(files, HOME, 'pi');
86
82
  (0, vitest_1.expect)(result).toBe(true);
87
- }));
83
+ });
88
84
  });
89
- (0, vitest_1.describe)("decodeJwtPayload", () => {
90
- (0, vitest_1.it)("decodes a valid JWT payload", () => {
91
- const payload = { sub: "123", realm_access: { roles: ["admin"] } };
85
+ (0, vitest_1.describe)('decodeJwtPayload', () => {
86
+ (0, vitest_1.it)('decodes a valid JWT payload', () => {
87
+ const payload = { realm_access: { roles: ['admin'] }, sub: '123' };
92
88
  const jwt = makeJwt(payload);
93
89
  (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)(jwt)).toEqual(payload);
94
90
  });
95
- (0, vitest_1.it)("returns null for invalid format", () => {
96
- (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)("not.a")).toBeNull();
97
- (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)("onlyOnePart")).toBeNull();
91
+ (0, vitest_1.it)('returns null for invalid format', () => {
92
+ (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)('not.a')).toBeNull();
93
+ (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)('onlyOnePart')).toBeNull();
98
94
  });
99
- (0, vitest_1.it)("returns null for invalid base64", () => {
100
- (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)("header.bad\.base64.signature")).toBeNull();
95
+ (0, vitest_1.it)('returns null for invalid base64', () => {
96
+ (0, vitest_1.expect)((0, auth_sync_1.decodeJwtPayload)('header.bad\.base64.signature')).toBeNull();
101
97
  });
102
98
  });
103
- (0, vitest_1.describe)("hasBergetCodeSeat", () => {
104
- (0, vitest_1.it)("returns true when berget_code_seat is present", () => {
99
+ (0, vitest_1.describe)('hasBergetCodeSeat', () => {
100
+ (0, vitest_1.it)('returns true when berget_code_seat is present', () => {
105
101
  const token = makeJwt({
106
- realm_access: { roles: ["berget_code_seat", "default-roles-berget"] },
102
+ realm_access: { roles: ['berget_code_seat', 'default-roles-berget'] },
107
103
  });
108
104
  (0, vitest_1.expect)((0, auth_sync_1.hasBergetCodeSeat)(token)).toBe(true);
109
105
  });
110
- (0, vitest_1.it)("returns false when role is missing", () => {
106
+ (0, vitest_1.it)('returns false when role is missing', () => {
111
107
  const token = makeJwt({
112
- realm_access: { roles: ["default-roles-berget"] },
108
+ realm_access: { roles: ['default-roles-berget'] },
113
109
  });
114
110
  (0, vitest_1.expect)((0, auth_sync_1.hasBergetCodeSeat)(token)).toBe(false);
115
111
  });
116
- (0, vitest_1.it)("returns false when realm_access is missing", () => {
117
- const token = makeJwt({ sub: "123" });
112
+ (0, vitest_1.it)('returns false when realm_access is missing', () => {
113
+ const token = makeJwt({ sub: '123' });
118
114
  (0, vitest_1.expect)((0, auth_sync_1.hasBergetCodeSeat)(token)).toBe(false);
119
115
  });
120
- (0, vitest_1.it)("returns false for invalid JWT", () => {
121
- (0, vitest_1.expect)((0, auth_sync_1.hasBergetCodeSeat)("invalid")).toBe(false);
116
+ (0, vitest_1.it)('returns false for invalid JWT', () => {
117
+ (0, vitest_1.expect)((0, auth_sync_1.hasBergetCodeSeat)('invalid')).toBe(false);
122
118
  });
123
119
  });
124
- (0, vitest_1.describe)("syncOAuthToTool", () => {
125
- (0, vitest_1.it)("writes oauth tokens to opencode auth file", () => __awaiter(void 0, void 0, void 0, function* () {
120
+ (0, vitest_1.describe)('syncOAuthToTool', () => {
121
+ (0, vitest_1.it)('writes oauth tokens to opencode auth file', async () => {
126
122
  const files = new fake_file_store_1.FakeFileStore();
127
123
  const auth = fakeCliAuth();
128
- yield (0, auth_sync_1.syncOAuthToTool)(files, HOME, "opencode", auth);
124
+ await (0, auth_sync_1.syncOAuthToTool)(files, HOME, 'opencode', auth);
129
125
  const written = files.getWrittenFiles();
130
- const content = written.get(HOME + "/.local/share/opencode/auth.json");
126
+ const content = written.get(HOME + '/.local/share/opencode/auth.json');
131
127
  const parsed = JSON.parse(content);
132
128
  // The expires field should now use the JWT's exp claim (converted to milliseconds)
133
- const jwtPayload = JSON.parse(Buffer.from(auth.access_token.split(".")[1], "base64url").toString());
129
+ const jwtPayload = JSON.parse(Buffer.from(auth.access_token.split('.')[1], 'base64url').toString());
134
130
  (0, vitest_1.expect)(parsed.berget).toEqual({
135
- type: "oauth",
136
131
  access: auth.access_token,
137
- refresh: auth.refresh_token,
138
132
  expires: jwtPayload.exp * 1000,
133
+ refresh: auth.refresh_token,
134
+ type: 'oauth',
139
135
  });
140
- }));
141
- (0, vitest_1.it)("writes oauth tokens to pi auth file", () => __awaiter(void 0, void 0, void 0, function* () {
136
+ });
137
+ (0, vitest_1.it)('writes oauth tokens to pi auth file', async () => {
142
138
  const files = new fake_file_store_1.FakeFileStore();
143
139
  const auth = fakeCliAuth();
144
- yield (0, auth_sync_1.syncOAuthToTool)(files, HOME, "pi", auth);
140
+ await (0, auth_sync_1.syncOAuthToTool)(files, HOME, 'pi', auth);
145
141
  const written = files.getWrittenFiles();
146
- const content = written.get(HOME + "/.pi/agent/auth.json");
142
+ const content = written.get(HOME + '/.pi/agent/auth.json');
147
143
  const parsed = JSON.parse(content);
148
- (0, vitest_1.expect)(parsed.berget.type).toBe("oauth");
149
- }));
150
- (0, vitest_1.it)("merges with existing providers", () => __awaiter(void 0, void 0, void 0, function* () {
144
+ (0, vitest_1.expect)(parsed.berget.type).toBe('oauth');
145
+ });
146
+ (0, vitest_1.it)('merges with existing providers', async () => {
151
147
  const files = new fake_file_store_1.FakeFileStore();
152
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ openai: { type: "api", key: "sk-openai" } }));
148
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ openai: { key: 'sk-openai', type: 'api' } }));
153
149
  const auth = fakeCliAuth();
154
- yield (0, auth_sync_1.syncOAuthToTool)(files, HOME, "opencode", auth);
150
+ await (0, auth_sync_1.syncOAuthToTool)(files, HOME, 'opencode', auth);
155
151
  const written = files.getWrittenFiles();
156
- const parsed = JSON.parse(written.get(HOME + "/.local/share/opencode/auth.json"));
157
- (0, vitest_1.expect)(parsed.openai).toEqual({ type: "api", key: "sk-openai" });
158
- (0, vitest_1.expect)(parsed.berget.type).toBe("oauth");
159
- }));
160
- (0, vitest_1.it)("sets 0o600 permissions on the auth file", () => __awaiter(void 0, void 0, void 0, function* () {
152
+ const parsed = JSON.parse(written.get(HOME + '/.local/share/opencode/auth.json'));
153
+ (0, vitest_1.expect)(parsed.openai).toEqual({ key: 'sk-openai', type: 'api' });
154
+ (0, vitest_1.expect)(parsed.berget.type).toBe('oauth');
155
+ });
156
+ (0, vitest_1.it)('sets 0o600 permissions on the auth file', async () => {
161
157
  const files = new fake_file_store_1.FakeFileStore();
162
158
  const auth = fakeCliAuth();
163
- yield (0, auth_sync_1.syncOAuthToTool)(files, HOME, "opencode", auth);
159
+ await (0, auth_sync_1.syncOAuthToTool)(files, HOME, 'opencode', auth);
164
160
  const chmodCalls = files.getChmodCalls();
165
161
  (0, vitest_1.expect)(chmodCalls).toHaveLength(1);
166
162
  (0, vitest_1.expect)(chmodCalls[0]).toEqual({
167
- path: HOME + "/.local/share/opencode/auth.json",
168
163
  mode: 0o600,
164
+ path: HOME + '/.local/share/opencode/auth.json',
169
165
  });
170
- }));
166
+ });
171
167
  });
172
- (0, vitest_1.describe)("syncApiKeyToTool", () => {
173
- (0, vitest_1.it)('writes api key to opencode auth file with type "api"', () => __awaiter(void 0, void 0, void 0, function* () {
168
+ (0, vitest_1.describe)('syncApiKeyToTool', () => {
169
+ (0, vitest_1.it)('writes api key to opencode auth file with type "api"', async () => {
174
170
  const files = new fake_file_store_1.FakeFileStore();
175
- yield (0, auth_sync_1.syncApiKeyToTool)(files, HOME, "opencode", "sk_ber_test");
171
+ await (0, auth_sync_1.syncApiKeyToTool)(files, HOME, 'opencode', 'sk_ber_test');
176
172
  const written = files.getWrittenFiles();
177
- const content = written.get(HOME + "/.local/share/opencode/auth.json");
173
+ const content = written.get(HOME + '/.local/share/opencode/auth.json');
178
174
  const parsed = JSON.parse(content);
179
175
  (0, vitest_1.expect)(parsed.berget).toEqual({
180
- type: "api",
181
- key: "sk_ber_test",
176
+ key: 'sk_ber_test',
177
+ type: 'api',
182
178
  });
183
- }));
184
- (0, vitest_1.it)('writes api key to pi auth file with type "api_key"', () => __awaiter(void 0, void 0, void 0, function* () {
179
+ });
180
+ (0, vitest_1.it)('writes api key to pi auth file with type "api_key"', async () => {
185
181
  const files = new fake_file_store_1.FakeFileStore();
186
- yield (0, auth_sync_1.syncApiKeyToTool)(files, HOME, "pi", "sk_ber_pi");
182
+ await (0, auth_sync_1.syncApiKeyToTool)(files, HOME, 'pi', 'sk_ber_pi');
187
183
  const written = files.getWrittenFiles();
188
- const content = written.get(HOME + "/.pi/agent/auth.json");
184
+ const content = written.get(HOME + '/.pi/agent/auth.json');
189
185
  const parsed = JSON.parse(content);
190
186
  (0, vitest_1.expect)(parsed.berget).toEqual({
191
- type: "api_key",
192
- key: "sk_ber_pi",
187
+ key: 'sk_ber_pi',
188
+ type: 'api_key',
193
189
  });
194
- }));
195
- (0, vitest_1.it)("merges with existing providers", () => __awaiter(void 0, void 0, void 0, function* () {
190
+ });
191
+ (0, vitest_1.it)('merges with existing providers', async () => {
196
192
  const files = new fake_file_store_1.FakeFileStore();
197
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ anthropic: { type: "api", key: "sk-ant" } }));
198
- yield (0, auth_sync_1.syncApiKeyToTool)(files, HOME, "opencode", "sk_ber_test");
193
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ anthropic: { key: 'sk-ant', type: 'api' } }));
194
+ await (0, auth_sync_1.syncApiKeyToTool)(files, HOME, 'opencode', 'sk_ber_test');
199
195
  const written = files.getWrittenFiles();
200
- const parsed = JSON.parse(written.get(HOME + "/.local/share/opencode/auth.json"));
201
- (0, vitest_1.expect)(parsed.anthropic).toEqual({ type: "api", key: "sk-ant" });
202
- }));
203
- (0, vitest_1.it)("sets 0o600 permissions on the auth file", () => __awaiter(void 0, void 0, void 0, function* () {
196
+ const parsed = JSON.parse(written.get(HOME + '/.local/share/opencode/auth.json'));
197
+ (0, vitest_1.expect)(parsed.anthropic).toEqual({ key: 'sk-ant', type: 'api' });
198
+ });
199
+ (0, vitest_1.it)('sets 0o600 permissions on the auth file', async () => {
204
200
  const files = new fake_file_store_1.FakeFileStore();
205
- yield (0, auth_sync_1.syncApiKeyToTool)(files, HOME, "opencode", "sk_ber_test");
201
+ await (0, auth_sync_1.syncApiKeyToTool)(files, HOME, 'opencode', 'sk_ber_test');
206
202
  const chmodCalls = files.getChmodCalls();
207
203
  (0, vitest_1.expect)(chmodCalls).toHaveLength(1);
208
204
  (0, vitest_1.expect)(chmodCalls[0]).toEqual({
209
- path: HOME + "/.local/share/opencode/auth.json",
210
205
  mode: 0o600,
206
+ path: HOME + '/.local/share/opencode/auth.json',
211
207
  });
212
- }));
208
+ });
213
209
  });
214
- (0, vitest_1.describe)("configureAuth", () => {
215
- const makeAuthDeps = (overrides = {}) => (Object.assign({ prompter: new fake_prompter_1.FakePrompter([]), files: new fake_file_store_1.FakeFileStore(), authService: new fake_auth_service_1.FakeAuthService(true), apiKeyService: new fake_api_key_service_1.FakeApiKeyService("sk_ber_test"), homeDir: HOME }, overrides));
216
- (0, vitest_1.it)("Case A: already authenticated — chooses keep → skips flow", () => __awaiter(void 0, void 0, void 0, function* () {
210
+ (0, vitest_1.describe)('configureAuth', () => {
211
+ const makeAuthDeps = (overrides = {}) => ({
212
+ apiKeyService: new fake_api_key_service_1.FakeApiKeyService('sk_ber_test'),
213
+ authService: new fake_auth_service_1.FakeAuthService(true),
214
+ files: new fake_file_store_1.FakeFileStore(),
215
+ homeDir: HOME,
216
+ prompter: new fake_prompter_1.FakePrompter([]),
217
+ ...overrides,
218
+ });
219
+ (0, vitest_1.it)('Case A: already authenticated — chooses keep → skips flow', async () => {
217
220
  const files = new fake_file_store_1.FakeFileStore();
218
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ berget: { type: "oauth" } }));
219
- const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)("keep")]);
221
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ berget: { type: 'oauth' } }));
222
+ const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)('keep')]);
220
223
  const deps = makeAuthDeps({ files, prompter });
221
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
224
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
222
225
  (0, vitest_1.expect)(result.authenticated).toBe(true);
223
226
  (0, vitest_1.expect)(deps.prompter.calls.length).toBe(1); // Only the select prompt
224
- }));
225
- (0, vitest_1.it)("Case A reconfigure: already authenticated — reconfigure with fresh browser login", () => __awaiter(void 0, void 0, void 0, function* () {
227
+ });
228
+ (0, vitest_1.it)('Case A reconfigure: already authenticated — reconfigure with fresh browser login', async () => {
226
229
  const files = new fake_file_store_1.FakeFileStore();
227
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ berget: { type: "oauth" } }));
228
- const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)("reconfigure"), (0, fake_prompter_1.select)("subscription")]);
230
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ berget: { type: 'oauth' } }));
231
+ const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)('reconfigure'), (0, fake_prompter_1.select)('subscription')]);
229
232
  const deps = makeAuthDeps({ files, prompter });
230
233
  const authService = deps.authService;
231
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
234
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
232
235
  (0, vitest_1.expect)(result.authenticated).toBe(true);
233
236
  // Should have called loginInteractive (no token reuse since no ~/.berget/auth.json seeded)
234
237
  (0, vitest_1.expect)(authService.loginInteractiveCallCount).toBeGreaterThanOrEqual(1);
235
- }));
236
- (0, vitest_1.it)("Case A reconfigure: already authenticated — reconfigure with valid CLI token → skips browser", () => __awaiter(void 0, void 0, void 0, function* () {
238
+ });
239
+ (0, vitest_1.it)('Case A reconfigure: already authenticated — reconfigure with valid CLI token → skips browser', async () => {
237
240
  const farFuture = Date.now() + 365 * 24 * 60 * 60 * 1000; // 1 year from now
238
241
  const files = new fake_file_store_1.FakeFileStore();
239
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ berget: { type: "oauth" } }));
240
- files.seed(HOME + "/.berget/auth.json", JSON.stringify({
241
- access_token: makeJwt({ realm_access: { roles: ["berget_code_seat"] }, exp: farFuture }),
242
- refresh_token: "ref",
242
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ berget: { type: 'oauth' } }));
243
+ files.seed(HOME + '/.berget/auth.json', JSON.stringify({
244
+ access_token: makeJwt({ exp: farFuture, realm_access: { roles: ['berget_code_seat'] } }),
243
245
  expires_at: farFuture,
246
+ refresh_token: 'ref',
244
247
  }));
245
- const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)("reconfigure"), (0, fake_prompter_1.select)("subscription")]);
248
+ const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)('reconfigure'), (0, fake_prompter_1.select)('subscription')]);
246
249
  const authService = new fake_auth_service_1.FakeAuthService(true);
247
- const deps = makeAuthDeps({ files, prompter, authService });
248
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
250
+ const deps = makeAuthDeps({ authService, files, prompter });
251
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
249
252
  (0, vitest_1.expect)(result.authenticated).toBe(true);
250
253
  // Should NOT have called loginInteractive since token was reused
251
254
  (0, vitest_1.expect)(authService.loginInteractiveCallCount).toBe(0);
252
- }));
253
- (0, vitest_1.it)("Case B: login success + berget_code_seat → chooses subscription", () => __awaiter(void 0, void 0, void 0, function* () {
255
+ });
256
+ (0, vitest_1.it)('Case B: login success + berget_code_seat → chooses subscription', async () => {
254
257
  const files = new fake_file_store_1.FakeFileStore();
255
258
  const jwt = makeJwt({
256
- realm_access: { roles: ["berget_code_seat"] },
257
259
  exp: 9999999999999,
260
+ realm_access: { roles: ['berget_code_seat'] },
258
261
  });
259
- files.seed(HOME + "/.berget/auth.json", JSON.stringify({
262
+ files.seed(HOME + '/.berget/auth.json', JSON.stringify({
260
263
  access_token: jwt,
261
- refresh_token: "ref",
262
264
  expires_at: 9999999999999,
265
+ refresh_token: 'ref',
263
266
  }));
264
- const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)("subscription")]);
267
+ const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)('subscription')]);
265
268
  const deps = makeAuthDeps({ files, prompter });
266
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
269
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
267
270
  (0, vitest_1.expect)(result.authenticated).toBe(true);
268
271
  const written = files.getWrittenFiles();
269
- (0, vitest_1.expect)(written.has(HOME + "/.local/share/opencode/auth.json")).toBe(true);
270
- const parsed = JSON.parse(written.get(HOME + "/.local/share/opencode/auth.json"));
271
- (0, vitest_1.expect)(parsed.berget.type).toBe("oauth");
272
- }));
273
- (0, vitest_1.it)("Case B variant: login success + seat → chooses api_key", () => __awaiter(void 0, void 0, void 0, function* () {
272
+ (0, vitest_1.expect)(written.has(HOME + '/.local/share/opencode/auth.json')).toBe(true);
273
+ const parsed = JSON.parse(written.get(HOME + '/.local/share/opencode/auth.json'));
274
+ (0, vitest_1.expect)(parsed.berget.type).toBe('oauth');
275
+ });
276
+ (0, vitest_1.it)('Case B variant: login success + seat → chooses api_key', async () => {
274
277
  const files = new fake_file_store_1.FakeFileStore();
275
278
  const jwt = makeJwt({
276
- realm_access: { roles: ["berget_code_seat"] },
277
279
  exp: 9999999999999,
280
+ realm_access: { roles: ['berget_code_seat'] },
278
281
  });
279
- files.seed(HOME + "/.berget/auth.json", JSON.stringify({
282
+ files.seed(HOME + '/.berget/auth.json', JSON.stringify({
280
283
  access_token: jwt,
281
- refresh_token: "ref",
282
284
  expires_at: 9999999999999,
285
+ refresh_token: 'ref',
283
286
  }));
284
- const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)("api_key")]);
287
+ const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)('api_key')]);
285
288
  const deps = makeAuthDeps({ files, prompter });
286
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
289
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
287
290
  (0, vitest_1.expect)(result.authenticated).toBe(true);
288
291
  const written = files.getWrittenFiles();
289
- const parsed = JSON.parse(written.get(HOME + "/.local/share/opencode/auth.json"));
290
- (0, vitest_1.expect)(parsed.berget.type).toBe("api");
291
- (0, vitest_1.expect)(parsed.berget.key).toBe("sk_ber_test");
292
- }));
293
- (0, vitest_1.it)("Case C: login success + no seat → creates api key", () => __awaiter(void 0, void 0, void 0, function* () {
292
+ const parsed = JSON.parse(written.get(HOME + '/.local/share/opencode/auth.json'));
293
+ (0, vitest_1.expect)(parsed.berget.type).toBe('api');
294
+ (0, vitest_1.expect)(parsed.berget.key).toBe('sk_ber_test');
295
+ });
296
+ (0, vitest_1.it)('Case C: login success + no seat → creates api key', async () => {
294
297
  const files = new fake_file_store_1.FakeFileStore();
295
298
  const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.confirm)(true)]);
296
- const deps = makeAuthDeps({ files, prompter, authService: new fake_auth_service_1.FakeAuthService(true, false) });
297
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
299
+ const deps = makeAuthDeps({ authService: new fake_auth_service_1.FakeAuthService(true, false), files, prompter });
300
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
298
301
  (0, vitest_1.expect)(result.authenticated).toBe(true);
299
302
  const written = files.getWrittenFiles();
300
- const parsed = JSON.parse(written.get(HOME + "/.local/share/opencode/auth.json"));
301
- (0, vitest_1.expect)(parsed.berget.type).toBe("api");
302
- (0, vitest_1.expect)(parsed.berget.key).toBe("sk_ber_test");
303
- }));
304
- (0, vitest_1.it)("Case D: login success + no seat → declines api key", () => __awaiter(void 0, void 0, void 0, function* () {
303
+ const parsed = JSON.parse(written.get(HOME + '/.local/share/opencode/auth.json'));
304
+ (0, vitest_1.expect)(parsed.berget.type).toBe('api');
305
+ (0, vitest_1.expect)(parsed.berget.key).toBe('sk_ber_test');
306
+ });
307
+ (0, vitest_1.it)('Case D: login success + no seat → declines api key', async () => {
305
308
  const files = new fake_file_store_1.FakeFileStore();
306
309
  const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.confirm)(false)]);
307
- const deps = makeAuthDeps({ files, prompter, authService: new fake_auth_service_1.FakeAuthService(true, false) });
308
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
310
+ const deps = makeAuthDeps({ authService: new fake_auth_service_1.FakeAuthService(true, false), files, prompter });
311
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
309
312
  (0, vitest_1.expect)(result.authenticated).toBe(false);
310
- (0, vitest_1.expect)(files.getWrittenFiles().has(HOME + "/.local/share/opencode/auth.json")).toBe(false);
311
- }));
312
- (0, vitest_1.it)("Case E: login fails", () => __awaiter(void 0, void 0, void 0, function* () {
313
+ (0, vitest_1.expect)(files.getWrittenFiles().has(HOME + '/.local/share/opencode/auth.json')).toBe(false);
314
+ });
315
+ (0, vitest_1.it)('Case E: login fails', async () => {
313
316
  const files = new fake_file_store_1.FakeFileStore();
314
317
  const authService = new fake_auth_service_1.FakeAuthService(false);
315
- const deps = makeAuthDeps({ files, authService });
316
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
318
+ const deps = makeAuthDeps({ authService, files });
319
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
317
320
  (0, vitest_1.expect)(result.authenticated).toBe(false);
318
- }));
319
- (0, vitest_1.it)("fails authentication when jwt decode fails", () => __awaiter(void 0, void 0, void 0, function* () {
321
+ });
322
+ (0, vitest_1.it)('fails authentication when jwt decode fails', async () => {
320
323
  const prompter = new fake_prompter_1.FakePrompter([]);
321
324
  const deps = makeAuthDeps({
322
- prompter,
323
325
  authService: new fake_auth_service_1.FakeAuthService(true, true, false), // valid login, has seat, but invalid token
326
+ prompter,
324
327
  });
325
- const result = yield (0, auth_sync_1.configureAuth)(deps, "opencode");
328
+ const result = await (0, auth_sync_1.configureAuth)(deps, 'opencode');
326
329
  (0, vitest_1.expect)(result.authenticated).toBe(false); // Should fail due to invalid JWT
327
330
  const written = deps.files.getWrittenFiles();
328
331
  (0, vitest_1.expect)(written.size).toBe(0); // No files should be written
329
- }));
330
- (0, vitest_1.it)("preserves existing providers during sync", () => __awaiter(void 0, void 0, void 0, function* () {
332
+ });
333
+ (0, vitest_1.it)('preserves existing providers during sync', async () => {
331
334
  const files = new fake_file_store_1.FakeFileStore();
332
- files.seed(HOME + "/.local/share/opencode/auth.json", JSON.stringify({ openai: { type: "api", key: "sk-openai" } }));
333
- files.seed(HOME + "/.berget/auth.json", JSON.stringify({
335
+ files.seed(HOME + '/.local/share/opencode/auth.json', JSON.stringify({ openai: { key: 'sk-openai', type: 'api' } }));
336
+ files.seed(HOME + '/.berget/auth.json', JSON.stringify({
334
337
  access_token: makeJwt({
335
- realm_access: { roles: ["berget_code_seat"], exp: 9999999999999 },
338
+ realm_access: { exp: 9999999999999, roles: ['berget_code_seat'] },
336
339
  }),
337
- refresh_token: "ref",
338
340
  expires_at: 9999999999999,
341
+ refresh_token: 'ref',
339
342
  }));
340
- const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)("subscription")]);
343
+ const prompter = new fake_prompter_1.FakePrompter([(0, fake_prompter_1.select)('subscription')]);
341
344
  const deps = makeAuthDeps({ files, prompter });
342
- yield (0, auth_sync_1.configureAuth)(deps, "opencode");
345
+ await (0, auth_sync_1.configureAuth)(deps, 'opencode');
343
346
  const written = files.getWrittenFiles();
344
- const parsed = JSON.parse(written.get(HOME + "/.local/share/opencode/auth.json"));
345
- (0, vitest_1.expect)(parsed.openai).toEqual({ type: "api", key: "sk-openai" });
347
+ const parsed = JSON.parse(written.get(HOME + '/.local/share/opencode/auth.json'));
348
+ (0, vitest_1.expect)(parsed.openai).toEqual({ key: 'sk-openai', type: 'api' });
346
349
  (0, vitest_1.expect)(parsed.berget).toBeDefined();
347
- }));
350
+ });
348
351
  });
@@ -1,23 +1,13 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  exports.FakeApiKeyService = void 0;
13
4
  class FakeApiKeyService {
5
+ _key;
14
6
  constructor(key) {
15
7
  this._key = key;
16
8
  }
17
- create(_options) {
18
- return __awaiter(this, void 0, void 0, function* () {
19
- return { key: this._key };
20
- });
9
+ async create(_options) {
10
+ return { key: this._key };
21
11
  }
22
12
  }
23
13
  exports.FakeApiKeyService = FakeApiKeyService;