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 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 an `OMOS <version>` badge beside the prompt without
42
- replacing OpenCode's built-in TUI footer. For manual setups, add
43
- `oh-my-opencode-slim` to the `plugin` array in both `opencode.json` and
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
- > Want to understand how automatic delegation works in practice? Review the **[Orchestrator prompt](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/src/agents/orchestrator.ts#L28)** — it contains the delegation rules, specialist routing logic, and the thresholds for when the main agent should hand work off to subagents.
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. Abbreviated:
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 OpenCode Go, Kimi, GitHub Copilot, ZAI Coding Plan, 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.
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
- You can also mix and match any models per agent. For model suggestions, see the **Recommended Models** listed under each agent below.
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
- ### 💡 Example Presets
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
- [![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg?style=flat-square)](#contributors-)
522
+ [![All Contributors](https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square)](#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/install.ts
7
- import { existsSync as existsSync4 } from "node:fs";
8
- import { createInterface } from "node:readline/promises";
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 statSync3 } from "node:fs";
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 = statSync3(opencodePath);
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 path = resolveOpenCodePath();
1096
- return path === "opencode" ? null : path;
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 path = getOpenCodePath();
1458
+ const path2 = getOpenCodePath();
1177
1459
  const detectedVersion = version ?? "";
1178
- const pathInfo = path ? ` (${DIM}${path}${RESET})` : "";
1460
+ const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
1179
1461
  printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
1180
- return { ok: true, version: version ?? undefined, path: path ?? undefined };
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 = existsSync4(configPath2);
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: bunx oh-my-opencode-slim install [OPTIONS]
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);
@@ -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 "zod";
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: "completed" | "failed" | "timed_out";
123
+ status: 'completed' | 'failed' | 'timed_out';
124
124
  result?: string;
125
125
  error?: string;
126
126
  }>;
@@ -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';