berget 2.2.7 → 2.2.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 (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 +7 -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 +181 -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 +8 -8
  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 +65 -30
  64. package/index.ts +30 -31
  65. package/package.json +7 -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 +180 -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 +170 -171
  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 +7 -7
  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,99 +1,150 @@
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
- exports.configureAuth = exports.isTokenExpired = exports.syncApiKeyToTool = exports.syncOAuthToTool = exports.hasBergetCodeSeat = exports.decodeJwtPayload = exports.isToolAuthenticated = exports.readCliAuth = void 0;
13
- const CLI_AUTH_PATH = (homeDir) => homeDir + "/.berget/auth.json";
3
+ exports.syncOAuthToTool = exports.syncApiKeyToTool = exports.readCliAuth = exports.isToolAuthenticated = exports.isTokenExpired = exports.hasBergetCodeSeat = exports.decodeJwtPayload = exports.configureAuth = void 0;
4
+ const CLI_AUTH_PATH = (homeDir) => homeDir + '/.berget/auth.json';
14
5
  const TOOL_AUTH_PATHS = {
15
- opencode: (homeDir) => homeDir + "/.local/share/opencode/auth.json",
16
- pi: (homeDir) => homeDir + "/.pi/agent/auth.json",
6
+ opencode: (homeDir) => homeDir + '/.local/share/opencode/auth.json',
7
+ pi: (homeDir) => homeDir + '/.pi/agent/auth.json',
17
8
  };
18
9
  const TOOL_API_KEY_TYPES = {
19
- opencode: "api",
20
- pi: "api_key",
10
+ opencode: 'api',
11
+ pi: 'api_key',
21
12
  };
22
- function extractJwtExpiresAt(accessToken) {
23
- try {
24
- const parts = accessToken.split(".");
25
- if (parts.length !== 3)
26
- return 0;
27
- const payload = Buffer.from(parts[1], "base64url").toString("utf8");
28
- const decoded = JSON.parse(payload);
29
- if (typeof decoded.exp === "number") {
30
- return decoded.exp * 1000; // JWT exp is in seconds, convert to milliseconds
13
+ async function configureAuth(deps, tool) {
14
+ const { apiKeyService, authService, files, homeDir, prompter } = deps;
15
+ const alreadyAuth = await isToolAuthenticated(files, homeDir, tool);
16
+ if (alreadyAuth) {
17
+ const choice = await prompter.select({
18
+ message: `Account is already connected to Berget AI (${tool === 'opencode' ? 'OpenCode' : 'Pi'}). How do you want to proceed?`,
19
+ options: [
20
+ { label: 'Keep existing authentication', value: 'keep' },
21
+ { label: 'Reconfigure choose a different method', value: 'reconfigure' },
22
+ ],
23
+ });
24
+ if (choice === 'keep') {
25
+ return { authenticated: true };
31
26
  }
27
+ // Fall through to reconfigure
32
28
  }
33
- catch (_a) {
34
- // If decoding fails, return 0 (treated as expired)
29
+ else {
30
+ prompter.note('Authentication required to use Berget AI.', 'Connect your account');
35
31
  }
36
- return 0;
37
- }
38
- function readCliAuth(files, homeDir) {
39
- return __awaiter(this, void 0, void 0, function* () {
40
- const content = yield files.readFile(CLI_AUTH_PATH(homeDir));
41
- if (!content)
42
- return null;
32
+ // Try to reuse existing CLI tokens (from ~/.berget/auth.json)
33
+ let cliAuth = await readCliAuth(files, homeDir);
34
+ if (!cliAuth || isTokenExpired(cliAuth.expires_at)) {
35
+ // No valid tokens full browser login
36
+ const s = prompter.spinner();
37
+ s.start('Waiting for browser login...');
38
+ const loginResult = await authService.loginInteractive();
39
+ if (!loginResult.success) {
40
+ s.stop('Login failed.');
41
+ prompter.note(`${loginResult.error || 'Login timed out or was cancelled.'}\n\nPlease run \`berget auth login\` manually, then run \`berget code setup\` again.`, 'Authentication Failed');
42
+ return { authenticated: false };
43
+ }
44
+ s.stop('Successfully logged in to Berget.');
45
+ const jwtExpiresAt = extractJwtExpiresAt(loginResult.accessToken);
46
+ if (jwtExpiresAt === 0) {
47
+ s.stop('Login succeeded but received invalid token.');
48
+ prompter.note('Please try logging in again or contact support.', 'Authentication Error');
49
+ return { authenticated: false };
50
+ }
51
+ cliAuth = {
52
+ access_token: loginResult.accessToken,
53
+ expires_at: jwtExpiresAt,
54
+ refresh_token: loginResult.refreshToken,
55
+ };
56
+ }
57
+ // Check Berget Code seat
58
+ const jwtPayload = decodeJwtPayload(cliAuth.access_token);
59
+ const hasSeat = jwtPayload ? hasBergetCodeSeat(cliAuth.access_token) : true;
60
+ // If we can't decode the JWT, sync OAuth anyway — the tokens are valid even if
61
+ // we can't verify the subscription role. Let the tool handle authorization.
62
+ if (!jwtPayload) {
63
+ const s = prompter.spinner();
64
+ s.start('Authenticating with Berget AI...');
65
+ await syncOAuthToTool(files, homeDir, tool, cliAuth);
66
+ s.stop('Authenticated.');
67
+ prompter.note('Warning: Could not verify Berget Code subscription status.\nIf you do not have a subscription, the tool may show an authorization error.', 'Authentication');
68
+ return { authenticated: true };
69
+ }
70
+ if (hasSeat) {
71
+ // Case B: Has seat — ask how to authenticate
72
+ const method = await prompter.select({
73
+ message: 'You have a Berget Code subscription. How do you want to authenticate?',
74
+ options: [
75
+ { label: 'Use my Berget Code subscription', value: 'subscription' },
76
+ { label: 'Use an API key instead', value: 'api_key' },
77
+ ],
78
+ });
79
+ if (method === 'subscription') {
80
+ const s = prompter.spinner();
81
+ s.start('Authenticating with Berget AI via subscription...');
82
+ await syncOAuthToTool(files, homeDir, tool, cliAuth);
83
+ s.stop('Authenticated.');
84
+ return { authenticated: true };
85
+ }
86
+ // Create API key instead
87
+ const s = prompter.spinner();
88
+ s.start('Creating API key...');
43
89
  try {
44
- const parsed = JSON.parse(content);
45
- if (parsed.access_token && parsed.refresh_token) {
46
- // Extract the actual expiry time from the JWT token instead of using the stored expires_at
47
- const jwtExpiresAt = extractJwtExpiresAt(parsed.access_token);
48
- if (jwtExpiresAt === 0) {
49
- // Invalid token, return null
50
- return null;
51
- }
52
- return {
53
- access_token: parsed.access_token,
54
- refresh_token: parsed.refresh_token,
55
- expires_at: jwtExpiresAt,
56
- };
57
- }
58
- return null;
90
+ const { key } = await apiKeyService.create({
91
+ description: 'Created by berget code setup',
92
+ name: `${tool === 'opencode' ? 'OpenCode' : 'Pi'} (created by berget CLI)`,
93
+ });
94
+ await syncApiKeyToTool(files, homeDir, tool, key);
95
+ s.stop('API key created and saved.');
96
+ return { authenticated: true };
59
97
  }
60
- catch (_a) {
61
- return null;
98
+ catch {
99
+ s.stop('API key creation failed.');
100
+ prompter.note('Could not create API key. Please create one manually with `berget api-keys create`.', 'Error');
101
+ return { authenticated: false };
62
102
  }
103
+ }
104
+ // No Berget Code seat — prompt for API key creation
105
+ const shouldCreate = await prompter.confirm({
106
+ initialValue: true,
107
+ message: 'You do not have a Berget Code subscription. Would you like to create a new API key?',
63
108
  });
64
- }
65
- exports.readCliAuth = readCliAuth;
66
- function isToolAuthenticated(files, homeDir, tool) {
67
- return __awaiter(this, void 0, void 0, function* () {
68
- const content = yield files.readFile(TOOL_AUTH_PATHS[tool](homeDir));
69
- if (!content)
70
- return false;
109
+ if (shouldCreate) {
110
+ const s = prompter.spinner();
111
+ s.start('Creating API key...');
71
112
  try {
72
- const parsed = JSON.parse(content);
73
- return typeof parsed.berget === "object" && parsed.berget !== null;
113
+ const { key } = await apiKeyService.create({
114
+ description: 'Created by berget code setup',
115
+ name: `${tool === 'opencode' ? 'OpenCode' : 'Pi'} (created by berget CLI)`,
116
+ });
117
+ await syncApiKeyToTool(files, homeDir, tool, key);
118
+ s.stop('API key created and saved.');
119
+ return { authenticated: true };
74
120
  }
75
- catch (_a) {
76
- return false;
121
+ catch {
122
+ s.stop('API key creation failed.');
123
+ prompter.note('Could not create API key. Please create one manually with `berget api-keys create`.', 'Error');
124
+ return { authenticated: false };
77
125
  }
78
- });
126
+ }
127
+ // Case D: Declined
128
+ prompter.note('Authentication skipped. You\'ll need to set up authentication manually:\n1. Run: berget api-keys create --name "My Key"\n2. Set BERGET_API_KEY environment variable, or\n3. Run `berget auth login` and try again', 'Authentication');
129
+ return { authenticated: false };
79
130
  }
80
- exports.isToolAuthenticated = isToolAuthenticated;
131
+ exports.configureAuth = configureAuth;
81
132
  function decodeJwtPayload(token) {
82
133
  try {
83
- const parts = token.split(".");
134
+ const parts = token.split('.');
84
135
  if (parts.length !== 3)
85
136
  return null;
86
- const payload = Buffer.from(parts[1], "base64url").toString("utf8");
137
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
87
138
  return JSON.parse(payload);
88
139
  }
89
- catch (_a) {
140
+ catch {
90
141
  return null;
91
142
  }
92
143
  }
93
144
  exports.decodeJwtPayload = decodeJwtPayload;
94
145
  function hasBergetCodeSeat(accessToken) {
95
146
  const payload = decodeJwtPayload(accessToken);
96
- if (!payload || typeof payload !== "object")
147
+ if (!payload || typeof payload !== 'object')
97
148
  return false;
98
149
  const p = payload;
99
150
  const realmAccess = p.realm_access;
@@ -102,57 +153,9 @@ function hasBergetCodeSeat(accessToken) {
102
153
  const roles = realmAccess.roles;
103
154
  if (!Array.isArray(roles))
104
155
  return false;
105
- return roles.includes("berget_code_seat");
156
+ return roles.includes('berget_code_seat');
106
157
  }
107
158
  exports.hasBergetCodeSeat = hasBergetCodeSeat;
108
- function syncOAuthToTool(files, homeDir, tool, cliAuth) {
109
- return __awaiter(this, void 0, void 0, function* () {
110
- const authPath = TOOL_AUTH_PATHS[tool](homeDir);
111
- let existing = {};
112
- const content = yield files.readFile(authPath);
113
- if (content) {
114
- try {
115
- existing = JSON.parse(content);
116
- }
117
- catch (_a) {
118
- existing = {};
119
- }
120
- }
121
- // Use the JWT's actual expiry time for consistency
122
- const jwtExpiresAt = extractJwtExpiresAt(cliAuth.access_token);
123
- const updated = Object.assign(Object.assign({}, existing), { berget: {
124
- type: "oauth",
125
- access: cliAuth.access_token,
126
- refresh: cliAuth.refresh_token,
127
- expires: jwtExpiresAt,
128
- } });
129
- yield files.writeFile(authPath, JSON.stringify(updated, null, 2) + "\n");
130
- yield files.chmod(authPath, 0o600);
131
- });
132
- }
133
- exports.syncOAuthToTool = syncOAuthToTool;
134
- function syncApiKeyToTool(files, homeDir, tool, apiKey) {
135
- return __awaiter(this, void 0, void 0, function* () {
136
- const authPath = TOOL_AUTH_PATHS[tool](homeDir);
137
- let existing = {};
138
- const content = yield files.readFile(authPath);
139
- if (content) {
140
- try {
141
- existing = JSON.parse(content);
142
- }
143
- catch (_a) {
144
- existing = {};
145
- }
146
- }
147
- const updated = Object.assign(Object.assign({}, existing), { berget: {
148
- type: TOOL_API_KEY_TYPES[tool],
149
- key: apiKey,
150
- } });
151
- yield files.writeFile(authPath, JSON.stringify(updated, null, 2) + "\n");
152
- yield files.chmod(authPath, 0o600);
153
- });
154
- }
155
- exports.syncApiKeyToTool = syncApiKeyToTool;
156
159
  function isTokenExpired(expiresAt) {
157
160
  const now = Date.now();
158
161
  const timeUntilExpiry = expiresAt - now;
@@ -160,124 +163,108 @@ function isTokenExpired(expiresAt) {
160
163
  return now + buffer >= expiresAt;
161
164
  }
162
165
  exports.isTokenExpired = isTokenExpired;
163
- function configureAuth(deps, tool) {
164
- return __awaiter(this, void 0, void 0, function* () {
165
- const { prompter, files, authService, apiKeyService, homeDir } = deps;
166
- const alreadyAuth = yield isToolAuthenticated(files, homeDir, tool);
167
- if (alreadyAuth) {
168
- const choice = yield prompter.select({
169
- message: `Account is already connected to Berget AI (${tool === "opencode" ? "OpenCode" : "Pi"}). How do you want to proceed?`,
170
- options: [
171
- { value: "keep", label: "Keep existing authentication" },
172
- { value: "reconfigure", label: "Reconfigure — choose a different method" },
173
- ],
174
- });
175
- if (choice === "keep") {
176
- return { authenticated: true };
177
- }
178
- // Fall through to reconfigure
179
- }
180
- else {
181
- prompter.note("Authentication required to use Berget AI.", "Connect your account");
182
- }
183
- // Try to reuse existing CLI tokens (from ~/.berget/auth.json)
184
- let cliAuth = yield readCliAuth(files, homeDir);
185
- if (!cliAuth || isTokenExpired(cliAuth.expires_at)) {
186
- // No valid tokens → full browser login
187
- const s = prompter.spinner();
188
- s.start("Waiting for browser login...");
189
- const loginResult = yield authService.loginInteractive();
190
- if (!loginResult.success) {
191
- s.stop("Login failed.");
192
- prompter.note(`${loginResult.error || "Login timed out or was cancelled."}\n\nPlease run \`berget auth login\` manually, then run \`berget code setup\` again.`, "Authentication Failed");
193
- return { authenticated: false };
194
- }
195
- s.stop("Successfully logged in to Berget.");
196
- const jwtExpiresAt = extractJwtExpiresAt(loginResult.accessToken);
166
+ async function isToolAuthenticated(files, homeDir, tool) {
167
+ const content = await files.readFile(TOOL_AUTH_PATHS[tool](homeDir));
168
+ if (!content)
169
+ return false;
170
+ try {
171
+ const parsed = JSON.parse(content);
172
+ return typeof parsed.berget === 'object' && parsed.berget !== null;
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ }
178
+ exports.isToolAuthenticated = isToolAuthenticated;
179
+ async function readCliAuth(files, homeDir) {
180
+ const content = await files.readFile(CLI_AUTH_PATH(homeDir));
181
+ if (!content)
182
+ return null;
183
+ try {
184
+ const parsed = JSON.parse(content);
185
+ if (parsed.access_token && parsed.refresh_token) {
186
+ // Extract the actual expiry time from the JWT token instead of using the stored expires_at
187
+ const jwtExpiresAt = extractJwtExpiresAt(parsed.access_token);
197
188
  if (jwtExpiresAt === 0) {
198
- s.stop("Login succeeded but received invalid token.");
199
- prompter.note("Please try logging in again or contact support.", "Authentication Error");
200
- return { authenticated: false };
189
+ // Invalid token, return null
190
+ return null;
201
191
  }
202
- cliAuth = {
203
- access_token: loginResult.accessToken,
204
- refresh_token: loginResult.refreshToken,
192
+ return {
193
+ access_token: parsed.access_token,
205
194
  expires_at: jwtExpiresAt,
195
+ refresh_token: parsed.refresh_token,
206
196
  };
207
197
  }
208
- // Check Berget Code seat
209
- const jwtPayload = decodeJwtPayload(cliAuth.access_token);
210
- const hasSeat = jwtPayload ? hasBergetCodeSeat(cliAuth.access_token) : true;
211
- // If we can't decode the JWT, sync OAuth anyway — the tokens are valid even if
212
- // we can't verify the subscription role. Let the tool handle authorization.
213
- if (!jwtPayload) {
214
- const s = prompter.spinner();
215
- s.start("Authenticating with Berget AI...");
216
- yield syncOAuthToTool(files, homeDir, tool, cliAuth);
217
- s.stop("Authenticated.");
218
- prompter.note("Warning: Could not verify Berget Code subscription status.\nIf you do not have a subscription, the tool may show an authorization error.", "Authentication");
219
- return { authenticated: true };
198
+ return null;
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ }
204
+ exports.readCliAuth = readCliAuth;
205
+ async function syncApiKeyToTool(files, homeDir, tool, apiKey) {
206
+ const authPath = TOOL_AUTH_PATHS[tool](homeDir);
207
+ let existing = {};
208
+ const content = await files.readFile(authPath);
209
+ if (content) {
210
+ try {
211
+ existing = JSON.parse(content);
220
212
  }
221
- if (hasSeat) {
222
- // Case B: Has seat — ask how to authenticate
223
- const method = yield prompter.select({
224
- message: "You have a Berget Code subscription. How do you want to authenticate?",
225
- options: [
226
- { value: "subscription", label: "Use my Berget Code subscription" },
227
- { value: "api_key", label: "Use an API key instead" },
228
- ],
229
- });
230
- if (method === "subscription") {
231
- const s = prompter.spinner();
232
- s.start("Authenticating with Berget AI via subscription...");
233
- yield syncOAuthToTool(files, homeDir, tool, cliAuth);
234
- s.stop("Authenticated.");
235
- return { authenticated: true };
236
- }
237
- // Create API key instead
238
- const s = prompter.spinner();
239
- s.start("Creating API key...");
240
- try {
241
- const { key } = yield apiKeyService.create({
242
- name: `${tool === "opencode" ? "OpenCode" : "Pi"} (created by berget CLI)`,
243
- description: "Created by berget code setup",
244
- });
245
- yield syncApiKeyToTool(files, homeDir, tool, key);
246
- s.stop("API key created and saved.");
247
- return { authenticated: true };
248
- }
249
- catch (_a) {
250
- s.stop("API key creation failed.");
251
- prompter.note("Could not create API key. Please create one manually with `berget api-keys create`.", "Error");
252
- return { authenticated: false };
253
- }
213
+ catch {
214
+ existing = {};
254
215
  }
255
- // No Berget Code seat — prompt for API key creation
256
- const shouldCreate = yield prompter.confirm({
257
- message: "You do not have a Berget Code subscription. Would you like to create a new API key?",
258
- initialValue: true,
259
- });
260
- if (shouldCreate) {
261
- const s = prompter.spinner();
262
- s.start("Creating API key...");
263
- try {
264
- const { key } = yield apiKeyService.create({
265
- name: `${tool === "opencode" ? "OpenCode" : "Pi"} (created by berget CLI)`,
266
- description: "Created by berget code setup",
267
- });
268
- yield syncApiKeyToTool(files, homeDir, tool, key);
269
- s.stop("API key created and saved.");
270
- return { authenticated: true };
271
- }
272
- catch (_b) {
273
- s.stop("API key creation failed.");
274
- prompter.note("Could not create API key. Please create one manually with `berget api-keys create`.", "Error");
275
- return { authenticated: false };
276
- }
216
+ }
217
+ const updated = {
218
+ ...existing,
219
+ berget: {
220
+ key: apiKey,
221
+ type: TOOL_API_KEY_TYPES[tool],
222
+ },
223
+ };
224
+ await files.writeFile(authPath, JSON.stringify(updated, null, 2) + '\n');
225
+ await files.chmod(authPath, 0o600);
226
+ }
227
+ exports.syncApiKeyToTool = syncApiKeyToTool;
228
+ async function syncOAuthToTool(files, homeDir, tool, cliAuth) {
229
+ const authPath = TOOL_AUTH_PATHS[tool](homeDir);
230
+ let existing = {};
231
+ const content = await files.readFile(authPath);
232
+ if (content) {
233
+ try {
234
+ existing = JSON.parse(content);
277
235
  }
278
- // Case D: Declined
279
- prompter.note('Authentication skipped. You\'ll need to set up authentication manually:\n1. Run: berget api-keys create --name "My Key"\n2. Set BERGET_API_KEY environment variable, or\n3. Run `berget auth login` and try again', "Authentication");
280
- return { authenticated: false };
281
- });
236
+ catch {
237
+ existing = {};
238
+ }
239
+ }
240
+ // Use the JWT's actual expiry time for consistency
241
+ const jwtExpiresAt = extractJwtExpiresAt(cliAuth.access_token);
242
+ const updated = {
243
+ ...existing,
244
+ berget: {
245
+ access: cliAuth.access_token,
246
+ expires: jwtExpiresAt,
247
+ refresh: cliAuth.refresh_token,
248
+ type: 'oauth',
249
+ },
250
+ };
251
+ await files.writeFile(authPath, JSON.stringify(updated, null, 2) + '\n');
252
+ await files.chmod(authPath, 0o600);
253
+ }
254
+ exports.syncOAuthToTool = syncOAuthToTool;
255
+ function extractJwtExpiresAt(accessToken) {
256
+ try {
257
+ const parts = accessToken.split('.');
258
+ if (parts.length !== 3)
259
+ return 0;
260
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
261
+ const decoded = JSON.parse(payload);
262
+ if (typeof decoded.exp === 'number') {
263
+ return decoded.exp * 1000; // JWT exp is in seconds, convert to milliseconds
264
+ }
265
+ }
266
+ catch {
267
+ // If decoding fails, return 0 (treated as expired)
268
+ }
269
+ return 0;
282
270
  }
283
- exports.configureAuth = configureAuth;
@@ -1,27 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CommandFailedError = exports.CancelledError = exports.PrerequisiteError = void 0;
4
- class PrerequisiteError extends Error {
5
- constructor(binary) {
6
- super(`Required binary not found: ${binary}`);
7
- this.binary = binary;
8
- this.name = "PrerequisiteError";
9
- }
10
- }
11
- exports.PrerequisiteError = PrerequisiteError;
3
+ exports.PrerequisiteError = exports.CommandFailedError = exports.CancelledError = void 0;
12
4
  class CancelledError extends Error {
13
5
  constructor() {
14
- super("Wizard cancelled");
15
- this.name = "CancelledError";
6
+ super('Wizard cancelled');
7
+ this.name = 'CancelledError';
16
8
  }
17
9
  }
18
10
  exports.CancelledError = CancelledError;
19
11
  class CommandFailedError extends Error {
12
+ command;
13
+ exitCode;
20
14
  constructor(command, exitCode) {
21
15
  super(`Command "${command}" failed with exit code ${exitCode}`);
22
16
  this.command = command;
23
17
  this.exitCode = exitCode;
24
- this.name = "CommandFailedError";
18
+ this.name = 'CommandFailedError';
25
19
  }
26
20
  }
27
21
  exports.CommandFailedError = CommandFailedError;
22
+ class PrerequisiteError extends Error {
23
+ binary;
24
+ constructor(binary) {
25
+ super(`Required binary not found: ${binary}`);
26
+ this.binary = binary;
27
+ this.name = 'PrerequisiteError';
28
+ }
29
+ }
30
+ exports.PrerequisiteError = PrerequisiteError;