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,402 +1,630 @@
|
|
|
1
|
-
import type { Prompter } from
|
|
2
|
-
import type { FileStore } from
|
|
3
|
-
import type { CommandRunner } from
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import type { Prompter } from "./ports/prompter";
|
|
2
|
+
import type { FileStore } from "./ports/file-store";
|
|
3
|
+
import type { CommandRunner } from "./ports/command-runner";
|
|
4
|
+
import type { AuthServicePort, ApiKeyServicePort } from "./ports/auth-services";
|
|
5
|
+
import { CancelledError, CommandFailedError, PrerequisiteError } from "./errors";
|
|
6
|
+
import { modify, parse, applyEdits } from "jsonc-parser";
|
|
7
|
+
import { configureAuth } from "./auth-sync.js";
|
|
8
|
+
import { ClackPrompter } from "./adapters/clack-prompter.js";
|
|
9
|
+
import { FsFileStore } from "./adapters/fs-file-store.js";
|
|
10
|
+
import { SpawnCommandRunner } from "./adapters/spawn-command-runner.js";
|
|
11
|
+
import { AuthService } from "../../services/auth-service.js";
|
|
12
|
+
import { ApiKeyService } from "../../services/api-key-service.js";
|
|
13
|
+
import { getAllAgents, toMarkdown, toPiPrompt } from "../../agents/index.js";
|
|
14
|
+
import * as os from "os";
|
|
15
|
+
|
|
16
|
+
const OPENCODE_PLUGIN = "@bergetai/opencode-auth@1.0.16";
|
|
17
|
+
const PI_PROVIDER = "npm:@bergetai/pi-provider";
|
|
18
|
+
const OPENCODE_PLUGIN_NAME = "@bergetai/opencode-auth";
|
|
19
|
+
const PI_PROVIDER_NAME = "@bergetai/pi-provider";
|
|
11
20
|
|
|
12
21
|
export interface WizardDeps {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
prompter: Prompter;
|
|
23
|
+
files: FileStore;
|
|
24
|
+
commands: CommandRunner;
|
|
25
|
+
authService: AuthServicePort;
|
|
26
|
+
apiKeyService: ApiKeyServicePort;
|
|
27
|
+
homeDir: string;
|
|
28
|
+
cwd: string;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export async function runSetup(deps: WizardDeps): Promise<void> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
32
|
+
const { prompter, files, commands, authService, apiKeyService, homeDir, cwd } = deps;
|
|
33
|
+
|
|
34
|
+
prompter.intro("\uD83D\uDD27 Berget Code Setup");
|
|
35
|
+
|
|
36
|
+
const ocState = await getOpencodeState(files, homeDir, cwd);
|
|
37
|
+
const piState = await getPiState(files, homeDir, cwd);
|
|
38
|
+
|
|
39
|
+
const tool = await prompter.select<"opencode" | "pi">({
|
|
40
|
+
message: "How do you want to use Berget AI?",
|
|
41
|
+
options: [
|
|
42
|
+
{
|
|
43
|
+
value: "opencode",
|
|
44
|
+
label: `OpenCode${getOpencodeLabel(ocState)}`,
|
|
45
|
+
hint: "Open source AI coding agent",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
value: "pi",
|
|
49
|
+
label: `Pi${getPiLabel(piState)}`,
|
|
50
|
+
hint: "Minimal terminal coding harness",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const scope = await prompter.select<"project" | "global">({
|
|
56
|
+
message: "Where should the configuration apply?",
|
|
57
|
+
options: [
|
|
58
|
+
{
|
|
59
|
+
value: "project",
|
|
60
|
+
label: "This project only",
|
|
61
|
+
hint:
|
|
62
|
+
tool === "opencode"
|
|
63
|
+
? ocState.project
|
|
64
|
+
? "Already configured"
|
|
65
|
+
: "opencode.json in current directory"
|
|
66
|
+
: piState.project
|
|
67
|
+
? "Already configured"
|
|
68
|
+
: ".pi/settings.json in current directory",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
value: "global",
|
|
72
|
+
label: "Globally for all projects",
|
|
73
|
+
hint:
|
|
74
|
+
tool === "opencode"
|
|
75
|
+
? ocState.global
|
|
76
|
+
? "Already configured"
|
|
77
|
+
: "~/.config/opencode/opencode.json"
|
|
78
|
+
: piState.global
|
|
79
|
+
? "Already configured"
|
|
80
|
+
: "~/.pi/agent/settings.json",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const authResult = await configureAuth(
|
|
86
|
+
{ prompter, files, authService, apiKeyService, homeDir },
|
|
87
|
+
tool
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (tool === "opencode") {
|
|
91
|
+
await setupOpenCode({ prompter, files, commands, homeDir, cwd, scope });
|
|
92
|
+
await setupOpenCodeAgents({ prompter, files, homeDir, cwd, scope });
|
|
93
|
+
|
|
94
|
+
if (authResult.authenticated) {
|
|
95
|
+
prompter.note(
|
|
96
|
+
`You're all set!\n\n1. Run: opencode\n2. Select model: /models\n\nFor more information, see official docs:\n\nhttps://github.com/berget-ai/opencode-berget-auth`,
|
|
97
|
+
"Successfully configured Berget AI for OpenCode"
|
|
98
|
+
);
|
|
67
99
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
prompter.note(
|
|
101
|
+
`Next steps:\n\n1. Run: opencode\n2. Type: /connect\n3. Choose your auth method:\n • "Login with Berget" — Berget Code plan\n • "Enter Berget API Key manually"\n • (or set BERGET_API_KEY env var)\n4. Select model: /models\n\nFor more information, see official docs:\n\nhttps://github.com/berget-ai/opencode-berget-auth`,
|
|
102
|
+
"Successfully configured Berget AI for OpenCode"
|
|
103
|
+
);
|
|
70
104
|
}
|
|
105
|
+
} else {
|
|
106
|
+
await setupPi({ prompter, files, commands, homeDir, cwd, scope });
|
|
107
|
+
await setupPiAgent({ prompter, files, homeDir, cwd, scope });
|
|
108
|
+
|
|
109
|
+
if (authResult.authenticated) {
|
|
110
|
+
prompter.note(
|
|
111
|
+
`You're all set!\n\n1. Restart Pi or run /reload\n2. Select model: /model\n\nFor more information, see official docs:\n\nhttps://github.com/berget-ai/pi-provider`,
|
|
112
|
+
"Successfully configured Berget AI for Pi"
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
prompter.note(
|
|
116
|
+
`Next steps:\n\n1. Restart Pi or run /reload\n2. Type: /login\n3. Choose your auth method:\n • "Use a subscription" → Berget AI\n • (or set BERGET_API_KEY env var)\n4. Select model: /model\n\nFor more information, see official docs:\n\nhttps://github.com/berget-ai/pi-provider`,
|
|
117
|
+
"Successfully configured Berget AI for Pi"
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
71
121
|
|
|
72
|
-
|
|
122
|
+
prompter.outro("Setup complete!");
|
|
73
123
|
}
|
|
74
124
|
|
|
75
125
|
// ─── OpenCode ────────────────────────────────────────────────────────────────
|
|
76
126
|
|
|
77
127
|
async function setupOpenCode(deps: {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
128
|
+
prompter: Prompter;
|
|
129
|
+
files: FileStore;
|
|
130
|
+
commands: CommandRunner;
|
|
131
|
+
homeDir: string;
|
|
132
|
+
cwd: string;
|
|
133
|
+
scope: "project" | "global";
|
|
84
134
|
}): Promise<void> {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
135
|
+
const { prompter, files, commands, homeDir, cwd, scope } = deps;
|
|
136
|
+
|
|
137
|
+
const installed = await commands.checkInstalled("opencode");
|
|
138
|
+
if (!installed) {
|
|
139
|
+
throw new PrerequisiteError("opencode");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const configPath = await resolveOpencodeConfigPath(files, homeDir, cwd, scope);
|
|
143
|
+
const existingContent = await files.readFile(configPath);
|
|
144
|
+
const newContent = generateModifiedContent(existingContent, configPath);
|
|
145
|
+
|
|
146
|
+
if (existingContent && existingContent === newContent) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (existingContent) {
|
|
151
|
+
prompter.note(generateDiff(existingContent, newContent, configPath), "Changes to be written");
|
|
152
|
+
} else {
|
|
153
|
+
prompter.note(`New config at ${configPath}:\n\n${newContent}`, "Config preview");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const shouldWrite = await prompter.confirm({
|
|
157
|
+
message: existingContent ? `Write these changes to ${configPath}?` : `Create ${configPath}?`,
|
|
158
|
+
initialValue: true,
|
|
159
|
+
});
|
|
160
|
+
if (!shouldWrite) throw new CancelledError();
|
|
161
|
+
|
|
162
|
+
const s = prompter.spinner();
|
|
163
|
+
s.start("Writing OpenCode configuration...");
|
|
164
|
+
await files.writeFile(configPath, newContent);
|
|
165
|
+
s.stop(`Wrote configuration to ${configPath}.`);
|
|
166
|
+
}
|
|
95
167
|
|
|
96
|
-
|
|
97
|
-
return
|
|
98
|
-
}
|
|
168
|
+
// ─── Pi ────────────────────────────────────────────────────────────────────────
|
|
99
169
|
|
|
100
|
-
|
|
101
|
-
|
|
170
|
+
async function setupPi(deps: {
|
|
171
|
+
prompter: Prompter;
|
|
172
|
+
files: FileStore;
|
|
173
|
+
commands: CommandRunner;
|
|
174
|
+
homeDir: string;
|
|
175
|
+
cwd: string;
|
|
176
|
+
scope: "project" | "global";
|
|
177
|
+
}): Promise<void> {
|
|
178
|
+
const { prompter, files, commands, homeDir, cwd, scope } = deps;
|
|
179
|
+
const s = prompter.spinner();
|
|
180
|
+
|
|
181
|
+
const installed = await commands.checkInstalled("pi");
|
|
182
|
+
if (!installed) {
|
|
183
|
+
throw new PrerequisiteError("pi");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const installArgs =
|
|
187
|
+
scope === "project" ? ["install", "-l", PI_PROVIDER] : ["install", PI_PROVIDER];
|
|
188
|
+
|
|
189
|
+
s.start(`Installing Berget AI provider for Pi...`);
|
|
190
|
+
try {
|
|
191
|
+
await commands.run("pi", installArgs);
|
|
192
|
+
s.stop("Installed Pi provider.");
|
|
193
|
+
} catch {
|
|
194
|
+
s.stop("Pi provider installation failed. Please try again or install manually.");
|
|
195
|
+
throw new CommandFailedError(`pi ${installArgs.join(" ")}`, 1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const settingsPath =
|
|
199
|
+
scope === "project"
|
|
200
|
+
? pathJoin(cwd, ".pi", "settings.json")
|
|
201
|
+
: pathJoin(homeDir, ".pi", "agent", "settings.json");
|
|
202
|
+
|
|
203
|
+
let settings = (await readJsonMaybe(files, settingsPath)) || {};
|
|
204
|
+
|
|
205
|
+
if (settings.defaultProvider === "berget") {
|
|
206
|
+
prompter.note(
|
|
207
|
+
"Berget AI is already set as your default provider.",
|
|
208
|
+
"Default provider already set"
|
|
209
|
+
);
|
|
210
|
+
} else {
|
|
211
|
+
if (settings.defaultProvider) {
|
|
212
|
+
const makeDefault = await prompter.confirm({
|
|
213
|
+
message: `Your default provider is ${settings.defaultProvider}. Switch to Berget AI instead?`,
|
|
214
|
+
initialValue: false,
|
|
215
|
+
});
|
|
216
|
+
if (makeDefault) {
|
|
217
|
+
settings.defaultProvider = "berget";
|
|
218
|
+
await writeJsonFile(files, settingsPath, settings);
|
|
219
|
+
prompter.note("Berget AI is now your default provider.", "Updated default provider");
|
|
220
|
+
}
|
|
102
221
|
} else {
|
|
103
|
-
|
|
222
|
+
settings.defaultProvider = "berget";
|
|
223
|
+
await writeJsonFile(files, settingsPath, settings);
|
|
224
|
+
prompter.note("Berget AI is now your default provider.", "Updated default provider");
|
|
104
225
|
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
105
228
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
229
|
+
async function setupOpenCodeAgents(deps: {
|
|
230
|
+
prompter: Prompter;
|
|
231
|
+
files: FileStore;
|
|
232
|
+
homeDir: string;
|
|
233
|
+
cwd: string;
|
|
234
|
+
scope: "project" | "global";
|
|
235
|
+
}): Promise<void> {
|
|
236
|
+
const { prompter, files, homeDir, cwd, scope } = deps;
|
|
237
|
+
|
|
238
|
+
const agents = getAllAgents().filter(a => a.config.mode === "primary");
|
|
239
|
+
|
|
240
|
+
if (agents.length === 0) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const selectedAgents = await prompter.multiselect({
|
|
245
|
+
message: "Select agents to set up (optional - press enter to skip):",
|
|
246
|
+
options: agents.map(agent => ({
|
|
247
|
+
value: agent.config.name,
|
|
248
|
+
label: agent.config.name,
|
|
249
|
+
hint: agent.config.description,
|
|
250
|
+
})),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (selectedAgents.length === 0) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const agentsDir =
|
|
258
|
+
scope === "project"
|
|
259
|
+
? pathJoin(cwd, ".opencode", "agents")
|
|
260
|
+
: pathJoin(homeDir, ".config", "opencode", "agents");
|
|
261
|
+
|
|
262
|
+
await files.mkdir(agentsDir);
|
|
263
|
+
|
|
264
|
+
const hasChanges = await Promise.all(
|
|
265
|
+
selectedAgents.map(async agentName => {
|
|
266
|
+
const agent = agents.find(a => a.config.name === agentName);
|
|
267
|
+
if (!agent) return false;
|
|
268
|
+
|
|
269
|
+
const agentPath = pathJoin(agentsDir, `${agentName}.md`);
|
|
270
|
+
const existing = await files.readFile(agentPath);
|
|
271
|
+
const newContent = toMarkdown(agent);
|
|
272
|
+
|
|
273
|
+
if (existing === newContent) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (existing) {
|
|
278
|
+
prompter.note(
|
|
279
|
+
generateDiff(existing, newContent, agentPath),
|
|
280
|
+
`Changes to ${agentName} agent`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return true;
|
|
111
285
|
})
|
|
112
|
-
|
|
286
|
+
);
|
|
113
287
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ─── Pi ────────────────────────────────────────────────────────────────────────
|
|
288
|
+
if (!hasChanges.some(Boolean)) {
|
|
289
|
+
prompter.note("Agent files are already up to date.", "No changes needed");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
121
292
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
homeDir: string
|
|
127
|
-
cwd: string
|
|
128
|
-
scope: 'project' | 'global'
|
|
129
|
-
}): Promise<void> {
|
|
130
|
-
const { prompter, files, commands, homeDir, cwd, scope } = deps
|
|
131
|
-
const s = prompter.spinner()
|
|
293
|
+
const shouldWrite = await prompter.confirm({
|
|
294
|
+
message: "Write agent configuration files?",
|
|
295
|
+
initialValue: true,
|
|
296
|
+
});
|
|
132
297
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
298
|
+
if (!shouldWrite) {
|
|
299
|
+
throw new CancelledError();
|
|
300
|
+
}
|
|
137
301
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
: ['install', PI_PROVIDER]
|
|
302
|
+
const s = prompter.spinner();
|
|
303
|
+
s.start("Writing agent configurations...");
|
|
141
304
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
s.stop('Installed Pi provider.')
|
|
146
|
-
} catch (err: any) {
|
|
147
|
-
s.stop('Pi provider installation failed. Please try again or install manually.')
|
|
148
|
-
throw new CommandFailedError(`pi ${installArgs.join(' ')}`, 1)
|
|
149
|
-
}
|
|
305
|
+
for (const agentName of selectedAgents) {
|
|
306
|
+
const agent = agents.find(a => a.config.name === agentName);
|
|
307
|
+
if (!agent) continue;
|
|
150
308
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
309
|
+
const agentPath = pathJoin(agentsDir, `${agentName}.md`);
|
|
310
|
+
const content = toMarkdown(agent);
|
|
311
|
+
await files.writeFile(agentPath, content);
|
|
312
|
+
}
|
|
154
313
|
|
|
155
|
-
|
|
314
|
+
s.stop(`Wrote ${selectedAgents.length} agent(s) to ${agentsDir}`);
|
|
315
|
+
}
|
|
156
316
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
317
|
+
async function setupPiAgent(deps: {
|
|
318
|
+
prompter: Prompter;
|
|
319
|
+
files: FileStore;
|
|
320
|
+
homeDir: string;
|
|
321
|
+
cwd: string;
|
|
322
|
+
scope: "project" | "global";
|
|
323
|
+
}): Promise<void> {
|
|
324
|
+
const { prompter, files, homeDir, cwd, scope } = deps;
|
|
325
|
+
|
|
326
|
+
const agents = getAllAgents().filter(a => a.config.mode === "primary");
|
|
327
|
+
|
|
328
|
+
if (agents.length === 0) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const selectedAgentName = await prompter.select({
|
|
333
|
+
message: "Select an agent (optional - press enter to skip):",
|
|
334
|
+
options: [
|
|
335
|
+
{ value: "__skip__", label: "Skip agent setup" },
|
|
336
|
+
...agents.map(agent => ({
|
|
337
|
+
value: agent.config.name,
|
|
338
|
+
label: agent.config.name,
|
|
339
|
+
hint: agent.config.description,
|
|
340
|
+
})),
|
|
341
|
+
],
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (selectedAgentName === "__skip__") {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const agent = agents.find(a => a.config.name === selectedAgentName);
|
|
349
|
+
if (!agent) return;
|
|
350
|
+
|
|
351
|
+
const systemPath =
|
|
352
|
+
scope === "project"
|
|
353
|
+
? pathJoin(cwd, ".pi", "SYSTEM.md")
|
|
354
|
+
: pathJoin(homeDir, ".pi", "agent", "SYSTEM.md");
|
|
355
|
+
|
|
356
|
+
const existing = await files.readFile(systemPath);
|
|
357
|
+
const newContent = toPiPrompt(agent);
|
|
358
|
+
|
|
359
|
+
if (existing === newContent) {
|
|
360
|
+
prompter.note("Agent configuration is already up to date.", "No changes needed");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (existing) {
|
|
365
|
+
prompter.note(generateDiff(existing, newContent, systemPath), "Changes to agent configuration");
|
|
366
|
+
} else {
|
|
367
|
+
prompter.note(newContent, "New agent configuration");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const shouldWrite = await prompter.confirm({
|
|
371
|
+
message: existing ? "Overwrite existing agent configuration?" : "Create agent configuration?",
|
|
372
|
+
initialValue: true,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
if (!shouldWrite) {
|
|
376
|
+
throw new CancelledError();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const s = prompter.spinner();
|
|
380
|
+
s.start("Writing agent configuration...");
|
|
381
|
+
|
|
382
|
+
const systemDir = scope === "project" ? pathJoin(cwd, ".pi") : pathJoin(homeDir, ".pi", "agent");
|
|
383
|
+
await files.mkdir(systemDir);
|
|
384
|
+
await files.writeFile(systemPath, newContent);
|
|
385
|
+
|
|
386
|
+
s.stop(`Wrote agent configuration to ${systemPath}`);
|
|
176
387
|
}
|
|
177
388
|
|
|
178
389
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
179
390
|
|
|
180
391
|
function pathJoin(...parts: string[]): string {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
392
|
+
// Simple path join that avoids importing 'path' module
|
|
393
|
+
// This is good enough for cross-platform testing since tests control the path format
|
|
394
|
+
return parts.join("/");
|
|
184
395
|
}
|
|
185
396
|
|
|
186
397
|
function stripJsoncComments(content: string): string {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
398
|
+
content = content.replace(/\/\/.*$/gm, "");
|
|
399
|
+
content = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
400
|
+
return content;
|
|
190
401
|
}
|
|
191
402
|
|
|
192
403
|
function generateDiff(oldText: string, newText: string, filePath: string): string {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
404
|
+
const oldLines = oldText.split("\n");
|
|
405
|
+
const newLines = newText.split("\n");
|
|
406
|
+
let result = `--- ${filePath}\n+++ ${filePath}\n`;
|
|
407
|
+
|
|
408
|
+
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
409
|
+
for (let i = 0; i < maxLen; i++) {
|
|
410
|
+
const oldLine = oldLines[i];
|
|
411
|
+
const newLine = newLines[i];
|
|
412
|
+
if (oldLine !== newLine) {
|
|
413
|
+
if (oldLine !== undefined) result += `- ${oldLine}\n`;
|
|
414
|
+
if (newLine !== undefined) result += `+ ${newLine}\n`;
|
|
205
415
|
}
|
|
206
|
-
|
|
416
|
+
}
|
|
417
|
+
return result.trimEnd();
|
|
207
418
|
}
|
|
208
419
|
|
|
209
420
|
async function readJsonMaybe(files: FileStore, filePath: string): Promise<any | null> {
|
|
210
|
-
|
|
211
|
-
|
|
421
|
+
const content = await files.readFile(filePath);
|
|
422
|
+
if (!content) return null;
|
|
423
|
+
try {
|
|
424
|
+
return JSON.parse(content);
|
|
425
|
+
} catch {
|
|
212
426
|
try {
|
|
213
|
-
|
|
427
|
+
return JSON.parse(stripJsoncComments(content));
|
|
214
428
|
} catch {
|
|
215
|
-
|
|
216
|
-
return JSON.parse(stripJsoncComments(content))
|
|
217
|
-
} catch {
|
|
218
|
-
return null
|
|
219
|
-
}
|
|
429
|
+
return null;
|
|
220
430
|
}
|
|
431
|
+
}
|
|
221
432
|
}
|
|
222
433
|
|
|
223
|
-
async function writeJsonFile(
|
|
224
|
-
|
|
434
|
+
async function writeJsonFile(
|
|
435
|
+
files: FileStore,
|
|
436
|
+
filePath: string,
|
|
437
|
+
data: Record<string, unknown>
|
|
438
|
+
): Promise<void> {
|
|
439
|
+
await files.writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
225
440
|
}
|
|
226
441
|
|
|
227
442
|
async function hasPluginInConfig(config: any): Promise<boolean> {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
443
|
+
if (!config) return false;
|
|
444
|
+
const plugins = config.plugin || config.plugins || [];
|
|
445
|
+
return plugins.some((p: string) => p.includes(OPENCODE_PLUGIN_NAME));
|
|
231
446
|
}
|
|
232
447
|
|
|
233
448
|
async function hasPiProviderInSettings(settings: any): Promise<boolean> {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
449
|
+
if (!settings) return false;
|
|
450
|
+
const packages = settings.packages || [];
|
|
451
|
+
return packages.some((p: any) => {
|
|
452
|
+
if (typeof p === "string") return p.includes(PI_PROVIDER_NAME);
|
|
453
|
+
if (typeof p === "object" && p.source) return p.source.includes(PI_PROVIDER_NAME);
|
|
454
|
+
return false;
|
|
455
|
+
});
|
|
241
456
|
}
|
|
242
457
|
|
|
243
458
|
async function getOpencodeState(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
459
|
+
files: FileStore,
|
|
460
|
+
homeDir: string,
|
|
461
|
+
cwd: string
|
|
247
462
|
): Promise<{ project: boolean; global: boolean }> {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
463
|
+
const projectJsonc = await readJsonMaybe(files, pathJoin(cwd, "opencode.jsonc"));
|
|
464
|
+
const projectJson = await readJsonMaybe(files, pathJoin(cwd, "opencode.json"));
|
|
465
|
+
const globalJsonc = await readJsonMaybe(
|
|
466
|
+
files,
|
|
467
|
+
pathJoin(homeDir, ".config", "opencode", "opencode.jsonc")
|
|
468
|
+
);
|
|
469
|
+
const globalJson = await readJsonMaybe(
|
|
470
|
+
files,
|
|
471
|
+
pathJoin(homeDir, ".config", "opencode", "opencode.json")
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
project: (await hasPluginInConfig(projectJsonc)) || (await hasPluginInConfig(projectJson)),
|
|
476
|
+
global: (await hasPluginInConfig(globalJsonc)) || (await hasPluginInConfig(globalJson)),
|
|
477
|
+
};
|
|
257
478
|
}
|
|
258
479
|
|
|
259
480
|
async function getPiState(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
481
|
+
files: FileStore,
|
|
482
|
+
homeDir: string,
|
|
483
|
+
cwd: string
|
|
263
484
|
): Promise<{ project: boolean; global: boolean }> {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
485
|
+
const projectSettings = await readJsonMaybe(files, pathJoin(cwd, ".pi", "settings.json"));
|
|
486
|
+
const globalSettings = await readJsonMaybe(
|
|
487
|
+
files,
|
|
488
|
+
pathJoin(homeDir, ".pi", "agent", "settings.json")
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
project: await hasPiProviderInSettings(projectSettings),
|
|
493
|
+
global: await hasPiProviderInSettings(globalSettings),
|
|
494
|
+
};
|
|
271
495
|
}
|
|
272
496
|
|
|
273
497
|
function getOpencodeLabel(state: { project: boolean; global: boolean }): string {
|
|
274
|
-
|
|
275
|
-
|
|
498
|
+
if (state.project || state.global) return " (already configured)";
|
|
499
|
+
return "";
|
|
276
500
|
}
|
|
277
501
|
|
|
278
502
|
function getPiLabel(state: { project: boolean; global: boolean }): string {
|
|
279
|
-
|
|
280
|
-
|
|
503
|
+
if (state.project || state.global) return " (already configured)";
|
|
504
|
+
return "";
|
|
281
505
|
}
|
|
282
506
|
|
|
283
507
|
async function resolveOpencodeConfigPath(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
508
|
+
files: FileStore,
|
|
509
|
+
homeDir: string,
|
|
510
|
+
cwd: string,
|
|
511
|
+
scope: "project" | "global"
|
|
288
512
|
): Promise<string> {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
513
|
+
if (scope === "project") {
|
|
514
|
+
const jsoncPath = pathJoin(cwd, "opencode.jsonc");
|
|
515
|
+
const jsonPath = pathJoin(cwd, "opencode.json");
|
|
516
|
+
if (await files.exists(jsoncPath)) return jsoncPath;
|
|
517
|
+
if (await files.exists(jsonPath)) return jsonPath;
|
|
518
|
+
return jsonPath;
|
|
519
|
+
} else {
|
|
520
|
+
const globalDir = pathJoin(homeDir, ".config", "opencode");
|
|
521
|
+
const jsoncPath = pathJoin(globalDir, "opencode.jsonc");
|
|
522
|
+
const jsonPath = pathJoin(globalDir, "opencode.json");
|
|
523
|
+
if (await files.exists(jsoncPath)) return jsoncPath;
|
|
524
|
+
if (await files.exists(jsonPath)) return jsonPath;
|
|
525
|
+
return jsonPath;
|
|
526
|
+
}
|
|
303
527
|
}
|
|
304
528
|
|
|
305
529
|
function generateModifiedContent(existingContent: string | null, configPath: string): string {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const existing: string[] = jsConfig[pluginsKey] || []
|
|
324
|
-
const filtered = existing.filter((p: string) => !p.includes(OPENCODE_PLUGIN_NAME))
|
|
325
|
-
filtered.push(OPENCODE_PLUGIN)
|
|
326
|
-
|
|
327
|
-
if (canModifyText) {
|
|
328
|
-
let modifiedContent = content
|
|
329
|
-
const pluginEdits = modify(modifiedContent, [pluginsKey], filtered, {
|
|
330
|
-
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
331
|
-
})
|
|
332
|
-
modifiedContent = applyEdits(modifiedContent, pluginEdits)
|
|
333
|
-
|
|
334
|
-
if (!jsConfig.$schema) {
|
|
335
|
-
const schemaEdits = modify(modifiedContent, ['$schema'], 'https://opencode.ai/config.json', {
|
|
336
|
-
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
337
|
-
})
|
|
338
|
-
modifiedContent = applyEdits(modifiedContent, schemaEdits)
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return modifiedContent
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Malformed, empty, or non-object JSONC — write a clean config
|
|
345
|
-
const config: Record<string, any> = {
|
|
346
|
-
[pluginsKey]: filtered,
|
|
347
|
-
$schema: 'https://opencode.ai/config.json',
|
|
348
|
-
}
|
|
349
|
-
return JSON.stringify(config, null, 2) + '\n'
|
|
530
|
+
if (configPath.endsWith(".jsonc")) {
|
|
531
|
+
const content = existingContent || "{}";
|
|
532
|
+
const parseErrors: any[] = [];
|
|
533
|
+
const parsed = parse(content, parseErrors, {
|
|
534
|
+
allowTrailingComma: true,
|
|
535
|
+
disallowComments: false,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
let jsConfig: Record<string, any> = {};
|
|
539
|
+
const canModifyText =
|
|
540
|
+
parsed !== undefined &&
|
|
541
|
+
typeof parsed === "object" &&
|
|
542
|
+
parsed !== null &&
|
|
543
|
+
!Array.isArray(parsed);
|
|
544
|
+
|
|
545
|
+
if (canModifyText) {
|
|
546
|
+
jsConfig = parsed as Record<string, any>;
|
|
350
547
|
}
|
|
351
548
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
549
|
+
const pluginsKey = jsConfig.plugins !== undefined ? "plugins" : "plugin";
|
|
550
|
+
const existing: string[] = jsConfig[pluginsKey] || [];
|
|
551
|
+
const filtered = existing.filter((p: string) => !p.includes(OPENCODE_PLUGIN_NAME));
|
|
552
|
+
filtered.push(OPENCODE_PLUGIN);
|
|
553
|
+
|
|
554
|
+
if (canModifyText) {
|
|
555
|
+
let modifiedContent = content;
|
|
556
|
+
const pluginEdits = modify(modifiedContent, [pluginsKey], filtered, {
|
|
557
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
558
|
+
});
|
|
559
|
+
modifiedContent = applyEdits(modifiedContent, pluginEdits);
|
|
560
|
+
|
|
561
|
+
if (!jsConfig.$schema) {
|
|
562
|
+
const schemaEdits = modify(
|
|
563
|
+
modifiedContent,
|
|
564
|
+
["$schema"],
|
|
565
|
+
"https://opencode.ai/config.json",
|
|
566
|
+
{
|
|
567
|
+
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
modifiedContent = applyEdits(modifiedContent, schemaEdits);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return modifiedContent;
|
|
360
574
|
}
|
|
361
575
|
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
config
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
576
|
+
// Malformed, empty, or non-object JSONC — write a clean config
|
|
577
|
+
const config: Record<string, any> = {
|
|
578
|
+
[pluginsKey]: filtered,
|
|
579
|
+
$schema: "https://opencode.ai/config.json",
|
|
580
|
+
};
|
|
581
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Plain JSON
|
|
585
|
+
let config: Record<string, any> = {};
|
|
586
|
+
if (existingContent) {
|
|
587
|
+
try {
|
|
588
|
+
config = JSON.parse(existingContent);
|
|
589
|
+
} catch {
|
|
590
|
+
// ignore malformed, overwrite
|
|
591
|
+
}
|
|
592
|
+
}
|
|
371
593
|
|
|
372
|
-
|
|
594
|
+
const pluginsKey = config.plugins !== undefined ? "plugins" : "plugin";
|
|
595
|
+
const existing: string[] = config[pluginsKey] || [];
|
|
596
|
+
const filtered = existing.filter((p: string) => !p.includes(OPENCODE_PLUGIN_NAME));
|
|
597
|
+
filtered.push(OPENCODE_PLUGIN);
|
|
598
|
+
config[pluginsKey] = filtered;
|
|
599
|
+
config.$schema = config.$schema || "https://opencode.ai/config.json";
|
|
373
600
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
import { SpawnCommandRunner } from './adapters/spawn-command-runner.js'
|
|
377
|
-
import * as os from 'os'
|
|
601
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
602
|
+
}
|
|
378
603
|
|
|
379
604
|
export async function runSetupCommand(): Promise<void> {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
605
|
+
try {
|
|
606
|
+
await runSetup({
|
|
607
|
+
prompter: new ClackPrompter(),
|
|
608
|
+
files: new FsFileStore(),
|
|
609
|
+
commands: new SpawnCommandRunner(),
|
|
610
|
+
authService: AuthService.getInstance(),
|
|
611
|
+
apiKeyService: ApiKeyService.getInstance(),
|
|
612
|
+
homeDir: os.homedir(),
|
|
613
|
+
cwd: process.cwd(),
|
|
614
|
+
});
|
|
615
|
+
process.exit(0);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
if (err instanceof CancelledError) {
|
|
618
|
+
process.exit(130);
|
|
619
|
+
}
|
|
620
|
+
if (err instanceof PrerequisiteError) {
|
|
621
|
+
console.error(`Missing required binary: ${err.binary}`);
|
|
622
|
+
process.exit(2);
|
|
623
|
+
}
|
|
624
|
+
if (err instanceof CommandFailedError) {
|
|
625
|
+
console.error(err.message);
|
|
626
|
+
process.exit(5);
|
|
401
627
|
}
|
|
628
|
+
throw err;
|
|
629
|
+
}
|
|
402
630
|
}
|