oh-my-opencode-slim 1.0.6 → 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 +22 -14
- package/dist/cli/divoom.d.ts +23 -0
- package/dist/cli/doctor.d.ts +38 -0
- package/dist/cli/index.js +306 -13
- 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 -0
- 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/index.js +722 -237
- 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.d.ts +1 -0
- package/dist/tui.js +623 -11
- package/dist/utils/session.d.ts +10 -4
- package/oh-my-opencode-slim.schema.json +59 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ the `plugin` array in both `opencode.json` and `tui.json`.
|
|
|
44
44
|
|
|
45
45
|
### Getting Started
|
|
46
46
|
|
|
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
|
|
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.
|
|
48
48
|
|
|
49
49
|
Then:
|
|
50
50
|
|
|
@@ -63,9 +63,9 @@ Then:
|
|
|
63
63
|
4. **Update the models you want for each agent**
|
|
64
64
|
|
|
65
65
|
> [!TIP]
|
|
66
|
-
>
|
|
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>`
|
|
67
67
|
|
|
68
|
-
The default generated configuration includes both `openai` and `opencode-go` presets.
|
|
68
|
+
The default generated configuration includes both `openai` and `opencode-go` presets.
|
|
69
69
|
|
|
70
70
|
```jsonc
|
|
71
71
|
{
|
|
@@ -79,24 +79,29 @@ The default generated configuration includes both `openai` and `opencode-go` pre
|
|
|
79
79
|
"explorer": { "model": "openai/gpt-5.4-mini", "variant": "low", "skills": [], "mcps": [] },
|
|
80
80
|
"designer": { "model": "openai/gpt-5.4-mini", "variant": "medium", "skills": ["agent-browser"], "mcps": [] },
|
|
81
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": [] }
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
```
|
|
86
95
|
|
|
87
|
-
Session management is enabled by default even though it is not shown in the
|
|
88
|
-
starter config. See **[Session Management](docs/session-management.md)** if you
|
|
89
|
-
want to customize how many resumable child-agent sessions are remembered.
|
|
90
|
-
|
|
91
96
|
### For Alternative Providers
|
|
92
97
|
|
|
93
|
-
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.
|
|
94
99
|
|
|
95
100
|
The configuration guide also covers custom subagents via `agents.<name>`, where
|
|
96
101
|
you can define both a normal `prompt` and an `orchestratorPrompt` block for
|
|
97
102
|
delegation.
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
For model suggestions, see the **Recommended Models** listed under each agent below.
|
|
100
105
|
|
|
101
106
|
### ✅ Verify Your Setup
|
|
102
107
|
|
|
@@ -425,7 +430,7 @@ If any agent fails to respond, check your provider authentication and config fil
|
|
|
425
430
|
### Observer: The Silent Witness
|
|
426
431
|
|
|
427
432
|
> [!NOTE]
|
|
428
|
-
> **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.
|
|
429
434
|
|
|
430
435
|
<table>
|
|
431
436
|
<tr>
|
|
@@ -439,7 +444,7 @@ If any agent fails to respond, check your provider authentication and config fil
|
|
|
439
444
|
|
|
440
445
|
- Images, screenshots, diagrams → `read` tool (native image support)
|
|
441
446
|
- PDFs and binary documents → `read` tool (text + structure extraction)
|
|
442
|
-
- **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`
|
|
443
448
|
|
|
444
449
|
</td>
|
|
445
450
|
</tr>
|
|
@@ -479,12 +484,13 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
479
484
|
| Doc | What it covers |
|
|
480
485
|
|-----|----------------|
|
|
481
486
|
| **[Council](docs/council.md)** | Run multiple models in parallel and synthesize a single answer with `@council` |
|
|
482
|
-
| **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
|
|
483
487
|
| **[Multiplexer Integration](docs/multiplexer-integration.md)** | Watch agents work live in Tmux or Zellij panes |
|
|
484
488
|
| **[Session Management](docs/session-management.md)** | Reuse recent child-agent sessions with short aliases instead of starting over |
|
|
485
489
|
| **[Todo Continuation](docs/todo-continuation.md)** | Auto-continue orchestrator sessions with cooldowns and safety checks |
|
|
486
490
|
| **[Preset Switching](docs/preset-switching.md)** | Switch agent model presets at runtime with `/preset` |
|
|
487
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 |
|
|
488
494
|
|
|
489
495
|
### ⚙️ Config & Reference
|
|
490
496
|
|
|
@@ -496,12 +502,13 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
496
502
|
| **[MCPs](docs/mcps.md)** | `websearch`, `context7`, `grep_app`, and how MCP permissions work per agent |
|
|
497
503
|
| **[Tools](docs/tools.md)** | Built-in tool capabilities like `webfetch`, LSP tools, code search, and formatters |
|
|
498
504
|
|
|
499
|
-
### 💡
|
|
505
|
+
### 💡 Presets
|
|
500
506
|
|
|
501
507
|
| Doc | What it covers |
|
|
502
508
|
|-----|----------------|
|
|
503
509
|
| **[Author's Preset](docs/authors-preset.md)** | The author's daily mixed-provider setup |
|
|
504
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 |
|
|
505
512
|
|
|
506
513
|
---
|
|
507
514
|
|
|
@@ -512,7 +519,7 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
512
519
|
<p><sub>Every merged contribution leaves a mark on the realm.</sub></p>
|
|
513
520
|
|
|
514
521
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
515
|
-
[](#contributors-)
|
|
516
523
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
517
524
|
</div>
|
|
518
525
|
|
|
@@ -582,6 +589,7 @@ Use this section as a map: start with installation, then jump to features, confi
|
|
|
582
589
|
<tr>
|
|
583
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>
|
|
584
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>
|
|
585
593
|
</tr>
|
|
586
594
|
</tbody>
|
|
587
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)"),
|
|
@@ -349,6 +370,7 @@ var PluginConfigSchema = z2.object({
|
|
|
349
370
|
websearch: WebsearchConfigSchema.optional(),
|
|
350
371
|
interview: InterviewConfigSchema.optional(),
|
|
351
372
|
sessionManager: SessionManagerConfigSchema.optional(),
|
|
373
|
+
divoom: DivoomConfigSchema.optional(),
|
|
352
374
|
todoContinuation: TodoContinuationConfigSchema.optional(),
|
|
353
375
|
fallback: FailoverConfigSchema.optional(),
|
|
354
376
|
council: CouncilConfigSchema.optional()
|
|
@@ -535,7 +557,8 @@ var MODEL_MAPPINGS = {
|
|
|
535
557
|
librarian: { model: "opencode-go/minimax-m2.7" },
|
|
536
558
|
explorer: { model: "opencode-go/minimax-m2.7" },
|
|
537
559
|
designer: { model: "opencode-go/kimi-k2.6", variant: "medium" },
|
|
538
|
-
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" }
|
|
539
562
|
}
|
|
540
563
|
};
|
|
541
564
|
function isGeneratedPresetName(value) {
|
|
@@ -554,6 +577,9 @@ function generateLiteConfig(installConfig) {
|
|
|
554
577
|
preset,
|
|
555
578
|
presets: {}
|
|
556
579
|
};
|
|
580
|
+
if (preset === "opencode-go") {
|
|
581
|
+
config.disabled_agents = [];
|
|
582
|
+
}
|
|
557
583
|
const createAgentConfig = (agentName, modelInfo) => {
|
|
558
584
|
const isOrchestrator = agentName === "orchestrator";
|
|
559
585
|
const skills = isOrchestrator ? ["*"] : [
|
|
@@ -919,9 +945,266 @@ function detectCurrentConfig() {
|
|
|
919
945
|
}
|
|
920
946
|
return result;
|
|
921
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";
|
|
922
1205
|
// src/cli/system.ts
|
|
923
1206
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
924
|
-
import { statSync as
|
|
1207
|
+
import { statSync as statSync4 } from "node:fs";
|
|
925
1208
|
|
|
926
1209
|
// src/utils/compat.ts
|
|
927
1210
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1042,7 +1325,7 @@ function resolveOpenCodePath() {
|
|
|
1042
1325
|
if (opencodePath === "opencode")
|
|
1043
1326
|
continue;
|
|
1044
1327
|
try {
|
|
1045
|
-
const stat =
|
|
1328
|
+
const stat = statSync4(opencodePath);
|
|
1046
1329
|
if (stat.isFile()) {
|
|
1047
1330
|
cachedOpenCodePath = opencodePath;
|
|
1048
1331
|
return opencodePath;
|
|
@@ -1091,8 +1374,8 @@ async function getOpenCodeVersion() {
|
|
|
1091
1374
|
return null;
|
|
1092
1375
|
}
|
|
1093
1376
|
function getOpenCodePath() {
|
|
1094
|
-
const
|
|
1095
|
-
return
|
|
1377
|
+
const path2 = resolveOpenCodePath();
|
|
1378
|
+
return path2 === "opencode" ? null : path2;
|
|
1096
1379
|
}
|
|
1097
1380
|
// src/cli/install.ts
|
|
1098
1381
|
var GREEN = "\x1B[32m";
|
|
@@ -1172,11 +1455,11 @@ async function checkOpenCodeInstalled() {
|
|
|
1172
1455
|
return { ok: false };
|
|
1173
1456
|
}
|
|
1174
1457
|
const version = await getOpenCodeVersion();
|
|
1175
|
-
const
|
|
1458
|
+
const path2 = getOpenCodePath();
|
|
1176
1459
|
const detectedVersion = version ?? "";
|
|
1177
|
-
const pathInfo =
|
|
1460
|
+
const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
|
|
1178
1461
|
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
1179
|
-
return { ok: true, version: version ?? undefined, path:
|
|
1462
|
+
return { ok: true, version: version ?? undefined, path: path2 ?? undefined };
|
|
1180
1463
|
}
|
|
1181
1464
|
function handleStepResult(result, successMsg) {
|
|
1182
1465
|
if (!result.success) {
|
|
@@ -1248,7 +1531,7 @@ ${JSON.stringify(liteConfig, null, 2)}
|
|
|
1248
1531
|
`);
|
|
1249
1532
|
} else {
|
|
1250
1533
|
const configPath2 = getExistingLiteConfigPath();
|
|
1251
|
-
const configExists =
|
|
1534
|
+
const configExists = existsSync5(configPath2);
|
|
1252
1535
|
if (configExists && !config.reset) {
|
|
1253
1536
|
printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
|
|
1254
1537
|
} else {
|
|
@@ -1386,7 +1669,9 @@ function printHelp() {
|
|
|
1386
1669
|
console.log(`
|
|
1387
1670
|
oh-my-opencode-slim installer
|
|
1388
1671
|
|
|
1389
|
-
Usage:
|
|
1672
|
+
Usage:
|
|
1673
|
+
bunx oh-my-opencode-slim install [OPTIONS]
|
|
1674
|
+
bunx oh-my-opencode-slim doctor [OPTIONS]
|
|
1390
1675
|
|
|
1391
1676
|
Options:
|
|
1392
1677
|
--skills=yes|no Install recommended and bundled skills (default: yes)
|
|
@@ -1396,6 +1681,9 @@ Options:
|
|
|
1396
1681
|
--reset Force overwrite of existing configuration
|
|
1397
1682
|
-h, --help Show this help message
|
|
1398
1683
|
|
|
1684
|
+
Doctor options:
|
|
1685
|
+
--json Print diagnostics as JSON
|
|
1686
|
+
|
|
1399
1687
|
Available presets: ${getGeneratedPresetNames2().join(", ")}
|
|
1400
1688
|
|
|
1401
1689
|
The installer generates OpenAI and OpenCode Go presets by default.
|
|
@@ -1407,6 +1695,7 @@ Examples:
|
|
|
1407
1695
|
bunx oh-my-opencode-slim install --no-tui --skills=yes
|
|
1408
1696
|
bunx oh-my-opencode-slim install --preset=opencode-go
|
|
1409
1697
|
bunx oh-my-opencode-slim install --reset
|
|
1698
|
+
bunx oh-my-opencode-slim doctor
|
|
1410
1699
|
`);
|
|
1411
1700
|
}
|
|
1412
1701
|
async function main() {
|
|
@@ -1416,6 +1705,10 @@ async function main() {
|
|
|
1416
1705
|
const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
|
|
1417
1706
|
const exitCode = await install(installArgs);
|
|
1418
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);
|
|
1419
1712
|
} else if (args[0] === "-h" || args[0] === "--help") {
|
|
1420
1713
|
printHelp();
|
|
1421
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';
|