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
@@ -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 invalidProps = ['maxTokens', 'contextWindow']
86
- invalidProps.forEach((prop) => {
87
- if (fixed[prop] !== undefined) {
88
- console.warn(`⚠️ Removing invalid property: ${prop}`)
89
- delete fixed[prop]
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 (model && typeof model === 'object') {
99
- // Move maxTokens/contextWindow to proper structure if needed
100
- if (model.maxTokens || model.contextWindow) {
101
- if (!model.limit) model.limit = {}
102
-
103
- // Use the larger of maxTokens/contextWindow for context
104
- const contextValues = [
105
- model.maxTokens,
106
- model.contextWindow,
107
- ].filter(Boolean)
108
- if (contextValues.length > 0) {
109
- const newContext = Math.max(...contextValues)
110
- if (!model.limit.context || newContext > model.limit.context) {
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
- delete model.maxTokens
122
- delete model.contextWindow
123
- console.warn(
124
- '⚠️ Moved maxTokens/contextWindow to limit.context/output',
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 path from 'path'
3
- import * as os from 'os'
4
- import chalk from 'chalk'
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
- refresh_token: string
10
- expires_at: number // timestamp in milliseconds
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 tokenFilePath: string
19
- private tokenData: TokenData | null = null
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
- * Load token data from file
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
- private saveToken(): void {
57
- try {
58
- if (this.tokenData) {
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(): string | null {
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(): string | null {
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
- accessToken: string,
138
- refreshToken: string,
139
- expiresIn: number,
140
- ): void {
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
- expires_at: Date.now() + expiresIn * 1000,
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 = Date.now() + expiresIn * 1000
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
- * Clear all token data
137
+ * Load token data from file
164
138
  */
165
- public clearTokens(): void {
166
- this.tokenData = null
167
- this.saveToken()
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
  }
@@ -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
- - Run comprehensive test suites (npm test, npm run test, jest, vitest)
16
- - Execute build processes (npm run build, webpack, vite, tsc)
17
- - Create and manage pull requests with proper descriptions
18
- - Handle merge conflicts and keep main updated
19
- - Monitor GitHub for reviewer comments and address them
20
- - Ensure code quality standards are met
21
- - Validate linting and formatting (npm run lint, prettier)
22
- - Check test coverage and performance benchmarks
23
- - Handle CI/CD pipeline validation
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
- 1. Ensure all tests pass: npm test
27
- 2. Build successfully: npm run build
28
- 3. Commit all changes with proper message
29
- 4. Push to feature branch
30
- 5. Update main branch and handle merge conflicts
31
- 6. Create or update PR with comprehensive description
32
- 7. Monitor for reviewer comments
33
- 8. Address feedback and push updates
34
- 9. Always provide PR URL for user review
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
- - npm test or npm run test (run test suite)
38
- - npm run build (build project)
39
- - npm run lint (run linting)
40
- - npm run format (format code)
41
- - npm run test:coverage (check coverage)
42
- - git add <specific-files> && git commit -m "message" && git push (commit and push)
43
- - git checkout main && git pull origin main (update main)
44
- - git checkout feature-branch && git merge main (handle conflicts)
45
- - gh pr create --title "title" --body "body" (create PR)
46
- - gh pr view --comments (check PR comments)
47
- - gh pr edit --title "title" --body "body" (update PR)
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
- - Always include clear summary of changes
51
- - List technical details and improvements
52
- - Include testing and validation results
53
- - Add any breaking changes or migration notes
54
- - Provide PR URL immediately after creation
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
- - NEVER push directly to main branch - ALWAYS use pull requests
58
- - NEVER use 'git add .' - ALWAYS add specific files with 'git add path/to/file'
59
- - ALWAYS clean up test files, documentation files, and temporary artifacts before committing
60
- - ALWAYS ensure git history maintains production quality - no test commits, no debugging code
61
- - ALWAYS create descriptive commit messages following project conventions
62
- - ALWAYS run tests and build before creating PR
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.