opencode-athena 0.0.1
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/LICENSE +18 -0
- package/README.md +178 -0
- package/commands/athena-debug.md +338 -0
- package/commands/athena-dev.md +322 -0
- package/commands/athena-info.md +325 -0
- package/commands/athena-parallel.md +266 -0
- package/commands/athena-research.md +326 -0
- package/commands/athena-review.md +441 -0
- package/commands/athena-status.md +279 -0
- package/config/presets/enterprise.json +37 -0
- package/config/presets/minimal.json +34 -0
- package/config/presets/solo-quick.json +34 -0
- package/config/presets/standard.json +37 -0
- package/config/schemas/athena.schema.json +128 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2185 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +628 -0
- package/dist/index.js +1360 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/index.d.ts +20 -0
- package/dist/plugin/index.js +1343 -0
- package/dist/plugin/index.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,2185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { confirm, select, checkbox } from '@inquirer/prompts';
|
|
5
|
+
import chalk3 from 'chalk';
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import ora3 from 'ora';
|
|
9
|
+
import { exec } from 'child_process';
|
|
10
|
+
import { existsSync, readFileSync } from 'fs';
|
|
11
|
+
import { mkdir, writeFile, readdir, copyFile, rm } from 'fs/promises';
|
|
12
|
+
import { promisify } from 'util';
|
|
13
|
+
import 'yaml';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
18
|
+
var __esm = (fn, res) => function __init() {
|
|
19
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
20
|
+
};
|
|
21
|
+
var __export = (target, all) => {
|
|
22
|
+
for (var name in all)
|
|
23
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
24
|
+
};
|
|
25
|
+
var init_esm_shims = __esm({
|
|
26
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/cli/questions/models.ts
|
|
31
|
+
var models_exports = {};
|
|
32
|
+
__export(models_exports, {
|
|
33
|
+
gatherModels: () => gatherModels,
|
|
34
|
+
getAvailableModels: () => getAvailableModels,
|
|
35
|
+
getModelList: () => getModelList,
|
|
36
|
+
validatePresetModels: () => validatePresetModels
|
|
37
|
+
});
|
|
38
|
+
function getAvailableModels(subscriptions) {
|
|
39
|
+
return AVAILABLE_MODELS.filter((model) => {
|
|
40
|
+
if (model.provider === "anthropic" && !subscriptions.hasClaude) return false;
|
|
41
|
+
if (model.provider === "openai" && !subscriptions.hasOpenAI) return false;
|
|
42
|
+
if (model.provider === "google" && !subscriptions.hasGoogle) return false;
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function isModelAvailable(modelId, availableModels) {
|
|
47
|
+
return availableModels.some((m) => m.id === modelId);
|
|
48
|
+
}
|
|
49
|
+
function getValidModelOrFallback(presetModel, role, subscriptions, availableModels) {
|
|
50
|
+
if (presetModel && isModelAvailable(presetModel, availableModels)) {
|
|
51
|
+
return presetModel;
|
|
52
|
+
}
|
|
53
|
+
return getSuggestedModel(role, subscriptions, availableModels);
|
|
54
|
+
}
|
|
55
|
+
function createModelChoices(models) {
|
|
56
|
+
return models.map((model) => ({
|
|
57
|
+
name: `${model.name} - ${model.description}`,
|
|
58
|
+
value: model.id
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
function getSuggestedModel(role, _subscriptions, availableModels) {
|
|
62
|
+
const suggestions = {
|
|
63
|
+
sisyphus: [
|
|
64
|
+
"anthropic/claude-opus-4-5-thinking",
|
|
65
|
+
"anthropic/claude-sonnet-4-5-thinking",
|
|
66
|
+
"openai/gpt-5.1-high",
|
|
67
|
+
"google/gemini-2.5-pro"
|
|
68
|
+
],
|
|
69
|
+
oracle: [
|
|
70
|
+
"openai/gpt-5.1-high",
|
|
71
|
+
"anthropic/claude-opus-4-5-thinking",
|
|
72
|
+
"anthropic/claude-sonnet-4-5-thinking",
|
|
73
|
+
"google/gemini-2.5-pro"
|
|
74
|
+
],
|
|
75
|
+
librarian: ["anthropic/claude-sonnet-4-5", "openai/gpt-4o", "google/gemini-2.5-flash"],
|
|
76
|
+
frontend: ["anthropic/claude-sonnet-4-5", "google/gemini-2.5-pro", "openai/gpt-4o"],
|
|
77
|
+
documentWriter: ["google/gemini-2.5-pro", "anthropic/claude-sonnet-4-5", "openai/gpt-4o"],
|
|
78
|
+
multimodalLooker: ["google/gemini-2.5-flash", "openai/gpt-4o", "anthropic/claude-sonnet-4-5"]
|
|
79
|
+
};
|
|
80
|
+
const roleDefaults = suggestions[role] || [];
|
|
81
|
+
const availableIds = availableModels.map((m) => m.id);
|
|
82
|
+
for (const modelId of roleDefaults) {
|
|
83
|
+
if (availableIds.includes(modelId)) {
|
|
84
|
+
return modelId;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return availableModels[0]?.id;
|
|
88
|
+
}
|
|
89
|
+
async function gatherModels(subscriptions, defaults) {
|
|
90
|
+
const availableModels = getAvailableModels(subscriptions);
|
|
91
|
+
if (availableModels.length === 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"No models available. Please enable at least one provider (Claude, OpenAI, or Google)."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const choices = createModelChoices(availableModels);
|
|
97
|
+
const sisyphusDefault = getValidModelOrFallback(
|
|
98
|
+
defaults?.sisyphus,
|
|
99
|
+
"sisyphus",
|
|
100
|
+
subscriptions,
|
|
101
|
+
availableModels
|
|
102
|
+
);
|
|
103
|
+
const oracleDefault = getValidModelOrFallback(
|
|
104
|
+
defaults?.oracle,
|
|
105
|
+
"oracle",
|
|
106
|
+
subscriptions,
|
|
107
|
+
availableModels
|
|
108
|
+
);
|
|
109
|
+
const librarianDefault = getValidModelOrFallback(
|
|
110
|
+
defaults?.librarian,
|
|
111
|
+
"librarian",
|
|
112
|
+
subscriptions,
|
|
113
|
+
availableModels
|
|
114
|
+
);
|
|
115
|
+
const sisyphus = await select({
|
|
116
|
+
message: "Model for Sisyphus (main orchestrator - implements stories)?",
|
|
117
|
+
choices,
|
|
118
|
+
default: sisyphusDefault
|
|
119
|
+
});
|
|
120
|
+
const oracle = await select({
|
|
121
|
+
message: "Model for Oracle (debugging and complex reasoning)?",
|
|
122
|
+
choices,
|
|
123
|
+
default: oracleDefault
|
|
124
|
+
});
|
|
125
|
+
const librarian = await select({
|
|
126
|
+
message: "Model for Librarian (research and documentation lookup)?",
|
|
127
|
+
choices,
|
|
128
|
+
default: librarianDefault
|
|
129
|
+
});
|
|
130
|
+
const frontend = getValidModelOrFallback(
|
|
131
|
+
defaults?.frontend,
|
|
132
|
+
"frontend",
|
|
133
|
+
subscriptions,
|
|
134
|
+
availableModels
|
|
135
|
+
);
|
|
136
|
+
const documentWriter = getValidModelOrFallback(
|
|
137
|
+
defaults?.documentWriter,
|
|
138
|
+
"documentWriter",
|
|
139
|
+
subscriptions,
|
|
140
|
+
availableModels
|
|
141
|
+
);
|
|
142
|
+
const multimodalLooker = getValidModelOrFallback(
|
|
143
|
+
defaults?.multimodalLooker,
|
|
144
|
+
"multimodalLooker",
|
|
145
|
+
subscriptions,
|
|
146
|
+
availableModels
|
|
147
|
+
);
|
|
148
|
+
return {
|
|
149
|
+
sisyphus,
|
|
150
|
+
oracle,
|
|
151
|
+
librarian,
|
|
152
|
+
frontend,
|
|
153
|
+
documentWriter,
|
|
154
|
+
multimodalLooker
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function getModelList(subscriptions) {
|
|
158
|
+
return getAvailableModels(subscriptions);
|
|
159
|
+
}
|
|
160
|
+
function validatePresetModels(presetModels, subscriptions) {
|
|
161
|
+
const warnings = [];
|
|
162
|
+
const availableModels = getAvailableModels(subscriptions);
|
|
163
|
+
const checkModel = (model, role) => {
|
|
164
|
+
if (model && !isModelAvailable(model, availableModels)) {
|
|
165
|
+
warnings.push(
|
|
166
|
+
`Preset model for ${role} (${model}) is not available with your subscriptions. A fallback will be used.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
checkModel(presetModels.sisyphus, "Sisyphus");
|
|
171
|
+
checkModel(presetModels.oracle, "Oracle");
|
|
172
|
+
checkModel(presetModels.librarian, "Librarian");
|
|
173
|
+
checkModel(presetModels.frontend, "Frontend");
|
|
174
|
+
checkModel(presetModels.documentWriter, "Document Writer");
|
|
175
|
+
checkModel(presetModels.multimodalLooker, "Multimodal Looker");
|
|
176
|
+
return warnings;
|
|
177
|
+
}
|
|
178
|
+
var AVAILABLE_MODELS;
|
|
179
|
+
var init_models = __esm({
|
|
180
|
+
"src/cli/questions/models.ts"() {
|
|
181
|
+
init_esm_shims();
|
|
182
|
+
AVAILABLE_MODELS = [
|
|
183
|
+
// Anthropic models
|
|
184
|
+
{
|
|
185
|
+
id: "anthropic/claude-sonnet-4-5",
|
|
186
|
+
name: "Claude Sonnet 4.5",
|
|
187
|
+
provider: "anthropic",
|
|
188
|
+
description: "Latest Sonnet - balanced performance and speed"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "anthropic/claude-opus-4-5",
|
|
192
|
+
name: "Claude Opus 4.5",
|
|
193
|
+
provider: "anthropic",
|
|
194
|
+
description: "Most capable Claude model"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "anthropic/claude-sonnet-4-5-thinking",
|
|
198
|
+
name: "Claude Sonnet 4.5 (Thinking)",
|
|
199
|
+
provider: "anthropic",
|
|
200
|
+
description: "Sonnet with extended thinking enabled"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "anthropic/claude-opus-4-5-thinking",
|
|
204
|
+
name: "Claude Opus 4.5 (Thinking)",
|
|
205
|
+
provider: "anthropic",
|
|
206
|
+
description: "Opus with extended thinking enabled"
|
|
207
|
+
},
|
|
208
|
+
// OpenAI models
|
|
209
|
+
{
|
|
210
|
+
id: "openai/gpt-4o",
|
|
211
|
+
name: "GPT-4o",
|
|
212
|
+
provider: "openai",
|
|
213
|
+
description: "Fast multimodal model"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "openai/gpt-5.1",
|
|
217
|
+
name: "GPT-5.1",
|
|
218
|
+
provider: "openai",
|
|
219
|
+
description: "Latest GPT model"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "openai/gpt-5.1-high",
|
|
223
|
+
name: "GPT-5.1 High",
|
|
224
|
+
provider: "openai",
|
|
225
|
+
description: "GPT-5.1 with high reasoning effort"
|
|
226
|
+
},
|
|
227
|
+
// Google models
|
|
228
|
+
{
|
|
229
|
+
id: "google/gemini-2.5-pro",
|
|
230
|
+
name: "Gemini 2.5 Pro",
|
|
231
|
+
provider: "google",
|
|
232
|
+
description: "Latest Gemini Pro model"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "google/gemini-2.5-flash",
|
|
236
|
+
name: "Gemini 2.5 Flash",
|
|
237
|
+
provider: "google",
|
|
238
|
+
description: "Fast Gemini model"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "google/gemini-2.0-flash",
|
|
242
|
+
name: "Gemini 2.0 Flash",
|
|
243
|
+
provider: "google",
|
|
244
|
+
description: "Previous generation fast model"
|
|
245
|
+
}
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// src/cli/index.ts
|
|
251
|
+
init_esm_shims();
|
|
252
|
+
|
|
253
|
+
// src/shared/constants.ts
|
|
254
|
+
init_esm_shims();
|
|
255
|
+
var VERSION = "0.0.1";
|
|
256
|
+
var DISPLAY_NAME = "OpenCode Athena";
|
|
257
|
+
var TAGLINE = "Strategic wisdom meets practical execution";
|
|
258
|
+
var CONFIG_PATHS = {
|
|
259
|
+
/** Global OpenCode config directory */
|
|
260
|
+
globalConfigDir: join(homedir(), ".config", "opencode"),
|
|
261
|
+
/** Global Athena config file */
|
|
262
|
+
globalAthenaConfig: join(homedir(), ".config", "opencode", "athena.json"),
|
|
263
|
+
/** Global OpenCode config file */
|
|
264
|
+
globalOpencodeConfig: join(homedir(), ".config", "opencode", "opencode.json"),
|
|
265
|
+
/** Global oh-my-opencode config file */
|
|
266
|
+
globalOmoConfig: join(homedir(), ".config", "opencode", "oh-my-opencode.json"),
|
|
267
|
+
/** Commands directory */
|
|
268
|
+
commandsDir: join(homedir(), ".config", "opencode", "command"),
|
|
269
|
+
/** Plugin directory */
|
|
270
|
+
pluginDir: join(homedir(), ".config", "opencode", "plugin"),
|
|
271
|
+
/** Athena state file (for story tracking) */
|
|
272
|
+
stateFile: join(homedir(), ".config", "opencode", "athena-state.json")
|
|
273
|
+
};
|
|
274
|
+
var DEFAULTS = {
|
|
275
|
+
/** Maximum parallel stories */
|
|
276
|
+
parallelStoryLimit: 3};
|
|
277
|
+
var MIN_VERSIONS = {
|
|
278
|
+
node: "20.0.0",
|
|
279
|
+
opencode: "1.0.132"
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/cli/commands/doctor.ts
|
|
283
|
+
init_esm_shims();
|
|
284
|
+
|
|
285
|
+
// src/cli/utils/file-manager.ts
|
|
286
|
+
init_esm_shims();
|
|
287
|
+
var execAsync = promisify(exec);
|
|
288
|
+
function getPackageRoot() {
|
|
289
|
+
const currentFileDir = dirname(fileURLToPath(import.meta.url));
|
|
290
|
+
const bundledRoot = join(currentFileDir, "..", "..");
|
|
291
|
+
if (existsSync(join(bundledRoot, "commands"))) {
|
|
292
|
+
return bundledRoot;
|
|
293
|
+
}
|
|
294
|
+
const devRoot = join(currentFileDir, "..", "..", "..");
|
|
295
|
+
if (existsSync(join(devRoot, "commands"))) {
|
|
296
|
+
return devRoot;
|
|
297
|
+
}
|
|
298
|
+
return bundledRoot;
|
|
299
|
+
}
|
|
300
|
+
var FileManager = class {
|
|
301
|
+
configDir;
|
|
302
|
+
constructor(configDir) {
|
|
303
|
+
this.configDir = configDir || CONFIG_PATHS.globalConfigDir;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get the configuration directory path
|
|
307
|
+
*/
|
|
308
|
+
getConfigDir() {
|
|
309
|
+
return this.configDir;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Ensure a directory exists
|
|
313
|
+
*/
|
|
314
|
+
async ensureDir(dir) {
|
|
315
|
+
if (!existsSync(dir)) {
|
|
316
|
+
await mkdir(dir, { recursive: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Write multiple files atomically
|
|
321
|
+
*/
|
|
322
|
+
async writeFiles(files) {
|
|
323
|
+
for (const file of files) {
|
|
324
|
+
const dir = dirname(file.path);
|
|
325
|
+
await this.ensureDir(dir);
|
|
326
|
+
await writeFile(file.path, file.content, "utf-8");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Read a JSON configuration file
|
|
331
|
+
*/
|
|
332
|
+
readJsonFile(path2) {
|
|
333
|
+
if (!existsSync(path2)) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const content = readFileSync(path2, "utf-8");
|
|
338
|
+
return JSON.parse(content);
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Write a JSON configuration file
|
|
345
|
+
*/
|
|
346
|
+
async writeJsonFile(path2, data) {
|
|
347
|
+
const dir = dirname(path2);
|
|
348
|
+
await this.ensureDir(dir);
|
|
349
|
+
await writeFile(path2, JSON.stringify(data, null, 2), "utf-8");
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Check if a file exists
|
|
353
|
+
*/
|
|
354
|
+
exists(path2) {
|
|
355
|
+
return existsSync(path2);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Install npm dependencies in the config directory
|
|
359
|
+
*/
|
|
360
|
+
async installDependencies(packages) {
|
|
361
|
+
if (packages.length === 0) return;
|
|
362
|
+
await this.ensureDir(this.configDir);
|
|
363
|
+
const packageJsonPath = join(this.configDir, "package.json");
|
|
364
|
+
if (!existsSync(packageJsonPath)) {
|
|
365
|
+
await writeFile(
|
|
366
|
+
packageJsonPath,
|
|
367
|
+
JSON.stringify(
|
|
368
|
+
{
|
|
369
|
+
name: "opencode-config",
|
|
370
|
+
private: true,
|
|
371
|
+
type: "module"
|
|
372
|
+
},
|
|
373
|
+
null,
|
|
374
|
+
2
|
|
375
|
+
)
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const packageList = packages.join(" ");
|
|
379
|
+
await execAsync(`npm install ${packageList}`, {
|
|
380
|
+
cwd: this.configDir,
|
|
381
|
+
timeout: 12e4
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Uninstall npm dependencies from the config directory
|
|
386
|
+
*/
|
|
387
|
+
async uninstallDependencies(packages) {
|
|
388
|
+
if (packages.length === 0) return;
|
|
389
|
+
const packageList = packages.join(" ");
|
|
390
|
+
try {
|
|
391
|
+
await execAsync(`npm uninstall ${packageList}`, {
|
|
392
|
+
cwd: this.configDir,
|
|
393
|
+
timeout: 6e4
|
|
394
|
+
});
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Copy bridge commands from package to config directory
|
|
400
|
+
*/
|
|
401
|
+
async copyCommands() {
|
|
402
|
+
const commandsDir = CONFIG_PATHS.commandsDir;
|
|
403
|
+
await this.ensureDir(commandsDir);
|
|
404
|
+
const packageRoot = getPackageRoot();
|
|
405
|
+
const sourceCommandsDir = join(packageRoot, "commands");
|
|
406
|
+
const copiedFiles = [];
|
|
407
|
+
if (existsSync(sourceCommandsDir)) {
|
|
408
|
+
const files = await readdir(sourceCommandsDir);
|
|
409
|
+
for (const file of files) {
|
|
410
|
+
if (file.endsWith(".md")) {
|
|
411
|
+
const sourcePath = join(sourceCommandsDir, file);
|
|
412
|
+
const destPath = join(commandsDir, file);
|
|
413
|
+
await copyFile(sourcePath, destPath);
|
|
414
|
+
copiedFiles.push(file);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return copiedFiles;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Remove bridge commands from config directory
|
|
422
|
+
*/
|
|
423
|
+
async removeCommands() {
|
|
424
|
+
const commandsDir = CONFIG_PATHS.commandsDir;
|
|
425
|
+
const removedFiles = [];
|
|
426
|
+
if (!existsSync(commandsDir)) {
|
|
427
|
+
return removedFiles;
|
|
428
|
+
}
|
|
429
|
+
const files = await readdir(commandsDir);
|
|
430
|
+
for (const file of files) {
|
|
431
|
+
if (file.startsWith("athena-") && file.endsWith(".md")) {
|
|
432
|
+
const filePath = join(commandsDir, file);
|
|
433
|
+
await rm(filePath);
|
|
434
|
+
removedFiles.push(file);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return removedFiles;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Remove Athena configuration files
|
|
441
|
+
*/
|
|
442
|
+
async removeConfigFiles() {
|
|
443
|
+
const removedFiles = [];
|
|
444
|
+
const filesToRemove = [CONFIG_PATHS.globalAthenaConfig, CONFIG_PATHS.stateFile];
|
|
445
|
+
for (const file of filesToRemove) {
|
|
446
|
+
if (existsSync(file)) {
|
|
447
|
+
await rm(file);
|
|
448
|
+
removedFiles.push(file);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return removedFiles;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Remove Athena from opencode.json plugin list
|
|
455
|
+
*/
|
|
456
|
+
async removeFromOpencodeConfig() {
|
|
457
|
+
const opencodeConfig = this.readJsonFile(CONFIG_PATHS.globalOpencodeConfig);
|
|
458
|
+
if (!opencodeConfig || !opencodeConfig.plugin) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
const athenaPlugins = [
|
|
462
|
+
"opencode-athena",
|
|
463
|
+
"oh-my-opencode",
|
|
464
|
+
"opencode-antigravity-auth",
|
|
465
|
+
"opencode-openai-codex-auth"
|
|
466
|
+
];
|
|
467
|
+
const originalLength = opencodeConfig.plugin.length;
|
|
468
|
+
opencodeConfig.plugin = opencodeConfig.plugin.filter(
|
|
469
|
+
(p) => !athenaPlugins.some((ap) => p.includes(ap))
|
|
470
|
+
);
|
|
471
|
+
if (opencodeConfig.plugin.length !== originalLength) {
|
|
472
|
+
await this.writeJsonFile(CONFIG_PATHS.globalOpencodeConfig, opencodeConfig);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Backup a file before modifying
|
|
479
|
+
*/
|
|
480
|
+
async backupFile(path2) {
|
|
481
|
+
if (!existsSync(path2)) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
const backupPath = `${path2}.backup`;
|
|
485
|
+
await copyFile(path2, backupPath);
|
|
486
|
+
return backupPath;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Restore a file from backup
|
|
490
|
+
*/
|
|
491
|
+
async restoreFromBackup(backupPath) {
|
|
492
|
+
const originalPath = backupPath.replace(/\.backup$/, "");
|
|
493
|
+
if (existsSync(backupPath)) {
|
|
494
|
+
await copyFile(backupPath, originalPath);
|
|
495
|
+
await rm(backupPath);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// src/cli/utils/logger.ts
|
|
501
|
+
init_esm_shims();
|
|
502
|
+
var logger = {
|
|
503
|
+
/**
|
|
504
|
+
* Log an informational message
|
|
505
|
+
*/
|
|
506
|
+
info: (message) => {
|
|
507
|
+
console.log(chalk3.blue("i"), message);
|
|
508
|
+
},
|
|
509
|
+
/**
|
|
510
|
+
* Log a success message
|
|
511
|
+
*/
|
|
512
|
+
success: (message) => {
|
|
513
|
+
console.log(chalk3.green("\u2713"), message);
|
|
514
|
+
},
|
|
515
|
+
/**
|
|
516
|
+
* Log a warning message
|
|
517
|
+
*/
|
|
518
|
+
warn: (message) => {
|
|
519
|
+
console.log(chalk3.yellow("!"), message);
|
|
520
|
+
},
|
|
521
|
+
/**
|
|
522
|
+
* Log an error message
|
|
523
|
+
*/
|
|
524
|
+
error: (message) => {
|
|
525
|
+
console.log(chalk3.red("\u2716"), message);
|
|
526
|
+
},
|
|
527
|
+
/**
|
|
528
|
+
* Log a debug message (only when DEBUG env var is set)
|
|
529
|
+
*/
|
|
530
|
+
debug: (message) => {
|
|
531
|
+
if (process.env.DEBUG) {
|
|
532
|
+
console.log(chalk3.gray("[debug]"), message);
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
/**
|
|
536
|
+
* Log a step in a process
|
|
537
|
+
*/
|
|
538
|
+
step: (step, total, message) => {
|
|
539
|
+
console.log(chalk3.cyan(`[${step}/${total}]`), message);
|
|
540
|
+
},
|
|
541
|
+
/**
|
|
542
|
+
* Log a blank line
|
|
543
|
+
*/
|
|
544
|
+
blank: () => {
|
|
545
|
+
console.log();
|
|
546
|
+
},
|
|
547
|
+
/**
|
|
548
|
+
* Log a section header
|
|
549
|
+
*/
|
|
550
|
+
section: (title) => {
|
|
551
|
+
console.log();
|
|
552
|
+
console.log(chalk3.bold(title));
|
|
553
|
+
console.log();
|
|
554
|
+
},
|
|
555
|
+
/**
|
|
556
|
+
* Log a key-value pair
|
|
557
|
+
*/
|
|
558
|
+
keyValue: (key, value, indent = 0) => {
|
|
559
|
+
const padding = " ".repeat(indent);
|
|
560
|
+
console.log(`${padding}${chalk3.gray(`${key}:`)} ${value}`);
|
|
561
|
+
},
|
|
562
|
+
/**
|
|
563
|
+
* Log a list item
|
|
564
|
+
*/
|
|
565
|
+
listItem: (item, indent = 0) => {
|
|
566
|
+
const padding = " ".repeat(indent);
|
|
567
|
+
console.log(`${padding}${chalk3.gray("-")} ${item}`);
|
|
568
|
+
},
|
|
569
|
+
/**
|
|
570
|
+
* Display the Athena banner
|
|
571
|
+
*/
|
|
572
|
+
banner: () => {
|
|
573
|
+
console.log(
|
|
574
|
+
chalk3.cyan(`
|
|
575
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
576
|
+
\u2551 OPENCODE ATHENA \u2551
|
|
577
|
+
\u2551 Strategic Wisdom Meets Practical Execution \u2551
|
|
578
|
+
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
579
|
+
\u2551 Unifying oh-my-opencode + BMAD METHOD for OpenCode \u2551
|
|
580
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
581
|
+
`)
|
|
582
|
+
);
|
|
583
|
+
},
|
|
584
|
+
/**
|
|
585
|
+
* Display a success banner
|
|
586
|
+
*/
|
|
587
|
+
successBanner: (message) => {
|
|
588
|
+
const line = "\u2550".repeat(message.length + 4);
|
|
589
|
+
console.log(
|
|
590
|
+
chalk3.green(`
|
|
591
|
+
\u2554${line}\u2557
|
|
592
|
+
\u2551 ${message} \u2551
|
|
593
|
+
\u255A${line}\u255D
|
|
594
|
+
`)
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/cli/utils/prerequisites.ts
|
|
600
|
+
init_esm_shims();
|
|
601
|
+
var execAsync2 = promisify(exec);
|
|
602
|
+
function parseVersion(version) {
|
|
603
|
+
const match = version.match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
604
|
+
if (!match) return null;
|
|
605
|
+
return {
|
|
606
|
+
major: Number.parseInt(match[1], 10),
|
|
607
|
+
minor: Number.parseInt(match[2], 10),
|
|
608
|
+
patch: Number.parseInt(match[3], 10)
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function compareVersions(a, b) {
|
|
612
|
+
const parsedA = parseVersion(a);
|
|
613
|
+
const parsedB = parseVersion(b);
|
|
614
|
+
if (!parsedA || !parsedB) return 0;
|
|
615
|
+
if (parsedA.major !== parsedB.major) {
|
|
616
|
+
return parsedA.major < parsedB.major ? -1 : 1;
|
|
617
|
+
}
|
|
618
|
+
if (parsedA.minor !== parsedB.minor) {
|
|
619
|
+
return parsedA.minor < parsedB.minor ? -1 : 1;
|
|
620
|
+
}
|
|
621
|
+
if (parsedA.patch !== parsedB.patch) {
|
|
622
|
+
return parsedA.patch < parsedB.patch ? -1 : 1;
|
|
623
|
+
}
|
|
624
|
+
return 0;
|
|
625
|
+
}
|
|
626
|
+
async function checkNode() {
|
|
627
|
+
try {
|
|
628
|
+
const { stdout } = await execAsync2("node --version");
|
|
629
|
+
const version = stdout.trim().replace(/^v/, "");
|
|
630
|
+
const compatible = compareVersions(version, MIN_VERSIONS.node) >= 0;
|
|
631
|
+
return { installed: true, version, compatible };
|
|
632
|
+
} catch {
|
|
633
|
+
return { installed: false, compatible: false };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function checkOpenCode() {
|
|
637
|
+
try {
|
|
638
|
+
const { stdout } = await execAsync2("opencode --version");
|
|
639
|
+
const version = stdout.trim();
|
|
640
|
+
const compatible = compareVersions(version, MIN_VERSIONS.opencode) >= 0;
|
|
641
|
+
return { installed: true, version, compatible };
|
|
642
|
+
} catch {
|
|
643
|
+
return { installed: false, compatible: false };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async function checkAthena() {
|
|
647
|
+
if (!existsSync(CONFIG_PATHS.globalAthenaConfig)) {
|
|
648
|
+
return { installed: false };
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
const content = readFileSync(CONFIG_PATHS.globalAthenaConfig, "utf-8");
|
|
652
|
+
const config = JSON.parse(content);
|
|
653
|
+
return {
|
|
654
|
+
installed: true,
|
|
655
|
+
version: config.version
|
|
656
|
+
};
|
|
657
|
+
} catch {
|
|
658
|
+
return { installed: true };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function checkPrerequisites() {
|
|
662
|
+
const [node, opencode, athena] = await Promise.all([checkNode(), checkOpenCode(), checkAthena()]);
|
|
663
|
+
return { node, opencode, athena };
|
|
664
|
+
}
|
|
665
|
+
async function checkOhMyOpenCode() {
|
|
666
|
+
try {
|
|
667
|
+
const { stdout } = await execAsync2("npm list oh-my-opencode --json", {
|
|
668
|
+
cwd: CONFIG_PATHS.globalConfigDir
|
|
669
|
+
});
|
|
670
|
+
const data = JSON.parse(stdout);
|
|
671
|
+
const version = data.dependencies?.["oh-my-opencode"]?.version;
|
|
672
|
+
return { installed: !!version, version };
|
|
673
|
+
} catch {
|
|
674
|
+
return { installed: false };
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function getInstalledPlugins() {
|
|
678
|
+
try {
|
|
679
|
+
const { stdout } = await execAsync2("npm list --depth=0 --json", {
|
|
680
|
+
cwd: CONFIG_PATHS.globalConfigDir
|
|
681
|
+
});
|
|
682
|
+
const data = JSON.parse(stdout);
|
|
683
|
+
const deps = data.dependencies || {};
|
|
684
|
+
const result = {};
|
|
685
|
+
for (const [name, info2] of Object.entries(deps)) {
|
|
686
|
+
result[name] = info2.version || "unknown";
|
|
687
|
+
}
|
|
688
|
+
return result;
|
|
689
|
+
} catch {
|
|
690
|
+
return {};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function validateJsonFile(path2) {
|
|
694
|
+
if (!existsSync(path2)) {
|
|
695
|
+
return { valid: false, error: "File does not exist" };
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const content = readFileSync(path2, "utf-8");
|
|
699
|
+
JSON.parse(content);
|
|
700
|
+
return { valid: true };
|
|
701
|
+
} catch (err) {
|
|
702
|
+
return {
|
|
703
|
+
valid: false,
|
|
704
|
+
error: err instanceof Error ? err.message : "Invalid JSON"
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/cli/utils/validators.ts
|
|
710
|
+
init_esm_shims();
|
|
711
|
+
|
|
712
|
+
// src/shared/schemas.ts
|
|
713
|
+
init_esm_shims();
|
|
714
|
+
var SubscriptionSchema = z.object({
|
|
715
|
+
claude: z.object({
|
|
716
|
+
enabled: z.boolean(),
|
|
717
|
+
tier: z.enum(["max5x", "max20x", "pro", "none"])
|
|
718
|
+
}),
|
|
719
|
+
openai: z.object({
|
|
720
|
+
enabled: z.boolean()
|
|
721
|
+
}),
|
|
722
|
+
google: z.object({
|
|
723
|
+
enabled: z.boolean(),
|
|
724
|
+
authMethod: z.enum(["antigravity", "personal", "api", "none"])
|
|
725
|
+
})
|
|
726
|
+
});
|
|
727
|
+
var BmadConfigSchema = z.object({
|
|
728
|
+
defaultTrack: z.enum(["quick-flow", "bmad-method", "enterprise"]),
|
|
729
|
+
autoStatusUpdate: z.boolean(),
|
|
730
|
+
parallelStoryLimit: z.number().int().min(0).max(10)
|
|
731
|
+
});
|
|
732
|
+
var FeaturesSchema = z.object({
|
|
733
|
+
bmadBridge: z.boolean(),
|
|
734
|
+
autoStatus: z.boolean(),
|
|
735
|
+
parallelExecution: z.boolean(),
|
|
736
|
+
notifications: z.boolean(),
|
|
737
|
+
contextMonitor: z.boolean(),
|
|
738
|
+
commentChecker: z.boolean(),
|
|
739
|
+
lspTools: z.boolean()
|
|
740
|
+
});
|
|
741
|
+
var McpsSchema = z.object({
|
|
742
|
+
context7: z.boolean(),
|
|
743
|
+
exa: z.boolean(),
|
|
744
|
+
grepApp: z.boolean()
|
|
745
|
+
});
|
|
746
|
+
var ModelsSchema = z.object({
|
|
747
|
+
sisyphus: z.string().describe("Model for main orchestrator agent"),
|
|
748
|
+
oracle: z.string().describe("Model for debugging/reasoning agent"),
|
|
749
|
+
librarian: z.string().describe("Model for research/documentation agent"),
|
|
750
|
+
frontend: z.string().optional().describe("Model for UI/UX agent"),
|
|
751
|
+
documentWriter: z.string().optional().describe("Model for documentation generation agent"),
|
|
752
|
+
multimodalLooker: z.string().optional().describe("Model for image analysis agent")
|
|
753
|
+
});
|
|
754
|
+
var AthenaConfigSchema = z.object({
|
|
755
|
+
$schema: z.string().optional(),
|
|
756
|
+
version: z.string(),
|
|
757
|
+
subscriptions: SubscriptionSchema,
|
|
758
|
+
models: ModelsSchema,
|
|
759
|
+
bmad: BmadConfigSchema,
|
|
760
|
+
features: FeaturesSchema,
|
|
761
|
+
mcps: McpsSchema
|
|
762
|
+
});
|
|
763
|
+
z.object({
|
|
764
|
+
storyId: z.string().optional().describe(
|
|
765
|
+
"Specific story ID (e.g., '2.3'). If omitted, loads the next pending story from sprint-status.yaml."
|
|
766
|
+
)
|
|
767
|
+
});
|
|
768
|
+
z.object({
|
|
769
|
+
storyId: z.string().describe("The story ID (e.g., '2.3')"),
|
|
770
|
+
status: z.enum(["in_progress", "completed", "blocked", "needs_review"]).describe("The new status for the story"),
|
|
771
|
+
notes: z.string().optional().describe("Notes about the status change (required for 'blocked' status)"),
|
|
772
|
+
completionSummary: z.string().optional().describe("Summary of what was implemented (required for 'completed' status)")
|
|
773
|
+
});
|
|
774
|
+
z.object({
|
|
775
|
+
includeArchitecture: z.boolean().optional().default(true),
|
|
776
|
+
includePrd: z.boolean().optional().default(false),
|
|
777
|
+
includeSprintStatus: z.boolean().optional().default(true)
|
|
778
|
+
});
|
|
779
|
+
z.object({
|
|
780
|
+
storyIds: z.array(z.string()).describe("Array of story IDs to implement in parallel"),
|
|
781
|
+
maxConcurrent: z.number().int().min(1).max(5).optional().default(3)
|
|
782
|
+
});
|
|
783
|
+
z.object({
|
|
784
|
+
action: z.enum(["get", "set", "reset"]).describe("Configuration action to perform"),
|
|
785
|
+
key: z.string().optional().describe("Configuration key (dot notation, e.g., 'bmad.autoStatusUpdate')"),
|
|
786
|
+
value: z.unknown().optional().describe("Value to set (for 'set' action)")
|
|
787
|
+
});
|
|
788
|
+
var StoryStatusEnum = z.enum([
|
|
789
|
+
"pending",
|
|
790
|
+
"in_progress",
|
|
791
|
+
"completed",
|
|
792
|
+
"blocked",
|
|
793
|
+
"needs_review"
|
|
794
|
+
]);
|
|
795
|
+
z.enum([
|
|
796
|
+
"pending",
|
|
797
|
+
"in_progress",
|
|
798
|
+
"completed",
|
|
799
|
+
"blocked",
|
|
800
|
+
"needs_review",
|
|
801
|
+
"loading"
|
|
802
|
+
]);
|
|
803
|
+
z.object({
|
|
804
|
+
sprint_number: z.number().int().optional(),
|
|
805
|
+
current_epic: z.string().optional(),
|
|
806
|
+
current_story: z.string().nullable().optional(),
|
|
807
|
+
completed_stories: z.array(z.string()).default([]),
|
|
808
|
+
pending_stories: z.array(z.string()).default([]),
|
|
809
|
+
in_progress_stories: z.array(z.string()).default([]),
|
|
810
|
+
blocked_stories: z.array(z.string()).default([]),
|
|
811
|
+
stories_needing_review: z.array(z.string()).optional(),
|
|
812
|
+
story_updates: z.record(
|
|
813
|
+
z.object({
|
|
814
|
+
status: StoryStatusEnum,
|
|
815
|
+
updated_at: z.string(),
|
|
816
|
+
notes: z.string().optional(),
|
|
817
|
+
completion_summary: z.string().optional()
|
|
818
|
+
})
|
|
819
|
+
).optional(),
|
|
820
|
+
last_modified: z.string().optional()
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// src/cli/utils/validators.ts
|
|
824
|
+
function validateAthenaConfig(config) {
|
|
825
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
826
|
+
const parseResult = AthenaConfigSchema.safeParse(config);
|
|
827
|
+
if (!parseResult.success) {
|
|
828
|
+
result.valid = false;
|
|
829
|
+
for (const issue of parseResult.error.issues) {
|
|
830
|
+
result.errors.push(`${issue.path.join(".")}: ${issue.message}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return result;
|
|
834
|
+
}
|
|
835
|
+
function validateJsonConfig(path2) {
|
|
836
|
+
const result = { valid: true, errors: [], warnings: [] };
|
|
837
|
+
if (!existsSync(path2)) {
|
|
838
|
+
result.valid = false;
|
|
839
|
+
result.errors.push("File does not exist");
|
|
840
|
+
return result;
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
const content = readFileSync(path2, "utf-8");
|
|
844
|
+
JSON.parse(content);
|
|
845
|
+
} catch (err) {
|
|
846
|
+
result.valid = false;
|
|
847
|
+
result.errors.push(err instanceof Error ? err.message : "Invalid JSON");
|
|
848
|
+
}
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/cli/commands/doctor.ts
|
|
853
|
+
async function doctor(options) {
|
|
854
|
+
logger.banner();
|
|
855
|
+
logger.section("Running Diagnostics");
|
|
856
|
+
const results = [];
|
|
857
|
+
const fileManager = new FileManager();
|
|
858
|
+
const prereqs = await checkPrerequisites();
|
|
859
|
+
results.push({
|
|
860
|
+
name: "Node.js",
|
|
861
|
+
status: prereqs.node.installed ? prereqs.node.compatible ? "pass" : "warn" : "fail",
|
|
862
|
+
message: prereqs.node.installed ? prereqs.node.compatible ? `Version ${prereqs.node.version} is compatible` : `Version ${prereqs.node.version} detected, 20+ recommended` : "Not installed"
|
|
863
|
+
});
|
|
864
|
+
results.push({
|
|
865
|
+
name: "OpenCode",
|
|
866
|
+
status: prereqs.opencode.installed ? prereqs.opencode.compatible ? "pass" : "warn" : "fail",
|
|
867
|
+
message: prereqs.opencode.installed ? prereqs.opencode.compatible ? `Version ${prereqs.opencode.version} is compatible` : `Version ${prereqs.opencode.version} detected, 1.0.132+ recommended` : "Not installed"
|
|
868
|
+
});
|
|
869
|
+
const athenaConfigValid = validateJsonFile(CONFIG_PATHS.globalAthenaConfig);
|
|
870
|
+
if (athenaConfigValid.valid) {
|
|
871
|
+
const config = fileManager.readJsonFile(CONFIG_PATHS.globalAthenaConfig);
|
|
872
|
+
const schemaValidation = validateAthenaConfig(config);
|
|
873
|
+
results.push({
|
|
874
|
+
name: "Athena Config",
|
|
875
|
+
status: schemaValidation.valid ? "pass" : "warn",
|
|
876
|
+
message: schemaValidation.valid ? "Valid configuration" : `Schema issues: ${schemaValidation.errors.join(", ")}`
|
|
877
|
+
});
|
|
878
|
+
} else {
|
|
879
|
+
results.push({
|
|
880
|
+
name: "Athena Config",
|
|
881
|
+
status: "fail",
|
|
882
|
+
message: athenaConfigValid.error || "Not found",
|
|
883
|
+
fix: async () => {
|
|
884
|
+
logger.info("Run 'opencode-athena install' to create configuration");
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
const opencodeConfigValid = validateJsonConfig(CONFIG_PATHS.globalOpencodeConfig);
|
|
889
|
+
results.push({
|
|
890
|
+
name: "OpenCode Config",
|
|
891
|
+
status: opencodeConfigValid.valid ? "pass" : "fail",
|
|
892
|
+
message: opencodeConfigValid.valid ? "Valid JSON" : opencodeConfigValid.errors[0] || "Invalid"
|
|
893
|
+
});
|
|
894
|
+
const omoConfigValid = validateJsonConfig(CONFIG_PATHS.globalOmoConfig);
|
|
895
|
+
results.push({
|
|
896
|
+
name: "oh-my-opencode Config",
|
|
897
|
+
status: omoConfigValid.valid ? "pass" : "warn",
|
|
898
|
+
message: omoConfigValid.valid ? "Valid JSON" : omoConfigValid.errors[0] || "Not found"
|
|
899
|
+
});
|
|
900
|
+
const omoInstalled = await checkOhMyOpenCode();
|
|
901
|
+
results.push({
|
|
902
|
+
name: "oh-my-opencode Plugin",
|
|
903
|
+
status: omoInstalled.installed ? "pass" : "fail",
|
|
904
|
+
message: omoInstalled.installed ? `Version ${omoInstalled.version}` : "Not installed",
|
|
905
|
+
fix: async () => {
|
|
906
|
+
const spinner = ora3("Installing oh-my-opencode...").start();
|
|
907
|
+
try {
|
|
908
|
+
await fileManager.installDependencies(["oh-my-opencode"]);
|
|
909
|
+
spinner.succeed("oh-my-opencode installed");
|
|
910
|
+
} catch (err) {
|
|
911
|
+
spinner.fail("Failed to install oh-my-opencode");
|
|
912
|
+
throw err;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
const commandsDirExists = fileManager.exists(CONFIG_PATHS.commandsDir);
|
|
917
|
+
results.push({
|
|
918
|
+
name: "Commands Directory",
|
|
919
|
+
status: commandsDirExists ? "pass" : "warn",
|
|
920
|
+
message: commandsDirExists ? "Exists" : "Not found",
|
|
921
|
+
fix: async () => {
|
|
922
|
+
const spinner = ora3("Creating commands directory...").start();
|
|
923
|
+
try {
|
|
924
|
+
await fileManager.ensureDir(CONFIG_PATHS.commandsDir);
|
|
925
|
+
await fileManager.copyCommands();
|
|
926
|
+
spinner.succeed("Commands directory created and populated");
|
|
927
|
+
} catch (err) {
|
|
928
|
+
spinner.fail("Failed to create commands directory");
|
|
929
|
+
throw err;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
logger.section("Diagnostic Results");
|
|
934
|
+
let hasFailures = false;
|
|
935
|
+
let hasWarnings = false;
|
|
936
|
+
const fixableIssues = [];
|
|
937
|
+
for (const result of results) {
|
|
938
|
+
let icon;
|
|
939
|
+
let color;
|
|
940
|
+
switch (result.status) {
|
|
941
|
+
case "pass":
|
|
942
|
+
icon = "\u2713";
|
|
943
|
+
color = chalk3.green;
|
|
944
|
+
break;
|
|
945
|
+
case "warn":
|
|
946
|
+
icon = "!";
|
|
947
|
+
color = chalk3.yellow;
|
|
948
|
+
hasWarnings = true;
|
|
949
|
+
break;
|
|
950
|
+
case "fail":
|
|
951
|
+
icon = "\u2716";
|
|
952
|
+
color = chalk3.red;
|
|
953
|
+
hasFailures = true;
|
|
954
|
+
if (result.fix) {
|
|
955
|
+
fixableIssues.push(result);
|
|
956
|
+
}
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
console.log(` ${color(icon)} ${result.name}: ${result.message}`);
|
|
960
|
+
}
|
|
961
|
+
console.log();
|
|
962
|
+
if (hasFailures) {
|
|
963
|
+
logger.error("Some checks failed.");
|
|
964
|
+
if (fixableIssues.length > 0 && options.fix) {
|
|
965
|
+
logger.section("Applying Fixes");
|
|
966
|
+
for (const issue of fixableIssues) {
|
|
967
|
+
if (issue.fix) {
|
|
968
|
+
try {
|
|
969
|
+
await issue.fix();
|
|
970
|
+
} catch (err) {
|
|
971
|
+
logger.error(
|
|
972
|
+
`Failed to fix ${issue.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
logger.blank();
|
|
978
|
+
logger.info("Run 'opencode-athena doctor' again to verify fixes.");
|
|
979
|
+
} else if (fixableIssues.length > 0) {
|
|
980
|
+
logger.info(`Run ${chalk3.cyan("opencode-athena doctor --fix")} to attempt automatic fixes.`);
|
|
981
|
+
}
|
|
982
|
+
} else if (hasWarnings) {
|
|
983
|
+
logger.warn("Some checks have warnings, but Athena should work.");
|
|
984
|
+
} else {
|
|
985
|
+
logger.success("All checks passed! OpenCode Athena is healthy.");
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/cli/commands/info.ts
|
|
990
|
+
init_esm_shims();
|
|
991
|
+
async function info() {
|
|
992
|
+
logger.banner();
|
|
993
|
+
const fileManager = new FileManager();
|
|
994
|
+
const athenaConfig = fileManager.readJsonFile(CONFIG_PATHS.globalAthenaConfig);
|
|
995
|
+
if (!athenaConfig) {
|
|
996
|
+
logger.warn("OpenCode Athena is not installed.");
|
|
997
|
+
logger.info(`Run ${chalk3.cyan("opencode-athena install")} to get started.`);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
logger.section("Version Information");
|
|
1001
|
+
logger.keyValue("Athena Version", athenaConfig.version || VERSION);
|
|
1002
|
+
logger.section("Prerequisites");
|
|
1003
|
+
const prereqs = await checkPrerequisites();
|
|
1004
|
+
const nodeStatus = prereqs.node.installed ? prereqs.node.compatible ? chalk3.green("\u2713") : chalk3.yellow("!") : chalk3.red("\u2716");
|
|
1005
|
+
logger.keyValue("Node.js", `${nodeStatus} ${prereqs.node.version || "not found"}`);
|
|
1006
|
+
const opencodeStatus = prereqs.opencode.installed ? prereqs.opencode.compatible ? chalk3.green("\u2713") : chalk3.yellow("!") : chalk3.red("\u2716");
|
|
1007
|
+
logger.keyValue("OpenCode", `${opencodeStatus} ${prereqs.opencode.version || "not found"}`);
|
|
1008
|
+
logger.section("Configured Providers");
|
|
1009
|
+
const claudeStatus = athenaConfig.subscriptions.claude.enabled ? chalk3.green("enabled") : chalk3.gray("disabled");
|
|
1010
|
+
logger.keyValue(
|
|
1011
|
+
"Claude",
|
|
1012
|
+
`${claudeStatus}${athenaConfig.subscriptions.claude.tier !== "none" ? ` (${athenaConfig.subscriptions.claude.tier})` : ""}`
|
|
1013
|
+
);
|
|
1014
|
+
const openaiStatus = athenaConfig.subscriptions.openai.enabled ? chalk3.green("enabled") : chalk3.gray("disabled");
|
|
1015
|
+
logger.keyValue("OpenAI", openaiStatus);
|
|
1016
|
+
const googleStatus = athenaConfig.subscriptions.google.enabled ? chalk3.green("enabled") : chalk3.gray("disabled");
|
|
1017
|
+
logger.keyValue(
|
|
1018
|
+
"Google",
|
|
1019
|
+
`${googleStatus}${athenaConfig.subscriptions.google.authMethod !== "none" ? ` (${athenaConfig.subscriptions.google.authMethod})` : ""}`
|
|
1020
|
+
);
|
|
1021
|
+
logger.section("Agent Models");
|
|
1022
|
+
logger.keyValue("Sisyphus", athenaConfig.models.sisyphus);
|
|
1023
|
+
logger.keyValue("Oracle", athenaConfig.models.oracle);
|
|
1024
|
+
logger.keyValue("Librarian", athenaConfig.models.librarian);
|
|
1025
|
+
if (athenaConfig.models.frontend) {
|
|
1026
|
+
logger.keyValue("Frontend", athenaConfig.models.frontend);
|
|
1027
|
+
}
|
|
1028
|
+
if (athenaConfig.models.documentWriter) {
|
|
1029
|
+
logger.keyValue("Doc Writer", athenaConfig.models.documentWriter);
|
|
1030
|
+
}
|
|
1031
|
+
if (athenaConfig.models.multimodalLooker) {
|
|
1032
|
+
logger.keyValue("Multimodal", athenaConfig.models.multimodalLooker);
|
|
1033
|
+
}
|
|
1034
|
+
logger.section("BMAD Settings");
|
|
1035
|
+
logger.keyValue("Default Track", athenaConfig.bmad.defaultTrack);
|
|
1036
|
+
logger.keyValue("Auto Status Update", athenaConfig.bmad.autoStatusUpdate ? "yes" : "no");
|
|
1037
|
+
logger.keyValue("Parallel Story Limit", athenaConfig.bmad.parallelStoryLimit.toString());
|
|
1038
|
+
logger.section("Features");
|
|
1039
|
+
const features = athenaConfig.features;
|
|
1040
|
+
const featureList = [
|
|
1041
|
+
{ name: "BMAD Bridge", enabled: features.bmadBridge },
|
|
1042
|
+
{ name: "Auto Status", enabled: features.autoStatus },
|
|
1043
|
+
{ name: "Parallel Exec", enabled: features.parallelExecution },
|
|
1044
|
+
{ name: "Notifications", enabled: features.notifications },
|
|
1045
|
+
{ name: "Context Monitor", enabled: features.contextMonitor },
|
|
1046
|
+
{ name: "Comment Checker", enabled: features.commentChecker },
|
|
1047
|
+
{ name: "LSP Tools", enabled: features.lspTools }
|
|
1048
|
+
];
|
|
1049
|
+
for (const feature of featureList) {
|
|
1050
|
+
const status = feature.enabled ? chalk3.green("on") : chalk3.gray("off");
|
|
1051
|
+
logger.keyValue(feature.name, status);
|
|
1052
|
+
}
|
|
1053
|
+
logger.section("MCP Servers");
|
|
1054
|
+
const mcps = athenaConfig.mcps;
|
|
1055
|
+
logger.keyValue("context7", mcps.context7 ? chalk3.green("on") : chalk3.gray("off"));
|
|
1056
|
+
logger.keyValue("exa", mcps.exa ? chalk3.green("on") : chalk3.gray("off"));
|
|
1057
|
+
logger.keyValue("grep_app", mcps.grepApp ? chalk3.green("on") : chalk3.gray("off"));
|
|
1058
|
+
logger.section("Installed Plugins");
|
|
1059
|
+
const plugins = await getInstalledPlugins();
|
|
1060
|
+
if (Object.keys(plugins).length === 0) {
|
|
1061
|
+
logger.info("No plugins installed in OpenCode config directory");
|
|
1062
|
+
} else {
|
|
1063
|
+
for (const [name, version] of Object.entries(plugins)) {
|
|
1064
|
+
logger.keyValue(name, version);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
logger.section("Configuration Paths");
|
|
1068
|
+
logger.keyValue("Config Dir", CONFIG_PATHS.globalConfigDir);
|
|
1069
|
+
logger.keyValue("Athena Config", CONFIG_PATHS.globalAthenaConfig);
|
|
1070
|
+
logger.keyValue("Commands Dir", CONFIG_PATHS.commandsDir);
|
|
1071
|
+
console.log();
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/cli/commands/install.ts
|
|
1075
|
+
init_esm_shims();
|
|
1076
|
+
|
|
1077
|
+
// src/cli/generators/config-generator.ts
|
|
1078
|
+
init_esm_shims();
|
|
1079
|
+
|
|
1080
|
+
// src/cli/generators/athena-config.ts
|
|
1081
|
+
init_esm_shims();
|
|
1082
|
+
|
|
1083
|
+
// src/cli/questions/features.ts
|
|
1084
|
+
init_esm_shims();
|
|
1085
|
+
var AVAILABLE_FEATURES = [
|
|
1086
|
+
{
|
|
1087
|
+
name: "BMAD Bridge Commands (/athena-dev, /athena-review, etc.)",
|
|
1088
|
+
value: "bmad-bridge"
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
name: "Auto Sprint Status Updates - Update sprint-status.yaml automatically",
|
|
1092
|
+
value: "auto-status"
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
name: "Parallel Story Execution - Work on multiple stories simultaneously",
|
|
1096
|
+
value: "parallel"
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
name: "Session Notifications - Desktop notifications for completions",
|
|
1100
|
+
value: "notifications"
|
|
1101
|
+
},
|
|
1102
|
+
{
|
|
1103
|
+
name: "Context Window Monitoring - Track context usage",
|
|
1104
|
+
value: "context-monitor"
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: "Comment Checker - Ensure code is not over-commented",
|
|
1108
|
+
value: "comment-checker"
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
name: "LSP Refactoring Tools - Enable lsp_rename, lsp_find_references, etc.",
|
|
1112
|
+
value: "lsp-tools"
|
|
1113
|
+
}
|
|
1114
|
+
];
|
|
1115
|
+
var ALL_FEATURE_VALUES = AVAILABLE_FEATURES.map((f) => f.value);
|
|
1116
|
+
var AVAILABLE_MCPS = [
|
|
1117
|
+
{
|
|
1118
|
+
name: "context7 - Documentation lookup and context retrieval",
|
|
1119
|
+
value: "context7"
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
name: "websearch_exa - Web search capabilities",
|
|
1123
|
+
value: "exa"
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
name: "grep_app - GitHub code search",
|
|
1127
|
+
value: "grep_app"
|
|
1128
|
+
}
|
|
1129
|
+
];
|
|
1130
|
+
var ALL_MCP_VALUES = AVAILABLE_MCPS.map((m) => m.value);
|
|
1131
|
+
function createFeatureChoices(defaults) {
|
|
1132
|
+
const enabledSet = new Set(defaults ?? ALL_FEATURE_VALUES);
|
|
1133
|
+
return AVAILABLE_FEATURES.map((feature) => ({
|
|
1134
|
+
...feature,
|
|
1135
|
+
checked: enabledSet.has(feature.value)
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1138
|
+
function createMcpChoices(defaults) {
|
|
1139
|
+
const enabledSet = new Set(defaults ?? ALL_MCP_VALUES);
|
|
1140
|
+
return AVAILABLE_MCPS.map((mcp) => ({
|
|
1141
|
+
...mcp,
|
|
1142
|
+
checked: enabledSet.has(mcp.value)
|
|
1143
|
+
}));
|
|
1144
|
+
}
|
|
1145
|
+
async function gatherFeatures(defaults) {
|
|
1146
|
+
const enabledFeatures = await checkbox({
|
|
1147
|
+
message: "Select features to enable:",
|
|
1148
|
+
choices: createFeatureChoices(defaults?.enabledFeatures)
|
|
1149
|
+
});
|
|
1150
|
+
const mcps = await checkbox({
|
|
1151
|
+
message: "Select MCP servers to enable:",
|
|
1152
|
+
choices: createMcpChoices(defaults?.mcps)
|
|
1153
|
+
});
|
|
1154
|
+
return {
|
|
1155
|
+
enabledFeatures,
|
|
1156
|
+
mcps
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
function featuresToFlags(enabledFeatures) {
|
|
1160
|
+
return {
|
|
1161
|
+
bmadBridge: enabledFeatures.includes("bmad-bridge"),
|
|
1162
|
+
autoStatus: enabledFeatures.includes("auto-status"),
|
|
1163
|
+
parallelExecution: enabledFeatures.includes("parallel"),
|
|
1164
|
+
notifications: enabledFeatures.includes("notifications"),
|
|
1165
|
+
contextMonitor: enabledFeatures.includes("context-monitor"),
|
|
1166
|
+
commentChecker: enabledFeatures.includes("comment-checker"),
|
|
1167
|
+
lspTools: enabledFeatures.includes("lsp-tools")
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function mcpsToFlags(mcps) {
|
|
1171
|
+
return {
|
|
1172
|
+
context7: mcps.includes("context7"),
|
|
1173
|
+
exa: mcps.includes("exa"),
|
|
1174
|
+
grepApp: mcps.includes("grep_app")
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/cli/generators/athena-config.ts
|
|
1179
|
+
function generateAthenaConfig(answers) {
|
|
1180
|
+
const { subscriptions, models, methodology, features, advanced } = answers;
|
|
1181
|
+
return {
|
|
1182
|
+
$schema: "https://raw.githubusercontent.com/ZebulonRouseFrantzich/opencode-athena/main/config/schemas/athena.schema.json",
|
|
1183
|
+
version: VERSION,
|
|
1184
|
+
subscriptions: {
|
|
1185
|
+
claude: {
|
|
1186
|
+
enabled: subscriptions.hasClaude,
|
|
1187
|
+
tier: subscriptions.claudeTier
|
|
1188
|
+
},
|
|
1189
|
+
openai: {
|
|
1190
|
+
enabled: subscriptions.hasOpenAI
|
|
1191
|
+
},
|
|
1192
|
+
google: {
|
|
1193
|
+
enabled: subscriptions.hasGoogle,
|
|
1194
|
+
authMethod: subscriptions.googleAuth
|
|
1195
|
+
}
|
|
1196
|
+
},
|
|
1197
|
+
models: {
|
|
1198
|
+
sisyphus: models.sisyphus,
|
|
1199
|
+
oracle: models.oracle,
|
|
1200
|
+
librarian: models.librarian,
|
|
1201
|
+
frontend: models.frontend,
|
|
1202
|
+
documentWriter: models.documentWriter,
|
|
1203
|
+
multimodalLooker: models.multimodalLooker
|
|
1204
|
+
},
|
|
1205
|
+
bmad: {
|
|
1206
|
+
defaultTrack: methodology.defaultTrack,
|
|
1207
|
+
autoStatusUpdate: methodology.autoStatusUpdate,
|
|
1208
|
+
parallelStoryLimit: advanced.parallelStoryLimit ?? 3
|
|
1209
|
+
},
|
|
1210
|
+
features: featuresToFlags(features.enabledFeatures),
|
|
1211
|
+
mcps: mcpsToFlags(features.mcps)
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// src/cli/generators/omo-config.ts
|
|
1216
|
+
init_esm_shims();
|
|
1217
|
+
function generateOmoConfig(answers) {
|
|
1218
|
+
const { subscriptions, models, features, advanced } = answers;
|
|
1219
|
+
const config = {
|
|
1220
|
+
$schema: "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json"
|
|
1221
|
+
};
|
|
1222
|
+
if (subscriptions.hasGoogle && subscriptions.googleAuth === "antigravity") {
|
|
1223
|
+
config.google_auth = false;
|
|
1224
|
+
}
|
|
1225
|
+
config.agents = {
|
|
1226
|
+
// Main orchestrator
|
|
1227
|
+
Sisyphus: {
|
|
1228
|
+
model: models.sisyphus
|
|
1229
|
+
},
|
|
1230
|
+
// Debugging/reasoning agent
|
|
1231
|
+
oracle: {
|
|
1232
|
+
model: models.oracle
|
|
1233
|
+
},
|
|
1234
|
+
// Research agent
|
|
1235
|
+
librarian: {
|
|
1236
|
+
model: models.librarian
|
|
1237
|
+
},
|
|
1238
|
+
// UI/UX agent
|
|
1239
|
+
"frontend-ui-ux-engineer": {
|
|
1240
|
+
model: models.frontend || models.sisyphus
|
|
1241
|
+
},
|
|
1242
|
+
// Documentation agent
|
|
1243
|
+
"document-writer": {
|
|
1244
|
+
model: models.documentWriter || models.librarian
|
|
1245
|
+
},
|
|
1246
|
+
// Image analysis agent
|
|
1247
|
+
"multimodal-looker": {
|
|
1248
|
+
model: models.multimodalLooker || models.librarian
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
const disabledHooks = [];
|
|
1252
|
+
if (!features.enabledFeatures.includes("context-monitor")) {
|
|
1253
|
+
disabledHooks.push("context-window-monitor");
|
|
1254
|
+
}
|
|
1255
|
+
if (!features.enabledFeatures.includes("comment-checker")) {
|
|
1256
|
+
disabledHooks.push("comment-checker");
|
|
1257
|
+
}
|
|
1258
|
+
if (!features.enabledFeatures.includes("notifications")) {
|
|
1259
|
+
disabledHooks.push("session-notification", "background-notification");
|
|
1260
|
+
}
|
|
1261
|
+
if (disabledHooks.length > 0) {
|
|
1262
|
+
config.disabled_hooks = disabledHooks;
|
|
1263
|
+
}
|
|
1264
|
+
const allMcps = ["context7", "websearch_exa", "grep_app"];
|
|
1265
|
+
const enabledMcpIds = features.mcps.map((mcp) => {
|
|
1266
|
+
if (mcp === "exa") return "websearch_exa";
|
|
1267
|
+
return mcp;
|
|
1268
|
+
});
|
|
1269
|
+
const disabledMcps = allMcps.filter((mcp) => !enabledMcpIds.includes(mcp));
|
|
1270
|
+
if (disabledMcps.length > 0) {
|
|
1271
|
+
config.disabled_mcps = disabledMcps;
|
|
1272
|
+
}
|
|
1273
|
+
if (advanced.experimental && advanced.experimental.length > 0) {
|
|
1274
|
+
config.experimental = {};
|
|
1275
|
+
if (advanced.experimental.includes("aggressive-truncation")) {
|
|
1276
|
+
config.experimental.aggressive_truncation = true;
|
|
1277
|
+
}
|
|
1278
|
+
if (advanced.experimental.includes("auto-resume")) {
|
|
1279
|
+
config.experimental.auto_resume = true;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return config;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// src/cli/generators/opencode-config.ts
|
|
1286
|
+
init_esm_shims();
|
|
1287
|
+
async function generateOpencodeConfig(answers, configDir) {
|
|
1288
|
+
const configPath = join(configDir, "opencode.json");
|
|
1289
|
+
let existingConfig = {};
|
|
1290
|
+
if (existsSync(configPath)) {
|
|
1291
|
+
try {
|
|
1292
|
+
const content = readFileSync(configPath, "utf-8");
|
|
1293
|
+
existingConfig = JSON.parse(content);
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const plugins = ["opencode-athena/plugin", "oh-my-opencode"];
|
|
1298
|
+
if (answers.subscriptions.hasGoogle && answers.subscriptions.googleAuth === "antigravity") {
|
|
1299
|
+
plugins.push("opencode-antigravity-auth");
|
|
1300
|
+
}
|
|
1301
|
+
if (answers.subscriptions.hasOpenAI) {
|
|
1302
|
+
plugins.push("opencode-openai-codex-auth");
|
|
1303
|
+
}
|
|
1304
|
+
const existingPlugins = existingConfig.plugin || [];
|
|
1305
|
+
const mergedPlugins = [.../* @__PURE__ */ new Set([...existingPlugins, ...plugins])];
|
|
1306
|
+
const config = {
|
|
1307
|
+
...existingConfig,
|
|
1308
|
+
$schema: existingConfig.$schema || "https://opencode.ai/config.json",
|
|
1309
|
+
plugin: mergedPlugins
|
|
1310
|
+
};
|
|
1311
|
+
if (answers.subscriptions.hasClaude) {
|
|
1312
|
+
const existingProvider = existingConfig.provider || {};
|
|
1313
|
+
const existingAnthropic = existingProvider.anthropic || {};
|
|
1314
|
+
const existingAnthropicModels = existingAnthropic.models || {};
|
|
1315
|
+
config.provider = {
|
|
1316
|
+
...existingProvider,
|
|
1317
|
+
anthropic: {
|
|
1318
|
+
...existingAnthropic,
|
|
1319
|
+
models: {
|
|
1320
|
+
...existingAnthropicModels,
|
|
1321
|
+
// Add thinking model variants
|
|
1322
|
+
"claude-opus-4-5-thinking": {
|
|
1323
|
+
id: "claude-opus-4-5",
|
|
1324
|
+
options: {
|
|
1325
|
+
thinking: {
|
|
1326
|
+
type: "enabled",
|
|
1327
|
+
budgetTokens: 32e3
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
"claude-sonnet-4-5-thinking": {
|
|
1332
|
+
id: "claude-sonnet-4-5",
|
|
1333
|
+
options: {
|
|
1334
|
+
thinking: {
|
|
1335
|
+
type: "enabled",
|
|
1336
|
+
budgetTokens: 1e4
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
if (answers.subscriptions.hasOpenAI) {
|
|
1345
|
+
const existingProvider = config.provider || {};
|
|
1346
|
+
const existingOpenAI = existingProvider.openai || {};
|
|
1347
|
+
const existingOpenAIModels = existingOpenAI.models || {};
|
|
1348
|
+
config.provider = {
|
|
1349
|
+
...existingProvider,
|
|
1350
|
+
openai: {
|
|
1351
|
+
...existingOpenAI,
|
|
1352
|
+
models: {
|
|
1353
|
+
...existingOpenAIModels,
|
|
1354
|
+
// Add reasoning effort configurations
|
|
1355
|
+
"gpt-5.1-high": {
|
|
1356
|
+
id: "gpt-5.1",
|
|
1357
|
+
options: {
|
|
1358
|
+
reasoningEffort: "high",
|
|
1359
|
+
reasoningSummary: "auto"
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
return config;
|
|
1367
|
+
}
|
|
1368
|
+
function getRequiredPlugins(answers) {
|
|
1369
|
+
const plugins = ["opencode-athena", "oh-my-opencode"];
|
|
1370
|
+
if (answers.subscriptions.hasGoogle && answers.subscriptions.googleAuth === "antigravity") {
|
|
1371
|
+
plugins.push("opencode-antigravity-auth");
|
|
1372
|
+
}
|
|
1373
|
+
if (answers.subscriptions.hasOpenAI) {
|
|
1374
|
+
plugins.push("opencode-openai-codex-auth");
|
|
1375
|
+
}
|
|
1376
|
+
return plugins;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/cli/generators/config-generator.ts
|
|
1380
|
+
var ConfigGenerator = class {
|
|
1381
|
+
answers;
|
|
1382
|
+
configDir;
|
|
1383
|
+
constructor(answers) {
|
|
1384
|
+
this.answers = answers;
|
|
1385
|
+
this.configDir = answers.installLocation === "local" ? join(process.cwd(), ".opencode") : CONFIG_PATHS.globalConfigDir;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Get the configuration directory path
|
|
1389
|
+
*/
|
|
1390
|
+
getConfigDir() {
|
|
1391
|
+
return this.configDir;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Generate all configuration files
|
|
1395
|
+
*/
|
|
1396
|
+
async generate() {
|
|
1397
|
+
const files = [];
|
|
1398
|
+
const opencodeConfig = await generateOpencodeConfig(this.answers, this.configDir);
|
|
1399
|
+
files.push({
|
|
1400
|
+
path: join(this.configDir, "opencode.json"),
|
|
1401
|
+
content: JSON.stringify(opencodeConfig, null, 2),
|
|
1402
|
+
exists: existsSync(join(this.configDir, "opencode.json"))
|
|
1403
|
+
});
|
|
1404
|
+
const omoConfig = generateOmoConfig(this.answers);
|
|
1405
|
+
files.push({
|
|
1406
|
+
path: join(this.configDir, "oh-my-opencode.json"),
|
|
1407
|
+
content: JSON.stringify(omoConfig, null, 2),
|
|
1408
|
+
exists: existsSync(join(this.configDir, "oh-my-opencode.json"))
|
|
1409
|
+
});
|
|
1410
|
+
const athenaConfig = generateAthenaConfig(this.answers);
|
|
1411
|
+
files.push({
|
|
1412
|
+
path: join(this.configDir, "athena.json"),
|
|
1413
|
+
content: JSON.stringify(athenaConfig, null, 2),
|
|
1414
|
+
exists: existsSync(join(this.configDir, "athena.json"))
|
|
1415
|
+
});
|
|
1416
|
+
return files;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Get the list of npm packages to install
|
|
1420
|
+
*/
|
|
1421
|
+
getRequiredPackages() {
|
|
1422
|
+
return getRequiredPlugins(this.answers);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
// src/cli/questions/index.ts
|
|
1427
|
+
init_esm_shims();
|
|
1428
|
+
|
|
1429
|
+
// src/cli/questions/subscriptions.ts
|
|
1430
|
+
init_esm_shims();
|
|
1431
|
+
async function gatherSubscriptions() {
|
|
1432
|
+
const hasClaude = await confirm({
|
|
1433
|
+
message: "Do you have a Claude Pro/Max subscription?",
|
|
1434
|
+
default: true
|
|
1435
|
+
});
|
|
1436
|
+
let claudeTier = "none";
|
|
1437
|
+
if (hasClaude) {
|
|
1438
|
+
claudeTier = await select({
|
|
1439
|
+
message: "Which Claude tier?",
|
|
1440
|
+
choices: [
|
|
1441
|
+
{ name: "Max 5x - 5x more usage than Pro", value: "max5x" },
|
|
1442
|
+
{ name: "Max 20x - 20x more usage than Pro", value: "max20x" },
|
|
1443
|
+
{ name: "Pro - Standard Pro subscription", value: "pro" }
|
|
1444
|
+
]
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
const hasOpenAI = await confirm({
|
|
1448
|
+
message: "Do you have a ChatGPT Plus/Pro subscription?",
|
|
1449
|
+
default: true
|
|
1450
|
+
});
|
|
1451
|
+
const hasGoogle = await confirm({
|
|
1452
|
+
message: "Will you use Google/Gemini models?",
|
|
1453
|
+
default: true
|
|
1454
|
+
});
|
|
1455
|
+
let googleAuth = "none";
|
|
1456
|
+
if (hasGoogle) {
|
|
1457
|
+
googleAuth = await select({
|
|
1458
|
+
message: "Google authentication method?",
|
|
1459
|
+
choices: [
|
|
1460
|
+
{
|
|
1461
|
+
name: "Google Workspace (Antigravity OAuth) - Recommended for Workspace users",
|
|
1462
|
+
value: "antigravity"
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
name: "Personal Google Account",
|
|
1466
|
+
value: "personal"
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
name: "API Key - Direct API access",
|
|
1470
|
+
value: "api"
|
|
1471
|
+
}
|
|
1472
|
+
]
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
return {
|
|
1476
|
+
hasClaude,
|
|
1477
|
+
claudeTier,
|
|
1478
|
+
hasOpenAI,
|
|
1479
|
+
hasGoogle,
|
|
1480
|
+
googleAuth
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/cli/questions/index.ts
|
|
1485
|
+
init_models();
|
|
1486
|
+
|
|
1487
|
+
// src/cli/questions/methodology.ts
|
|
1488
|
+
init_esm_shims();
|
|
1489
|
+
async function gatherMethodology(defaults) {
|
|
1490
|
+
const defaultTrack = await select({
|
|
1491
|
+
message: "Default BMAD track for new projects?",
|
|
1492
|
+
choices: [
|
|
1493
|
+
{
|
|
1494
|
+
name: "Quick Flow - Fast implementation for small features and bug fixes",
|
|
1495
|
+
value: "quick-flow"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
name: "BMad Method - Full planning for products and platforms (recommended)",
|
|
1499
|
+
value: "bmad-method"
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
name: "Enterprise - Extended planning with compliance and scale considerations",
|
|
1503
|
+
value: "enterprise"
|
|
1504
|
+
}
|
|
1505
|
+
],
|
|
1506
|
+
default: defaults?.defaultTrack ?? "bmad-method"
|
|
1507
|
+
});
|
|
1508
|
+
const autoStatusUpdate = await confirm({
|
|
1509
|
+
message: "Automatically update sprint-status.yaml when stories complete?",
|
|
1510
|
+
default: defaults?.autoStatusUpdate ?? true
|
|
1511
|
+
});
|
|
1512
|
+
return {
|
|
1513
|
+
defaultTrack,
|
|
1514
|
+
autoStatusUpdate
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// src/cli/questions/advanced.ts
|
|
1519
|
+
init_esm_shims();
|
|
1520
|
+
async function gatherAdvanced(defaults) {
|
|
1521
|
+
const parallelStoryLimit = await select({
|
|
1522
|
+
message: "Maximum parallel stories?",
|
|
1523
|
+
choices: [
|
|
1524
|
+
{ name: "1 (sequential - one story at a time)", value: 1 },
|
|
1525
|
+
{ name: "2", value: 2 },
|
|
1526
|
+
{ name: "3 (recommended)", value: 3 },
|
|
1527
|
+
{ name: "5", value: 5 },
|
|
1528
|
+
{ name: "Unlimited (0)", value: 0 }
|
|
1529
|
+
],
|
|
1530
|
+
default: defaults?.parallelStoryLimit ?? 3
|
|
1531
|
+
});
|
|
1532
|
+
const experimentalDefaults = new Set(defaults?.experimental ?? []);
|
|
1533
|
+
const experimental = await checkbox({
|
|
1534
|
+
message: "Enable experimental features?",
|
|
1535
|
+
choices: [
|
|
1536
|
+
{
|
|
1537
|
+
name: "Aggressive Truncation - More aggressive context management",
|
|
1538
|
+
value: "aggressive-truncation",
|
|
1539
|
+
checked: experimentalDefaults.has("aggressive-truncation")
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
name: "Auto Resume - Automatically resume interrupted sessions",
|
|
1543
|
+
value: "auto-resume",
|
|
1544
|
+
checked: experimentalDefaults.has("auto-resume")
|
|
1545
|
+
}
|
|
1546
|
+
]
|
|
1547
|
+
});
|
|
1548
|
+
return {
|
|
1549
|
+
parallelStoryLimit,
|
|
1550
|
+
experimental
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/cli/utils/preset-loader.ts
|
|
1555
|
+
init_esm_shims();
|
|
1556
|
+
var PRESET_NAMES = ["minimal", "standard", "enterprise", "solo-quick"];
|
|
1557
|
+
function getPresetsDir() {
|
|
1558
|
+
const currentFileDir = dirname(fileURLToPath(import.meta.url));
|
|
1559
|
+
const bundledRoot = join(currentFileDir, "..", "..");
|
|
1560
|
+
const bundledPresetsDir = join(bundledRoot, "config", "presets");
|
|
1561
|
+
if (existsSync(bundledPresetsDir)) {
|
|
1562
|
+
return bundledPresetsDir;
|
|
1563
|
+
}
|
|
1564
|
+
const devRoot = join(currentFileDir, "..", "..", "..");
|
|
1565
|
+
const devPresetsDir = join(devRoot, "config", "presets");
|
|
1566
|
+
if (existsSync(devPresetsDir)) {
|
|
1567
|
+
return devPresetsDir;
|
|
1568
|
+
}
|
|
1569
|
+
const nodeModulesPath = currentFileDir.split("node_modules")[0];
|
|
1570
|
+
if (nodeModulesPath !== currentFileDir) {
|
|
1571
|
+
const packagePresetsDir = join(
|
|
1572
|
+
nodeModulesPath,
|
|
1573
|
+
"node_modules",
|
|
1574
|
+
"opencode-athena",
|
|
1575
|
+
"config",
|
|
1576
|
+
"presets"
|
|
1577
|
+
);
|
|
1578
|
+
if (existsSync(packagePresetsDir)) {
|
|
1579
|
+
return packagePresetsDir;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
throw new Error(
|
|
1583
|
+
`Could not find presets directory. Searched:
|
|
1584
|
+
- ${bundledPresetsDir}
|
|
1585
|
+
- ${devPresetsDir}`
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
function isValidPresetName(name) {
|
|
1589
|
+
return PRESET_NAMES.includes(name);
|
|
1590
|
+
}
|
|
1591
|
+
function loadPreset(name) {
|
|
1592
|
+
if (!isValidPresetName(name)) {
|
|
1593
|
+
const validNames = PRESET_NAMES.join(", ");
|
|
1594
|
+
throw new Error(`Invalid preset name: "${name}". Valid presets are: ${validNames}`);
|
|
1595
|
+
}
|
|
1596
|
+
const presetsDir = getPresetsDir();
|
|
1597
|
+
const presetPath = join(presetsDir, `${name}.json`);
|
|
1598
|
+
if (!existsSync(presetPath)) {
|
|
1599
|
+
throw new Error(
|
|
1600
|
+
`Preset file not found: ${presetPath}
|
|
1601
|
+
This may indicate a corrupted installation. Try reinstalling opencode-athena.`
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
try {
|
|
1605
|
+
const content = readFileSync(presetPath, "utf-8");
|
|
1606
|
+
const preset = JSON.parse(content);
|
|
1607
|
+
if (!preset.version || !preset.models || !preset.bmad || !preset.features) {
|
|
1608
|
+
throw new Error(`Preset "${name}" is missing required fields`);
|
|
1609
|
+
}
|
|
1610
|
+
return preset;
|
|
1611
|
+
} catch (error) {
|
|
1612
|
+
if (error instanceof SyntaxError) {
|
|
1613
|
+
throw new Error(`Invalid JSON in preset file: ${presetPath}
|
|
1614
|
+
Error: ${error.message}`);
|
|
1615
|
+
}
|
|
1616
|
+
throw error;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function listPresets() {
|
|
1620
|
+
const presetsDir = getPresetsDir();
|
|
1621
|
+
const summaries = [];
|
|
1622
|
+
for (const name of PRESET_NAMES) {
|
|
1623
|
+
const presetPath = join(presetsDir, `${name}.json`);
|
|
1624
|
+
if (existsSync(presetPath)) {
|
|
1625
|
+
try {
|
|
1626
|
+
const content = readFileSync(presetPath, "utf-8");
|
|
1627
|
+
const preset = JSON.parse(content);
|
|
1628
|
+
summaries.push({
|
|
1629
|
+
name,
|
|
1630
|
+
description: preset.description || `${name} preset`,
|
|
1631
|
+
path: presetPath
|
|
1632
|
+
});
|
|
1633
|
+
} catch {
|
|
1634
|
+
summaries.push({
|
|
1635
|
+
name,
|
|
1636
|
+
description: `${name} preset (unable to read)`,
|
|
1637
|
+
path: presetPath
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
return summaries;
|
|
1643
|
+
}
|
|
1644
|
+
function presetToDefaults(preset) {
|
|
1645
|
+
return {
|
|
1646
|
+
models: {
|
|
1647
|
+
sisyphus: preset.models.sisyphus,
|
|
1648
|
+
oracle: preset.models.oracle,
|
|
1649
|
+
librarian: preset.models.librarian,
|
|
1650
|
+
frontend: preset.models.frontend,
|
|
1651
|
+
documentWriter: preset.models.documentWriter,
|
|
1652
|
+
multimodalLooker: preset.models.multimodalLooker
|
|
1653
|
+
},
|
|
1654
|
+
methodology: {
|
|
1655
|
+
defaultTrack: preset.bmad.defaultTrack,
|
|
1656
|
+
autoStatusUpdate: preset.bmad.autoStatusUpdate
|
|
1657
|
+
},
|
|
1658
|
+
features: {
|
|
1659
|
+
enabledFeatures: flagsToFeatures(preset.features),
|
|
1660
|
+
mcps: flagsToMcps(preset.mcps)
|
|
1661
|
+
},
|
|
1662
|
+
advanced: {
|
|
1663
|
+
parallelStoryLimit: preset.bmad.parallelStoryLimit,
|
|
1664
|
+
experimental: []
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function flagsToFeatures(flags) {
|
|
1669
|
+
const features = [];
|
|
1670
|
+
if (flags.bmadBridge) features.push("bmad-bridge");
|
|
1671
|
+
if (flags.autoStatus) features.push("auto-status");
|
|
1672
|
+
if (flags.parallelExecution) features.push("parallel");
|
|
1673
|
+
if (flags.notifications) features.push("notifications");
|
|
1674
|
+
if (flags.contextMonitor) features.push("context-monitor");
|
|
1675
|
+
if (flags.commentChecker) features.push("comment-checker");
|
|
1676
|
+
if (flags.lspTools) features.push("lsp-tools");
|
|
1677
|
+
return features;
|
|
1678
|
+
}
|
|
1679
|
+
function flagsToMcps(mcps) {
|
|
1680
|
+
const result = [];
|
|
1681
|
+
if (mcps.context7) result.push("context7");
|
|
1682
|
+
if (mcps.exa) result.push("exa");
|
|
1683
|
+
if (mcps.grepApp) result.push("grep_app");
|
|
1684
|
+
return result;
|
|
1685
|
+
}
|
|
1686
|
+
function formatPresetSummary(preset, name) {
|
|
1687
|
+
const lines = [];
|
|
1688
|
+
lines.push(`Preset: ${name}`);
|
|
1689
|
+
lines.push(`Description: ${preset.description}`);
|
|
1690
|
+
lines.push("");
|
|
1691
|
+
lines.push("Models:");
|
|
1692
|
+
lines.push(` Sisyphus: ${preset.models.sisyphus}`);
|
|
1693
|
+
lines.push(` Oracle: ${preset.models.oracle}`);
|
|
1694
|
+
lines.push(` Librarian: ${preset.models.librarian}`);
|
|
1695
|
+
lines.push("");
|
|
1696
|
+
lines.push("BMAD Settings:");
|
|
1697
|
+
lines.push(` Default Track: ${preset.bmad.defaultTrack}`);
|
|
1698
|
+
lines.push(` Auto Status Update: ${preset.bmad.autoStatusUpdate ? "Yes" : "No"}`);
|
|
1699
|
+
lines.push(` Parallel Story Limit: ${preset.bmad.parallelStoryLimit}`);
|
|
1700
|
+
lines.push("");
|
|
1701
|
+
lines.push("Features:");
|
|
1702
|
+
const enabledFeatures = flagsToFeatures(preset.features);
|
|
1703
|
+
if (enabledFeatures.length > 0) {
|
|
1704
|
+
lines.push(` Enabled: ${enabledFeatures.join(", ")}`);
|
|
1705
|
+
} else {
|
|
1706
|
+
lines.push(" Enabled: none");
|
|
1707
|
+
}
|
|
1708
|
+
lines.push("");
|
|
1709
|
+
lines.push("MCP Servers:");
|
|
1710
|
+
const enabledMcps = flagsToMcps(preset.mcps);
|
|
1711
|
+
if (enabledMcps.length > 0) {
|
|
1712
|
+
lines.push(` Enabled: ${enabledMcps.join(", ")}`);
|
|
1713
|
+
} else {
|
|
1714
|
+
lines.push(" Enabled: none");
|
|
1715
|
+
}
|
|
1716
|
+
return lines.join("\n");
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// src/cli/commands/install.ts
|
|
1720
|
+
async function install(options) {
|
|
1721
|
+
logger.banner();
|
|
1722
|
+
const spinner = ora3("Checking prerequisites...").start();
|
|
1723
|
+
const prereqs = await checkPrerequisites();
|
|
1724
|
+
if (!prereqs.node.installed) {
|
|
1725
|
+
spinner.fail("Node.js not found");
|
|
1726
|
+
logger.error("Please install Node.js 20+ first: https://nodejs.org/");
|
|
1727
|
+
process.exit(1);
|
|
1728
|
+
}
|
|
1729
|
+
if (!prereqs.node.compatible) {
|
|
1730
|
+
spinner.warn(`Node.js ${prereqs.node.version} detected. Recommended: 20+`);
|
|
1731
|
+
}
|
|
1732
|
+
if (!prereqs.opencode.installed) {
|
|
1733
|
+
spinner.fail("OpenCode not found");
|
|
1734
|
+
logger.error("Please install OpenCode first: https://opencode.ai/docs");
|
|
1735
|
+
process.exit(1);
|
|
1736
|
+
}
|
|
1737
|
+
if (!prereqs.opencode.compatible) {
|
|
1738
|
+
spinner.warn(`OpenCode ${prereqs.opencode.version} detected. Recommended: 1.0.132+`);
|
|
1739
|
+
} else {
|
|
1740
|
+
spinner.succeed(`OpenCode ${prereqs.opencode.version || ""} detected`);
|
|
1741
|
+
}
|
|
1742
|
+
if (prereqs.athena.installed && !options.yes) {
|
|
1743
|
+
const overwrite = await confirm({
|
|
1744
|
+
message: `OpenCode Athena ${prereqs.athena.version || ""} is already installed. Overwrite configuration?`,
|
|
1745
|
+
default: false
|
|
1746
|
+
});
|
|
1747
|
+
if (!overwrite) {
|
|
1748
|
+
logger.info("Installation cancelled.");
|
|
1749
|
+
process.exit(0);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
let preset = null;
|
|
1753
|
+
let presetDefaults = null;
|
|
1754
|
+
let presetName = null;
|
|
1755
|
+
if (options.preset && options.preset !== "none") {
|
|
1756
|
+
if (!isValidPresetName(options.preset)) {
|
|
1757
|
+
logger.error(`Invalid preset: "${options.preset}"`);
|
|
1758
|
+
logger.info(`Valid presets: ${PRESET_NAMES.join(", ")}`);
|
|
1759
|
+
process.exit(1);
|
|
1760
|
+
}
|
|
1761
|
+
try {
|
|
1762
|
+
preset = loadPreset(options.preset);
|
|
1763
|
+
presetDefaults = presetToDefaults(preset);
|
|
1764
|
+
presetName = options.preset;
|
|
1765
|
+
logger.success(`Loaded preset: ${options.preset}`);
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
1768
|
+
process.exit(1);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (!preset && !options.yes) {
|
|
1772
|
+
const selectedPreset = await askForPreset();
|
|
1773
|
+
if (selectedPreset) {
|
|
1774
|
+
preset = selectedPreset.preset;
|
|
1775
|
+
presetDefaults = presetToDefaults(preset);
|
|
1776
|
+
presetName = selectedPreset.name;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
logger.section("LLM Subscriptions");
|
|
1780
|
+
const subscriptions = await gatherSubscriptions();
|
|
1781
|
+
let shouldCustomize = true;
|
|
1782
|
+
if (preset && presetDefaults && presetName) {
|
|
1783
|
+
const modelWarnings = validatePresetModels(presetDefaults.models, subscriptions);
|
|
1784
|
+
if (modelWarnings.length > 0) {
|
|
1785
|
+
console.log(chalk3.yellow("\nPreset model compatibility warnings:"));
|
|
1786
|
+
for (const warning of modelWarnings) {
|
|
1787
|
+
console.log(chalk3.yellow(` - ${warning}`));
|
|
1788
|
+
}
|
|
1789
|
+
console.log();
|
|
1790
|
+
}
|
|
1791
|
+
console.log(chalk3.bold("\nPreset Configuration:\n"));
|
|
1792
|
+
console.log(chalk3.gray(formatPresetSummary(preset, presetName)));
|
|
1793
|
+
console.log();
|
|
1794
|
+
if (options.yes) {
|
|
1795
|
+
shouldCustomize = false;
|
|
1796
|
+
logger.info("Using preset defaults (--yes flag)");
|
|
1797
|
+
} else {
|
|
1798
|
+
shouldCustomize = await confirm({
|
|
1799
|
+
message: "Would you like to customize these settings?",
|
|
1800
|
+
default: false
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
let models;
|
|
1805
|
+
let methodology;
|
|
1806
|
+
let features;
|
|
1807
|
+
let advanced;
|
|
1808
|
+
if (!shouldCustomize && presetDefaults) {
|
|
1809
|
+
logger.section("Applying Preset Configuration");
|
|
1810
|
+
const availableModels = await Promise.resolve().then(() => (init_models(), models_exports)).then(
|
|
1811
|
+
(m) => m.getAvailableModels(subscriptions)
|
|
1812
|
+
);
|
|
1813
|
+
if (availableModels.length === 0) {
|
|
1814
|
+
logger.error(
|
|
1815
|
+
"No models available. Please enable at least one provider (Claude, OpenAI, or Google)."
|
|
1816
|
+
);
|
|
1817
|
+
process.exit(1);
|
|
1818
|
+
}
|
|
1819
|
+
models = {
|
|
1820
|
+
sisyphus: getValidModelOrFirst(presetDefaults.models.sisyphus, availableModels),
|
|
1821
|
+
oracle: getValidModelOrFirst(presetDefaults.models.oracle, availableModels),
|
|
1822
|
+
librarian: getValidModelOrFirst(presetDefaults.models.librarian, availableModels),
|
|
1823
|
+
frontend: getValidModelOrFirst(presetDefaults.models.frontend, availableModels),
|
|
1824
|
+
documentWriter: getValidModelOrFirst(presetDefaults.models.documentWriter, availableModels),
|
|
1825
|
+
multimodalLooker: getValidModelOrFirst(
|
|
1826
|
+
presetDefaults.models.multimodalLooker,
|
|
1827
|
+
availableModels
|
|
1828
|
+
)
|
|
1829
|
+
};
|
|
1830
|
+
methodology = presetDefaults.methodology;
|
|
1831
|
+
features = presetDefaults.features;
|
|
1832
|
+
advanced = presetDefaults.advanced;
|
|
1833
|
+
logger.success("Preset configuration applied");
|
|
1834
|
+
} else {
|
|
1835
|
+
logger.section("Model Selection");
|
|
1836
|
+
models = await gatherModels(subscriptions, presetDefaults?.models);
|
|
1837
|
+
logger.section("Methodology Preferences");
|
|
1838
|
+
methodology = await gatherMethodology(presetDefaults?.methodology);
|
|
1839
|
+
logger.section("Feature Selection");
|
|
1840
|
+
features = await gatherFeatures(presetDefaults?.features);
|
|
1841
|
+
if (options.advanced) {
|
|
1842
|
+
logger.section("Advanced Configuration");
|
|
1843
|
+
advanced = await gatherAdvanced(presetDefaults?.advanced);
|
|
1844
|
+
} else {
|
|
1845
|
+
advanced = presetDefaults?.advanced ?? {
|
|
1846
|
+
parallelStoryLimit: DEFAULTS.parallelStoryLimit,
|
|
1847
|
+
experimental: []
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
logger.section("Generating Configuration");
|
|
1852
|
+
const answers = {
|
|
1853
|
+
subscriptions,
|
|
1854
|
+
models,
|
|
1855
|
+
methodology,
|
|
1856
|
+
features,
|
|
1857
|
+
advanced,
|
|
1858
|
+
installLocation: options.local ? "local" : "global"
|
|
1859
|
+
};
|
|
1860
|
+
const generator = new ConfigGenerator(answers);
|
|
1861
|
+
const files = await generator.generate();
|
|
1862
|
+
console.log(chalk3.bold("Files to be created/modified:\n"));
|
|
1863
|
+
for (const file of files) {
|
|
1864
|
+
const action = file.exists ? chalk3.yellow("update") : chalk3.green("create");
|
|
1865
|
+
console.log(chalk3.gray(` [${action}] ${file.path}`));
|
|
1866
|
+
}
|
|
1867
|
+
console.log();
|
|
1868
|
+
if (!options.yes) {
|
|
1869
|
+
const proceed = await confirm({
|
|
1870
|
+
message: "Proceed with installation?",
|
|
1871
|
+
default: true
|
|
1872
|
+
});
|
|
1873
|
+
if (!proceed) {
|
|
1874
|
+
logger.info("Installation cancelled.");
|
|
1875
|
+
process.exit(0);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
const installSpinner = ora3("Installing OpenCode Athena...").start();
|
|
1879
|
+
try {
|
|
1880
|
+
const fileManager = new FileManager(generator.getConfigDir());
|
|
1881
|
+
await fileManager.writeFiles(files);
|
|
1882
|
+
installSpinner.text = "Configuration files written...";
|
|
1883
|
+
const packages = generator.getRequiredPackages();
|
|
1884
|
+
if (packages.length > 0) {
|
|
1885
|
+
installSpinner.text = `Installing dependencies: ${packages.join(", ")}...`;
|
|
1886
|
+
await fileManager.installDependencies(packages);
|
|
1887
|
+
}
|
|
1888
|
+
installSpinner.text = "Installing commands...";
|
|
1889
|
+
const copiedCommands = await fileManager.copyCommands();
|
|
1890
|
+
installSpinner.succeed("Installation complete!");
|
|
1891
|
+
if (copiedCommands.length > 0) {
|
|
1892
|
+
logger.info(`Installed ${copiedCommands.length} bridge commands`);
|
|
1893
|
+
}
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
installSpinner.fail("Installation failed");
|
|
1896
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
1897
|
+
process.exit(1);
|
|
1898
|
+
}
|
|
1899
|
+
printNextSteps(subscriptions);
|
|
1900
|
+
}
|
|
1901
|
+
async function askForPreset() {
|
|
1902
|
+
const presets = listPresets();
|
|
1903
|
+
const choices = [
|
|
1904
|
+
{ name: "No preset - Configure everything manually", value: "none" },
|
|
1905
|
+
...presets.map((p) => ({
|
|
1906
|
+
name: `${p.name} - ${p.description}`,
|
|
1907
|
+
value: p.name
|
|
1908
|
+
}))
|
|
1909
|
+
];
|
|
1910
|
+
const selected = await select({
|
|
1911
|
+
message: "Start from a preset?",
|
|
1912
|
+
choices,
|
|
1913
|
+
default: "standard"
|
|
1914
|
+
});
|
|
1915
|
+
if (selected === "none") {
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1918
|
+
try {
|
|
1919
|
+
const preset = loadPreset(selected);
|
|
1920
|
+
return { preset, name: selected };
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
logger.warn(`Failed to load preset: ${error instanceof Error ? error.message : String(error)}`);
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
function getValidModelOrFirst(modelId, availableModels) {
|
|
1927
|
+
if (modelId && availableModels.some((m) => m.id === modelId)) {
|
|
1928
|
+
return modelId;
|
|
1929
|
+
}
|
|
1930
|
+
return availableModels[0]?.id ?? "";
|
|
1931
|
+
}
|
|
1932
|
+
function printNextSteps(subscriptions) {
|
|
1933
|
+
const steps = [];
|
|
1934
|
+
if (subscriptions.hasClaude) {
|
|
1935
|
+
steps.push(`Run: ${chalk3.cyan("opencode auth login")} -> Select Anthropic -> Claude Pro/Max`);
|
|
1936
|
+
}
|
|
1937
|
+
if (subscriptions.hasOpenAI) {
|
|
1938
|
+
steps.push(`Run: ${chalk3.cyan("opencode auth login")} -> Select OpenAI -> ChatGPT Plus/Pro`);
|
|
1939
|
+
}
|
|
1940
|
+
if (subscriptions.hasGoogle) {
|
|
1941
|
+
steps.push(`Run: ${chalk3.cyan("opencode auth login")} -> Select Google -> OAuth with Google`);
|
|
1942
|
+
}
|
|
1943
|
+
logger.successBanner("OPENCODE ATHENA INSTALLED SUCCESSFULLY!");
|
|
1944
|
+
console.log(chalk3.bold("Next Steps:\n"));
|
|
1945
|
+
steps.forEach((step, i) => {
|
|
1946
|
+
console.log(` ${i + 1}. ${step}`);
|
|
1947
|
+
});
|
|
1948
|
+
console.log(chalk3.bold("\nThen start OpenCode and try:\n"));
|
|
1949
|
+
console.log(` ${chalk3.cyan("/athena-dev")} Implement a BMAD story with Sisyphus`);
|
|
1950
|
+
console.log(` ${chalk3.cyan("/athena-status")} Check sprint status`);
|
|
1951
|
+
console.log(` ${chalk3.cyan("/athena-info")} View toolkit configuration`);
|
|
1952
|
+
console.log(chalk3.bold("\nFor BMAD project setup:\n"));
|
|
1953
|
+
console.log(` ${chalk3.cyan("npx bmad-method@alpha install")} Install BMAD in your project`);
|
|
1954
|
+
console.log(
|
|
1955
|
+
chalk3.gray("\nDocumentation: https://github.com/ZebulonRouseFrantzich/opencode-athena")
|
|
1956
|
+
);
|
|
1957
|
+
console.log();
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// src/cli/commands/uninstall.ts
|
|
1961
|
+
init_esm_shims();
|
|
1962
|
+
async function uninstall(options) {
|
|
1963
|
+
logger.banner();
|
|
1964
|
+
logger.warn("This will remove OpenCode Athena from your system.");
|
|
1965
|
+
console.log();
|
|
1966
|
+
const proceed = await confirm({
|
|
1967
|
+
message: "Are you sure you want to uninstall OpenCode Athena?",
|
|
1968
|
+
default: false
|
|
1969
|
+
});
|
|
1970
|
+
if (!proceed) {
|
|
1971
|
+
logger.info("Uninstall cancelled.");
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
logger.section("Uninstalling OpenCode Athena");
|
|
1975
|
+
const fileManager = new FileManager();
|
|
1976
|
+
const commandsSpinner = ora3("Removing bridge commands...").start();
|
|
1977
|
+
try {
|
|
1978
|
+
const removedCommands = await fileManager.removeCommands();
|
|
1979
|
+
if (removedCommands.length > 0) {
|
|
1980
|
+
commandsSpinner.succeed(`Removed ${removedCommands.length} bridge command(s)`);
|
|
1981
|
+
} else {
|
|
1982
|
+
commandsSpinner.info("No bridge commands found");
|
|
1983
|
+
}
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
commandsSpinner.fail("Failed to remove bridge commands");
|
|
1986
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
1987
|
+
}
|
|
1988
|
+
if (!options.keepConfig) {
|
|
1989
|
+
const configSpinner = ora3("Removing configuration files...").start();
|
|
1990
|
+
try {
|
|
1991
|
+
const removedFiles = await fileManager.removeConfigFiles();
|
|
1992
|
+
if (removedFiles.length > 0) {
|
|
1993
|
+
configSpinner.succeed(`Removed ${removedFiles.length} configuration file(s)`);
|
|
1994
|
+
} else {
|
|
1995
|
+
configSpinner.info("No Athena configuration files found");
|
|
1996
|
+
}
|
|
1997
|
+
} catch (err) {
|
|
1998
|
+
configSpinner.fail("Failed to remove configuration files");
|
|
1999
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
2000
|
+
}
|
|
2001
|
+
} else {
|
|
2002
|
+
logger.info("Keeping configuration files (--keep-config)");
|
|
2003
|
+
}
|
|
2004
|
+
const opencodeSpinner = ora3("Updating opencode.json...").start();
|
|
2005
|
+
try {
|
|
2006
|
+
const updated = await fileManager.removeFromOpencodeConfig();
|
|
2007
|
+
if (updated) {
|
|
2008
|
+
opencodeSpinner.succeed("Removed Athena plugins from opencode.json");
|
|
2009
|
+
} else {
|
|
2010
|
+
opencodeSpinner.info("No Athena plugins found in opencode.json");
|
|
2011
|
+
}
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
opencodeSpinner.fail("Failed to update opencode.json");
|
|
2014
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
2015
|
+
}
|
|
2016
|
+
if (!options.keepDeps) {
|
|
2017
|
+
const depsSpinner = ora3("Removing npm dependencies...").start();
|
|
2018
|
+
try {
|
|
2019
|
+
const packagesToRemove = [
|
|
2020
|
+
"oh-my-opencode",
|
|
2021
|
+
"opencode-antigravity-auth",
|
|
2022
|
+
"opencode-openai-codex-auth"
|
|
2023
|
+
];
|
|
2024
|
+
await fileManager.uninstallDependencies(packagesToRemove);
|
|
2025
|
+
depsSpinner.succeed("Removed npm dependencies");
|
|
2026
|
+
} catch (_err) {
|
|
2027
|
+
depsSpinner.warn("Some dependencies could not be removed");
|
|
2028
|
+
}
|
|
2029
|
+
} else {
|
|
2030
|
+
logger.info("Keeping npm dependencies (--keep-deps)");
|
|
2031
|
+
}
|
|
2032
|
+
console.log();
|
|
2033
|
+
logger.success("OpenCode Athena has been uninstalled.");
|
|
2034
|
+
if (options.keepConfig) {
|
|
2035
|
+
logger.info("Configuration files were preserved.");
|
|
2036
|
+
logger.info(`Run ${chalk3.cyan("opencode-athena install")} to reinstall with existing config.`);
|
|
2037
|
+
}
|
|
2038
|
+
console.log();
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// src/cli/commands/update.ts
|
|
2042
|
+
init_esm_shims();
|
|
2043
|
+
var execAsync3 = promisify(exec);
|
|
2044
|
+
async function getLatestVersion(packageName) {
|
|
2045
|
+
try {
|
|
2046
|
+
const { stdout } = await execAsync3(`npm view ${packageName} version`);
|
|
2047
|
+
return stdout.trim();
|
|
2048
|
+
} catch {
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
async function checkPackageUpdate(name, currentVersion) {
|
|
2053
|
+
const latest = await getLatestVersion(name);
|
|
2054
|
+
return {
|
|
2055
|
+
name,
|
|
2056
|
+
current: currentVersion,
|
|
2057
|
+
latest: latest || currentVersion,
|
|
2058
|
+
updateAvailable: latest !== null && latest !== currentVersion
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
async function update(options) {
|
|
2062
|
+
logger.banner();
|
|
2063
|
+
logger.section("Checking for Updates");
|
|
2064
|
+
const spinner = ora3("Checking package versions...").start();
|
|
2065
|
+
const installedPlugins = await getInstalledPlugins();
|
|
2066
|
+
const packagesToCheck = [
|
|
2067
|
+
"oh-my-opencode",
|
|
2068
|
+
"opencode-antigravity-auth",
|
|
2069
|
+
"opencode-openai-codex-auth"
|
|
2070
|
+
];
|
|
2071
|
+
const updates = [];
|
|
2072
|
+
const athenaLatest = await getLatestVersion("opencode-athena");
|
|
2073
|
+
if (athenaLatest) {
|
|
2074
|
+
updates.push({
|
|
2075
|
+
name: "opencode-athena",
|
|
2076
|
+
current: VERSION,
|
|
2077
|
+
latest: athenaLatest,
|
|
2078
|
+
updateAvailable: athenaLatest !== VERSION
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
for (const pkgName of packagesToCheck) {
|
|
2082
|
+
const currentVersion = installedPlugins[pkgName];
|
|
2083
|
+
if (currentVersion) {
|
|
2084
|
+
const updateInfo = await checkPackageUpdate(pkgName, currentVersion);
|
|
2085
|
+
updates.push(updateInfo);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
spinner.stop();
|
|
2089
|
+
logger.section("Package Versions");
|
|
2090
|
+
const updatesAvailable = updates.filter((u) => u.updateAvailable);
|
|
2091
|
+
for (const pkg of updates) {
|
|
2092
|
+
const status = pkg.updateAvailable ? chalk3.yellow(`${pkg.current} -> ${pkg.latest}`) : chalk3.green(pkg.current);
|
|
2093
|
+
logger.keyValue(pkg.name, status);
|
|
2094
|
+
}
|
|
2095
|
+
console.log();
|
|
2096
|
+
if (updatesAvailable.length === 0) {
|
|
2097
|
+
logger.success("All packages are up to date!");
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
logger.info(`${updatesAvailable.length} update(s) available`);
|
|
2101
|
+
if (options.check) {
|
|
2102
|
+
console.log();
|
|
2103
|
+
logger.info(`Run ${chalk3.cyan("opencode-athena update")} (without --check) to apply updates.`);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
const proceed = await confirm({
|
|
2107
|
+
message: `Update ${updatesAvailable.length} package(s)?`,
|
|
2108
|
+
default: true
|
|
2109
|
+
});
|
|
2110
|
+
if (!proceed) {
|
|
2111
|
+
logger.info("Update cancelled.");
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
logger.section("Applying Updates");
|
|
2115
|
+
const fileManager = new FileManager();
|
|
2116
|
+
const athenaUpdate = updatesAvailable.find((u) => u.name === "opencode-athena");
|
|
2117
|
+
if (athenaUpdate) {
|
|
2118
|
+
const athenaSpinner = ora3("Updating opencode-athena...").start();
|
|
2119
|
+
try {
|
|
2120
|
+
await execAsync3("npm install -g opencode-athena@latest");
|
|
2121
|
+
athenaSpinner.succeed(`opencode-athena updated to ${athenaUpdate.latest}`);
|
|
2122
|
+
} catch (err) {
|
|
2123
|
+
athenaSpinner.fail("Failed to update opencode-athena");
|
|
2124
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
const pluginUpdates = updatesAvailable.filter((u) => u.name !== "opencode-athena");
|
|
2128
|
+
if (pluginUpdates.length > 0) {
|
|
2129
|
+
const pluginSpinner = ora3("Updating plugins...").start();
|
|
2130
|
+
try {
|
|
2131
|
+
const packages = pluginUpdates.map((u) => `${u.name}@latest`);
|
|
2132
|
+
await fileManager.installDependencies(packages);
|
|
2133
|
+
pluginSpinner.succeed(`Updated ${pluginUpdates.length} plugin(s)`);
|
|
2134
|
+
} catch (err) {
|
|
2135
|
+
pluginSpinner.fail("Failed to update plugins");
|
|
2136
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
const commandsSpinner = ora3("Updating bridge commands...").start();
|
|
2140
|
+
try {
|
|
2141
|
+
await fileManager.copyCommands();
|
|
2142
|
+
commandsSpinner.succeed("Bridge commands updated");
|
|
2143
|
+
} catch (_err) {
|
|
2144
|
+
commandsSpinner.warn("Could not update bridge commands");
|
|
2145
|
+
}
|
|
2146
|
+
console.log();
|
|
2147
|
+
logger.success("Update complete!");
|
|
2148
|
+
logger.info("Restart OpenCode to use the updated version.");
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
// src/cli/index.ts
|
|
2152
|
+
var program = new Command();
|
|
2153
|
+
program.name("opencode-athena").description(
|
|
2154
|
+
`${chalk3.cyan(DISPLAY_NAME)} - ${TAGLINE}
|
|
2155
|
+
Unified oh-my-opencode + BMAD METHOD toolkit for OpenCode`
|
|
2156
|
+
).version(VERSION);
|
|
2157
|
+
program.command("install").description("Install and configure OpenCode Athena").option(
|
|
2158
|
+
"-p, --preset <preset>",
|
|
2159
|
+
"Use a preset configuration (minimal, standard, enterprise, solo-quick)",
|
|
2160
|
+
"standard"
|
|
2161
|
+
).option("-y, --yes", "Skip confirmation prompts", false).option("--advanced", "Show advanced configuration options", false).option("--global", "Install globally (default)", true).option("--local", "Install to current project only", false).option("--list-presets", "List available presets and exit", false).action(async (options) => {
|
|
2162
|
+
if (options.listPresets) {
|
|
2163
|
+
displayPresets();
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
await install(options);
|
|
2167
|
+
});
|
|
2168
|
+
program.command("update").description("Update OpenCode Athena to latest version").option("--check", "Check for updates without installing", false).action(update);
|
|
2169
|
+
program.command("doctor").description("Diagnose and fix common issues").option("--fix", "Automatically fix issues", false).action(doctor);
|
|
2170
|
+
program.command("uninstall").description("Remove OpenCode Athena").option("--keep-config", "Keep configuration files", false).option("--keep-deps", "Keep npm dependencies", false).action(uninstall);
|
|
2171
|
+
program.command("info").description("Show current configuration and status").action(info);
|
|
2172
|
+
function displayPresets() {
|
|
2173
|
+
console.log(chalk3.bold.cyan("\nAvailable Presets:\n"));
|
|
2174
|
+
const presets = listPresets();
|
|
2175
|
+
for (const preset of presets) {
|
|
2176
|
+
console.log(chalk3.bold(` ${preset.name}`));
|
|
2177
|
+
console.log(chalk3.gray(` ${preset.description}`));
|
|
2178
|
+
console.log();
|
|
2179
|
+
}
|
|
2180
|
+
console.log(chalk3.gray("Usage: opencode-athena install --preset <name>"));
|
|
2181
|
+
console.log(chalk3.gray(" opencode-athena install --preset standard --yes\n"));
|
|
2182
|
+
}
|
|
2183
|
+
program.parse();
|
|
2184
|
+
//# sourceMappingURL=index.js.map
|
|
2185
|
+
//# sourceMappingURL=index.js.map
|