oh-my-opencode-slim 1.0.6 → 1.1.0

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.
Files changed (56) hide show
  1. package/README.md +30 -17
  2. package/dist/cli/config-io.d.ts +1 -0
  3. package/dist/cli/divoom.d.ts +23 -0
  4. package/dist/cli/doctor.d.ts +38 -0
  5. package/dist/cli/index.js +469 -58
  6. package/dist/cli/providers.d.ts +3 -0
  7. package/dist/config/council-schema.d.ts +2 -2
  8. package/dist/config/index.d.ts +1 -1
  9. package/dist/config/loader.d.ts +46 -1
  10. package/dist/config/schema.d.ts +23 -0
  11. package/dist/divoom/council.gif +0 -0
  12. package/dist/divoom/designer.gif +0 -0
  13. package/dist/divoom/explorer.gif +0 -0
  14. package/dist/divoom/fixer.gif +0 -0
  15. package/dist/divoom/input.gif +0 -0
  16. package/dist/divoom/intro.gif +0 -0
  17. package/dist/divoom/librarian.gif +0 -0
  18. package/dist/divoom/manager.d.ts +57 -0
  19. package/dist/divoom/oracle.gif +0 -0
  20. package/dist/divoom/orchestrator.gif +0 -0
  21. package/dist/index.js +1304 -291
  22. package/dist/integrations/divoom/index.d.ts +3 -0
  23. package/dist/integrations/divoom/status-manager.d.ts +31 -0
  24. package/dist/integrations/divoom/swift-helper-source.d.ts +1 -0
  25. package/dist/integrations/divoom/swift-transport.d.ts +26 -0
  26. package/dist/integrations/divoom/types.d.ts +41 -0
  27. package/dist/multiplexer/tmux/index.d.ts +5 -0
  28. package/dist/tools/council.d.ts +2 -2
  29. package/dist/tools/fork/command.d.ts +28 -0
  30. package/dist/tools/fork/files.d.ts +33 -0
  31. package/dist/tools/fork/index.d.ts +10 -0
  32. package/dist/tools/fork/state.d.ts +7 -0
  33. package/dist/tools/fork/tools.d.ts +23 -0
  34. package/dist/tools/fork/vendor.d.ts +28 -0
  35. package/dist/tools/handoff/command.d.ts +29 -0
  36. package/dist/tools/handoff/files.d.ts +33 -0
  37. package/dist/tools/handoff/index.d.ts +10 -0
  38. package/dist/tools/handoff/state.d.ts +7 -0
  39. package/dist/tools/handoff/tools.d.ts +23 -0
  40. package/dist/tools/handoff/vendor.d.ts +28 -0
  41. package/dist/tools/index.d.ts +2 -0
  42. package/dist/tools/subtask/command.d.ts +30 -0
  43. package/dist/tools/subtask/files.d.ts +34 -0
  44. package/dist/tools/subtask/index.d.ts +11 -0
  45. package/dist/tools/subtask/state.d.ts +7 -0
  46. package/dist/tools/subtask/tools.d.ts +23 -0
  47. package/dist/tools/subtask/vendor.d.ts +27 -0
  48. package/dist/tui.d.ts +1 -0
  49. package/dist/tui.js +679 -11
  50. package/dist/utils/session.d.ts +11 -4
  51. package/oh-my-opencode-slim.schema.json +59 -0
  52. package/package.json +3 -2
  53. package/src/skills/clonedeps/README.md +23 -0
  54. package/src/skills/clonedeps/SKILL.md +237 -0
  55. package/src/skills/clonedeps/codemap.md +41 -0
  56. package/src/skills/codemap.md +8 -5
package/dist/cli/index.js CHANGED
@@ -3,21 +3,73 @@
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 {
12
16
  copyFileSync as copyFileSync2,
13
17
  existsSync as existsSync3,
18
+ mkdirSync as mkdirSync3,
14
19
  readFileSync,
15
20
  renameSync,
16
21
  statSync as statSync2,
17
22
  writeFileSync
18
23
  } from "node:fs";
24
+ import { homedir as homedir2 } from "node:os";
19
25
  import { dirname as dirname3, join as join3 } from "node:path";
20
26
 
27
+ // src/utils/compat.ts
28
+ import { spawn as nodeSpawn } from "node:child_process";
29
+ var isBun = typeof globalThis.Bun !== "undefined";
30
+ function collectStream(stream) {
31
+ if (!stream)
32
+ return () => Promise.resolve("");
33
+ const chunks = [];
34
+ stream.on("data", (chunk) => chunks.push(chunk));
35
+ return () => new Promise((resolve, reject) => {
36
+ if (!stream.readable) {
37
+ resolve(Buffer.concat(chunks).toString("utf-8"));
38
+ return;
39
+ }
40
+ stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
41
+ stream.on("error", reject);
42
+ });
43
+ }
44
+ function crossSpawn(command, options) {
45
+ const [cmd, ...args] = command;
46
+ const proc = nodeSpawn(cmd, args, {
47
+ stdio: [
48
+ options?.stdin ?? "ignore",
49
+ options?.stdout ?? "pipe",
50
+ options?.stderr ?? "pipe"
51
+ ],
52
+ cwd: options?.cwd,
53
+ env: options?.env
54
+ });
55
+ const stdoutCollector = collectStream(proc.stdout);
56
+ const stderrCollector = collectStream(proc.stderr);
57
+ const exited = new Promise((resolve, reject) => {
58
+ proc.on("error", reject);
59
+ proc.on("close", (code) => resolve(code ?? 1));
60
+ });
61
+ return {
62
+ proc,
63
+ stdout: stdoutCollector,
64
+ stderr: stderrCollector,
65
+ exited,
66
+ kill: (signal) => proc.kill(signal),
67
+ get exitCode() {
68
+ return proc.exitCode;
69
+ }
70
+ };
71
+ }
72
+
21
73
  // src/cli/paths.ts
22
74
  import { existsSync, mkdirSync } from "node:fs";
23
75
  import { homedir } from "node:os";
@@ -41,6 +93,12 @@ function getConfigDir() {
41
93
  }
42
94
  return getDefaultOpenCodeConfigDir();
43
95
  }
96
+ function getConfigSearchDirs() {
97
+ const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
98
+ return dirs.filter((dir, index) => {
99
+ return Boolean(dir) && dirs.indexOf(dir) === index;
100
+ });
101
+ }
44
102
  function getOpenCodeConfigPaths() {
45
103
  const configDir = getDefaultOpenCodeConfigDir();
46
104
  return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
@@ -298,6 +356,17 @@ var SessionManagerConfigSchema = z2.object({
298
356
  readContextMinLines: z2.number().int().min(0).max(1000).default(10),
299
357
  readContextMaxFiles: z2.number().int().min(0).max(50).default(8)
300
358
  });
359
+ var DivoomConfigSchema = z2.object({
360
+ enabled: z2.boolean().default(false),
361
+ python: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/.venv/bin/python"),
362
+ script: z2.string().min(1).default("/Applications/Divoom MiniToo.app/Contents/Resources/tools/divoom_send.py"),
363
+ size: z2.number().int().min(1).max(1024).default(128),
364
+ fps: z2.number().int().min(1).max(60).default(8),
365
+ speed: z2.number().int().min(1).max(1e4).default(125),
366
+ maxFrames: z2.number().int().min(1).max(500).default(24),
367
+ posterizeBits: z2.number().int().min(1).max(8).default(3),
368
+ gifs: z2.record(z2.string(), z2.string().min(1)).optional()
369
+ });
301
370
  var TodoContinuationConfigSchema = z2.object({
302
371
  maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
303
372
  cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
@@ -349,6 +418,7 @@ var PluginConfigSchema = z2.object({
349
418
  websearch: WebsearchConfigSchema.optional(),
350
419
  interview: InterviewConfigSchema.optional(),
351
420
  sessionManager: SessionManagerConfigSchema.optional(),
421
+ divoom: DivoomConfigSchema.optional(),
352
422
  todoContinuation: TodoContinuationConfigSchema.optional(),
353
423
  fallback: FailoverConfigSchema.optional(),
354
424
  council: CouncilConfigSchema.optional()
@@ -397,6 +467,12 @@ var CUSTOM_SKILLS = [
397
467
  description: "Repository understanding and hierarchical codemap generation",
398
468
  allowedAgents: ["orchestrator"],
399
469
  sourcePath: "src/skills/codemap"
470
+ },
471
+ {
472
+ name: "clonedeps",
473
+ description: "Clone important dependency source for local inspection",
474
+ allowedAgents: ["orchestrator"],
475
+ sourcePath: "src/skills/clonedeps"
400
476
  }
401
477
  ];
402
478
  function getCustomSkillsDir() {
@@ -535,7 +611,8 @@ var MODEL_MAPPINGS = {
535
611
  librarian: { model: "opencode-go/minimax-m2.7" },
536
612
  explorer: { model: "opencode-go/minimax-m2.7" },
537
613
  designer: { model: "opencode-go/kimi-k2.6", variant: "medium" },
538
- fixer: { model: "opencode-go/deepseek-v4-flash", variant: "high" }
614
+ fixer: { model: "opencode-go/deepseek-v4-flash", variant: "high" },
615
+ observer: { model: "opencode-go/kimi-k2.6" }
539
616
  }
540
617
  };
541
618
  function isGeneratedPresetName(value) {
@@ -554,6 +631,9 @@ function generateLiteConfig(installConfig) {
554
631
  preset,
555
632
  presets: {}
556
633
  };
634
+ if (preset === "opencode-go") {
635
+ config.disabled_agents = [];
636
+ }
557
637
  const createAgentConfig = (agentName, modelInfo) => {
558
638
  const isOrchestrator = agentName === "orchestrator";
559
639
  const skills = isOrchestrator ? ["*"] : [
@@ -632,10 +712,6 @@ function findPackageRoot(startPath) {
632
712
  currentPath = parentPath;
633
713
  }
634
714
  }
635
- function isPackageManagerInstall(path) {
636
- const normalizedPath = normalizePathForMatch(path);
637
- return normalizedPath.includes(`/node_modules/${PACKAGE_NAME}`);
638
- }
639
715
  function isLocalPackageRootEntry(entry) {
640
716
  if (!entry || entry.startsWith("file://")) {
641
717
  return false;
@@ -651,6 +727,10 @@ function isLocalPackageRootEntry(entry) {
651
727
  return false;
652
728
  }
653
729
  }
730
+ function isPackageManagerInstall(path) {
731
+ const normalizedPath = normalizePathForMatch(path);
732
+ return normalizedPath.includes(`/node_modules/${PACKAGE_NAME}`);
733
+ }
654
734
  function isPluginEntry(entry) {
655
735
  return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.startsWith("file://") && entry.includes(PACKAGE_NAME) || isLocalPackageRootEntry(entry);
656
736
  }
@@ -673,6 +753,104 @@ function getPluginEntry() {
673
753
  return PACKAGE_NAME;
674
754
  }
675
755
  }
756
+ function getOpenCodePluginCacheDir() {
757
+ const cacheDir = process.env.XDG_CACHE_HOME?.trim() || join3(homedir2(), ".cache");
758
+ return join3(cacheDir, "opencode", "packages", `${PACKAGE_NAME}@latest`);
759
+ }
760
+ function writeOpenCodePluginCacheManifest(cacheDir) {
761
+ try {
762
+ writeFileSync(join3(cacheDir, "package.json"), JSON.stringify({
763
+ name: `${PACKAGE_NAME}-cache`,
764
+ private: true,
765
+ dependencies: {
766
+ [PACKAGE_NAME]: "latest"
767
+ }
768
+ }, null, 2));
769
+ return null;
770
+ } catch (err) {
771
+ return {
772
+ success: false,
773
+ configPath: cacheDir,
774
+ error: `Failed to write cache package.json: ${err}`
775
+ };
776
+ }
777
+ }
778
+ function verifyOpenCodePluginCache(cacheDir) {
779
+ const pluginPackageJsonPath = join3(cacheDir, "node_modules", PACKAGE_NAME, "package.json");
780
+ if (!existsSync3(pluginPackageJsonPath)) {
781
+ return {
782
+ success: false,
783
+ configPath: cacheDir,
784
+ error: `Cached plugin package not found at ${pluginPackageJsonPath}`
785
+ };
786
+ }
787
+ try {
788
+ const packageJson = JSON.parse(readFileSync(pluginPackageJsonPath, "utf-8"));
789
+ if (packageJson.name !== PACKAGE_NAME) {
790
+ return {
791
+ success: false,
792
+ configPath: cacheDir,
793
+ error: `Cached plugin package has unexpected name: ${packageJson.name}`
794
+ };
795
+ }
796
+ } catch (err) {
797
+ return {
798
+ success: false,
799
+ configPath: cacheDir,
800
+ error: `Failed to verify cached plugin package: ${err}`
801
+ };
802
+ }
803
+ return null;
804
+ }
805
+ async function warmOpenCodePluginCache() {
806
+ const cliEntryPath = process.argv[1];
807
+ if (!cliEntryPath) {
808
+ return null;
809
+ }
810
+ const packageRoot = findPackageRoot(cliEntryPath);
811
+ if (!packageRoot || !isPackageManagerInstall(packageRoot)) {
812
+ return null;
813
+ }
814
+ const cacheDir = getOpenCodePluginCacheDir();
815
+ try {
816
+ mkdirSync3(cacheDir, { recursive: true });
817
+ } catch (err) {
818
+ return {
819
+ success: false,
820
+ configPath: cacheDir,
821
+ error: `Failed to create OpenCode cache directory: ${err}`
822
+ };
823
+ }
824
+ const manifestError = writeOpenCodePluginCacheManifest(cacheDir);
825
+ if (manifestError)
826
+ return manifestError;
827
+ try {
828
+ const proc = crossSpawn(["bun", "install", "--ignore-scripts"], {
829
+ cwd: cacheDir,
830
+ stdout: "pipe",
831
+ stderr: "pipe"
832
+ });
833
+ await proc.exited;
834
+ if (proc.exitCode !== 0) {
835
+ const stderr = (await proc.stderr()).trim();
836
+ return {
837
+ success: false,
838
+ configPath: cacheDir,
839
+ error: stderr || `bun install exited with code ${proc.exitCode}`
840
+ };
841
+ }
842
+ const verificationError = verifyOpenCodePluginCache(cacheDir);
843
+ if (verificationError)
844
+ return verificationError;
845
+ return { success: true, configPath: cacheDir };
846
+ } catch (err) {
847
+ return {
848
+ success: false,
849
+ configPath: cacheDir,
850
+ error: `Failed to warm OpenCode cache: ${err}`
851
+ };
852
+ }
853
+ }
676
854
  function stripJsonComments(json) {
677
855
  const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
678
856
  const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
@@ -919,57 +1097,266 @@ function detectCurrentConfig() {
919
1097
  }
920
1098
  return result;
921
1099
  }
922
- // src/cli/system.ts
923
- import { spawnSync as spawnSync2 } from "node:child_process";
924
- import { statSync as statSync3 } from "node:fs";
925
1100
 
926
- // src/utils/compat.ts
927
- import { spawn as nodeSpawn } from "node:child_process";
928
- var isBun = typeof globalThis.Bun !== "undefined";
929
- function collectStream(stream) {
930
- if (!stream)
931
- return () => Promise.resolve("");
932
- const chunks = [];
933
- stream.on("data", (chunk) => chunks.push(chunk));
934
- return () => new Promise((resolve, reject) => {
935
- if (!stream.readable) {
936
- resolve(Buffer.concat(chunks).toString("utf-8"));
937
- return;
1101
+ // src/config/loader.ts
1102
+ function findConfigPath(basePath) {
1103
+ const jsoncPath = `${basePath}.jsonc`;
1104
+ const jsonPath = `${basePath}.json`;
1105
+ if (fs.existsSync(jsoncPath)) {
1106
+ return jsoncPath;
1107
+ }
1108
+ if (fs.existsSync(jsonPath)) {
1109
+ return jsonPath;
1110
+ }
1111
+ return null;
1112
+ }
1113
+ function findConfigPathInDirs(configDirs, baseName) {
1114
+ for (const configDir of configDirs) {
1115
+ const configPath = findConfigPath(path.join(configDir, baseName));
1116
+ if (configPath) {
1117
+ return configPath;
938
1118
  }
939
- stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
940
- stream.on("error", reject);
941
- });
1119
+ }
1120
+ return null;
942
1121
  }
943
- function crossSpawn(command, options) {
944
- const [cmd, ...args] = command;
945
- const proc = nodeSpawn(cmd, args, {
946
- stdio: [
947
- options?.stdin ?? "ignore",
948
- options?.stdout ?? "pipe",
949
- options?.stderr ?? "pipe"
950
- ],
951
- cwd: options?.cwd,
952
- env: options?.env
953
- });
954
- const stdoutCollector = collectStream(proc.stdout);
955
- const stderrCollector = collectStream(proc.stderr);
956
- const exited = new Promise((resolve, reject) => {
957
- proc.on("error", reject);
958
- proc.on("close", (code) => resolve(code ?? 1));
959
- });
1122
+ function findPluginConfigPaths(directory) {
1123
+ const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
1124
+ const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
1125
+ const projectConfigPath = findConfigPath(projectConfigBasePath);
1126
+ return { userConfigPath, projectConfigPath };
1127
+ }
1128
+ function mergePluginConfigs(base, override) {
960
1129
  return {
961
- proc,
962
- stdout: stdoutCollector,
963
- stderr: stderrCollector,
964
- exited,
965
- kill: (signal) => proc.kill(signal),
966
- get exitCode() {
967
- return proc.exitCode;
1130
+ ...base,
1131
+ ...override,
1132
+ agents: deepMerge(base.agents, override.agents),
1133
+ tmux: deepMerge(base.tmux, override.tmux),
1134
+ multiplexer: deepMerge(base.multiplexer, override.multiplexer),
1135
+ interview: deepMerge(base.interview, override.interview),
1136
+ sessionManager: deepMerge(base.sessionManager, override.sessionManager),
1137
+ divoom: deepMerge(base.divoom, override.divoom),
1138
+ fallback: deepMerge(base.fallback, override.fallback),
1139
+ council: deepMerge(base.council, override.council)
1140
+ };
1141
+ }
1142
+ function deepMerge(base, override) {
1143
+ if (!base)
1144
+ return override;
1145
+ if (!override)
1146
+ return base;
1147
+ const result = { ...base };
1148
+ for (const key of Object.keys(override)) {
1149
+ const baseVal = base[key];
1150
+ const overrideVal = override[key];
1151
+ if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
1152
+ result[key] = deepMerge(baseVal, overrideVal);
1153
+ } else {
1154
+ result[key] = overrideVal;
1155
+ }
1156
+ }
1157
+ return result;
1158
+ }
1159
+
1160
+ // src/cli/doctor.ts
1161
+ function parseDoctorArgs(args) {
1162
+ const result = {};
1163
+ for (const arg of args) {
1164
+ if (arg === "--json") {
1165
+ result.json = true;
1166
+ } else if (arg === "--help" || arg === "-h") {
1167
+ result.help = true;
1168
+ } else {
1169
+ result.error ??= `Unknown doctor option: ${arg}`;
1170
+ }
1171
+ }
1172
+ return result;
1173
+ }
1174
+ function checkConfigFile(scope, configPath) {
1175
+ if (configPath === null) {
1176
+ return { scope, path: null, exists: false, ok: true };
1177
+ }
1178
+ try {
1179
+ const stat = fs2.statSync(configPath);
1180
+ if (stat.size === 0) {
1181
+ return {
1182
+ scope,
1183
+ path: configPath,
1184
+ exists: true,
1185
+ ok: false,
1186
+ error: {
1187
+ kind: "invalid-json",
1188
+ message: "Empty file is not valid JSON"
1189
+ }
1190
+ };
1191
+ }
1192
+ const content = fs2.readFileSync(configPath, "utf-8");
1193
+ const rawConfig = JSON.parse(stripJsonComments(content));
1194
+ const parseResult = PluginConfigSchema.safeParse(rawConfig);
1195
+ if (!parseResult.success) {
1196
+ return {
1197
+ scope,
1198
+ path: configPath,
1199
+ exists: true,
1200
+ ok: false,
1201
+ error: {
1202
+ kind: "invalid-schema",
1203
+ message: z3.prettifyError(parseResult.error),
1204
+ issues: parseResult.error.issues
1205
+ }
1206
+ };
1207
+ }
1208
+ return {
1209
+ scope,
1210
+ path: configPath,
1211
+ exists: true,
1212
+ ok: true,
1213
+ config: parseResult.data
1214
+ };
1215
+ } catch (err) {
1216
+ if (err instanceof SyntaxError) {
1217
+ return {
1218
+ scope,
1219
+ path: configPath,
1220
+ exists: true,
1221
+ ok: false,
1222
+ error: {
1223
+ kind: "invalid-json",
1224
+ message: err.message
1225
+ }
1226
+ };
1227
+ } else if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1228
+ return {
1229
+ scope,
1230
+ path: configPath,
1231
+ exists: false,
1232
+ ok: false,
1233
+ error: {
1234
+ kind: "read-error",
1235
+ message: "File was not found while reading"
1236
+ }
1237
+ };
968
1238
  }
1239
+ return {
1240
+ scope,
1241
+ path: configPath,
1242
+ exists: true,
1243
+ ok: false,
1244
+ error: {
1245
+ kind: "read-error",
1246
+ message: err instanceof Error ? err.message : String(err)
1247
+ }
1248
+ };
1249
+ }
1250
+ }
1251
+ function checkPreset(mergedConfig) {
1252
+ const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
1253
+ const presetName = envPreset || mergedConfig.preset;
1254
+ if (presetName === undefined) {
1255
+ return;
1256
+ }
1257
+ if (!mergedConfig.presets?.[presetName]) {
1258
+ return {
1259
+ preset: presetName,
1260
+ ok: false,
1261
+ error: {
1262
+ kind: "missing-preset",
1263
+ message: `Preset "${presetName}" not found in config`
1264
+ }
1265
+ };
1266
+ }
1267
+ return { preset: presetName, ok: true };
1268
+ }
1269
+ function getMergedConfig(userConfig, projectConfig) {
1270
+ return projectConfig ? mergePluginConfigs(userConfig ?? {}, projectConfig) : userConfig ?? {};
1271
+ }
1272
+ function runDoctorCheck(cwd) {
1273
+ const { userConfigPath, projectConfigPath } = findPluginConfigPaths(cwd);
1274
+ const userCheck = checkConfigFile("user", userConfigPath);
1275
+ const projectCheck = checkConfigFile("project", projectConfigPath);
1276
+ const configs = [userCheck, projectCheck];
1277
+ const hasInvalidConfig = configs.some((c) => !c.ok);
1278
+ let presetCheckResult;
1279
+ if (!hasInvalidConfig) {
1280
+ const mergedConfig = getMergedConfig(userCheck.config, projectCheck.config);
1281
+ presetCheckResult = checkPreset(mergedConfig);
1282
+ }
1283
+ return {
1284
+ ok: configs.every((c) => c.ok) && (!presetCheckResult || presetCheckResult.ok),
1285
+ project: cwd,
1286
+ configs,
1287
+ presetCheck: presetCheckResult
969
1288
  };
970
1289
  }
1290
+ function formatHumanDoctorResult(result) {
1291
+ const lines = [];
1292
+ lines.push(`Project: ${result.project}`);
1293
+ lines.push("");
1294
+ for (const config of result.configs) {
1295
+ if (config.path === null) {
1296
+ lines.push(`[${config.scope}] No config file found`);
1297
+ } else {
1298
+ const status = config.ok ? "✓" : "✗";
1299
+ lines.push(`[${config.scope}] ${config.path} ${status}`);
1300
+ if (!config.ok && config.error) {
1301
+ if (config.error.kind === "invalid-json") {
1302
+ lines.push(` Invalid JSON: ${config.error.message}`);
1303
+ } else if (config.error.kind === "invalid-schema") {
1304
+ lines.push(" Schema error:");
1305
+ for (const line of config.error.message.split(`
1306
+ `)) {
1307
+ lines.push(` ${line}`);
1308
+ }
1309
+ } else if (config.error.kind === "read-error") {
1310
+ lines.push(` Read error: ${config.error.message}`);
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ if (result.presetCheck) {
1316
+ lines.push("");
1317
+ const status = result.presetCheck.ok ? "✓" : "✗";
1318
+ lines.push(`[preset] ${result.presetCheck.preset} ${status}`);
1319
+ if (result.presetCheck.error) {
1320
+ lines.push(` ${result.presetCheck.error.message}`);
1321
+ }
1322
+ }
1323
+ return lines.join(`
1324
+ `);
1325
+ }
1326
+ function formatJsonDoctorResult(result) {
1327
+ return JSON.stringify({
1328
+ ...result,
1329
+ configs: result.configs.map(({ config: _config, ...config }) => config)
1330
+ }, null, 2);
1331
+ }
1332
+ async function doctor(args) {
1333
+ if (args.help) {
1334
+ console.log(`Usage: oh-my-opencode-slim doctor [OPTIONS]
1335
+
1336
+ Options:
1337
+ --json Print diagnostics as JSON
1338
+ -h, --help Show this help message`);
1339
+ return 0;
1340
+ }
1341
+ if (args.error) {
1342
+ console.error(args.error);
1343
+ return 1;
1344
+ }
1345
+ const result = runDoctorCheck(process.cwd());
1346
+ if (args.json) {
1347
+ console.log(formatJsonDoctorResult(result));
1348
+ } else {
1349
+ console.log(formatHumanDoctorResult(result));
1350
+ }
1351
+ return result.ok ? 0 : 1;
1352
+ }
971
1353
 
1354
+ // src/cli/install.ts
1355
+ import { existsSync as existsSync5 } from "node:fs";
1356
+ import { createInterface } from "node:readline/promises";
972
1357
  // src/cli/system.ts
1358
+ import { spawnSync as spawnSync2 } from "node:child_process";
1359
+ import { statSync as statSync4 } from "node:fs";
973
1360
  var cachedOpenCodePath = null;
974
1361
  function resolvePathCommand(command) {
975
1362
  try {
@@ -1042,7 +1429,7 @@ function resolveOpenCodePath() {
1042
1429
  if (opencodePath === "opencode")
1043
1430
  continue;
1044
1431
  try {
1045
- const stat = statSync3(opencodePath);
1432
+ const stat = statSync4(opencodePath);
1046
1433
  if (stat.isFile()) {
1047
1434
  cachedOpenCodePath = opencodePath;
1048
1435
  return opencodePath;
@@ -1091,8 +1478,8 @@ async function getOpenCodeVersion() {
1091
1478
  return null;
1092
1479
  }
1093
1480
  function getOpenCodePath() {
1094
- const path = resolveOpenCodePath();
1095
- return path === "opencode" ? null : path;
1481
+ const path2 = resolveOpenCodePath();
1482
+ return path2 === "opencode" ? null : path2;
1096
1483
  }
1097
1484
  // src/cli/install.ts
1098
1485
  var GREEN = "\x1B[32m";
@@ -1172,11 +1559,11 @@ async function checkOpenCodeInstalled() {
1172
1559
  return { ok: false };
1173
1560
  }
1174
1561
  const version = await getOpenCodeVersion();
1175
- const path = getOpenCodePath();
1562
+ const path2 = getOpenCodePath();
1176
1563
  const detectedVersion = version ?? "";
1177
- const pathInfo = path ? ` (${DIM}${path}${RESET})` : "";
1564
+ const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
1178
1565
  printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
1179
- return { ok: true, version: version ?? undefined, path: path ?? undefined };
1566
+ return { ok: true, version: version ?? undefined, path: path2 ?? undefined };
1180
1567
  }
1181
1568
  function handleStepResult(result, successMsg) {
1182
1569
  if (!result.success) {
@@ -1195,6 +1582,7 @@ async function runInstall(config) {
1195
1582
  totalSteps += 1;
1196
1583
  if (config.installCustomSkills)
1197
1584
  totalSteps += 1;
1585
+ totalSteps += 1;
1198
1586
  let step = 1;
1199
1587
  printStep(step++, totalSteps, "Checking OpenCode installation...");
1200
1588
  if (config.dryRun) {
@@ -1223,6 +1611,19 @@ async function runInstall(config) {
1223
1611
  handleStepResult(tuiResult, "TUI badge added");
1224
1612
  }
1225
1613
  }
1614
+ printStep(step++, totalSteps, "Warming OpenCode plugin cache...");
1615
+ if (config.dryRun) {
1616
+ printInfo("Dry run mode - skipping cache warm-up");
1617
+ } else {
1618
+ const cacheResult = await warmOpenCodePluginCache();
1619
+ if (cacheResult === null) {
1620
+ printInfo("Local development install - cache warm-up not required");
1621
+ } else if (!cacheResult.success) {
1622
+ printInfo(`Skipped cache warm-up: ${cacheResult.error}`);
1623
+ } else {
1624
+ handleStepResult(cacheResult, "OpenCode cache warmed");
1625
+ }
1626
+ }
1226
1627
  printStep(step++, totalSteps, "Disabling OpenCode default agents...");
1227
1628
  if (config.dryRun) {
1228
1629
  printInfo("Dry run mode - skipping agent disabling");
@@ -1248,7 +1649,7 @@ ${JSON.stringify(liteConfig, null, 2)}
1248
1649
  `);
1249
1650
  } else {
1250
1651
  const configPath2 = getExistingLiteConfigPath();
1251
- const configExists = existsSync4(configPath2);
1652
+ const configExists = existsSync5(configPath2);
1252
1653
  if (configExists && !config.reset) {
1253
1654
  printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
1254
1655
  } else {
@@ -1386,7 +1787,9 @@ function printHelp() {
1386
1787
  console.log(`
1387
1788
  oh-my-opencode-slim installer
1388
1789
 
1389
- Usage: bunx oh-my-opencode-slim install [OPTIONS]
1790
+ Usage:
1791
+ bunx oh-my-opencode-slim install [OPTIONS]
1792
+ bunx oh-my-opencode-slim doctor [OPTIONS]
1390
1793
 
1391
1794
  Options:
1392
1795
  --skills=yes|no Install recommended and bundled skills (default: yes)
@@ -1396,6 +1799,9 @@ Options:
1396
1799
  --reset Force overwrite of existing configuration
1397
1800
  -h, --help Show this help message
1398
1801
 
1802
+ Doctor options:
1803
+ --json Print diagnostics as JSON
1804
+
1399
1805
  Available presets: ${getGeneratedPresetNames2().join(", ")}
1400
1806
 
1401
1807
  The installer generates OpenAI and OpenCode Go presets by default.
@@ -1407,6 +1813,7 @@ Examples:
1407
1813
  bunx oh-my-opencode-slim install --no-tui --skills=yes
1408
1814
  bunx oh-my-opencode-slim install --preset=opencode-go
1409
1815
  bunx oh-my-opencode-slim install --reset
1816
+ bunx oh-my-opencode-slim doctor
1410
1817
  `);
1411
1818
  }
1412
1819
  async function main() {
@@ -1416,6 +1823,10 @@ async function main() {
1416
1823
  const installArgs = parseArgs(args.slice(hasSubcommand ? 1 : 0));
1417
1824
  const exitCode = await install(installArgs);
1418
1825
  process.exit(exitCode);
1826
+ } else if (args[0] === "doctor") {
1827
+ const doctorArgs = parseDoctorArgs(args.slice(1));
1828
+ const exitCode = await doctor(doctorArgs);
1829
+ process.exit(exitCode);
1419
1830
  } else if (args[0] === "-h" || args[0] === "--help") {
1420
1831
  printHelp();
1421
1832
  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;