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.
- package/README.md +30 -17
- package/dist/cli/config-io.d.ts +1 -0
- package/dist/cli/divoom.d.ts +23 -0
- package/dist/cli/doctor.d.ts +38 -0
- package/dist/cli/index.js +469 -58
- 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 +1304 -291
- 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/tools/fork/command.d.ts +28 -0
- package/dist/tools/fork/files.d.ts +33 -0
- package/dist/tools/fork/index.d.ts +10 -0
- package/dist/tools/fork/state.d.ts +7 -0
- package/dist/tools/fork/tools.d.ts +23 -0
- package/dist/tools/fork/vendor.d.ts +28 -0
- package/dist/tools/handoff/command.d.ts +29 -0
- package/dist/tools/handoff/files.d.ts +33 -0
- package/dist/tools/handoff/index.d.ts +10 -0
- package/dist/tools/handoff/state.d.ts +7 -0
- package/dist/tools/handoff/tools.d.ts +23 -0
- package/dist/tools/handoff/vendor.d.ts +28 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/subtask/command.d.ts +30 -0
- package/dist/tools/subtask/files.d.ts +34 -0
- package/dist/tools/subtask/index.d.ts +11 -0
- package/dist/tools/subtask/state.d.ts +7 -0
- package/dist/tools/subtask/tools.d.ts +23 -0
- package/dist/tools/subtask/vendor.d.ts +27 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +679 -11
- package/dist/utils/session.d.ts +11 -4
- package/oh-my-opencode-slim.schema.json +59 -0
- package/package.json +3 -2
- package/src/skills/clonedeps/README.md +23 -0
- package/src/skills/clonedeps/SKILL.md +237 -0
- package/src/skills/clonedeps/codemap.md +41 -0
- 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/
|
|
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 {
|
|
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/
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (
|
|
931
|
-
return
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
940
|
-
|
|
941
|
-
});
|
|
1119
|
+
}
|
|
1120
|
+
return null;
|
|
942
1121
|
}
|
|
943
|
-
function
|
|
944
|
-
const
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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 =
|
|
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
|
|
1095
|
-
return
|
|
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
|
|
1562
|
+
const path2 = getOpenCodePath();
|
|
1176
1563
|
const detectedVersion = version ?? "";
|
|
1177
|
-
const pathInfo =
|
|
1564
|
+
const pathInfo = path2 ? ` (${DIM}${path2}${RESET})` : "";
|
|
1178
1565
|
printSuccess(`OpenCode ${detectedVersion} detected${pathInfo}`);
|
|
1179
|
-
return { ok: true, version: version ?? undefined, path:
|
|
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 =
|
|
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:
|
|
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);
|
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;
|