oh-my-opencode-slim 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -18
- package/dist/cli/divoom.d.ts +23 -0
- package/dist/cli/doctor.d.ts +38 -0
- package/dist/cli/index.js +306 -14
- package/dist/cli/providers.d.ts +3 -0
- package/dist/config/council-schema.d.ts +2 -2
- package/dist/config/index.d.ts +1 -1
- package/dist/config/loader.d.ts +46 -1
- package/dist/config/schema.d.ts +23 -1
- package/dist/divoom/council.gif +0 -0
- package/dist/divoom/designer.gif +0 -0
- package/dist/divoom/explorer.gif +0 -0
- package/dist/divoom/fixer.gif +0 -0
- package/dist/divoom/input.gif +0 -0
- package/dist/divoom/intro.gif +0 -0
- package/dist/divoom/librarian.gif +0 -0
- package/dist/divoom/manager.d.ts +57 -0
- package/dist/divoom/oracle.gif +0 -0
- package/dist/divoom/orchestrator.gif +0 -0
- package/dist/hooks/auto-update-checker/types.d.ts +0 -1
- package/dist/index.js +844 -270
- package/dist/integrations/divoom/index.d.ts +3 -0
- package/dist/integrations/divoom/status-manager.d.ts +31 -0
- package/dist/integrations/divoom/swift-helper-source.d.ts +1 -0
- package/dist/integrations/divoom/swift-transport.d.ts +26 -0
- package/dist/integrations/divoom/types.d.ts +41 -0
- package/dist/multiplexer/tmux/index.d.ts +5 -0
- package/dist/tools/council.d.ts +2 -2
- package/dist/tui-state.d.ts +15 -0
- package/dist/tui.d.ts +4 -0
- package/dist/tui.js +787 -9
- package/dist/utils/session.d.ts +10 -4
- package/oh-my-opencode-slim.schema.json +59 -4
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -38,14 +38,13 @@ bunx oh-my-opencode-slim@latest install
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
The installer also registers the companion TUI plugin in OpenCode's
|
|
41
|
-
`tui.json`, which adds
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`tui.json`.
|
|
41
|
+
`tui.json`, which adds a small sidebar showing specialist-agent status plus
|
|
42
|
+
active/reusable task sessions. For manual setups, add `oh-my-opencode-slim` to
|
|
43
|
+
the `plugin` array in both `opencode.json` and `tui.json`.
|
|
45
44
|
|
|
46
45
|
### Getting Started
|
|
47
46
|
|
|
48
|
-
The installer generates both OpenAI and OpenCode Go presets, with OpenAI active by default. OpenAI uses `openai/gpt-5.5` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents. To make OpenCode Go active during install, run `bunx oh-my-opencode-slim@latest install --preset=opencode-go
|
|
47
|
+
The installer generates both OpenAI and OpenCode Go presets, with OpenAI active by default. OpenAI uses `openai/gpt-5.5` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents. To make OpenCode Go active during install, run `bunx oh-my-opencode-slim@latest install --preset=opencode-go` or change the default preset name in `~/.config/opencode/oh-my-opencode-slim.json` after installation.
|
|
49
48
|
|
|
50
49
|
Then:
|
|
51
50
|
|
|
@@ -64,9 +63,9 @@ Then:
|
|
|
64
63
|
4. **Update the models you want for each agent**
|
|
65
64
|
|
|
66
65
|
> [!TIP]
|
|
67
|
-
>
|
|
66
|
+
> It's **recommended** to understand how automatic delegation works. The **[Orchestrator prompt](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/src/agents/orchestrator.ts#L28)** contains the delegation rules, specialist routing logic, and the thresholds for when the main agent should hand work off to subagents. You can alway delegate manually by calling a subagent via: `@agentName <task>`
|
|
68
67
|
|
|
69
|
-
The default generated configuration includes both `openai` and `opencode-go` presets.
|
|
68
|
+
The default generated configuration includes both `openai` and `opencode-go` presets.
|
|
70
69
|
|
|
71
70
|
```jsonc
|
|
72
71
|
{
|
|
@@ -80,24 +79,29 @@ The default generated configuration includes both `openai` and `opencode-go` pre
|
|
|
80
79
|
"explorer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] },
|
|
81
80
|
"designer": { "model": "openai/gpt-5.4-mini", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
|
|
82
81
|
"fixer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] }
|
|
82
|
+
},
|
|
83
|
+
"opencode-go": {
|
|
84
|
+
"orchestrator": { "model": "opencode-go/glm-5.1", "skills": [ "*" ], "mcps": [ "*", "!context7" ] },
|
|
85
|
+
"oracle": { "model": "opencode-go/deepseek-v4-pro", "variant": "max", "skills": ["simplify"], "mcps": [] },
|
|
86
|
+
"council": { "model": "opencode-go/deepseek-v4-pro", "variant": "high", "skills": [], "mcps": [] },
|
|
87
|
+
"librarian": { "model": "opencode-go/minimax-m2.7", "skills": [], "mcps": [ "websearch", "context7", "grep_app" ] },
|
|
88
|
+
"explorer": { "model": "opencode-go/minimax-m2.7", "skills": [], "mcps": [] },
|
|
89
|
+
"designer": { "model": "opencode-go/kimi-k2.6", "variant": "medium", "skills": [ "agent-browser" ], "mcps": [] },
|
|
90
|
+
"fixer": { "model": "opencode-go/deepseek-v4-flash", "variant": "high", "skills": [], "mcps": [] }
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
```
|
|
87
95
|
|
|
88
|
-
Session management is enabled by default even though it is not shown in the
|
|
89
|
-
starter config. See **[Session Management](docs/session-management.md)** if you
|
|
90
|
-
want to customize how many resumable child-agent sessions are remembered.
|
|
91
|
-
|
|
92
96
|
### For Alternative Providers
|
|
93
97
|
|
|
94
|
-
To use
|
|
98
|
+
To use custom providers or a mixed-provider setup, use **[Configuration](docs/configuration.md)** for the full reference. If you want a ready-made starting point, check the **[Author's Preset](docs/authors-preset.md)** and **[$30 Preset](docs/thirty-dollars-preset.md)** - the `$30` preset is the best cheap setup.
|
|
95
99
|
|
|
96
100
|
The configuration guide also covers custom subagents via `agents.<name>`, where
|
|
97
101
|
you can define both a normal `prompt` and an `orchestratorPrompt` block for
|
|
98
102
|
delegation.
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
For model suggestions, see the **Recommended Models** listed under each agent below.
|
|
101
105
|
|
|
102
106
|
### ✅ Verify Your Setup
|
|
103
107
|
|
|
@@ -426,7 +430,7 @@ If any agent fails to respond, check your provider authentication and config fil
|
|
|
426
430
|
### Observer: The Silent Witness
|
|
427
431
|
|
|
428
432
|
> [!NOTE]
|
|
429
|
-
> **Why a separate agent?** If your Orchestrator model is not multimodal, enable Observer to handle images, screenshots, PDFs, and other visual files. Observer is disabled by default and gives the Orchestrator a dedicated multimodal reader without forcing you to change your main reasoning model. Set `disabled_agents: []` and an `observer` model in your configuration.
|
|
433
|
+
> **Why a separate agent?** If your Orchestrator model is not multimodal, enable Observer to handle images, screenshots, PDFs, and other visual files. Observer is disabled by default and gives the Orchestrator a dedicated multimodal reader without forcing you to change your main reasoning model. Set `disabled_agents: []` and an `observer` model in your configuration. The bundled `opencode-go` install preset does this automatically because its GLM Orchestrator is not multimodal.
|
|
430
434
|
|
|
431
435
|
<table>
|
|
432
436
|
<tr>
|
|
@@ -440,7 +444,7 @@ If any agent fails to respond, check your provider authentication and config fil
|
|
|
440
444
|
|
|
441
445
|
- Images, screenshots, diagrams → `read` tool (native image support)
|
|
442
446
|
- PDFs and binary documents → `read` tool (text + structure extraction)
|
|
443
|
-
- **Disabled by default** — enable with `"disabled_agents": []` and configure a vision-capable model
|
|
447
|
+
- **Disabled by default** — enable with `"disabled_agents": []` and configure a vision-capable model; installing with `--preset=opencode-go` enables it with `opencode-go/kimi-k2.6`
|
|
444
448
|
|
|
445
449
|
</td>
|
|
446
450
|
</tr>
|
|
@@ -480,12 +484,13 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
480
484
|
| Doc | What it covers |
|
|
481
485
|
|-----|----------------|
|
|
482
486
|
| **[Council](docs/council.md)** | Run multiple models in parallel and synthesize a single answer with `@council` |
|
|
483
|
-
| **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
|
|
484
487
|
| **[Multiplexer Integration](docs/multiplexer-integration.md)** | Watch agents work live in Tmux or Zellij panes |
|
|
485
488
|
| **[Session Management](docs/session-management.md)** | Reuse recent child-agent sessions with short aliases instead of starting over |
|
|
486
489
|
| **[Todo Continuation](docs/todo-continuation.md)** | Auto-continue orchestrator sessions with cooldowns and safety checks |
|
|
487
490
|
| **[Preset Switching](docs/preset-switching.md)** | Switch agent model presets at runtime with `/preset` |
|
|
488
491
|
| **[Codemap](docs/codemap.md)** | Generate hierarchical codemaps to understand large codebases faster |
|
|
492
|
+
| **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
|
|
493
|
+
| **[Divoom Display](docs/divoom.md)** | Mirror orchestrator and specialist-agent activity to a Divoom MiniToo Bluetooth display |
|
|
489
494
|
|
|
490
495
|
### ⚙️ Config & Reference
|
|
491
496
|
|
|
@@ -497,12 +502,13 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
497
502
|
| **[MCPs](docs/mcps.md)** | `websearch`, `context7`, `grep_app`, and how MCP permissions work per agent |
|
|
498
503
|
| **[Tools](docs/tools.md)** | Built-in tool capabilities like `webfetch`, LSP tools, code search, and formatters |
|
|
499
504
|
|
|
500
|
-
### 💡
|
|
505
|
+
### 💡 Presets
|
|
501
506
|
|
|
502
507
|
| Doc | What it covers |
|
|
503
508
|
|-----|----------------|
|
|
504
509
|
| **[Author's Preset](docs/authors-preset.md)** | The author's daily mixed-provider setup |
|
|
505
510
|
| **[$30 Preset](docs/thirty-dollars-preset.md)** | A budget mixed-provider setup for around $30/month |
|
|
511
|
+
| **[OpenCode Go Preset](docs/opencode-go-preset.md)** | The bundled `opencode-go` preset generated by the installer |
|
|
506
512
|
|
|
507
513
|
---
|
|
508
514
|
|
|
@@ -513,7 +519,7 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
513
519
|
<p><sub>Every merged contribution leaves a mark on the realm.</sub></p>
|
|
514
520
|
|
|
515
521
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
516
|
-
[](#contributors-)
|
|
517
523
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
518
524
|
</div>
|
|
519
525
|
|
|
@@ -583,6 +589,7 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
583
589
|
<tr>
|
|
584
590
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/gustavocaiano"><img src="https://avatars.githubusercontent.com/u/104129313?v=4?s=100" width="100px;" alt="Gustavo Caiano"/><br /><sub><b>Gustavo Caiano</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=gustavocaiano" title="Code">💻</a></td>
|
|
585
591
|
<td align="center" valign="top" width="16.66%"><a href="https://github.com/ThomasMldr"><img src="https://avatars.githubusercontent.com/u/6631765?v=4?s=100" width="100px;" alt="Thomas Mulder"/><br /><sub><b>Thomas Mulder</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=ThomasMldr" title="Code">💻</a></td>
|
|
592
|
+
<td align="center" valign="top" width="16.66%"><a href="https://github.com/maou-shonen"><img src="https://avatars.githubusercontent.com/u/22576780?v=4?s=100" width="100px;" alt="魔王少年(maou shonen)"/><br /><sub><b>魔王少年(maou shonen)</b></sub></a><br /><a href="https://github.com/alvinunreal/oh-my-opencode-slim/commits?author=maou-shonen" title="Code">💻</a></td>
|
|
586
593
|
</tr>
|
|
587
594
|
</tbody>
|
|
588
595
|
</table>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DivoomStatus } from '../integrations/divoom/types';
|
|
2
|
+
export type DivoomCliResult = {
|
|
3
|
+
exitCode: number;
|
|
4
|
+
message?: string;
|
|
5
|
+
error?: string;
|
|
6
|
+
};
|
|
7
|
+
export type DivoomCliOptions = {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
detector?: () => Promise<string | null>;
|
|
11
|
+
transportFactory?: (config: {
|
|
12
|
+
status: DivoomStatus;
|
|
13
|
+
directory: string;
|
|
14
|
+
}) => {
|
|
15
|
+
sendStatus(update: {
|
|
16
|
+
status: DivoomStatus;
|
|
17
|
+
reason: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export declare function runDivoomCli(args: string[], options?: DivoomCliOptions): Promise<DivoomCliResult>;
|
|
23
|
+
export declare function detectDivoomAddress(): Promise<string | null>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type PluginConfig } from '../config/schema';
|
|
3
|
+
export type DoctorArgs = {
|
|
4
|
+
json?: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
help?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseDoctorArgs(args: string[]): DoctorArgs;
|
|
9
|
+
export type ConfigCheckResult = {
|
|
10
|
+
scope: 'user' | 'project';
|
|
11
|
+
path: string | null;
|
|
12
|
+
exists: boolean;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
config?: PluginConfig;
|
|
15
|
+
error?: {
|
|
16
|
+
kind: 'invalid-json' | 'invalid-schema' | 'read-error';
|
|
17
|
+
message: string;
|
|
18
|
+
issues?: z.ZodIssue[];
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type PresetCheckResult = {
|
|
22
|
+
preset: string;
|
|
23
|
+
ok: boolean;
|
|
24
|
+
error?: {
|
|
25
|
+
kind: 'missing-preset';
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export type DoctorResult = {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
project: string;
|
|
32
|
+
configs: ConfigCheckResult[];
|
|
33
|
+
presetCheck?: PresetCheckResult;
|
|
34
|
+
};
|
|
35
|
+
export declare function runDoctorCheck(cwd: string): DoctorResult;
|
|
36
|
+
export declare function formatHumanDoctorResult(result: DoctorResult): string;
|
|
37
|
+
export declare function formatJsonDoctorResult(result: DoctorResult): string;
|
|
38
|
+
export declare function doctor(args: DoctorArgs): Promise<number>;
|
package/dist/cli/index.js
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
5
|
|
|
6
|
-
// src/cli/
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
6
|
+
// src/cli/doctor.ts
|
|
7
|
+
import * as fs2 from "node:fs";
|
|
8
|
+
import { z as z3 } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/config/loader.ts
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
9
13
|
|
|
10
14
|
// src/cli/config-io.ts
|
|
11
15
|
import {
|
|
@@ -41,6 +45,12 @@ function getConfigDir() {
|
|
|
41
45
|
}
|
|
42
46
|
return getDefaultOpenCodeConfigDir();
|
|
43
47
|
}
|
|
48
|
+
function getConfigSearchDirs() {
|
|
49
|
+
const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
|
|
50
|
+
return dirs.filter((dir, index) => {
|
|
51
|
+
return Boolean(dir) && dirs.indexOf(dir) === index;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
44
54
|
function getOpenCodeConfigPaths() {
|
|
45
55
|
const configDir = getDefaultOpenCodeConfigDir();
|
|
46
56
|
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
@@ -298,6 +308,17 @@ var SessionManagerConfigSchema = z2.object({
|
|
|
298
308
|
readContextMinLines: z2.number().int().min(0).max(1000).default(10),
|
|
299
309
|
readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
|
|
300
310
|
});
|
|
311
|
+
var DivoomConfigSchema = z2.object({
|
|
312
|
+
enabled: z2.boolean().default(false),
|
|
313
|
+
python: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python"),
|
|
314
|
+
script: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py"),
|
|
315
|
+
size: z2.number().int().min(1).max(1024).default(128),
|
|
316
|
+
fps: z2.number().int().min(1).max(60).default(8),
|
|
317
|
+
speed: z2.number().int().min(1).max(1e4).default(125),
|
|
318
|
+
maxFrames: z2.number().int().min(1).max(500).default(24),
|
|
319
|
+
posterizeBits: z2.number().int().min(1).max(8).default(3),
|
|
320
|
+
gifs: z2.record(z2.string(), z2.string().min(1)).optional()
|
|
321
|
+
});
|
|
301
322
|
var TodoContinuationConfigSchema = z2.object({
|
|
302
323
|
maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
|
|
303
324
|
cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
|
|
@@ -338,7 +359,6 @@ var PluginConfigSchema = z2.object({
|
|
|
338
359
|
setDefaultAgent: z2.boolean().optional(),
|
|
339
360
|
scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
340
361
|
balanceProviderUsage: z2.boolean().optional(),
|
|
341
|
-
showStartupToast: z2.boolean().optional().describe("Show the startup activation toast when OpenCode starts. Defaults to true."),
|
|
342
362
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
343
363
|
manualPlan: ManualPlanSchema.optional(),
|
|
344
364
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
@@ -350,6 +370,7 @@ var PluginConfigSchema = z2.object({
|
|
|
350
370
|
websearch: WebsearchConfigSchema.optional(),
|
|
351
371
|
interview: InterviewConfigSchema.optional(),
|
|
352
372
|
sessionManager: SessionManagerConfigSchema.optional(),
|
|
373
|
+
divoom: DivoomConfigSchema.optional(),
|
|
353
374
|
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
354
375
|
fallback: FailoverConfigSchema.optional(),
|
|
355
376
|
council: CouncilConfigSchema.optional()
|
|
@@ -536,7 +557,8 @@ var MODEL_MAPPINGS = {
|
|
|
536
557
|
librarian: { model: "opencode-go/minimax-m2.7" },
|
|
537
558
|
explorer: { model: "opencode-go/minimax-m2.7" },
|
|
538
559
|
designer: { model: "opencode-go/kimi-k2.6", variant: "medium" },
|
|
539
|
-
fixer: { model: "opencode-go/deepseek-v4-flash", variant: "high" }
|
|
560
|
+
fixer: { model: "opencode-go/deepseek-v4-flash", variant: "high" },
|
|
561
|
+
observer: { model: "opencode-go/kimi-k2.6" }
|
|
540
562
|
}
|
|
541
563
|
};
|
|
542
564
|
function isGeneratedPresetName(value) {
|
|
@@ -555,6 +577,9 @@ function generateLiteConfig(installConfig) {
|
|
|
555
577
|
preset,
|
|
556
578
|
presets: {}
|
|
557
579
|
};
|
|
580
|
+
if (preset === "opencode-go") {
|
|
581
|
+
config.disabled_agents = [];
|
|
582
|
+
}
|
|
558
583
|
const createAgentConfig = (agentName, modelInfo) => {
|
|
559
584
|
const isOrchestrator = agentName === "orchestrator";
|
|
560
585
|
const skills = isOrchestrator ? ["*"] : [
|
|
@@ -920,9 +945,266 @@ function detectCurrentConfig() {
|
|
|
920
945
|
}
|
|
921
946
|
return result;
|
|
922
947
|
}
|
|
948
|
+
|
|
949
|
+
// src/config/loader.ts
|
|
950
|
+
function findConfigPath(basePath) {
|
|
951
|
+
const jsoncPath = `${basePath}.jsonc`;
|
|
952
|
+
const jsonPath = `${basePath}.json`;
|
|
953
|
+
if (fs.existsSync(jsoncPath)) {
|
|
954
|
+
return jsoncPath;
|
|
955
|
+
}
|
|
956
|
+
if (fs.existsSync(jsonPath)) {
|
|
957
|
+
return jsonPath;
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
function findConfigPathInDirs(configDirs, baseName) {
|
|
962
|
+
for (const configDir of configDirs) {
|
|
963
|
+
const configPath = findConfigPath(path.join(configDir, baseName));
|
|
964
|
+
if (configPath) {
|
|
965
|
+
return configPath;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
970
|
+
function findPluginConfigPaths(directory) {
|
|
971
|
+
const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
|
|
972
|
+
const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
|
|
973
|
+
const projectConfigPath = findConfigPath(projectConfigBasePath);
|
|
974
|
+
return { userConfigPath, projectConfigPath };
|
|
975
|
+
}
|
|
976
|
+
function mergePluginConfigs(base, override) {
|
|
977
|
+
return {
|
|
978
|
+
...base,
|
|
979
|
+
...override,
|
|
980
|
+
agents: deepMerge(base.agents, override.agents),
|
|
981
|
+
tmux: deepMerge(base.tmux, override.tmux),
|
|
982
|
+
multiplexer: deepMerge(base.multiplexer, override.multiplexer),
|
|
983
|
+
interview: deepMerge(base.interview, override.interview),
|
|
984
|
+
sessionManager: deepMerge(base.sessionManager, override.sessionManager),
|
|
985
|
+
divoom: deepMerge(base.divoom, override.divoom),
|
|
986
|
+
fallback: deepMerge(base.fallback, override.fallback),
|
|
987
|
+
council: deepMerge(base.council, override.council)
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
function deepMerge(base, override) {
|
|
991
|
+
if (!base)
|
|
992
|
+
return override;
|
|
993
|
+
if (!override)
|
|
994
|
+
return base;
|
|
995
|
+
const result = { ...base };
|
|
996
|
+
for (const key of Object.keys(override)) {
|
|
997
|
+
const baseVal = base[key];
|
|
998
|
+
const overrideVal = override[key];
|
|
999
|
+
if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
|
|
1000
|
+
result[key] = deepMerge(baseVal, overrideVal);
|
|
1001
|
+
} else {
|
|
1002
|
+
result[key] = overrideVal;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/cli/doctor.ts
|
|
1009
|
+
function parseDoctorArgs(args) {
|
|
1010
|
+
const result = {};
|
|
1011
|
+
for (const arg of args) {
|
|
1012
|
+
if (arg === "--json") {
|
|
1013
|
+
result.json = true;
|
|
1014
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
1015
|
+
result.help = true;
|
|
1016
|
+
} else {
|
|
1017
|
+
result.error ??= `Unknown doctor option: ${arg}`;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return result;
|
|
1021
|
+
}
|
|
1022
|
+
function checkConfigFile(scope, configPath) {
|
|
1023
|
+
if (configPath === null) {
|
|
1024
|
+
return { scope, path: null, exists: false, ok: true };
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
const stat = fs2.statSync(configPath);
|
|
1028
|
+
if (stat.size === 0) {
|
|
1029
|
+
return {
|
|
1030
|
+
scope,
|
|
1031
|
+
path: configPath,
|
|
1032
|
+
exists: true,
|
|
1033
|
+
ok: false,
|
|
1034
|
+
error: {
|
|
1035
|
+
kind: "invalid-json",
|
|
1036
|
+
message: "Empty file is not valid JSON"
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1041
|
+
const rawConfig = JSON.parse(stripJsonComments(content));
|
|
1042
|
+
const parseResult = PluginConfigSchema.safeParse(rawConfig);
|
|
1043
|
+
if (!parseResult.success) {
|
|
1044
|
+
return {
|
|
1045
|
+
scope,
|
|
1046
|
+
path: configPath,
|
|
1047
|
+
exists: true,
|
|
1048
|
+
ok: false,
|
|
1049
|
+
error: {
|
|
1050
|
+
kind: "invalid-schema",
|
|
1051
|
+
message: z3.prettifyError(parseResult.error),
|
|
1052
|
+
issues: parseResult.error.issues
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
scope,
|
|
1058
|
+
path: configPath,
|
|
1059
|
+
exists: true,
|
|
1060
|
+
ok: true,
|
|
1061
|
+
config: parseResult.data
|
|
1062
|
+
};
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
if (err instanceof SyntaxError) {
|
|
1065
|
+
return {
|
|
1066
|
+
scope,
|
|
1067
|
+
path: configPath,
|
|
1068
|
+
exists: true,
|
|
1069
|
+
ok: false,
|
|
1070
|
+
error: {
|
|
1071
|
+
kind: "invalid-json",
|
|
1072
|
+
message: err.message
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
} else if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1076
|
+
return {
|
|
1077
|
+
scope,
|
|
1078
|
+
path: configPath,
|
|
1079
|
+
exists: false,
|
|
1080
|
+
ok: false,
|
|
1081
|
+
error: {
|
|
1082
|
+
kind: "read-error",
|
|
1083
|
+
message: "File was not found while reading"
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
scope,
|
|
1089
|
+
path: configPath,
|
|
1090
|
+
exists: true,
|
|
1091
|
+
ok: false,
|
|
1092
|
+
error: {
|
|
1093
|
+
kind: "read-error",
|
|
1094
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function checkPreset(mergedConfig) {
|
|
1100
|
+
const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
|
|
1101
|
+
const presetName = envPreset || mergedConfig.preset;
|
|
1102
|
+
if (presetName === undefined) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (!mergedConfig.presets?.[presetName]) {
|
|
1106
|
+
return {
|
|
1107
|
+
preset: presetName,
|
|
1108
|
+
ok: false,
|
|
1109
|
+
error: {
|
|
1110
|
+
kind: "missing-preset",
|
|
1111
|
+
message: `Preset "${presetName}" not found in config`
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
return { preset: presetName, ok: true };
|
|
1116
|
+
}
|
|
1117
|
+
function getMergedConfig(userConfig, projectConfig) {
|
|
1118
|
+
return projectConfig ? mergePluginConfigs(userConfig ?? {}, projectConfig) : userConfig ?? {};
|
|
1119
|
+
}
|
|
1120
|
+
function runDoctorCheck(cwd) {
|
|
1121
|
+
const { userConfigPath, projectConfigPath } = findPluginConfigPaths(cwd);
|
|
1122
|
+
const userCheck = checkConfigFile("user", userConfigPath);
|
|
1123
|
+
const projectCheck = checkConfigFile("project", projectConfigPath);
|
|
1124
|
+
const configs = [userCheck, projectCheck];
|
|
1125
|
+
const hasInvalidConfig = configs.some((c) => !c.ok);
|
|
1126
|
+
let presetCheckResult;
|
|
1127
|
+
if (!hasInvalidConfig) {
|
|
1128
|
+
const mergedConfig = getMergedConfig(userCheck.config, projectCheck.config);
|
|
1129
|
+
presetCheckResult = checkPreset(mergedConfig);
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
ok: configs.every((c) => c.ok) && (!presetCheckResult || presetCheckResult.ok),
|
|
1133
|
+
project: cwd,
|
|
1134
|
+
configs,
|
|
1135
|
+
presetCheck: presetCheckResult
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function formatHumanDoctorResult(result) {
|
|
1139
|
+
const lines = [];
|
|
1140
|
+
lines.push(`Project: ${result.project}`);
|
|
1141
|
+
lines.push("");
|
|
1142
|
+
for (const config of result.configs) {
|
|
1143
|
+
if (config.path === null) {
|
|
1144
|
+
lines.push(`[${config.scope}] No config file found`);
|
|
1145
|
+
} else {
|
|
1146
|
+
const status = config.ok ? "✓" : "✗";
|
|
1147
|
+
lines.push(`[${config.scope}] ${config.path} ${status}`);
|
|
1148
|
+
if (!config.ok && config.error) {
|
|
1149
|
+
if (config.error.kind === "invalid-json") {
|
|
1150
|
+
lines.push(` Invalid JSON: ${config.error.message}`);
|
|
1151
|
+
} else if (config.error.kind === "invalid-schema") {
|
|
1152
|
+
lines.push(" Schema error:");
|
|
1153
|
+
for (const line of config.error.message.split(`
|
|
1154
|
+
`)) {
|
|
1155
|
+
lines.push(` ${line}`);
|
|
1156
|
+
}
|
|
1157
|
+
} else if (config.error.kind === "read-error") {
|
|
1158
|
+
lines.push(` Read error: ${config.error.message}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (result.presetCheck) {
|
|
1164
|
+
lines.push("");
|
|
1165
|
+
const status = result.presetCheck.ok ? "✓" : "✗";
|
|
1166
|
+
lines.push(`[preset] ${result.presetCheck.preset} ${status}`);
|
|
1167
|
+
if (result.presetCheck.error) {
|
|
1168
|
+
lines.push(` ${result.presetCheck.error.message}`);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return lines.join(`
|
|
1172
|
+
`);
|
|
1173
|
+
}
|
|
1174
|
+
function formatJsonDoctorResult(result) {
|
|
1175
|
+
return JSON.stringify({
|
|
1176
|
+
...result,
|
|
1177
|
+
configs: result.configs.map(({ config: _config, ...config }) => config)
|
|
1178
|
+
}, null, 2);
|
|
1179
|
+
}
|
|
1180
|
+
async function doctor(args) {
|
|
1181
|
+
if (args.help) {
|
|
1182
|
+
console.log(`Usage: oh-my-opencode-slim doctor [OPTIONS]
|
|
1183
|
+
|
|
1184
|
+
Options:
|
|
1185
|
+
--json Print diagnostics as JSON
|
|
1186
|
+
-h, --help Show this help message`);
|
|
1187
|
+
return 0;
|
|
1188
|
+
}
|
|
1189
|
+
if (args.error) {
|
|
1190
|
+
console.error(args.error);
|
|
1191
|
+
return 1;
|
|
1192
|
+
}
|
|
1193
|
+
const result = runDoctorCheck(process.cwd());
|
|
1194
|
+
if (args.json) {
|
|
1195
|
+
console.log(formatJsonDoctorResult(result));
|
|
1196
|
+
} else {
|
|
1197
|
+
console.log(formatHumanDoctorResult(result));
|
|
1198
|
+
}
|
|
1199
|
+
return result.ok ? 0 : 1;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// src/cli/install.ts
|
|
1203
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1204
|
+
import { createInterface } from "node:readline/promises";
|
|
923
1205
|
// src/cli/system.ts
|
|
924
1206
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
925
|
-
import { statSync as
|
|
1207
|
+
import { statSync as statSync4 } from "node:fs";
|
|
926
1208
|
|
|
927
1209
|
// src/utils/compat.ts
|
|
928
1210
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1043,7 +1325,7 @@ function resolveOpenCodePath() {
|
|
|
1043
1325
|
if (opencodePath === "opencode")
|
|
1044
1326
|
continue;
|
|
1045
1327
|
try {
|
|
1046
|
-
const stat =
|
|
1328
|
+
const stat = statSync4(opencodePath);
|
|
1047
1329
|
if (stat.isFile()) {
|
|
1048
1330
|
cachedOpenCodePath = opencodePath;
|
|
1049
1331
|
return opencodePath;
|
|
@@ -1092,8 +1374,8 @@ async function getOpenCodeVersion() {
|
|
|
1092
1374
|
return null;
|
|
1093
1375
|
}
|
|
1094
1376
|
function getOpenCodePath() {
|
|
1095
|
-
const
|
|
1096
|
-
return
|
|
1377
|
+
const path2 = resolveOpenCodePath();
|
|
1378
|
+
return path2 === "opencode" ? null : path2;
|
|
1097
1379
|
}
|
|
1098
1380
|
// src/cli/install.ts
|
|
1099
1381
|
var GREEN = "\x1B[32m";
|
|
@@ -1173,11 +1455,11 @@ async function checkOpenCodeInstalled() {
|
|
|
1173
1455
|
return { ok: false };
|
|
1174
1456
|
}
|
|
1175
1457
|
const version = await getOpenCodeVersion();
|
|
1176
|
-
const
|
|
1458
|
+
const path2 = getOpenCodePath();
|
|
1177
1459
|
const detectedVersion = version ?? "";
|
|
1178
|
-
const pathInfo =
|
|
1460
|
+
const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
|
|
1179
1461
|
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
1180
|
-
return { ok: true, version: version ?? undefined, path:
|
|
1462
|
+
return { ok: true, version: version ?? undefined, path: path2 ?? undefined };
|
|
1181
1463
|
}
|
|
1182
1464
|
function handleStepResult(result, successMsg) {
|
|
1183
1465
|
if (!result.success) {
|
|
@@ -1249,7 +1531,7 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1249
1531
|
`);
|
|
1250
1532
|
} else {
|
|
1251
1533
|
const configPath2 = getExistingLiteConfigPath();
|
|
1252
|
-
const configExists =
|
|
1534
|
+
const configExists = existsSync5(configPath2);
|
|
1253
1535
|
if (configExists && !config.reset) {
|
|
1254
1536
|
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
1255
1537
|
} else {
|
|
@@ -1387,7 +1669,9 @@ function printHelp() {
|
|
|
1387
1669
|
console.log(`
|
|
1388
1670
|
oh-my-opencode-slim installer
|
|
1389
1671
|
|
|
1390
|
-
Usage:
|
|
1672
|
+
Usage:
|
|
1673
|
+
bunx oh-my-opencode-slim install [OPTIONS]
|
|
1674
|
+
bunx oh-my-opencode-slim doctor [OPTIONS]
|
|
1391
1675
|
|
|
1392
1676
|
Options:
|
|
1393
1677
|
--skills=yes|no Install recommended and bundled skills (default: yes)
|
|
@@ -1397,6 +1681,9 @@ Options:
|
|
|
1397
1681
|
--reset Force overwrite of existing configuration
|
|
1398
1682
|
-h, --help Show this help message
|
|
1399
1683
|
|
|
1684
|
+
Doctor options:
|
|
1685
|
+
--json Print diagnostics as JSON
|
|
1686
|
+
|
|
1400
1687
|
Available presets: ${getGeneratedPresetNames2().join(", ")}
|
|
1401
1688
|
|
|
1402
1689
|
The installer generates OpenAI and OpenCode Go presets by default.
|
|
@@ -1408,6 +1695,7 @@ Examples:
|
|
|
1408
1695
|
bunx oh-my-opencode-slim install --no-tui --skills=yes
|
|
1409
1696
|
bunx oh-my-opencode-slim install --preset=opencode-go
|
|
1410
1697
|
bunx oh-my-opencode-slim install --reset
|
|
1698
|
+
bunx oh-my-opencode-slim doctor
|
|
1411
1699
|
`);
|
|
1412
1700
|
}
|
|
1413
1701
|
async function main() {
|
|
@@ -1417,6 +1705,10 @@ async function main() {
|
|
|
1417
1705
|
const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
|
|
1418
1706
|
const exitCode = await install(installArgs);
|
|
1419
1707
|
process.exit(exitCode);
|
|
1708
|
+
} else if (args[0] === "doctor") {
|
|
1709
|
+
const doctorArgs = parseDoctorArgs(args.slice(1));
|
|
1710
|
+
const exitCode = await doctor(doctorArgs);
|
|
1711
|
+
process.exit(exitCode);
|
|
1420
1712
|
} else if (args[0] === "-h" || args[0] === "--help") {
|
|
1421
1713
|
printHelp();
|
|
1422
1714
|
process.exit(0);
|
package/dist/cli/providers.d.ts
CHANGED
|
@@ -127,6 +127,9 @@ export declare const MODEL_MAPPINGS: {
|
|
|
127
127
|
readonly model: "opencode-go/deepseek-v4-flash";
|
|
128
128
|
readonly variant: "high";
|
|
129
129
|
};
|
|
130
|
+
readonly observer: {
|
|
131
|
+
readonly model: "opencode-go/kimi-k2.6";
|
|
132
|
+
};
|
|
130
133
|
};
|
|
131
134
|
};
|
|
132
135
|
export type PresetName = keyof typeof MODEL_MAPPINGS;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
/**
|
|
3
3
|
* Configuration for a single councillor within a preset.
|
|
4
4
|
* Each councillor is an independent LLM that processes the same prompt.
|
|
@@ -120,7 +120,7 @@ export interface CouncilResult {
|
|
|
120
120
|
councillorResults: Array<{
|
|
121
121
|
name: string;
|
|
122
122
|
model: string;
|
|
123
|
-
status:
|
|
123
|
+
status: 'completed' | 'failed' | 'timed_out';
|
|
124
124
|
result?: string;
|
|
125
125
|
error?: string;
|
|
126
126
|
}>;
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './constants';
|
|
2
2
|
export * from './council-schema';
|
|
3
|
-
export { deepMerge, loadAgentPrompt, loadPluginConfig } from './loader';
|
|
3
|
+
export { deepMerge, loadAgentPrompt, loadPluginConfig, } from './loader';
|
|
4
4
|
export * from './schema';
|
|
5
5
|
export { getAgentOverride, getCustomAgentNames } from './utils';
|