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.
- package/.github/workflows/publish.yml +6 -6
- package/.github/workflows/test.yml +1 -1
- package/.prettierrc +5 -3
- package/dist/index.js +24 -25
- package/dist/package.json +5 -3
- package/dist/src/agents/app.js +8 -8
- package/dist/src/agents/backend.js +3 -3
- package/dist/src/agents/devops.js +8 -8
- package/dist/src/agents/frontend.js +3 -3
- package/dist/src/agents/fullstack.js +3 -3
- package/dist/src/agents/index.js +18 -18
- package/dist/src/agents/quality.js +8 -8
- package/dist/src/agents/security.js +8 -8
- package/dist/src/client.js +115 -127
- package/dist/src/commands/api-keys.js +195 -202
- package/dist/src/commands/auth.js +16 -25
- package/dist/src/commands/autocomplete.js +8 -8
- package/dist/src/commands/billing.js +10 -19
- package/dist/src/commands/chat.js +139 -170
- package/dist/src/commands/clusters.js +21 -30
- package/dist/src/commands/code/__tests__/auth-sync.test.js +189 -186
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +3 -13
- package/dist/src/commands/code/__tests__/fake-auth-service.js +21 -29
- package/dist/src/commands/code/__tests__/fake-command-runner.js +22 -33
- package/dist/src/commands/code/__tests__/fake-file-store.js +19 -41
- package/dist/src/commands/code/__tests__/fake-prompter.js +81 -97
- package/dist/src/commands/code/__tests__/setup-flow.test.js +295 -295
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -32
- package/dist/src/commands/code/adapters/fs-file-store.js +25 -44
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -41
- package/dist/src/commands/code/auth-sync.js +215 -228
- package/dist/src/commands/code/errors.js +15 -12
- package/dist/src/commands/code/setup.js +390 -425
- package/dist/src/commands/code.js +279 -294
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +16 -25
- package/dist/src/commands/users.js +9 -18
- package/dist/src/constants/command-structure.js +138 -138
- package/dist/src/services/api-key-service.js +132 -152
- package/dist/src/services/auth-service.js +81 -95
- package/dist/src/services/browser-auth.js +121 -131
- package/dist/src/services/chat-service.js +369 -386
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +9 -21
- package/dist/src/services/flux-service.js +13 -25
- package/dist/src/services/helm-service.js +9 -21
- package/dist/src/services/kubectl-service.js +15 -29
- package/dist/src/utils/config-checker.js +7 -7
- package/dist/src/utils/config-loader.js +109 -109
- package/dist/src/utils/default-api-key.js +129 -139
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +62 -62
- package/dist/src/utils/logger.js +74 -67
- package/dist/src/utils/markdown-renderer.js +28 -28
- package/dist/src/utils/opencode-validator.js +67 -69
- package/dist/src/utils/token-manager.js +67 -65
- package/dist/tests/commands/chat.test.js +30 -39
- package/dist/tests/commands/code.test.js +186 -195
- package/dist/tests/utils/config-loader.test.js +107 -107
- package/dist/tests/utils/env-manager.test.js +81 -90
- package/dist/tests/utils/opencode-validator.test.js +42 -41
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +50 -30
- package/index.ts +30 -31
- package/package.json +5 -3
- package/src/agents/app.ts +9 -9
- package/src/agents/backend.ts +4 -4
- package/src/agents/devops.ts +9 -9
- package/src/agents/frontend.ts +4 -4
- package/src/agents/fullstack.ts +4 -4
- package/src/agents/index.ts +27 -25
- package/src/agents/quality.ts +9 -9
- package/src/agents/security.ts +9 -9
- package/src/agents/types.ts +10 -10
- package/src/client.ts +85 -77
- package/src/commands/api-keys.ts +190 -185
- package/src/commands/auth.ts +15 -14
- package/src/commands/autocomplete.ts +10 -10
- package/src/commands/billing.ts +13 -12
- package/src/commands/chat.ts +145 -142
- package/src/commands/clusters.ts +20 -19
- package/src/commands/code/__tests__/auth-sync.test.ts +176 -175
- package/src/commands/code/__tests__/fake-api-key-service.ts +2 -2
- package/src/commands/code/__tests__/fake-auth-service.ts +18 -18
- package/src/commands/code/__tests__/fake-command-runner.ts +28 -22
- package/src/commands/code/__tests__/fake-file-store.ts +15 -15
- package/src/commands/code/__tests__/fake-prompter.ts +86 -85
- package/src/commands/code/__tests__/setup-flow.test.ts +253 -251
- package/src/commands/code/adapters/clack-prompter.ts +32 -30
- package/src/commands/code/adapters/fs-file-store.ts +18 -17
- package/src/commands/code/adapters/spawn-command-runner.ts +20 -15
- package/src/commands/code/auth-sync.ts +210 -210
- package/src/commands/code/errors.ts +11 -11
- package/src/commands/code/ports/auth-services.ts +7 -7
- package/src/commands/code/ports/command-runner.ts +2 -2
- package/src/commands/code/ports/file-store.ts +3 -3
- package/src/commands/code/ports/prompter.ts +13 -13
- package/src/commands/code/setup.ts +408 -406
- package/src/commands/code.ts +288 -287
- package/src/commands/index.ts +11 -10
- package/src/commands/models.ts +19 -18
- package/src/commands/users.ts +11 -10
- package/src/constants/command-structure.ts +159 -159
- package/src/services/api-key-service.ts +85 -85
- package/src/services/auth-service.ts +55 -54
- package/src/services/browser-auth.ts +62 -62
- package/src/services/chat-service.ts +169 -170
- package/src/services/cluster-service.ts +28 -28
- package/src/services/collaborator-service.ts +6 -6
- package/src/services/flux-service.ts +17 -17
- package/src/services/helm-service.ts +11 -11
- package/src/services/kubectl-service.ts +12 -12
- package/src/types/api.d.ts +1933 -1933
- package/src/types/json.d.ts +1 -1
- package/src/utils/config-checker.ts +6 -6
- package/src/utils/config-loader.ts +130 -129
- package/src/utils/default-api-key.ts +81 -80
- package/src/utils/env-manager.ts +37 -37
- package/src/utils/error-handler.ts +64 -64
- package/src/utils/logger.ts +72 -66
- package/src/utils/markdown-renderer.ts +28 -28
- package/src/utils/opencode-validator.ts +72 -71
- package/src/utils/token-manager.ts +69 -68
- package/tests/commands/chat.test.ts +32 -31
- package/tests/commands/code.test.ts +182 -181
- package/tests/utils/config-loader.test.ts +111 -110
- package/tests/utils/env-manager.test.ts +83 -79
- package/tests/utils/opencode-validator.test.ts +43 -42
- package/tsconfig.json +2 -1
- 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.
|
|
13
|
-
const CLI_AUTH_PATH = (homeDir) => homeDir +
|
|
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 +
|
|
16
|
-
pi: (homeDir) => homeDir +
|
|
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:
|
|
20
|
-
pi:
|
|
10
|
+
opencode: 'api',
|
|
11
|
+
pi: 'api_key',
|
|
21
12
|
};
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
29
|
+
else {
|
|
30
|
+
prompter.note('Authentication required to use Berget AI.', 'Connect your account');
|
|
35
31
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
|
73
|
-
|
|
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
|
|
76
|
-
|
|
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.
|
|
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],
|
|
137
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
|
|
87
138
|
return JSON.parse(payload);
|
|
88
139
|
}
|
|
89
|
-
catch
|
|
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 !==
|
|
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(
|
|
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
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
return { authenticated: false };
|
|
189
|
+
// Invalid token, return null
|
|
190
|
+
return null;
|
|
201
191
|
}
|
|
202
|
-
|
|
203
|
-
access_token:
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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.
|
|
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(
|
|
15
|
-
this.name =
|
|
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 =
|
|
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;
|