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.
- package/.github/workflows/publish.yml +2 -2
- package/.github/workflows/test.yml +10 -4
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +7 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +10 -11
- package/dist/package.json +30 -2
- package/dist/src/agents/app.js +28 -0
- package/dist/src/agents/backend.js +25 -0
- package/dist/src/agents/devops.js +34 -0
- package/dist/src/agents/frontend.js +25 -0
- package/dist/src/agents/fullstack.js +25 -0
- package/dist/src/agents/index.js +61 -0
- package/dist/src/agents/quality.js +70 -0
- package/dist/src/agents/security.js +26 -0
- package/dist/src/agents/types.js +2 -0
- package/dist/src/client.js +97 -117
- package/dist/src/commands/api-keys.js +75 -90
- package/dist/src/commands/auth.js +7 -16
- package/dist/src/commands/autocomplete.js +1 -1
- package/dist/src/commands/billing.js +6 -17
- package/dist/src/commands/chat.js +68 -101
- package/dist/src/commands/clusters.js +9 -18
- package/dist/src/commands/code/__tests__/auth-sync.test.js +351 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +13 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +47 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +21 -34
- package/dist/src/commands/code/__tests__/fake-file-store.js +20 -33
- package/dist/src/commands/code/__tests__/fake-prompter.js +83 -57
- package/dist/src/commands/code/__tests__/setup-flow.test.js +359 -92
- package/dist/src/commands/code/adapters/clack-prompter.js +15 -22
- package/dist/src/commands/code/adapters/fs-file-store.js +26 -40
- package/dist/src/commands/code/adapters/spawn-command-runner.js +27 -37
- package/dist/src/commands/code/auth-sync.js +270 -0
- package/dist/src/commands/code/errors.js +12 -9
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +387 -281
- package/dist/src/commands/code.js +205 -332
- package/dist/src/commands/index.js +5 -5
- package/dist/src/commands/models.js +6 -17
- package/dist/src/commands/users.js +5 -16
- package/dist/src/constants/command-structure.js +104 -104
- package/dist/src/services/api-key-service.js +132 -157
- package/dist/src/services/auth-service.js +89 -342
- package/dist/src/services/browser-auth.js +268 -0
- package/dist/src/services/chat-service.js +371 -401
- package/dist/src/services/cluster-service.js +47 -62
- package/dist/src/services/collaborator-service.js +10 -25
- package/dist/src/services/flux-service.js +14 -29
- package/dist/src/services/helm-service.js +10 -25
- package/dist/src/services/kubectl-service.js +16 -33
- package/dist/src/utils/config-checker.js +3 -3
- package/dist/src/utils/config-loader.js +95 -95
- package/dist/src/utils/default-api-key.js +124 -134
- package/dist/src/utils/env-manager.js +55 -66
- package/dist/src/utils/error-handler.js +20 -21
- package/dist/src/utils/logger.js +72 -65
- package/dist/src/utils/markdown-renderer.js +27 -27
- package/dist/src/utils/opencode-validator.js +63 -68
- package/dist/src/utils/token-manager.js +74 -45
- package/dist/tests/commands/chat.test.js +16 -25
- package/dist/tests/commands/code.test.js +95 -104
- package/dist/tests/utils/config-loader.test.js +48 -48
- package/dist/tests/utils/env-manager.test.js +43 -52
- package/dist/tests/utils/opencode-validator.test.js +22 -21
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +67 -0
- package/index.ts +35 -42
- package/package.json +30 -2
- package/src/agents/app.ts +27 -0
- package/src/agents/backend.ts +24 -0
- package/src/agents/devops.ts +33 -0
- package/src/agents/frontend.ts +24 -0
- package/src/agents/fullstack.ts +24 -0
- package/src/agents/index.ts +73 -0
- package/src/agents/quality.ts +69 -0
- package/src/agents/security.ts +26 -0
- package/src/agents/types.ts +17 -0
- package/src/client.ts +118 -152
- package/src/commands/api-keys.ts +241 -333
- package/src/commands/auth.ts +22 -27
- package/src/commands/autocomplete.ts +9 -9
- package/src/commands/billing.ts +20 -24
- package/src/commands/chat.ts +248 -338
- package/src/commands/clusters.ts +27 -26
- package/src/commands/code/__tests__/auth-sync.test.ts +482 -0
- package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
- package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
- package/src/commands/code/__tests__/fake-command-runner.ts +45 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +116 -77
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -268
- package/src/commands/code/adapters/clack-prompter.ts +53 -39
- package/src/commands/code/adapters/fs-file-store.ts +32 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +38 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +18 -18
- package/src/commands/code/ports/auth-services.ts +14 -0
- package/src/commands/code/ports/command-runner.ts +8 -4
- package/src/commands/code/ports/file-store.ts +5 -4
- package/src/commands/code/ports/prompter.ts +24 -18
- package/src/commands/code/setup.ts +570 -340
- package/src/commands/code.ts +338 -539
- package/src/commands/index.ts +20 -19
- package/src/commands/models.ts +28 -32
- package/src/commands/users.ts +15 -21
- package/src/constants/command-structure.ts +134 -157
- package/src/services/api-key-service.ts +105 -122
- package/src/services/auth-service.ts +99 -345
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +265 -299
- package/src/services/cluster-service.ts +42 -45
- package/src/services/collaborator-service.ts +14 -19
- package/src/services/flux-service.ts +23 -25
- package/src/services/helm-service.ts +19 -21
- package/src/services/kubectl-service.ts +17 -19
- package/src/types/api.d.ts +1905 -1907
- package/src/types/json.d.ts +2 -2
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +162 -178
- package/src/utils/default-api-key.ts +114 -125
- package/src/utils/env-manager.ts +53 -57
- package/src/utils/error-handler.ts +61 -56
- package/src/utils/logger.ts +79 -73
- package/src/utils/markdown-renderer.ts +31 -31
- package/src/utils/opencode-validator.ts +85 -89
- package/src/utils/token-manager.ts +108 -87
- package/templates/agents/app.md +1 -0
- package/templates/agents/backend.md +1 -0
- package/templates/agents/devops.md +2 -0
- package/templates/agents/frontend.md +1 -0
- package/templates/agents/fullstack.md +1 -0
- package/templates/agents/quality.md +45 -40
- package/templates/agents/security.md +1 -0
- package/tests/commands/chat.test.ts +53 -62
- package/tests/commands/code.test.ts +265 -310
- package/tests/utils/config-loader.test.ts +189 -188
- package/tests/utils/env-manager.test.ts +110 -113
- package/tests/utils/opencode-validator.test.ts +52 -56
- package/tsconfig.json +4 -3
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- package/TODO.md +0 -19
|
@@ -1,134 +1,130 @@
|
|
|
1
|
-
import Ajv from 'ajv'
|
|
2
|
-
import addFormats from 'ajv-formats'
|
|
3
|
-
import { readFileSync } from 'fs'
|
|
4
|
-
import { join } from 'path'
|
|
5
|
-
import { dirname } from 'path'
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import addFormats from 'ajv-formats';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
6
|
|
|
7
7
|
// Load the official OpenCode JSON Schema
|
|
8
|
-
const __dirname = dirname(__filename)
|
|
9
|
-
const schemaPath = join(__dirname, '..', 'schemas', 'opencode-schema.json')
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
const schemaPath = join(__dirname, '..', 'schemas', 'opencode-schema.json');
|
|
10
10
|
|
|
11
|
-
let ajv: Ajv
|
|
12
|
-
let openCodeSchema: any
|
|
13
|
-
let validateFunction: any
|
|
11
|
+
let ajv: Ajv;
|
|
12
|
+
let openCodeSchema: any;
|
|
13
|
+
let validateFunction: any;
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
|
-
const schemaContent = readFileSync(schemaPath, 'utf-8')
|
|
17
|
-
openCodeSchema = JSON.parse(schemaContent)
|
|
16
|
+
const schemaContent = readFileSync(schemaPath, 'utf-8');
|
|
17
|
+
openCodeSchema = JSON.parse(schemaContent);
|
|
18
18
|
|
|
19
19
|
// Initialize AJV with formats and options
|
|
20
20
|
ajv = new Ajv({
|
|
21
21
|
allErrors: true,
|
|
22
|
-
verbose: true,
|
|
23
|
-
strict: false,
|
|
24
22
|
allowUnionTypes: true,
|
|
25
23
|
removeAdditional: false,
|
|
26
|
-
|
|
24
|
+
strict: false,
|
|
25
|
+
verbose: true,
|
|
26
|
+
});
|
|
27
27
|
|
|
28
28
|
// Add JSON Schema formats
|
|
29
|
-
addFormats(ajv)
|
|
29
|
+
addFormats(ajv);
|
|
30
30
|
|
|
31
31
|
// Compile the schema
|
|
32
|
-
validateFunction = ajv.compile(openCodeSchema)
|
|
32
|
+
validateFunction = ajv.compile(openCodeSchema);
|
|
33
33
|
} catch (error) {
|
|
34
|
-
console.error('Failed to load OpenCode schema:', error)
|
|
35
|
-
throw new Error('Could not initialize OpenCode validator')
|
|
34
|
+
console.error('Failed to load OpenCode schema:', error);
|
|
35
|
+
throw new Error('Could not initialize OpenCode validator');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export type OpenCodeConfig = any
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Validate OpenCode configuration against the official JSON Schema
|
|
42
|
-
*/
|
|
43
|
-
export function validateOpenCodeConfig(config: any): {
|
|
44
|
-
valid: boolean
|
|
45
|
-
errors?: string[]
|
|
46
|
-
} {
|
|
47
|
-
try {
|
|
48
|
-
if (!validateFunction) {
|
|
49
|
-
return { valid: false, errors: ['Schema validator not initialized'] }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const isValid = validateFunction(config)
|
|
53
|
-
|
|
54
|
-
if (isValid) {
|
|
55
|
-
return { valid: true }
|
|
56
|
-
} else {
|
|
57
|
-
const errors = validateFunction.errors?.map((err: any) => {
|
|
58
|
-
const path = err.instancePath || err.schemaPath || 'root'
|
|
59
|
-
const message = err.message || 'Unknown error'
|
|
60
|
-
return `${path}: ${message}`
|
|
61
|
-
}) || ['Unknown validation error']
|
|
62
|
-
|
|
63
|
-
return { valid: false, errors }
|
|
64
|
-
}
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error('Validation error:', error)
|
|
67
|
-
return { valid: false, errors: ['Validation process failed'] }
|
|
68
|
-
}
|
|
69
|
-
}
|
|
38
|
+
export type OpenCodeConfig = any;
|
|
70
39
|
|
|
71
40
|
/**
|
|
72
41
|
* Fix common OpenCode configuration issues
|
|
73
42
|
*/
|
|
74
43
|
export function fixOpenCodeConfig(config: any): OpenCodeConfig {
|
|
75
|
-
const fixed = { ...config }
|
|
44
|
+
const fixed = { ...config };
|
|
76
45
|
|
|
77
46
|
// Fix tools.compact - should be boolean, not object
|
|
78
47
|
if (fixed.tools && typeof fixed.tools.compact === 'object') {
|
|
79
|
-
console.warn('⚠️ Converting tools.compact from object to boolean')
|
|
48
|
+
console.warn('⚠️ Converting tools.compact from object to boolean');
|
|
80
49
|
// If it has properties, assume it should be enabled
|
|
81
|
-
fixed.tools.compact = true
|
|
50
|
+
fixed.tools.compact = true;
|
|
82
51
|
}
|
|
83
52
|
|
|
84
53
|
// Remove invalid properties
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
if (fixed[
|
|
88
|
-
console.warn(`⚠️ Removing invalid property: ${
|
|
89
|
-
delete fixed[
|
|
54
|
+
const invalidProperties = ['maxTokens', 'contextWindow'];
|
|
55
|
+
for (const property of invalidProperties) {
|
|
56
|
+
if (fixed[property] !== undefined) {
|
|
57
|
+
console.warn(`⚠️ Removing invalid property: ${property}`);
|
|
58
|
+
delete fixed[property];
|
|
90
59
|
}
|
|
91
|
-
}
|
|
60
|
+
}
|
|
92
61
|
|
|
93
62
|
// Fix provider models with invalid properties
|
|
94
63
|
if (fixed.provider) {
|
|
95
64
|
Object.values(fixed.provider).forEach((provider: any) => {
|
|
96
65
|
if (provider?.models) {
|
|
97
66
|
Object.values(provider.models).forEach((model: any) => {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
model.limit.context = newContext
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Set a reasonable default for output if not present
|
|
116
|
-
// (typically 1/4 to 1/8 of context window)
|
|
117
|
-
if (!model.limit.output && model.limit.context) {
|
|
118
|
-
model.limit.output = Math.floor(model.limit.context / 4)
|
|
67
|
+
if (
|
|
68
|
+
model &&
|
|
69
|
+
typeof model === 'object' && // Move maxTokens/contextWindow to proper structure if needed
|
|
70
|
+
(model.maxTokens || model.contextWindow)
|
|
71
|
+
) {
|
|
72
|
+
if (!model.limit) model.limit = {};
|
|
73
|
+
|
|
74
|
+
// Use the larger of maxTokens/contextWindow for context
|
|
75
|
+
const contextValues = [model.maxTokens, model.contextWindow].filter(Boolean);
|
|
76
|
+
if (contextValues.length > 0) {
|
|
77
|
+
const newContext = Math.max(...contextValues);
|
|
78
|
+
if (!model.limit.context || newContext > model.limit.context) {
|
|
79
|
+
model.limit.context = newContext;
|
|
119
80
|
}
|
|
81
|
+
}
|
|
120
82
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
83
|
+
// Set a reasonable default for output if not present
|
|
84
|
+
// (typically 1/4 to 1/8 of context window)
|
|
85
|
+
if (!model.limit.output && model.limit.context) {
|
|
86
|
+
model.limit.output = Math.floor(model.limit.context / 4);
|
|
126
87
|
}
|
|
88
|
+
|
|
89
|
+
delete model.maxTokens;
|
|
90
|
+
delete model.contextWindow;
|
|
91
|
+
console.warn('⚠️ Moved maxTokens/contextWindow to limit.context/output');
|
|
127
92
|
}
|
|
128
|
-
})
|
|
93
|
+
});
|
|
129
94
|
}
|
|
130
|
-
})
|
|
95
|
+
});
|
|
131
96
|
}
|
|
132
97
|
|
|
133
|
-
return fixed
|
|
98
|
+
return fixed;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validate OpenCode configuration against the official JSON Schema
|
|
103
|
+
*/
|
|
104
|
+
export function validateOpenCodeConfig(config: any): {
|
|
105
|
+
errors?: string[];
|
|
106
|
+
valid: boolean;
|
|
107
|
+
} {
|
|
108
|
+
try {
|
|
109
|
+
if (!validateFunction) {
|
|
110
|
+
return { errors: ['Schema validator not initialized'], valid: false };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const isValid = validateFunction(config);
|
|
114
|
+
|
|
115
|
+
if (isValid) {
|
|
116
|
+
return { valid: true };
|
|
117
|
+
} else {
|
|
118
|
+
const errors = validateFunction.errors?.map((error: any) => {
|
|
119
|
+
const path = error.instancePath || error.schemaPath || 'root';
|
|
120
|
+
const message = error.message || 'Unknown error';
|
|
121
|
+
return `${path}: ${message}`;
|
|
122
|
+
}) || ['Unknown validation error'];
|
|
123
|
+
|
|
124
|
+
return { errors, valid: false };
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Validation error:', error);
|
|
128
|
+
return { errors: ['Validation process failed'], valid: false };
|
|
129
|
+
}
|
|
134
130
|
}
|
|
@@ -1,94 +1,64 @@
|
|
|
1
|
-
import * as fs from 'fs'
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
4
|
-
|
|
5
|
-
import { logger } from './logger'
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { logger } from './logger';
|
|
6
6
|
|
|
7
7
|
interface TokenData {
|
|
8
|
-
access_token: string
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
access_token: string;
|
|
9
|
+
expires_at: number; // timestamp in milliseconds
|
|
10
|
+
refresh_token: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Manages authentication tokens including refresh functionality
|
|
15
15
|
*/
|
|
16
16
|
export class TokenManager {
|
|
17
|
-
private static instance: TokenManager
|
|
18
|
-
private
|
|
19
|
-
private
|
|
17
|
+
private static instance: TokenManager;
|
|
18
|
+
private tokenData: null | TokenData = null;
|
|
19
|
+
private tokenFilePath: string;
|
|
20
20
|
|
|
21
21
|
private constructor() {
|
|
22
22
|
// Set up token file path in user's home directory
|
|
23
|
-
const bergetDir = path.join(os.homedir(), '.berget')
|
|
23
|
+
const bergetDir = path.join(os.homedir(), '.berget');
|
|
24
24
|
if (!fs.existsSync(bergetDir)) {
|
|
25
|
-
fs.mkdirSync(bergetDir, { recursive: true })
|
|
25
|
+
fs.mkdirSync(bergetDir, { recursive: true });
|
|
26
26
|
}
|
|
27
|
-
this.tokenFilePath = path.join(bergetDir, 'auth.json')
|
|
28
|
-
this.loadToken()
|
|
27
|
+
this.tokenFilePath = path.join(bergetDir, 'auth.json');
|
|
28
|
+
this.loadToken();
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
public static getInstance(): TokenManager {
|
|
32
32
|
if (!TokenManager.instance) {
|
|
33
|
-
TokenManager.instance = new TokenManager()
|
|
33
|
+
TokenManager.instance = new TokenManager();
|
|
34
34
|
}
|
|
35
|
-
return TokenManager.instance
|
|
35
|
+
return TokenManager.instance;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
*/
|
|
41
|
-
private loadToken(): void {
|
|
42
|
-
try {
|
|
43
|
-
if (fs.existsSync(this.tokenFilePath)) {
|
|
44
|
-
const data = fs.readFileSync(this.tokenFilePath, 'utf8')
|
|
45
|
-
this.tokenData = JSON.parse(data)
|
|
46
|
-
}
|
|
47
|
-
} catch (error) {
|
|
48
|
-
logger.error('Failed to load authentication token')
|
|
49
|
-
this.tokenData = null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Save token data to file
|
|
39
|
+
* Clear all token data
|
|
55
40
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fs.writeFileSync(
|
|
60
|
-
this.tokenFilePath,
|
|
61
|
-
JSON.stringify(this.tokenData, null, 2),
|
|
62
|
-
)
|
|
63
|
-
// Set file permissions to be readable only by the owner
|
|
64
|
-
fs.chmodSync(this.tokenFilePath, 0o600)
|
|
65
|
-
} else {
|
|
66
|
-
// If token data is null, remove the file
|
|
67
|
-
if (fs.existsSync(this.tokenFilePath)) {
|
|
68
|
-
fs.unlinkSync(this.tokenFilePath)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
} catch (error) {
|
|
72
|
-
logger.error('Failed to save authentication token')
|
|
73
|
-
}
|
|
41
|
+
public clearTokens(): void {
|
|
42
|
+
this.tokenData = null;
|
|
43
|
+
this.saveToken();
|
|
74
44
|
}
|
|
75
45
|
|
|
76
46
|
/**
|
|
77
47
|
* Get the current access token
|
|
78
48
|
* @returns The access token or null if not available
|
|
79
49
|
*/
|
|
80
|
-
public getAccessToken():
|
|
81
|
-
if (!this.tokenData) return null
|
|
82
|
-
return this.tokenData.access_token
|
|
50
|
+
public getAccessToken(): null | string {
|
|
51
|
+
if (!this.tokenData) return null;
|
|
52
|
+
return this.tokenData.access_token;
|
|
83
53
|
}
|
|
84
54
|
|
|
85
55
|
/**
|
|
86
56
|
* Get the refresh token
|
|
87
57
|
* @returns The refresh token or null if not available
|
|
88
58
|
*/
|
|
89
|
-
public getRefreshToken():
|
|
90
|
-
if (!this.tokenData) return null
|
|
91
|
-
return this.tokenData.refresh_token
|
|
59
|
+
public getRefreshToken(): null | string {
|
|
60
|
+
if (!this.tokenData) return null;
|
|
61
|
+
return this.tokenData.refresh_token;
|
|
92
62
|
}
|
|
93
63
|
|
|
94
64
|
/**
|
|
@@ -96,34 +66,34 @@ export class TokenManager {
|
|
|
96
66
|
* @returns true if expired or about to expire (within 10% of lifetime or 30 seconds), false otherwise
|
|
97
67
|
*/
|
|
98
68
|
public isTokenExpired(): boolean {
|
|
99
|
-
if (!this.tokenData || !this.tokenData.expires_at) return true
|
|
69
|
+
if (!this.tokenData || !this.tokenData.expires_at) return true;
|
|
100
70
|
|
|
101
71
|
try {
|
|
102
|
-
const now = Date.now()
|
|
103
|
-
const expiresAt = this.tokenData.expires_at
|
|
104
|
-
const timeUntilExpiry = expiresAt - now
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const expiresAt = this.tokenData.expires_at;
|
|
74
|
+
const timeUntilExpiry = expiresAt - now;
|
|
105
75
|
|
|
106
76
|
// Use 10% of remaining lifetime or 30 seconds, whichever is smaller
|
|
107
77
|
// This ensures we don't refresh tokens that were just issued
|
|
108
|
-
const minBuffer = 30 * 1000 // 30 seconds minimum
|
|
109
|
-
const percentBuffer = timeUntilExpiry * 0.1 // 10% of lifetime
|
|
110
|
-
const expirationBuffer = Math.min(minBuffer, percentBuffer)
|
|
78
|
+
const minBuffer = 30 * 1000; // 30 seconds minimum
|
|
79
|
+
const percentBuffer = timeUntilExpiry * 0.1; // 10% of lifetime
|
|
80
|
+
const expirationBuffer = Math.min(minBuffer, percentBuffer);
|
|
111
81
|
|
|
112
|
-
const isExpired = now + expirationBuffer >= expiresAt
|
|
82
|
+
const isExpired = now + expirationBuffer >= expiresAt;
|
|
113
83
|
|
|
114
84
|
if (isExpired) {
|
|
115
85
|
logger.debug(
|
|
116
86
|
`Token expired or expiring soon. Current time: ${new Date().toISOString()}, Expiry: ${new Date(expiresAt).toISOString()}`,
|
|
117
|
-
)
|
|
87
|
+
);
|
|
118
88
|
}
|
|
119
89
|
|
|
120
|
-
return isExpired
|
|
90
|
+
return isExpired;
|
|
121
91
|
} catch (error) {
|
|
122
92
|
// If there's any error checking expiration, assume token is expired
|
|
123
93
|
logger.error(
|
|
124
94
|
`Error checking token expiration: ${error instanceof Error ? error.message : String(error)}`,
|
|
125
|
-
)
|
|
126
|
-
return true
|
|
95
|
+
);
|
|
96
|
+
return true;
|
|
127
97
|
}
|
|
128
98
|
}
|
|
129
99
|
|
|
@@ -131,39 +101,90 @@ export class TokenManager {
|
|
|
131
101
|
* Set new token data
|
|
132
102
|
* @param accessToken The new access token
|
|
133
103
|
* @param refreshToken The new refresh token
|
|
134
|
-
* @param expiresIn Expiration time in seconds
|
|
104
|
+
* @param expiresIn Expiration time in seconds (fallback if JWT parsing fails)
|
|
135
105
|
*/
|
|
136
|
-
public setTokens(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
106
|
+
public setTokens(accessToken: string, refreshToken: string, expiresIn: number): void {
|
|
107
|
+
// Extract the actual expiry time from the JWT token
|
|
108
|
+
const jwtExpiresAt = extractJwtExpiresAt(accessToken);
|
|
109
|
+
const expiresAt = jwtExpiresAt > 0 ? jwtExpiresAt : Date.now() + expiresIn * 1000;
|
|
110
|
+
|
|
141
111
|
this.tokenData = {
|
|
142
112
|
access_token: accessToken,
|
|
113
|
+
expires_at: expiresAt,
|
|
143
114
|
refresh_token: refreshToken,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.saveToken()
|
|
115
|
+
};
|
|
116
|
+
this.saveToken();
|
|
147
117
|
}
|
|
148
118
|
|
|
149
119
|
/**
|
|
150
120
|
* Update just the access token and its expiration
|
|
151
121
|
* @param accessToken The new access token
|
|
152
|
-
* @param expiresIn Expiration time in seconds
|
|
122
|
+
* @param expiresIn Expiration time in seconds (fallback if JWT parsing fails)
|
|
153
123
|
*/
|
|
154
124
|
public updateAccessToken(accessToken: string, expiresIn: number): void {
|
|
155
|
-
if (!this.tokenData) return
|
|
125
|
+
if (!this.tokenData) return;
|
|
126
|
+
|
|
127
|
+
// Extract the actual expiry time from the JWT token
|
|
128
|
+
const jwtExpiresAt = extractJwtExpiresAt(accessToken);
|
|
129
|
+
const expiresAt = jwtExpiresAt > 0 ? jwtExpiresAt : Date.now() + expiresIn * 1000;
|
|
156
130
|
|
|
157
|
-
this.tokenData.access_token = accessToken
|
|
158
|
-
this.tokenData.expires_at =
|
|
159
|
-
this.saveToken()
|
|
131
|
+
this.tokenData.access_token = accessToken;
|
|
132
|
+
this.tokenData.expires_at = expiresAt;
|
|
133
|
+
this.saveToken();
|
|
160
134
|
}
|
|
161
135
|
|
|
162
136
|
/**
|
|
163
|
-
*
|
|
137
|
+
* Load token data from file
|
|
164
138
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
139
|
+
private loadToken(): void {
|
|
140
|
+
try {
|
|
141
|
+
if (fs.existsSync(this.tokenFilePath)) {
|
|
142
|
+
const data = fs.readFileSync(this.tokenFilePath, 'utf8');
|
|
143
|
+
this.tokenData = JSON.parse(data);
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
logger.error('Failed to load authentication token');
|
|
147
|
+
this.tokenData = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Save token data to file
|
|
153
|
+
*/
|
|
154
|
+
private saveToken(): void {
|
|
155
|
+
try {
|
|
156
|
+
if (this.tokenData) {
|
|
157
|
+
fs.writeFileSync(this.tokenFilePath, JSON.stringify(this.tokenData, null, 2));
|
|
158
|
+
// Set file permissions to be readable only by the owner
|
|
159
|
+
fs.chmodSync(this.tokenFilePath, 0o600);
|
|
160
|
+
} else {
|
|
161
|
+
// If token data is null, remove the file
|
|
162
|
+
if (fs.existsSync(this.tokenFilePath)) {
|
|
163
|
+
fs.unlinkSync(this.tokenFilePath);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
logger.error('Failed to save authentication token');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extract the expiration time from a JWT token
|
|
174
|
+
* @param accessToken The JWT access token
|
|
175
|
+
* @returns The expiration timestamp in milliseconds, or 0 if invalid
|
|
176
|
+
*/
|
|
177
|
+
function extractJwtExpiresAt(accessToken: string): number {
|
|
178
|
+
try {
|
|
179
|
+
const parts = accessToken.split('.');
|
|
180
|
+
if (parts.length !== 3) return 0;
|
|
181
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf8');
|
|
182
|
+
const decoded = JSON.parse(payload);
|
|
183
|
+
if (typeof decoded.exp === 'number') {
|
|
184
|
+
return decoded.exp * 1000; // JWT exp is in seconds, convert to milliseconds
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// If decoding fails, return 0 (treated as expired)
|
|
168
188
|
}
|
|
189
|
+
return 0;
|
|
169
190
|
}
|
package/templates/agents/app.md
CHANGED
|
@@ -12,6 +12,7 @@ permission:
|
|
|
12
12
|
You are Berget Code App agent. Voice: Scandinavian calm—precise, concise, confident. Expo + React Native + TypeScript. Structure by components/hooks/services/navigation. Components are pure; data via props; refactor shared logic into hooks/stores. Share tokens with frontend. Mock data in /data via typed hooks; later replace with live APIs. Offline via SQLite/MMKV; notifications via Expo. Request permissions only when needed. Subtle, meaningful motion; light/dark parity.
|
|
13
13
|
|
|
14
14
|
GIT WORKFLOW RULES (CRITICAL):
|
|
15
|
+
|
|
15
16
|
- NEVER push directly to main branch - ALWAYS use pull requests
|
|
16
17
|
- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
|
|
17
18
|
- ALWAYS clean up test files, documentation files, and temporary artifacts before committing
|
|
@@ -12,6 +12,7 @@ permission:
|
|
|
12
12
|
You are Berget Code Backend agent. Voice: Scandinavian calm—precise, concise, confident. TypeScript + Koa. Prefer many small pure functions; avoid big try/catch blocks. Routes thin; logic in services/adapters/domain. Validate with Zod; auto-generate OpenAPI. Adapters isolate external systems; domain never depends on framework. Test with supertest; idempotent and stateless by default. Each microservice emits an OpenAPI contract; changes propagate upward to types. Code Quality & Refactoring Principles: Apply Single Responsibility Principle, fail fast with explicit errors, eliminate code duplication, remove nested complexity, use descriptive error codes, keep functions under 30 lines. Always leave code cleaner and more readable than you found it.
|
|
13
13
|
|
|
14
14
|
GIT WORKFLOW RULES (CRITICAL):
|
|
15
|
+
|
|
15
16
|
- NEVER push directly to main branch - ALWAYS use pull requests
|
|
16
17
|
- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
|
|
17
18
|
- ALWAYS clean up test files, documentation files, and temporary artifacts before committing
|
|
@@ -12,6 +12,7 @@ permission:
|
|
|
12
12
|
You are Berget Code DevOps agent. Voice: Scandinavian calm—precise, concise, confident. Start simple: k8s/{deployment,service,ingress}. Add FluxCD sync to repo and image automation. Use Kustomize bases/overlays (staging, production). Add dependencies via Helm from upstream sources; prefer native operators when available (CloudNativePG, cert-manager, external-dns). SemVer with -rc tags keeps CI environments current. Observability with Prometheus/Grafana. No manual kubectl in production—Git is the source of truth.
|
|
13
13
|
|
|
14
14
|
GIT WORKFLOW RULES (CRITICAL):
|
|
15
|
+
|
|
15
16
|
- NEVER push directly to main branch - ALWAYS use pull requests
|
|
16
17
|
- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
|
|
17
18
|
- ALWAYS clean up test files, documentation files, and temporary artifacts before committing
|
|
@@ -20,6 +21,7 @@ GIT WORKFLOW RULES (CRITICAL):
|
|
|
20
21
|
- ALWAYS run tests and build before creating PR
|
|
21
22
|
|
|
22
23
|
Helm Values Configuration Process:
|
|
24
|
+
|
|
23
25
|
1. Documentation First Approach: Always fetch official documentation from Artifact Hub/GitHub for the specific chart version before writing values. Search Artifact Hub for exact chart version documentation, check the chart's GitHub repository for official docs and examples, verify the exact version being used in the deployment.
|
|
24
26
|
2. Validation Requirements: Check for available validation schemas before committing YAML files. Use Helm's built-in validation tools (helm lint, helm template). Validate against JSON schema if available for the chart. Ensure YAML syntax correctness with linters.
|
|
25
27
|
3. Standard Workflow: Identify chart name and exact version. Fetch official documentation from Artifact Hub/GitHub. Check for available schemas and validation tools. Write values according to official documentation. Validate against schema (if available). Test with helm template or helm lint. Commit validated YAML files.
|
|
@@ -14,6 +14,7 @@ You are Berget Code Frontend agent. Voice: Scandinavian calm—precise, concise,
|
|
|
14
14
|
IMPORTANT: You have NO bash access and cannot run git commands. When your frontend implementation tasks are complete, inform the user that changes are ready and suggest using /pr command to create a pull request with proper testing and quality checks.
|
|
15
15
|
|
|
16
16
|
CODE QUALITY RULES:
|
|
17
|
+
|
|
17
18
|
- Write clean, production-ready code
|
|
18
19
|
- Follow React and TypeScript best practices
|
|
19
20
|
- Ensure accessibility and responsive design
|
|
@@ -12,6 +12,7 @@ permission:
|
|
|
12
12
|
Voice: Scandinavian calm—precise, concise, confident; no fluff. You are Berget Code Fullstack agent. Act as a router and coordinator in a monorepo. Bottom-up schema: database → OpenAPI → generated types. Top-down types: API → UI → components. Use openapi-fetch and Zod at every boundary; compile-time errors are desired when contracts change. Routing rules: if task/paths match /apps/frontend or React (.tsx) → use frontend; if /apps/app or Expo/React Native → app; if /infra, /k8s, flux-system, kustomization.yaml, Helm values → devops; if /services, Koa routers, services/adapters/domain → backend. If ambiguous, remain fullstack and outline the end-to-end plan, then delegate subtasks to the right persona. Security: validate inputs; secrets via FluxCD SOPS/Sealed Secrets. Documentation is generated from code—never duplicated.
|
|
13
13
|
|
|
14
14
|
GIT WORKFLOW RULES (CRITICAL):
|
|
15
|
+
|
|
15
16
|
- NEVER push directly to main branch - ALWAYS use pull requests
|
|
16
17
|
- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
|
|
17
18
|
- ALWAYS clean up test files, documentation files, and temporary artifacts before committing
|
|
@@ -12,53 +12,58 @@ permission:
|
|
|
12
12
|
Voice: Scandinavian calm—precise, concise, confident. You are Berget Code Quality agent. Specialist in code quality assurance, testing, building, and pull request lifecycle management.
|
|
13
13
|
|
|
14
14
|
Core responsibilities:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
|
|
16
|
+
- Run comprehensive test suites (npm test, npm run test, jest, vitest)
|
|
17
|
+
- Execute build processes (npm run build, webpack, vite, tsc)
|
|
18
|
+
- Create and manage pull requests with proper descriptions
|
|
19
|
+
- Handle merge conflicts and keep main updated
|
|
20
|
+
- Monitor GitHub for reviewer comments and address them
|
|
21
|
+
- Ensure code quality standards are met
|
|
22
|
+
- Validate linting and formatting (npm run lint, prettier)
|
|
23
|
+
- Check test coverage and performance benchmarks
|
|
24
|
+
- Handle CI/CD pipeline validation
|
|
24
25
|
|
|
25
26
|
Complete PR Workflow:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
|
|
28
|
+
1. Ensure all tests pass: npm test
|
|
29
|
+
2. Build successfully: npm run build
|
|
30
|
+
3. Commit all changes with proper message
|
|
31
|
+
4. Push to feature branch
|
|
32
|
+
5. Update main branch and handle merge conflicts
|
|
33
|
+
6. Create or update PR with comprehensive description
|
|
34
|
+
7. Monitor for reviewer comments
|
|
35
|
+
8. Address feedback and push updates
|
|
36
|
+
9. Always provide PR URL for user review
|
|
35
37
|
|
|
36
38
|
Essential CLI commands:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
|
|
40
|
+
- npm test or npm run test (run test suite)
|
|
41
|
+
- npm run build (build project)
|
|
42
|
+
- npm run lint (run linting)
|
|
43
|
+
- npm run format (format code)
|
|
44
|
+
- npm run test:coverage (check coverage)
|
|
45
|
+
- git add <specific-files> && git commit -m "message" && git push (commit and push)
|
|
46
|
+
- git checkout main && git pull origin main (update main)
|
|
47
|
+
- git checkout feature-branch && git merge main (handle conflicts)
|
|
48
|
+
- gh pr create --title "title" --body "body" (create PR)
|
|
49
|
+
- gh pr view --comments (check PR comments)
|
|
50
|
+
- gh pr edit --title "title" --body "body" (update PR)
|
|
48
51
|
|
|
49
52
|
PR Creation Process:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
|
|
54
|
+
- Always include clear summary of changes
|
|
55
|
+
- List technical details and improvements
|
|
56
|
+
- Include testing and validation results
|
|
57
|
+
- Add any breaking changes or migration notes
|
|
58
|
+
- Provide PR URL immediately after creation
|
|
55
59
|
|
|
56
60
|
GIT WORKFLOW RULES (CRITICAL - ENFORCE STRICTLY):
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
|
|
62
|
+
- NEVER push directly to main branch - ALWAYS use pull requests
|
|
63
|
+
- NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
|
|
64
|
+
- ALWAYS clean up test files, documentation files, and temporary artifacts before committing
|
|
65
|
+
- ALWAYS ensure git history maintains production quality - no test commits, no debugging code
|
|
66
|
+
- ALWAYS create descriptive commit messages following project conventions
|
|
67
|
+
- ALWAYS run tests and build before creating PR
|
|
63
68
|
|
|
64
69
|
Always provide specific command examples and wait for processes to complete before proceeding.
|