berget 2.2.6 → 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 (145) hide show
  1. package/.github/workflows/publish.yml +2 -2
  2. package/.github/workflows/test.yml +10 -4
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +15 -0
  5. package/.prettierrc +7 -3
  6. package/CONTRIBUTING.md +38 -0
  7. package/README.md +2 -148
  8. package/dist/index.js +10 -11
  9. package/dist/package.json +30 -2
  10. package/dist/src/agents/app.js +28 -0
  11. package/dist/src/agents/backend.js +25 -0
  12. package/dist/src/agents/devops.js +34 -0
  13. package/dist/src/agents/frontend.js +25 -0
  14. package/dist/src/agents/fullstack.js +25 -0
  15. package/dist/src/agents/index.js +61 -0
  16. package/dist/src/agents/quality.js +70 -0
  17. package/dist/src/agents/security.js +26 -0
  18. package/dist/src/agents/types.js +2 -0
  19. package/dist/src/client.js +97 -117
  20. package/dist/src/commands/api-keys.js +75 -90
  21. package/dist/src/commands/auth.js +7 -16
  22. package/dist/src/commands/autocomplete.js +1 -1
  23. package/dist/src/commands/billing.js +6 -17
  24. package/dist/src/commands/chat.js +68 -101
  25. package/dist/src/commands/clusters.js +9 -18
  26. package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
  27. package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
  28. package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
  29. package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
  30. package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
  31. package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
  32. package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
  33. package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
  34. package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
  35. package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
  36. package/dist/src/commands/code/auth-sync.js +270 -0
  37. package/dist/src/commands/code/errors.js +12 -9
  38. package/dist/src/commands/code/ports/auth-services.js +2 -0
  39. package/dist/src/commands/code/setup.js +387 -281
  40. package/dist/src/commands/code.js +205 -332
  41. package/dist/src/commands/index.js +5 -5
  42. package/dist/src/commands/models.js +6 -17
  43. package/dist/src/commands/users.js +5 -16
  44. package/dist/src/constants/command-structure.js +104 -104
  45. package/dist/src/services/api-key-service.js +132 -157
  46. package/dist/src/services/auth-service.js +89 -342
  47. package/dist/src/services/browser-auth.js +268 -0
  48. package/dist/src/services/chat-service.js +371 -401
  49. package/dist/src/services/cluster-service.js +47 -62
  50. package/dist/src/services/collaborator-service.js +10 -25
  51. package/dist/src/services/flux-service.js +14 -29
  52. package/dist/src/services/helm-service.js +10 -25
  53. package/dist/src/services/kubectl-service.js +16 -33
  54. package/dist/src/utils/config-checker.js +3 -3
  55. package/dist/src/utils/config-loader.js +95 -95
  56. package/dist/src/utils/default-api-key.js +124 -134
  57. package/dist/src/utils/env-manager.js +55 -66
  58. package/dist/src/utils/error-handler.js +20 -21
  59. package/dist/src/utils/logger.js +72 -65
  60. package/dist/src/utils/markdown-renderer.js +27 -27
  61. package/dist/src/utils/opencode-validator.js +63 -68
  62. package/dist/src/utils/token-manager.js +74 -45
  63. package/dist/tests/commands/chat.test.js +16 -25
  64. package/dist/tests/commands/code.test.js +95 -104
  65. package/dist/tests/utils/config-loader.test.js +48 -48
  66. package/dist/tests/utils/env-manager.test.js +43 -52
  67. package/dist/tests/utils/opencode-validator.test.js +22 -21
  68. package/dist/vitest.config.js +1 -1
  69. package/eslint.config.mjs +67 -0
  70. package/index.ts +35 -42
  71. package/package.json +30 -2
  72. package/src/agents/app.ts +27 -0
  73. package/src/agents/backend.ts +24 -0
  74. package/src/agents/devops.ts +33 -0
  75. package/src/agents/frontend.ts +24 -0
  76. package/src/agents/fullstack.ts +24 -0
  77. package/src/agents/index.ts +73 -0
  78. package/src/agents/quality.ts +69 -0
  79. package/src/agents/security.ts +26 -0
  80. package/src/agents/types.ts +17 -0
  81. package/src/client.ts +118 -152
  82. package/src/commands/api-keys.ts +241 -333
  83. package/src/commands/auth.ts +22 -27
  84. package/src/commands/autocomplete.ts +9 -9
  85. package/src/commands/billing.ts +20 -24
  86. package/src/commands/chat.ts +248 -338
  87. package/src/commands/clusters.ts +27 -26
  88. package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
  89. package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
  90. package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
  91. package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
  92. package/src/commands/code/__tests__/fake-file-store.ts +32 -23
  93. package/src/commands/code/__tests__/fake-prompter.ts +116 -77
  94. package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
  95. package/src/commands/code/adapters/clack-prompter.ts +53 -39
  96. package/src/commands/code/adapters/fs-file-store.ts +32 -27
  97. package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
  98. package/src/commands/code/auth-sync.ts +329 -0
  99. package/src/commands/code/errors.ts +18 -18
  100. package/src/commands/code/ports/auth-services.ts +14 -0
  101. package/src/commands/code/ports/command-runner.ts +8 -4
  102. package/src/commands/code/ports/file-store.ts +5 -4
  103. package/src/commands/code/ports/prompter.ts +24 -18
  104. package/src/commands/code/setup.ts +570 -340
  105. package/src/commands/code.ts +338 -539
  106. package/src/commands/index.ts +20 -19
  107. package/src/commands/models.ts +28 -32
  108. package/src/commands/users.ts +15 -21
  109. package/src/constants/command-structure.ts +134 -157
  110. package/src/services/api-key-service.ts +105 -122
  111. package/src/services/auth-service.ts +99 -345
  112. package/src/services/browser-auth.ts +296 -0
  113. package/src/services/chat-service.ts +265 -299
  114. package/src/services/cluster-service.ts +42 -45
  115. package/src/services/collaborator-service.ts +14 -19
  116. package/src/services/flux-service.ts +23 -25
  117. package/src/services/helm-service.ts +19 -21
  118. package/src/services/kubectl-service.ts +17 -19
  119. package/src/types/api.d.ts +1905 -1907
  120. package/src/types/json.d.ts +2 -2
  121. package/src/utils/config-checker.ts +10 -10
  122. package/src/utils/config-loader.ts +162 -178
  123. package/src/utils/default-api-key.ts +114 -125
  124. package/src/utils/env-manager.ts +53 -57
  125. package/src/utils/error-handler.ts +61 -56
  126. package/src/utils/logger.ts +79 -73
  127. package/src/utils/markdown-renderer.ts +31 -31
  128. package/src/utils/opencode-validator.ts +85 -89
  129. package/src/utils/token-manager.ts +108 -87
  130. package/templates/agents/app.md +1 -0
  131. package/templates/agents/backend.md +1 -0
  132. package/templates/agents/devops.md +2 -0
  133. package/templates/agents/frontend.md +1 -0
  134. package/templates/agents/fullstack.md +1 -0
  135. package/templates/agents/quality.md +45 -40
  136. package/templates/agents/security.md +1 -0
  137. package/tests/commands/chat.test.ts +53 -62
  138. package/tests/commands/code.test.ts +265 -310
  139. package/tests/utils/config-loader.test.ts +189 -188
  140. package/tests/utils/env-manager.test.ts +110 -113
  141. package/tests/utils/opencode-validator.test.ts +52 -56
  142. package/tsconfig.json +4 -3
  143. package/vitest.config.ts +3 -3
  144. package/AGENTS.md +0 -374
  145. package/TODO.md +0 -19
@@ -22,15 +22,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
25
  Object.defineProperty(exports, "__esModule", { value: true });
35
26
  exports.ClackPrompter = void 0;
36
27
  const p = __importStar(require("@clack/prompts"));
@@ -41,31 +32,33 @@ const unwrap = (v) => {
41
32
  return v;
42
33
  };
43
34
  class ClackPrompter {
35
+ async confirm(options) {
36
+ return unwrap(await p.confirm(options));
37
+ }
44
38
  intro(message) {
45
39
  p.intro(message);
46
40
  }
47
- outro(message) {
48
- p.outro(message);
41
+ async multiselect(options) {
42
+ return unwrap(await p.multiselect(options));
49
43
  }
50
44
  note(message, title) {
51
45
  p.note(message, title);
52
46
  }
47
+ outro(message) {
48
+ p.outro(message);
49
+ }
50
+ async select(options) {
51
+ return unwrap(await p.select(options));
52
+ }
53
53
  spinner() {
54
54
  const s = p.spinner();
55
55
  return {
56
- start: (msg) => s.start(msg),
57
- stop: (msg) => s.stop(msg),
56
+ start: (message) => s.start(message),
57
+ stop: (message) => s.stop(message),
58
58
  };
59
59
  }
60
- select(opts) {
61
- return __awaiter(this, void 0, void 0, function* () {
62
- return unwrap(yield p.select(opts));
63
- });
64
- }
65
- confirm(opts) {
66
- return __awaiter(this, void 0, void 0, function* () {
67
- return unwrap(yield p.confirm(opts));
68
- });
60
+ async text(options) {
61
+ return unwrap(await p.text(options));
69
62
  }
70
63
  }
71
64
  exports.ClackPrompter = ClackPrompter;
@@ -22,54 +22,40 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
25
  Object.defineProperty(exports, "__esModule", { value: true });
35
26
  exports.FsFileStore = void 0;
36
27
  const node_fs_1 = require("node:fs");
37
28
  const path = __importStar(require("node:path"));
38
29
  class FsFileStore {
39
- exists(filePath) {
40
- return __awaiter(this, void 0, void 0, function* () {
41
- try {
42
- yield node_fs_1.promises.access(filePath);
43
- return true;
44
- }
45
- catch (_a) {
46
- return false;
47
- }
48
- });
30
+ async chmod(filePath, mode) {
31
+ await node_fs_1.promises.chmod(filePath, mode);
32
+ }
33
+ async exists(filePath) {
34
+ try {
35
+ await node_fs_1.promises.access(filePath);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
49
41
  }
50
- readFile(filePath) {
51
- return __awaiter(this, void 0, void 0, function* () {
52
- try {
53
- return yield node_fs_1.promises.readFile(filePath, 'utf8');
54
- }
55
- catch (err) {
56
- if (err.code === 'ENOENT')
57
- return null;
58
- throw err;
59
- }
60
- });
42
+ async mkdir(dir) {
43
+ await node_fs_1.promises.mkdir(dir, { recursive: true });
61
44
  }
62
- writeFile(filePath, content) {
63
- return __awaiter(this, void 0, void 0, function* () {
64
- const dir = path.dirname(filePath);
65
- yield node_fs_1.promises.mkdir(dir, { recursive: true });
66
- yield node_fs_1.promises.writeFile(filePath, content, 'utf8');
67
- });
45
+ async readFile(filePath) {
46
+ try {
47
+ return await node_fs_1.promises.readFile(filePath, 'utf8');
48
+ }
49
+ catch (error) {
50
+ if (error.code === 'ENOENT')
51
+ return null;
52
+ throw error;
53
+ }
68
54
  }
69
- mkdir(dir) {
70
- return __awaiter(this, void 0, void 0, function* () {
71
- yield node_fs_1.promises.mkdir(dir, { recursive: true });
72
- });
55
+ async writeFile(filePath, content) {
56
+ const dir = path.dirname(filePath);
57
+ await node_fs_1.promises.mkdir(dir, { recursive: true });
58
+ await node_fs_1.promises.writeFile(filePath, content, 'utf8');
73
59
  }
74
60
  }
75
61
  exports.FsFileStore = FsFileStore;
@@ -1,48 +1,38 @@
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.SpawnCommandRunner = void 0;
13
4
  const node_child_process_1 = require("node:child_process");
14
5
  class SpawnCommandRunner {
15
- checkInstalled(binary) {
16
- return __awaiter(this, void 0, void 0, function* () {
17
- return new Promise((resolve) => {
18
- const child = (0, node_child_process_1.spawn)('which', [binary], { stdio: 'pipe' });
19
- child.on('close', (code) => resolve(code === 0));
20
- child.on('error', () => resolve(false));
21
- });
6
+ async checkInstalled(binary) {
7
+ return new Promise((resolve) => {
8
+ const child = (0, node_child_process_1.spawn)('which', [binary], { stdio: 'pipe' });
9
+ child.on('close', (code) => resolve(code === 0));
10
+ child.on('error', () => resolve(false));
22
11
  });
23
12
  }
24
- run(command, args, options) {
25
- return __awaiter(this, void 0, void 0, function* () {
26
- return new Promise((resolve, reject) => {
27
- var _a, _b;
28
- const child = (0, node_child_process_1.spawn)(command, args, {
29
- stdio: 'pipe',
30
- cwd: (options === null || options === void 0 ? void 0 : options.cwd) || process.cwd(),
31
- });
32
- let stdout = '';
33
- let stderr = '';
34
- (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (d) => { stdout += d.toString(); });
35
- (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on('data', (d) => { stderr += d.toString(); });
36
- child.on('close', (code) => {
37
- if (code === 0) {
38
- resolve(stdout);
39
- }
40
- else {
41
- reject(new Error(stderr.trim() || `Command failed with exit code ${code}`));
42
- }
43
- });
44
- child.on('error', (err) => reject(err));
13
+ async run(command, arguments_, options) {
14
+ return new Promise((resolve, reject) => {
15
+ const child = (0, node_child_process_1.spawn)(command, arguments_, {
16
+ cwd: options?.cwd || process.cwd(),
17
+ stdio: 'pipe',
18
+ });
19
+ let stdout = '';
20
+ let stderr = '';
21
+ child.stdout?.on('data', (d) => {
22
+ stdout += d.toString();
23
+ });
24
+ child.stderr?.on('data', (d) => {
25
+ stderr += d.toString();
26
+ });
27
+ child.on('close', (code) => {
28
+ if (code === 0) {
29
+ resolve(stdout);
30
+ }
31
+ else {
32
+ reject(new Error(stderr.trim() || `Command failed with exit code ${code}`));
33
+ }
45
34
  });
35
+ child.on('error', (error) => reject(error));
46
36
  });
47
37
  }
48
38
  }
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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';
5
+ const TOOL_AUTH_PATHS = {
6
+ opencode: (homeDir) => homeDir + '/.local/share/opencode/auth.json',
7
+ pi: (homeDir) => homeDir + '/.pi/agent/auth.json',
8
+ };
9
+ const TOOL_API_KEY_TYPES = {
10
+ opencode: 'api',
11
+ pi: 'api_key',
12
+ };
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 };
26
+ }
27
+ // Fall through to reconfigure
28
+ }
29
+ else {
30
+ prompter.note('Authentication required to use Berget AI.', 'Connect your account');
31
+ }
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...');
89
+ try {
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 };
97
+ }
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 };
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?',
108
+ });
109
+ if (shouldCreate) {
110
+ const s = prompter.spinner();
111
+ s.start('Creating API key...');
112
+ try {
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 };
120
+ }
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 };
125
+ }
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 };
130
+ }
131
+ exports.configureAuth = configureAuth;
132
+ function decodeJwtPayload(token) {
133
+ try {
134
+ const parts = token.split('.');
135
+ if (parts.length !== 3)
136
+ return null;
137
+ const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
138
+ return JSON.parse(payload);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ exports.decodeJwtPayload = decodeJwtPayload;
145
+ function hasBergetCodeSeat(accessToken) {
146
+ const payload = decodeJwtPayload(accessToken);
147
+ if (!payload || typeof payload !== 'object')
148
+ return false;
149
+ const p = payload;
150
+ const realmAccess = p.realm_access;
151
+ if (!realmAccess)
152
+ return false;
153
+ const roles = realmAccess.roles;
154
+ if (!Array.isArray(roles))
155
+ return false;
156
+ return roles.includes('berget_code_seat');
157
+ }
158
+ exports.hasBergetCodeSeat = hasBergetCodeSeat;
159
+ function isTokenExpired(expiresAt) {
160
+ const now = Date.now();
161
+ const timeUntilExpiry = expiresAt - now;
162
+ const buffer = Math.min(30 * 1000, timeUntilExpiry * 0.1);
163
+ return now + buffer >= expiresAt;
164
+ }
165
+ exports.isTokenExpired = isTokenExpired;
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);
188
+ if (jwtExpiresAt === 0) {
189
+ // Invalid token, return null
190
+ return null;
191
+ }
192
+ return {
193
+ access_token: parsed.access_token,
194
+ expires_at: jwtExpiresAt,
195
+ refresh_token: parsed.refresh_token,
196
+ };
197
+ }
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);
212
+ }
213
+ catch {
214
+ existing = {};
215
+ }
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);
235
+ }
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;
270
+ }
@@ -1,14 +1,6 @@
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
6
  super('Wizard cancelled');
@@ -17,6 +9,8 @@ class CancelledError extends Error {
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;
@@ -25,3 +19,12 @@ class CommandFailedError extends Error {
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;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });