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.
- package/.github/workflows/publish.yml +6 -6
- package/.github/workflows/test.yml +11 -5
- package/.husky/pre-commit +1 -0
- package/.prettierignore +15 -0
- package/.prettierrc +5 -3
- package/CONTRIBUTING.md +38 -0
- package/README.md +2 -148
- package/dist/index.js +21 -21
- package/dist/package.json +28 -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 +54 -62
- package/dist/src/commands/api-keys.js +132 -140
- package/dist/src/commands/auth.js +9 -9
- package/dist/src/commands/autocomplete.js +9 -9
- package/dist/src/commands/billing.js +7 -9
- package/dist/src/commands/chat.js +90 -92
- package/dist/src/commands/clusters.js +12 -12
- package/dist/src/commands/code/__tests__/auth-sync.test.js +348 -0
- package/dist/src/commands/code/__tests__/fake-api-key-service.js +23 -0
- package/dist/src/commands/code/__tests__/fake-auth-service.js +55 -0
- package/dist/src/commands/code/__tests__/fake-command-runner.js +5 -7
- package/dist/src/commands/code/__tests__/fake-file-store.js +9 -0
- package/dist/src/commands/code/__tests__/fake-prompter.js +60 -18
- package/dist/src/commands/code/__tests__/setup-flow.test.js +374 -107
- package/dist/src/commands/code/adapters/clack-prompter.js +10 -0
- package/dist/src/commands/code/adapters/fs-file-store.js +8 -3
- package/dist/src/commands/code/adapters/spawn-command-runner.js +15 -11
- package/dist/src/commands/code/auth-sync.js +283 -0
- package/dist/src/commands/code/errors.js +4 -4
- package/dist/src/commands/code/ports/auth-services.js +2 -0
- package/dist/src/commands/code/setup.js +234 -93
- package/dist/src/commands/code.js +139 -251
- package/dist/src/commands/models.js +13 -15
- package/dist/src/commands/users.js +6 -8
- package/dist/src/constants/command-structure.js +116 -116
- package/dist/src/services/api-key-service.js +43 -48
- package/dist/src/services/auth-service.js +60 -299
- package/dist/src/services/browser-auth.js +278 -0
- package/dist/src/services/chat-service.js +78 -91
- package/dist/src/services/cluster-service.js +6 -6
- package/dist/src/services/collaborator-service.js +5 -8
- package/dist/src/services/flux-service.js +5 -8
- package/dist/src/services/helm-service.js +5 -8
- package/dist/src/services/kubectl-service.js +7 -10
- package/dist/src/utils/config-checker.js +5 -5
- package/dist/src/utils/config-loader.js +25 -25
- package/dist/src/utils/default-api-key.js +23 -23
- package/dist/src/utils/env-manager.js +7 -7
- package/dist/src/utils/error-handler.js +60 -61
- package/dist/src/utils/logger.js +7 -7
- package/dist/src/utils/markdown-renderer.js +2 -2
- package/dist/src/utils/opencode-validator.js +17 -20
- package/dist/src/utils/token-manager.js +38 -11
- package/dist/tests/commands/chat.test.js +24 -24
- package/dist/tests/commands/code.test.js +147 -147
- package/dist/tests/utils/config-loader.test.js +114 -114
- package/dist/tests/utils/env-manager.test.js +57 -57
- package/dist/tests/utils/opencode-validator.test.js +33 -33
- package/dist/vitest.config.js +1 -1
- package/eslint.config.mjs +47 -0
- package/index.ts +42 -48
- package/package.json +28 -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 +71 -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 +125 -167
- package/src/commands/api-keys.ts +261 -358
- package/src/commands/auth.ts +24 -30
- package/src/commands/autocomplete.ts +12 -12
- package/src/commands/billing.ts +22 -27
- package/src/commands/chat.ts +230 -323
- package/src/commands/clusters.ts +33 -33
- package/src/commands/code/__tests__/auth-sync.test.ts +481 -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 +39 -42
- package/src/commands/code/__tests__/fake-file-store.ts +32 -23
- package/src/commands/code/__tests__/fake-prompter.ts +107 -69
- package/src/commands/code/__tests__/setup-flow.test.ts +624 -270
- package/src/commands/code/adapters/clack-prompter.ts +50 -38
- package/src/commands/code/adapters/fs-file-store.ts +31 -27
- package/src/commands/code/adapters/spawn-command-runner.ts +33 -29
- package/src/commands/code/auth-sync.ts +329 -0
- package/src/commands/code/errors.ts +15 -15
- 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 +545 -317
- package/src/commands/code.ts +271 -473
- package/src/commands/index.ts +19 -19
- package/src/commands/models.ts +32 -37
- package/src/commands/users.ts +15 -22
- package/src/constants/command-structure.ts +119 -142
- package/src/services/api-key-service.ts +96 -113
- package/src/services/auth-service.ts +92 -339
- package/src/services/browser-auth.ts +296 -0
- package/src/services/chat-service.ts +246 -279
- package/src/services/cluster-service.ts +29 -32
- package/src/services/collaborator-service.ts +13 -18
- package/src/services/flux-service.ts +16 -18
- package/src/services/helm-service.ts +16 -18
- package/src/services/kubectl-service.ts +12 -14
- package/src/types/api.d.ts +924 -926
- package/src/types/json.d.ts +3 -3
- package/src/utils/config-checker.ts +10 -10
- package/src/utils/config-loader.ts +110 -127
- package/src/utils/default-api-key.ts +81 -93
- package/src/utils/env-manager.ts +36 -40
- package/src/utils/error-handler.ts +83 -78
- package/src/utils/logger.ts +41 -41
- package/src/utils/markdown-renderer.ts +11 -11
- package/src/utils/opencode-validator.ts +51 -56
- package/src/utils/token-manager.ts +84 -64
- 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 +60 -70
- package/tests/commands/code.test.ts +330 -376
- package/tests/utils/config-loader.test.ts +260 -260
- package/tests/utils/env-manager.test.ts +127 -134
- package/tests/utils/opencode-validator.test.ts +58 -63
- package/tsconfig.json +2 -2
- package/vitest.config.ts +3 -3
- package/AGENTS.md +0 -374
- package/TODO.md +0 -19
|
@@ -1,47 +1,44 @@
|
|
|
1
|
-
import type { CommandRunner } from
|
|
1
|
+
import type { CommandRunner } from "../ports/command-runner";
|
|
2
2
|
|
|
3
3
|
type Handler = {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
1
|
+
import type { FileStore } from "../ports/file-store";
|
|
2
2
|
|
|
3
3
|
export interface FileEntry {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
content: string;
|
|
5
|
+
isDirectory?: boolean;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export class FakeFileStore implements FileStore {
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
seed(path: string, content: string): void {
|
|
14
|
+
this.files.set(path, content);
|
|
15
|
+
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
async exists(path: string): Promise<boolean> {
|
|
18
|
+
return this.files.has(path) || this.dirs.has(path);
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
async readFile(path: string): Promise<string | null> {
|
|
22
|
+
return this.files.get(path) ?? null;
|
|
23
|
+
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
async writeFile(path: string, content: string): Promise<void> {
|
|
26
|
+
this.files.set(path, content);
|
|
27
|
+
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
async mkdir(path: string): Promise<void> {
|
|
30
|
+
this.dirs.add(path);
|
|
31
|
+
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
2
|
-
import type { Prompter, Spinner } from
|
|
1
|
+
import { CancelledError } from "../errors";
|
|
2
|
+
import type { Prompter, Spinner } from "../ports/prompter";
|
|
3
3
|
|
|
4
|
-
export const CANCEL = Symbol(
|
|
4
|
+
export const CANCEL = Symbol("cancel");
|
|
5
5
|
|
|
6
6
|
type PromptEntry =
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
get calls() {
|
|
113
|
+
return this._calls;
|
|
114
|
+
}
|
|
77
115
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
}
|