berget 2.2.6 → 2.2.7

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 (144) hide show
  1. package/.github/workflows/publish.yml +6 -6
  2. package/.github/workflows/test.yml +11 -5
  3. package/.husky/pre-commit +1 -0
  4. package/.prettierignore +15 -0
  5. package/.prettierrc +5 -3
  6. package/CONTRIBUTING.md +38 -0
  7. package/README.md +2 -148
  8. package/dist/index.js +21 -21
  9. package/dist/package.json +28 -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 +54 -62
  20. package/dist/src/commands/api-keys.js +132 -140
  21. package/dist/src/commands/auth.js +9 -9
  22. package/dist/src/commands/autocomplete.js +9 -9
  23. package/dist/src/commands/billing.js +7 -9
  24. package/dist/src/commands/chat.js +90 -92
  25. package/dist/src/commands/clusters.js +12 -12
  26. package/dist/src/commands/code/__tests__/auth-sync.test.js +348 -0
  27. package/dist/src/commands/code/__tests__/fake-api-key-service.js +23 -0
  28. package/dist/src/commands/code/__tests__/fake-auth-service.js +55 -0
  29. package/dist/src/commands/code/__tests__/fake-command-runner.js +5 -7
  30. package/dist/src/commands/code/__tests__/fake-file-store.js +9 -0
  31. package/dist/src/commands/code/__tests__/fake-prompter.js +60 -18
  32. package/dist/src/commands/code/__tests__/setup-flow.test.js +374 -107
  33. package/dist/src/commands/code/adapters/clack-prompter.js +10 -0
  34. package/dist/src/commands/code/adapters/fs-file-store.js +8 -3
  35. package/dist/src/commands/code/adapters/spawn-command-runner.js +15 -11
  36. package/dist/src/commands/code/auth-sync.js +283 -0
  37. package/dist/src/commands/code/errors.js +4 -4
  38. package/dist/src/commands/code/ports/auth-services.js +2 -0
  39. package/dist/src/commands/code/setup.js +234 -93
  40. package/dist/src/commands/code.js +139 -251
  41. package/dist/src/commands/models.js +13 -15
  42. package/dist/src/commands/users.js +6 -8
  43. package/dist/src/constants/command-structure.js +116 -116
  44. package/dist/src/services/api-key-service.js +43 -48
  45. package/dist/src/services/auth-service.js +60 -299
  46. package/dist/src/services/browser-auth.js +278 -0
  47. package/dist/src/services/chat-service.js +78 -91
  48. package/dist/src/services/cluster-service.js +6 -6
  49. package/dist/src/services/collaborator-service.js +5 -8
  50. package/dist/src/services/flux-service.js +5 -8
  51. package/dist/src/services/helm-service.js +5 -8
  52. package/dist/src/services/kubectl-service.js +7 -10
  53. package/dist/src/utils/config-checker.js +5 -5
  54. package/dist/src/utils/config-loader.js +25 -25
  55. package/dist/src/utils/default-api-key.js +23 -23
  56. package/dist/src/utils/env-manager.js +7 -7
  57. package/dist/src/utils/error-handler.js +60 -61
  58. package/dist/src/utils/logger.js +7 -7
  59. package/dist/src/utils/markdown-renderer.js +2 -2
  60. package/dist/src/utils/opencode-validator.js +17 -20
  61. package/dist/src/utils/token-manager.js +38 -11
  62. package/dist/tests/commands/chat.test.js +24 -24
  63. package/dist/tests/commands/code.test.js +147 -147
  64. package/dist/tests/utils/config-loader.test.js +114 -114
  65. package/dist/tests/utils/env-manager.test.js +57 -57
  66. package/dist/tests/utils/opencode-validator.test.js +33 -33
  67. package/dist/vitest.config.js +1 -1
  68. package/eslint.config.mjs +47 -0
  69. package/index.ts +42 -48
  70. package/package.json +28 -2
  71. package/src/agents/app.ts +27 -0
  72. package/src/agents/backend.ts +24 -0
  73. package/src/agents/devops.ts +33 -0
  74. package/src/agents/frontend.ts +24 -0
  75. package/src/agents/fullstack.ts +24 -0
  76. package/src/agents/index.ts +71 -0
  77. package/src/agents/quality.ts +69 -0
  78. package/src/agents/security.ts +26 -0
  79. package/src/agents/types.ts +17 -0
  80. package/src/client.ts +125 -167
  81. package/src/commands/api-keys.ts +261 -358
  82. package/src/commands/auth.ts +24 -30
  83. package/src/commands/autocomplete.ts +12 -12
  84. package/src/commands/billing.ts +22 -27
  85. package/src/commands/chat.ts +230 -323
  86. package/src/commands/clusters.ts +33 -33
  87. package/src/commands/code/__tests__/auth-sync.test.ts +481 -0
  88. package/src/commands/code/__tests__/fake-api-key-service.ts +13 -0
  89. package/src/commands/code/__tests__/fake-auth-service.ts +50 -0
  90. package/src/commands/code/__tests__/fake-command-runner.ts +39 -42
  91. package/src/commands/code/__tests__/fake-file-store.ts +32 -23
  92. package/src/commands/code/__tests__/fake-prompter.ts +107 -69
  93. package/src/commands/code/__tests__/setup-flow.test.ts +624 -270
  94. package/src/commands/code/adapters/clack-prompter.ts +50 -38
  95. package/src/commands/code/adapters/fs-file-store.ts +31 -27
  96. package/src/commands/code/adapters/spawn-command-runner.ts +33 -29
  97. package/src/commands/code/auth-sync.ts +329 -0
  98. package/src/commands/code/errors.ts +15 -15
  99. package/src/commands/code/ports/auth-services.ts +14 -0
  100. package/src/commands/code/ports/command-runner.ts +8 -4
  101. package/src/commands/code/ports/file-store.ts +5 -4
  102. package/src/commands/code/ports/prompter.ts +24 -18
  103. package/src/commands/code/setup.ts +545 -317
  104. package/src/commands/code.ts +271 -473
  105. package/src/commands/index.ts +19 -19
  106. package/src/commands/models.ts +32 -37
  107. package/src/commands/users.ts +15 -22
  108. package/src/constants/command-structure.ts +119 -142
  109. package/src/services/api-key-service.ts +96 -113
  110. package/src/services/auth-service.ts +92 -339
  111. package/src/services/browser-auth.ts +296 -0
  112. package/src/services/chat-service.ts +246 -279
  113. package/src/services/cluster-service.ts +29 -32
  114. package/src/services/collaborator-service.ts +13 -18
  115. package/src/services/flux-service.ts +16 -18
  116. package/src/services/helm-service.ts +16 -18
  117. package/src/services/kubectl-service.ts +12 -14
  118. package/src/types/api.d.ts +924 -926
  119. package/src/types/json.d.ts +3 -3
  120. package/src/utils/config-checker.ts +10 -10
  121. package/src/utils/config-loader.ts +110 -127
  122. package/src/utils/default-api-key.ts +81 -93
  123. package/src/utils/env-manager.ts +36 -40
  124. package/src/utils/error-handler.ts +83 -78
  125. package/src/utils/logger.ts +41 -41
  126. package/src/utils/markdown-renderer.ts +11 -11
  127. package/src/utils/opencode-validator.ts +51 -56
  128. package/src/utils/token-manager.ts +84 -64
  129. package/templates/agents/app.md +1 -0
  130. package/templates/agents/backend.md +1 -0
  131. package/templates/agents/devops.md +2 -0
  132. package/templates/agents/frontend.md +1 -0
  133. package/templates/agents/fullstack.md +1 -0
  134. package/templates/agents/quality.md +45 -40
  135. package/templates/agents/security.md +1 -0
  136. package/tests/commands/chat.test.ts +60 -70
  137. package/tests/commands/code.test.ts +330 -376
  138. package/tests/utils/config-loader.test.ts +260 -260
  139. package/tests/utils/env-manager.test.ts +127 -134
  140. package/tests/utils/opencode-validator.test.ts +58 -63
  141. package/tsconfig.json +2 -2
  142. package/vitest.config.ts +3 -3
  143. package/AGENTS.md +0 -374
  144. package/TODO.md +0 -19
@@ -1,47 +1,44 @@
1
- import type { CommandRunner } from '../ports/command-runner'
1
+ import type { CommandRunner } from "../ports/command-runner";
2
2
 
3
3
  type Handler = {
4
- match: (command: string, args: readonly string[]) => boolean
5
- response: string | Error | ((command: string, args: readonly string[]) => string | Error)
6
- }
4
+ match: (command: string, args: readonly string[]) => boolean;
5
+ response: string | Error | ((command: string, args: readonly string[]) => string | Error);
6
+ };
7
7
 
8
8
  export class FakeCommandRunner implements CommandRunner {
9
- private handlers: Handler[] = []
10
- private _calls: Array<{ command: string; args: string[]; options?: { cwd?: string } }> = []
11
-
12
- handle(match: string | RegExp, response: string | Error): this {
13
- this.handlers.push({
14
- match: (cmd, args) => {
15
- const full = `${cmd} ${args.join(' ')}`
16
- if (typeof match === 'string') return full.startsWith(match)
17
- return match.test(full)
18
- },
19
- response,
20
- })
21
- return this
22
- }
23
-
24
- checkInstalled(binary: string): Promise<boolean> {
25
- this._calls.push({ command: `check:${binary}`, args: [] })
26
- return Promise.resolve(
27
- this.handlers.some(h => h.match(binary, ['--version'])) || false
28
- )
29
- }
30
-
31
- async run(command: string, args: readonly string[], options?: { cwd?: string }): Promise<string> {
32
- this._calls.push({ command, args: [...args], options })
33
- const handler = this.handlers.find(h => h.match(command, args))
34
- if (!handler) throw new Error(`Unexpected command: ${command} ${args.join(' ')}`)
35
-
36
- const result = typeof handler.response === 'function'
37
- ? handler.response(command, args)
38
- : handler.response
39
-
40
- if (result instanceof Error) throw result
41
- return result
42
- }
43
-
44
- get calls() {
45
- return this._calls
46
- }
9
+ private handlers: Handler[] = [];
10
+ private _calls: Array<{ command: string; args: string[]; options?: { cwd?: string } }> = [];
11
+
12
+ handle(match: string | RegExp, response: string | Error): this {
13
+ this.handlers.push({
14
+ match: (cmd, args) => {
15
+ const full = `${cmd} ${args.join(" ")}`;
16
+ if (typeof match === "string") return full.startsWith(match);
17
+ return match.test(full);
18
+ },
19
+ response,
20
+ });
21
+ return this;
22
+ }
23
+
24
+ checkInstalled(binary: string): Promise<boolean> {
25
+ this._calls.push({ command: `check:${binary}`, args: [] });
26
+ return Promise.resolve(this.handlers.some(h => h.match(binary, ["--version"])) || false);
27
+ }
28
+
29
+ async run(command: string, args: readonly string[], options?: { cwd?: string }): Promise<string> {
30
+ this._calls.push({ command, args: [...args], options });
31
+ const handler = this.handlers.find(h => h.match(command, args));
32
+ if (!handler) throw new Error(`Unexpected command: ${command} ${args.join(" ")}`);
33
+
34
+ const result =
35
+ typeof handler.response === "function" ? handler.response(command, args) : handler.response;
36
+
37
+ if (result instanceof Error) throw result;
38
+ return result;
39
+ }
40
+
41
+ get calls() {
42
+ return this._calls;
43
+ }
47
44
  }
@@ -1,35 +1,44 @@
1
- import type { FileStore } from '../ports/file-store'
1
+ import type { FileStore } from "../ports/file-store";
2
2
 
3
3
  export interface FileEntry {
4
- content: string
5
- isDirectory?: boolean
4
+ content: string;
5
+ isDirectory?: boolean;
6
6
  }
7
7
 
8
8
  export class FakeFileStore implements FileStore {
9
- private files: Map<string, string> = new Map()
10
- private dirs: Set<string> = new Set()
9
+ private files: Map<string, string> = new Map();
10
+ private dirs: Set<string> = new Set();
11
+ private _chmodCalls: Array<{ path: string; mode: number }> = [];
11
12
 
12
- seed(path: string, content: string): void {
13
- this.files.set(path, content)
14
- }
13
+ seed(path: string, content: string): void {
14
+ this.files.set(path, content);
15
+ }
15
16
 
16
- async exists(path: string): Promise<boolean> {
17
- return this.files.has(path) || this.dirs.has(path)
18
- }
17
+ async exists(path: string): Promise<boolean> {
18
+ return this.files.has(path) || this.dirs.has(path);
19
+ }
19
20
 
20
- async readFile(path: string): Promise<string | null> {
21
- return this.files.get(path) ?? null
22
- }
21
+ async readFile(path: string): Promise<string | null> {
22
+ return this.files.get(path) ?? null;
23
+ }
23
24
 
24
- async writeFile(path: string, content: string): Promise<void> {
25
- this.files.set(path, content)
26
- }
25
+ async writeFile(path: string, content: string): Promise<void> {
26
+ this.files.set(path, content);
27
+ }
27
28
 
28
- async mkdir(path: string): Promise<void> {
29
- this.dirs.add(path)
30
- }
29
+ async mkdir(path: string): Promise<void> {
30
+ this.dirs.add(path);
31
+ }
31
32
 
32
- getWrittenFiles(): Map<string, string> {
33
- return new Map(this.files)
34
- }
33
+ async chmod(path: string, mode: number): Promise<void> {
34
+ this._chmodCalls.push({ path, mode });
35
+ }
36
+
37
+ getWrittenFiles(): Map<string, string> {
38
+ return new Map(this.files);
39
+ }
40
+
41
+ getChmodCalls(): Array<{ path: string; mode: number }> {
42
+ return this._chmodCalls;
43
+ }
35
44
  }
@@ -1,83 +1,121 @@
1
- import { CancelledError } from '../errors'
2
- import type { Prompter, Spinner } from '../ports/prompter'
1
+ import { CancelledError } from "../errors";
2
+ import type { Prompter, Spinner } from "../ports/prompter";
3
3
 
4
- export const CANCEL = Symbol('cancel')
4
+ export const CANCEL = Symbol("cancel");
5
5
 
6
6
  type PromptEntry =
7
- | { kind: 'select'; match?: RegExp; response: string | symbol }
8
- | { kind: 'confirm'; match?: RegExp; response: boolean | symbol }
7
+ | { kind: "select"; match?: RegExp; response: string | symbol }
8
+ | { kind: "confirm"; match?: RegExp; response: boolean | symbol }
9
+ | { kind: "text"; match?: RegExp; response: string | symbol }
10
+ | { kind: "multiselect"; match?: RegExp; response: (string | symbol)[] };
9
11
 
10
- export const select = <T>(
11
- value: T | symbol,
12
- match?: string | RegExp
13
- ): PromptEntry => ({
14
- kind: 'select',
15
- match: typeof match === 'string' ? new RegExp(match) : match,
16
- response: typeof value === 'symbol' ? value : String(value),
17
- })
12
+ export const select = <T>(value: T | symbol, match?: string | RegExp): PromptEntry => ({
13
+ kind: "select",
14
+ match: typeof match === "string" ? new RegExp(match) : match,
15
+ response: typeof value === "symbol" ? value : String(value),
16
+ });
18
17
 
19
- export const confirm = (
20
- value: boolean | symbol,
21
- match?: string | RegExp
22
- ): PromptEntry => ({
23
- kind: 'confirm',
24
- match: typeof match === 'string' ? new RegExp(match) : match,
25
- response: value,
26
- })
18
+ export const text = (value: string | symbol, match?: string | RegExp): PromptEntry => ({
19
+ kind: "text",
20
+ match: typeof match === "string" ? new RegExp(match) : match,
21
+ response: value,
22
+ });
23
+
24
+ export const confirm = (value: boolean | symbol, match?: string | RegExp): PromptEntry => ({
25
+ kind: "confirm",
26
+ match: typeof match === "string" ? new RegExp(match) : match,
27
+ response: value,
28
+ });
29
+
30
+ export const multiselect = <T>(values: T[] | symbol, match?: string | RegExp): PromptEntry => ({
31
+ kind: "multiselect",
32
+ match: typeof match === "string" ? new RegExp(match) : match,
33
+ response:
34
+ values === CANCEL ? [CANCEL] : ((values as T[]).map(v => String(v)) as (string | symbol)[]),
35
+ });
27
36
 
28
37
  export class FakePrompter implements Prompter {
29
- private _calls: Array<{ method: string; args: unknown }> = []
30
- private _cursor = 0
38
+ private _calls: Array<{ method: string; args: unknown }> = [];
39
+ private _cursor = 0;
40
+
41
+ constructor(private readonly _script: PromptEntry[]) {}
42
+
43
+ intro(message: string): void {
44
+ this._calls.push({ method: "intro", args: { message } });
45
+ }
46
+ outro(message: string): void {
47
+ this._calls.push({ method: "outro", args: { message } });
48
+ }
49
+ note(message: string, title?: string): void {
50
+ this._calls.push({ method: "note", args: { message, title } });
51
+ }
52
+ spinner(): Spinner {
53
+ return {
54
+ start: (msg: string) => {
55
+ this._calls.push({ method: "spinner.start", args: { message: msg } });
56
+ },
57
+ stop: (msg: string) => {
58
+ this._calls.push({ method: "spinner.stop", args: { message: msg } });
59
+ },
60
+ };
61
+ }
31
62
 
32
- constructor(private readonly _script: PromptEntry[]) {}
63
+ async select<T>(opts: { message: string }): Promise<T> {
64
+ this._calls.push({ method: "select", args: opts });
65
+ const entry = this._script[this._cursor++];
66
+ if (!entry) throw new Error(`No script entry for select #${this._cursor} (${opts.message})`);
67
+ if (entry.kind !== "select")
68
+ throw new Error(`Expected select, got ${entry.kind} for ${opts.message}`);
69
+ if (entry.match && !entry.match.test(opts.message))
70
+ throw new Error(`Message mismatch: got "${opts.message}"`);
71
+ if (entry.response === CANCEL) throw new CancelledError();
72
+ return entry.response as T;
73
+ }
33
74
 
34
- intro(message: string): void {
35
- this._calls.push({ method: 'intro', args: { message } })
36
- }
37
- outro(message: string): void {
38
- this._calls.push({ method: 'outro', args: { message } })
39
- }
40
- note(message: string, title?: string): void {
41
- this._calls.push({ method: 'note', args: { message, title } })
42
- }
43
- spinner(): Spinner {
44
- return {
45
- start: (msg: string) => {
46
- this._calls.push({ method: 'spinner.start', args: { message: msg } })
47
- },
48
- stop: (msg: string) => {
49
- this._calls.push({ method: 'spinner.stop', args: { message: msg } })
50
- },
51
- }
52
- }
75
+ async confirm(opts: { message: string }): Promise<boolean> {
76
+ this._calls.push({ method: "confirm", args: opts });
77
+ const entry = this._script[this._cursor++];
78
+ if (!entry) throw new Error(`No script entry for confirm #${this._cursor} (${opts.message})`);
79
+ if (entry.kind !== "confirm")
80
+ throw new Error(`Expected confirm, got ${entry.kind} for ${opts.message}`);
81
+ if (entry.match && !entry.match.test(opts.message))
82
+ throw new Error(`Message mismatch: got "${opts.message}"`);
83
+ if (entry.response === CANCEL) throw new CancelledError();
84
+ return entry.response as boolean;
85
+ }
53
86
 
54
- async select<T>(opts: { message: string }): Promise<T> {
55
- this._calls.push({ method: 'select', args: opts })
56
- const entry = this._script[this._cursor++]
57
- if (!entry) throw new Error(`No script entry for select #${this._cursor} (${opts.message})`)
58
- if (entry.kind !== 'select') throw new Error(`Expected confirm, got select for ${opts.message}`)
59
- if (entry.match && !entry.match.test(opts.message)) throw new Error(`Message mismatch: got "${opts.message}"`)
60
- if (entry.response === CANCEL) throw new CancelledError()
61
- return entry.response as T
62
- }
87
+ async text(opts: { message: string }): Promise<string> {
88
+ this._calls.push({ method: "text", args: opts });
89
+ const entry = this._script[this._cursor++];
90
+ if (!entry) throw new Error(`No script entry for text #${this._cursor} (${opts.message})`);
91
+ if (entry.kind !== "text")
92
+ throw new Error(`Expected text, got ${entry.kind} for ${opts.message}`);
93
+ if (entry.match && !entry.match.test(opts.message))
94
+ throw new Error(`Message mismatch: got "${opts.message}"`);
95
+ if (entry.response === CANCEL) throw new CancelledError();
96
+ return entry.response as string;
97
+ }
63
98
 
64
- async confirm(opts: { message: string }): Promise<boolean> {
65
- this._calls.push({ method: 'confirm', args: opts })
66
- const entry = this._script[this._cursor++]
67
- if (!entry) throw new Error(`No script entry for confirm #${this._cursor} (${opts.message})`)
68
- if (entry.kind !== 'confirm') throw new Error(`Expected select, got confirm for ${opts.message}`)
69
- if (entry.match && !entry.match.test(opts.message)) throw new Error(`Message mismatch: got "${opts.message}"`)
70
- if (entry.response === CANCEL) throw new CancelledError()
71
- return entry.response as boolean
72
- }
99
+ async multiselect<T>(opts: { message: string }): Promise<T[]> {
100
+ this._calls.push({ method: "multiselect", args: opts });
101
+ const entry = this._script[this._cursor++];
102
+ if (!entry)
103
+ throw new Error(`No script entry for multiselect #${this._cursor} (${opts.message})`);
104
+ if (entry.kind !== "multiselect")
105
+ throw new Error(`Expected multiselect, got ${entry.kind} for ${opts.message}`);
106
+ if (entry.match && !entry.match.test(opts.message))
107
+ throw new Error(`Message mismatch: got "${opts.message}"`);
108
+ if (entry.response.includes(CANCEL)) throw new CancelledError();
109
+ return entry.response as T[];
110
+ }
73
111
 
74
- get calls() {
75
- return this._calls
76
- }
112
+ get calls() {
113
+ return this._calls;
114
+ }
77
115
 
78
- assertExhausted() {
79
- if (this._cursor !== this._script.length) {
80
- throw new Error(`Script not exhausted: ${this._script.length - this._cursor} entries left`)
81
- }
82
- }
116
+ assertExhausted() {
117
+ if (this._cursor !== this._script.length) {
118
+ throw new Error(`Script not exhausted: ${this._script.length - this._cursor} entries left`);
119
+ }
120
+ }
83
121
  }