oh-my-opencode 0.1.30 → 0.1.32

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 (40) hide show
  1. package/README.ko.md +13 -0
  2. package/README.md +24 -0
  3. package/dist/features/claude-code-agent-loader/index.d.ts +2 -0
  4. package/dist/features/claude-code-agent-loader/loader.d.ts +3 -0
  5. package/dist/features/claude-code-agent-loader/types.d.ts +14 -0
  6. package/dist/features/claude-code-command-loader/index.d.ts +2 -0
  7. package/dist/features/claude-code-command-loader/loader.d.ts +5 -0
  8. package/dist/features/claude-code-command-loader/types.d.ts +23 -0
  9. package/dist/features/claude-code-mcp-loader/env-expander.d.ts +2 -0
  10. package/dist/features/claude-code-mcp-loader/index.d.ts +10 -0
  11. package/dist/features/claude-code-mcp-loader/loader.d.ts +3 -0
  12. package/dist/features/claude-code-mcp-loader/transformer.d.ts +2 -0
  13. package/dist/features/claude-code-mcp-loader/types.d.ts +35 -0
  14. package/dist/features/claude-code-session-state/detector.d.ts +1 -0
  15. package/dist/features/claude-code-session-state/index.d.ts +3 -0
  16. package/dist/features/claude-code-session-state/state.d.ts +13 -0
  17. package/dist/features/claude-code-session-state/types.d.ts +7 -0
  18. package/dist/features/claude-code-skill-loader/index.d.ts +2 -0
  19. package/dist/features/claude-code-skill-loader/loader.d.ts +3 -0
  20. package/dist/features/claude-code-skill-loader/types.d.ts +13 -0
  21. package/dist/hooks/index.d.ts +1 -0
  22. package/dist/hooks/think-mode/detector.d.ts +5 -0
  23. package/dist/hooks/think-mode/index.d.ts +14 -0
  24. package/dist/hooks/think-mode/switcher.d.ts +4 -0
  25. package/dist/hooks/think-mode/types.d.ts +20 -0
  26. package/dist/index.js +1293 -125
  27. package/dist/shared/command-executor.d.ts +21 -0
  28. package/dist/shared/file-reference-resolver.d.ts +1 -0
  29. package/dist/shared/frontmatter.d.ts +5 -0
  30. package/dist/shared/index.d.ts +5 -0
  31. package/dist/shared/logger.d.ts +2 -0
  32. package/dist/shared/model-sanitizer.d.ts +11 -0
  33. package/dist/tools/index.d.ts +18 -0
  34. package/dist/tools/skill/index.d.ts +2 -0
  35. package/dist/tools/skill/tools.d.ts +9 -0
  36. package/dist/tools/skill/types.d.ts +24 -0
  37. package/dist/tools/slashcommand/index.d.ts +2 -0
  38. package/dist/tools/slashcommand/tools.d.ts +9 -0
  39. package/dist/tools/slashcommand/types.d.ts +16 -0
  40. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1811,6 +1811,592 @@ function createEmptyTaskResponseDetectorHook(_ctx) {
1811
1811
  }
1812
1812
  };
1813
1813
  }
1814
+ // src/hooks/think-mode/detector.ts
1815
+ var ENGLISH_PATTERNS = [/\bultrathink\b/i, /\bthink\b/i];
1816
+ var MULTILINGUAL_KEYWORDS = [
1817
+ "\uC0DD\uAC01",
1818
+ "\uACE0\uBBFC",
1819
+ "\uAC80\uD1A0",
1820
+ "\uC81C\uB300\uB85C",
1821
+ "\u601D\u8003",
1822
+ "\u8003\u8651",
1823
+ "\u8003\u616E",
1824
+ "\u601D\u8003",
1825
+ "\u8003\u3048",
1826
+ "\u719F\u8003",
1827
+ "\u0938\u094B\u091A",
1828
+ "\u0935\u093F\u091A\u093E\u0930",
1829
+ "\u062A\u0641\u0643\u064A\u0631",
1830
+ "\u062A\u0623\u0645\u0644",
1831
+ "\u099A\u09BF\u09A8\u09CD\u09A4\u09BE",
1832
+ "\u09AD\u09BE\u09AC\u09A8\u09BE",
1833
+ "\u0434\u0443\u043C\u0430\u0442\u044C",
1834
+ "\u0434\u0443\u043C\u0430\u0439",
1835
+ "\u0440\u0430\u0437\u043C\u044B\u0448\u043B\u044F\u0442\u044C",
1836
+ "\u0440\u0430\u0437\u043C\u044B\u0448\u043B\u044F\u0439",
1837
+ "pensar",
1838
+ "pense",
1839
+ "refletir",
1840
+ "reflita",
1841
+ "pensar",
1842
+ "piensa",
1843
+ "reflexionar",
1844
+ "reflexiona",
1845
+ "penser",
1846
+ "pense",
1847
+ "r\xE9fl\xE9chir",
1848
+ "r\xE9fl\xE9chis",
1849
+ "denken",
1850
+ "denk",
1851
+ "nachdenken",
1852
+ "suy ngh\u0129",
1853
+ "c\xE2n nh\u1EAFc",
1854
+ "d\xFC\u015F\xFCn",
1855
+ "d\xFC\u015F\xFCnmek",
1856
+ "pensare",
1857
+ "pensa",
1858
+ "riflettere",
1859
+ "rifletti",
1860
+ "\u0E04\u0E34\u0E14",
1861
+ "\u0E1E\u0E34\u0E08\u0E32\u0E23\u0E13\u0E32",
1862
+ "my\u015Bl",
1863
+ "my\u015Ble\u0107",
1864
+ "zastan\xF3w",
1865
+ "denken",
1866
+ "denk",
1867
+ "nadenken",
1868
+ "berpikir",
1869
+ "pikir",
1870
+ "pertimbangkan",
1871
+ "\u0434\u0443\u043C\u0430\u0442\u0438",
1872
+ "\u0434\u0443\u043C\u0430\u0439",
1873
+ "\u0440\u043E\u0437\u0434\u0443\u043C\u0443\u0432\u0430\u0442\u0438",
1874
+ "\u03C3\u03BA\u03AD\u03C8\u03BF\u03C5",
1875
+ "\u03C3\u03BA\u03AD\u03C6\u03C4\u03BF\u03BC\u03B1\u03B9",
1876
+ "myslet",
1877
+ "mysli",
1878
+ "p\u0159em\xFD\u0161let",
1879
+ "g\xE2nde\u0219te",
1880
+ "g\xE2ndi",
1881
+ "reflect\u0103",
1882
+ "t\xE4nka",
1883
+ "t\xE4nk",
1884
+ "fundera",
1885
+ "gondolkodj",
1886
+ "gondolkodni",
1887
+ "ajattele",
1888
+ "ajatella",
1889
+ "pohdi",
1890
+ "t\xE6nk",
1891
+ "t\xE6nke",
1892
+ "overvej",
1893
+ "tenk",
1894
+ "tenke",
1895
+ "gruble",
1896
+ "\u05D7\u05E9\u05D5\u05D1",
1897
+ "\u05DC\u05D7\u05E9\u05D5\u05D1",
1898
+ "\u05DC\u05D4\u05E8\u05D4\u05E8",
1899
+ "fikir",
1900
+ "berfikir"
1901
+ ];
1902
+ var MULTILINGUAL_PATTERNS = MULTILINGUAL_KEYWORDS.map((kw) => new RegExp(kw, "i"));
1903
+ var THINK_PATTERNS = [...ENGLISH_PATTERNS, ...MULTILINGUAL_PATTERNS];
1904
+ var CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
1905
+ var INLINE_CODE_PATTERN = /`[^`]+`/g;
1906
+ function removeCodeBlocks(text) {
1907
+ return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "");
1908
+ }
1909
+ function detectThinkKeyword(text) {
1910
+ const textWithoutCode = removeCodeBlocks(text);
1911
+ return THINK_PATTERNS.some((pattern) => pattern.test(textWithoutCode));
1912
+ }
1913
+ function extractPromptText(parts) {
1914
+ return parts.filter((p) => p.type === "text").map((p) => p.text || "").join("");
1915
+ }
1916
+
1917
+ // src/hooks/think-mode/switcher.ts
1918
+ var HIGH_VARIANT_MAP = {
1919
+ "claude-sonnet-4-5": "claude-sonnet-4-5-high",
1920
+ "claude-opus-4-5": "claude-opus-4-5-high",
1921
+ "gpt-5.1": "gpt-5.1-high",
1922
+ "gpt-5.1-medium": "gpt-5.1-high",
1923
+ "gpt-5.1-codex": "gpt-5.1-codex-high",
1924
+ "gemini-3-pro": "gemini-3-pro-high",
1925
+ "gemini-3-pro-low": "gemini-3-pro-high"
1926
+ };
1927
+ var ALREADY_HIGH = new Set([
1928
+ "claude-sonnet-4-5-high",
1929
+ "claude-opus-4-5-high",
1930
+ "gpt-5.1-high",
1931
+ "gpt-5.1-codex-high",
1932
+ "gemini-3-pro-high"
1933
+ ]);
1934
+ function getHighVariant(modelID) {
1935
+ if (ALREADY_HIGH.has(modelID)) {
1936
+ return null;
1937
+ }
1938
+ return HIGH_VARIANT_MAP[modelID] ?? null;
1939
+ }
1940
+ function isAlreadyHighVariant(modelID) {
1941
+ return ALREADY_HIGH.has(modelID) || modelID.endsWith("-high");
1942
+ }
1943
+
1944
+ // src/hooks/think-mode/index.ts
1945
+ var thinkModeState = new Map;
1946
+ function createThinkModeHook() {
1947
+ return {
1948
+ "chat.params": async (output, sessionID) => {
1949
+ const promptText = extractPromptText(output.parts);
1950
+ const state = {
1951
+ requested: false,
1952
+ modelSwitched: false
1953
+ };
1954
+ if (!detectThinkKeyword(promptText)) {
1955
+ thinkModeState.set(sessionID, state);
1956
+ return;
1957
+ }
1958
+ state.requested = true;
1959
+ const currentModel = output.message.model;
1960
+ if (!currentModel) {
1961
+ thinkModeState.set(sessionID, state);
1962
+ return;
1963
+ }
1964
+ state.providerID = currentModel.providerID;
1965
+ state.modelID = currentModel.modelID;
1966
+ if (isAlreadyHighVariant(currentModel.modelID)) {
1967
+ thinkModeState.set(sessionID, state);
1968
+ return;
1969
+ }
1970
+ const highVariant = getHighVariant(currentModel.modelID);
1971
+ if (!highVariant) {
1972
+ thinkModeState.set(sessionID, state);
1973
+ return;
1974
+ }
1975
+ output.message.model = {
1976
+ providerID: currentModel.providerID,
1977
+ modelID: highVariant
1978
+ };
1979
+ state.modelSwitched = true;
1980
+ thinkModeState.set(sessionID, state);
1981
+ },
1982
+ event: async ({ event }) => {
1983
+ if (event.type === "session.deleted") {
1984
+ const props = event.properties;
1985
+ if (props?.info?.id) {
1986
+ thinkModeState.delete(props.info.id);
1987
+ }
1988
+ }
1989
+ }
1990
+ };
1991
+ }
1992
+ // src/features/claude-code-command-loader/loader.ts
1993
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
1994
+ import { homedir as homedir2 } from "os";
1995
+ import { join as join8, basename } from "path";
1996
+
1997
+ // src/shared/frontmatter.ts
1998
+ function parseFrontmatter(content) {
1999
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
2000
+ const match = content.match(frontmatterRegex);
2001
+ if (!match) {
2002
+ return { data: {}, body: content };
2003
+ }
2004
+ const yamlContent = match[1];
2005
+ const body = match[2];
2006
+ const data = {};
2007
+ for (const line of yamlContent.split(`
2008
+ `)) {
2009
+ const colonIndex = line.indexOf(":");
2010
+ if (colonIndex !== -1) {
2011
+ const key = line.slice(0, colonIndex).trim();
2012
+ let value = line.slice(colonIndex + 1).trim();
2013
+ if (value === "true")
2014
+ value = true;
2015
+ else if (value === "false")
2016
+ value = false;
2017
+ data[key] = value;
2018
+ }
2019
+ }
2020
+ return { data, body };
2021
+ }
2022
+
2023
+ // src/shared/model-sanitizer.ts
2024
+ function sanitizeModelField(_model) {
2025
+ return;
2026
+ }
2027
+
2028
+ // src/features/claude-code-command-loader/loader.ts
2029
+ function isMarkdownFile(entry) {
2030
+ return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
2031
+ }
2032
+ function loadCommandsFromDir(commandsDir, scope) {
2033
+ if (!existsSync7(commandsDir)) {
2034
+ return [];
2035
+ }
2036
+ const entries = readdirSync2(commandsDir, { withFileTypes: true });
2037
+ const commands = [];
2038
+ for (const entry of entries) {
2039
+ if (!isMarkdownFile(entry))
2040
+ continue;
2041
+ const commandPath = join8(commandsDir, entry.name);
2042
+ const commandName = basename(entry.name, ".md");
2043
+ try {
2044
+ const content = readFileSync4(commandPath, "utf-8");
2045
+ const { data, body } = parseFrontmatter(content);
2046
+ const wrappedTemplate = `<command-instruction>
2047
+ ${body.trim()}
2048
+ </command-instruction>
2049
+
2050
+ <user-request>
2051
+ $ARGUMENTS
2052
+ </user-request>`;
2053
+ const formattedDescription = `(${scope}) ${data.description || ""}`;
2054
+ const definition = {
2055
+ name: commandName,
2056
+ description: formattedDescription,
2057
+ template: wrappedTemplate,
2058
+ agent: data.agent,
2059
+ model: sanitizeModelField(data.model),
2060
+ subtask: data.subtask,
2061
+ argumentHint: data["argument-hint"]
2062
+ };
2063
+ commands.push({
2064
+ name: commandName,
2065
+ path: commandPath,
2066
+ definition,
2067
+ scope
2068
+ });
2069
+ } catch {
2070
+ continue;
2071
+ }
2072
+ }
2073
+ return commands;
2074
+ }
2075
+ function commandsToRecord(commands) {
2076
+ const result = {};
2077
+ for (const cmd of commands) {
2078
+ result[cmd.name] = cmd.definition;
2079
+ }
2080
+ return result;
2081
+ }
2082
+ function loadUserCommands() {
2083
+ const userCommandsDir = join8(homedir2(), ".claude", "commands");
2084
+ const commands = loadCommandsFromDir(userCommandsDir, "user");
2085
+ return commandsToRecord(commands);
2086
+ }
2087
+ function loadProjectCommands() {
2088
+ const projectCommandsDir = join8(process.cwd(), ".claude", "commands");
2089
+ const commands = loadCommandsFromDir(projectCommandsDir, "project");
2090
+ return commandsToRecord(commands);
2091
+ }
2092
+ function loadOpencodeGlobalCommands() {
2093
+ const opencodeCommandsDir = join8(homedir2(), ".config", "opencode", "command");
2094
+ const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
2095
+ return commandsToRecord(commands);
2096
+ }
2097
+ function loadOpencodeProjectCommands() {
2098
+ const opencodeProjectDir = join8(process.cwd(), ".opencode", "command");
2099
+ const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
2100
+ return commandsToRecord(commands);
2101
+ }
2102
+ // src/features/claude-code-skill-loader/loader.ts
2103
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync, readlinkSync } from "fs";
2104
+ import { homedir as homedir3 } from "os";
2105
+ import { join as join9, resolve as resolve2 } from "path";
2106
+ function loadSkillsFromDir(skillsDir, scope) {
2107
+ if (!existsSync8(skillsDir)) {
2108
+ return [];
2109
+ }
2110
+ const entries = readdirSync3(skillsDir, { withFileTypes: true });
2111
+ const skills = [];
2112
+ for (const entry of entries) {
2113
+ if (entry.name.startsWith("."))
2114
+ continue;
2115
+ const skillPath = join9(skillsDir, entry.name);
2116
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
2117
+ continue;
2118
+ let resolvedPath = skillPath;
2119
+ if (statSync(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
2120
+ resolvedPath = resolve2(skillPath, "..", readlinkSync(skillPath));
2121
+ }
2122
+ const skillMdPath = join9(resolvedPath, "SKILL.md");
2123
+ if (!existsSync8(skillMdPath))
2124
+ continue;
2125
+ try {
2126
+ const content = readFileSync5(skillMdPath, "utf-8");
2127
+ const { data, body } = parseFrontmatter(content);
2128
+ const skillName = data.name || entry.name;
2129
+ const originalDescription = data.description || "";
2130
+ const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
2131
+ const wrappedTemplate = `<skill-instruction>
2132
+ ${body.trim()}
2133
+ </skill-instruction>
2134
+
2135
+ <user-request>
2136
+ $ARGUMENTS
2137
+ </user-request>`;
2138
+ const definition = {
2139
+ name: skillName,
2140
+ description: formattedDescription,
2141
+ template: wrappedTemplate,
2142
+ model: sanitizeModelField(data.model)
2143
+ };
2144
+ skills.push({
2145
+ name: skillName,
2146
+ path: resolvedPath,
2147
+ definition,
2148
+ scope
2149
+ });
2150
+ } catch {
2151
+ continue;
2152
+ }
2153
+ }
2154
+ return skills;
2155
+ }
2156
+ function loadUserSkillsAsCommands() {
2157
+ const userSkillsDir = join9(homedir3(), ".claude", "skills");
2158
+ const skills = loadSkillsFromDir(userSkillsDir, "user");
2159
+ return skills.reduce((acc, skill) => {
2160
+ acc[skill.name] = skill.definition;
2161
+ return acc;
2162
+ }, {});
2163
+ }
2164
+ function loadProjectSkillsAsCommands() {
2165
+ const projectSkillsDir = join9(process.cwd(), ".claude", "skills");
2166
+ const skills = loadSkillsFromDir(projectSkillsDir, "project");
2167
+ return skills.reduce((acc, skill) => {
2168
+ acc[skill.name] = skill.definition;
2169
+ return acc;
2170
+ }, {});
2171
+ }
2172
+ // src/features/claude-code-agent-loader/loader.ts
2173
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "fs";
2174
+ import { homedir as homedir4 } from "os";
2175
+ import { join as join10, basename as basename2 } from "path";
2176
+ function parseToolsConfig(toolsStr) {
2177
+ if (!toolsStr)
2178
+ return;
2179
+ const tools = toolsStr.split(",").map((t) => t.trim()).filter(Boolean);
2180
+ if (tools.length === 0)
2181
+ return;
2182
+ const result = {};
2183
+ for (const tool of tools) {
2184
+ result[tool.toLowerCase()] = true;
2185
+ }
2186
+ return result;
2187
+ }
2188
+ function isMarkdownFile2(entry) {
2189
+ return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
2190
+ }
2191
+ function loadAgentsFromDir(agentsDir, scope) {
2192
+ if (!existsSync9(agentsDir)) {
2193
+ return [];
2194
+ }
2195
+ const entries = readdirSync4(agentsDir, { withFileTypes: true });
2196
+ const agents = [];
2197
+ for (const entry of entries) {
2198
+ if (!isMarkdownFile2(entry))
2199
+ continue;
2200
+ const agentPath = join10(agentsDir, entry.name);
2201
+ const agentName = basename2(entry.name, ".md");
2202
+ try {
2203
+ const content = readFileSync6(agentPath, "utf-8");
2204
+ const { data, body } = parseFrontmatter(content);
2205
+ const name = data.name || agentName;
2206
+ const originalDescription = data.description || "";
2207
+ const formattedDescription = `(${scope}) ${originalDescription}`;
2208
+ const config = {
2209
+ description: formattedDescription,
2210
+ mode: "subagent",
2211
+ prompt: body.trim()
2212
+ };
2213
+ const toolsConfig = parseToolsConfig(data.tools);
2214
+ if (toolsConfig) {
2215
+ config.tools = toolsConfig;
2216
+ }
2217
+ agents.push({
2218
+ name,
2219
+ path: agentPath,
2220
+ config,
2221
+ scope
2222
+ });
2223
+ } catch {
2224
+ continue;
2225
+ }
2226
+ }
2227
+ return agents;
2228
+ }
2229
+ function loadUserAgents() {
2230
+ const userAgentsDir = join10(homedir4(), ".claude", "agents");
2231
+ const agents = loadAgentsFromDir(userAgentsDir, "user");
2232
+ const result = {};
2233
+ for (const agent of agents) {
2234
+ result[agent.name] = agent.config;
2235
+ }
2236
+ return result;
2237
+ }
2238
+ function loadProjectAgents() {
2239
+ const projectAgentsDir = join10(process.cwd(), ".claude", "agents");
2240
+ const agents = loadAgentsFromDir(projectAgentsDir, "project");
2241
+ const result = {};
2242
+ for (const agent of agents) {
2243
+ result[agent.name] = agent.config;
2244
+ }
2245
+ return result;
2246
+ }
2247
+ // src/features/claude-code-mcp-loader/loader.ts
2248
+ import { existsSync as existsSync10 } from "fs";
2249
+ import { homedir as homedir5 } from "os";
2250
+ import { join as join12 } from "path";
2251
+
2252
+ // src/features/claude-code-mcp-loader/env-expander.ts
2253
+ function expandEnvVars(value) {
2254
+ return value.replace(/\$\{([^}:]+)(?::-([^}]*))?\}/g, (_, varName, defaultValue) => {
2255
+ const envValue = process.env[varName];
2256
+ if (envValue !== undefined)
2257
+ return envValue;
2258
+ if (defaultValue !== undefined)
2259
+ return defaultValue;
2260
+ return "";
2261
+ });
2262
+ }
2263
+ function expandEnvVarsInObject(obj) {
2264
+ if (obj === null || obj === undefined)
2265
+ return obj;
2266
+ if (typeof obj === "string")
2267
+ return expandEnvVars(obj);
2268
+ if (Array.isArray(obj)) {
2269
+ return obj.map((item) => expandEnvVarsInObject(item));
2270
+ }
2271
+ if (typeof obj === "object") {
2272
+ const result = {};
2273
+ for (const [key, value] of Object.entries(obj)) {
2274
+ result[key] = expandEnvVarsInObject(value);
2275
+ }
2276
+ return result;
2277
+ }
2278
+ return obj;
2279
+ }
2280
+
2281
+ // src/features/claude-code-mcp-loader/transformer.ts
2282
+ function transformMcpServer(name, server) {
2283
+ const expanded = expandEnvVarsInObject(server);
2284
+ const serverType = expanded.type ?? "stdio";
2285
+ if (serverType === "http" || serverType === "sse") {
2286
+ if (!expanded.url) {
2287
+ throw new Error(`MCP server "${name}" requires url for type "${serverType}"`);
2288
+ }
2289
+ const config2 = {
2290
+ type: "remote",
2291
+ url: expanded.url,
2292
+ enabled: true
2293
+ };
2294
+ if (expanded.headers && Object.keys(expanded.headers).length > 0) {
2295
+ config2.headers = expanded.headers;
2296
+ }
2297
+ return config2;
2298
+ }
2299
+ if (!expanded.command) {
2300
+ throw new Error(`MCP server "${name}" requires command for stdio type`);
2301
+ }
2302
+ const commandArray = [expanded.command, ...expanded.args ?? []];
2303
+ const config = {
2304
+ type: "local",
2305
+ command: commandArray,
2306
+ enabled: true
2307
+ };
2308
+ if (expanded.env && Object.keys(expanded.env).length > 0) {
2309
+ config.environment = expanded.env;
2310
+ }
2311
+ return config;
2312
+ }
2313
+
2314
+ // src/shared/logger.ts
2315
+ import * as fs3 from "fs";
2316
+ import * as os2 from "os";
2317
+ import * as path2 from "path";
2318
+ var logFile = path2.join(os2.tmpdir(), "oh-my-opencode.log");
2319
+ function log(message, data) {
2320
+ try {
2321
+ const timestamp = new Date().toISOString();
2322
+ const logEntry = `[${timestamp}] ${message} ${data ? JSON.stringify(data) : ""}
2323
+ `;
2324
+ fs3.appendFileSync(logFile, logEntry);
2325
+ } catch {}
2326
+ }
2327
+
2328
+ // src/features/claude-code-mcp-loader/loader.ts
2329
+ function getMcpConfigPaths() {
2330
+ const home = homedir5();
2331
+ const cwd = process.cwd();
2332
+ return [
2333
+ { path: join12(home, ".claude", ".mcp.json"), scope: "user" },
2334
+ { path: join12(cwd, ".mcp.json"), scope: "project" },
2335
+ { path: join12(cwd, ".claude", ".mcp.json"), scope: "local" }
2336
+ ];
2337
+ }
2338
+ async function loadMcpConfigFile(filePath) {
2339
+ if (!existsSync10(filePath)) {
2340
+ return null;
2341
+ }
2342
+ try {
2343
+ const content = await Bun.file(filePath).text();
2344
+ return JSON.parse(content);
2345
+ } catch (error) {
2346
+ log(`Failed to load MCP config from ${filePath}`, error);
2347
+ return null;
2348
+ }
2349
+ }
2350
+ async function loadMcpConfigs() {
2351
+ const servers = {};
2352
+ const loadedServers = [];
2353
+ const paths = getMcpConfigPaths();
2354
+ for (const { path: path3, scope } of paths) {
2355
+ const config = await loadMcpConfigFile(path3);
2356
+ if (!config?.mcpServers)
2357
+ continue;
2358
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
2359
+ if (serverConfig.disabled) {
2360
+ log(`Skipping disabled MCP server "${name}"`, { path: path3 });
2361
+ continue;
2362
+ }
2363
+ try {
2364
+ const transformed = transformMcpServer(name, serverConfig);
2365
+ servers[name] = transformed;
2366
+ const existingIndex = loadedServers.findIndex((s) => s.name === name);
2367
+ if (existingIndex !== -1) {
2368
+ loadedServers.splice(existingIndex, 1);
2369
+ }
2370
+ loadedServers.push({ name, scope, config: transformed });
2371
+ log(`Loaded MCP server "${name}" from ${scope}`, { path: path3 });
2372
+ } catch (error) {
2373
+ log(`Failed to transform MCP server "${name}"`, error);
2374
+ }
2375
+ }
2376
+ }
2377
+ return { servers, loadedServers };
2378
+ }
2379
+ // src/features/claude-code-session-state/state.ts
2380
+ var sessionErrorState = new Map;
2381
+ var sessionInterruptState = new Map;
2382
+ var subagentSessions = new Set;
2383
+ var sessionFirstMessageProcessed = new Set;
2384
+ var currentSessionID;
2385
+ var currentSessionTitle;
2386
+ var mainSessionID;
2387
+ function setCurrentSession(id, title) {
2388
+ currentSessionID = id;
2389
+ currentSessionTitle = title;
2390
+ }
2391
+ function setMainSession(id) {
2392
+ mainSessionID = id;
2393
+ }
2394
+ function getCurrentSessionTitle() {
2395
+ return currentSessionTitle;
2396
+ }
2397
+ function getMainSessionID() {
2398
+ return mainSessionID;
2399
+ }
1814
2400
  // src/features/terminal/title.ts
1815
2401
  var STATUS_ICONS = {
1816
2402
  ready: "",
@@ -2036,14 +2622,14 @@ var EXT_TO_LANG = {
2036
2622
  ".tfvars": "terraform"
2037
2623
  };
2038
2624
  // src/tools/lsp/config.ts
2039
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
2040
- import { join as join8 } from "path";
2041
- import { homedir as homedir2 } from "os";
2042
- function loadJsonFile(path2) {
2043
- if (!existsSync7(path2))
2625
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
2626
+ import { join as join13 } from "path";
2627
+ import { homedir as homedir6 } from "os";
2628
+ function loadJsonFile(path3) {
2629
+ if (!existsSync11(path3))
2044
2630
  return null;
2045
2631
  try {
2046
- return JSON.parse(readFileSync4(path2, "utf-8"));
2632
+ return JSON.parse(readFileSync7(path3, "utf-8"));
2047
2633
  } catch {
2048
2634
  return null;
2049
2635
  }
@@ -2051,9 +2637,9 @@ function loadJsonFile(path2) {
2051
2637
  function getConfigPaths() {
2052
2638
  const cwd = process.cwd();
2053
2639
  return {
2054
- project: join8(cwd, ".opencode", "oh-my-opencode.json"),
2055
- user: join8(homedir2(), ".config", "opencode", "oh-my-opencode.json"),
2056
- opencode: join8(homedir2(), ".config", "opencode", "opencode.json")
2640
+ project: join13(cwd, ".opencode", "oh-my-opencode.json"),
2641
+ user: join13(homedir6(), ".config", "opencode", "oh-my-opencode.json"),
2642
+ opencode: join13(homedir6(), ".config", "opencode", "opencode.json")
2057
2643
  };
2058
2644
  }
2059
2645
  function loadAllConfigs() {
@@ -2146,7 +2732,7 @@ function isServerInstalled(command) {
2146
2732
  const pathEnv = process.env.PATH || "";
2147
2733
  const paths = pathEnv.split(":");
2148
2734
  for (const p of paths) {
2149
- if (existsSync7(join8(p, cmd))) {
2735
+ if (existsSync11(join13(p, cmd))) {
2150
2736
  return true;
2151
2737
  }
2152
2738
  }
@@ -2196,8 +2782,8 @@ function getAllServers() {
2196
2782
  }
2197
2783
  // src/tools/lsp/client.ts
2198
2784
  var {spawn: spawn3 } = globalThis.Bun;
2199
- import { readFileSync as readFileSync5 } from "fs";
2200
- import { extname, resolve as resolve2 } from "path";
2785
+ import { readFileSync as readFileSync8 } from "fs";
2786
+ import { extname, resolve as resolve3 } from "path";
2201
2787
  class LSPServerManager {
2202
2788
  static instance;
2203
2789
  clients = new Map;
@@ -2347,7 +2933,7 @@ class LSPClient {
2347
2933
  }
2348
2934
  this.startReading();
2349
2935
  this.startStderrReading();
2350
- await new Promise((resolve3) => setTimeout(resolve3, 100));
2936
+ await new Promise((resolve4) => setTimeout(resolve4, 100));
2351
2937
  if (this.proc.exitCode !== null) {
2352
2938
  const stderr = this.stderrBuffer.join(`
2353
2939
  `);
@@ -2484,8 +3070,8 @@ stderr: ${stderr}` : ""));
2484
3070
  \r
2485
3071
  `;
2486
3072
  this.proc.stdin.write(header + msg);
2487
- return new Promise((resolve3, reject) => {
2488
- this.pending.set(id, { resolve: resolve3, reject });
3073
+ return new Promise((resolve4, reject) => {
3074
+ this.pending.set(id, { resolve: resolve4, reject });
2489
3075
  setTimeout(() => {
2490
3076
  if (this.pending.has(id)) {
2491
3077
  this.pending.delete(id);
@@ -2593,10 +3179,10 @@ ${msg}`);
2593
3179
  await new Promise((r) => setTimeout(r, 300));
2594
3180
  }
2595
3181
  async openFile(filePath) {
2596
- const absPath = resolve2(filePath);
3182
+ const absPath = resolve3(filePath);
2597
3183
  if (this.openedFiles.has(absPath))
2598
3184
  return;
2599
- const text = readFileSync5(absPath, "utf-8");
3185
+ const text = readFileSync8(absPath, "utf-8");
2600
3186
  const ext = extname(absPath);
2601
3187
  const languageId = getLanguageId(ext);
2602
3188
  this.notify("textDocument/didOpen", {
@@ -2611,7 +3197,7 @@ ${msg}`);
2611
3197
  await new Promise((r) => setTimeout(r, 1000));
2612
3198
  }
2613
3199
  async hover(filePath, line, character) {
2614
- const absPath = resolve2(filePath);
3200
+ const absPath = resolve3(filePath);
2615
3201
  await this.openFile(absPath);
2616
3202
  return this.send("textDocument/hover", {
2617
3203
  textDocument: { uri: `file://${absPath}` },
@@ -2619,7 +3205,7 @@ ${msg}`);
2619
3205
  });
2620
3206
  }
2621
3207
  async definition(filePath, line, character) {
2622
- const absPath = resolve2(filePath);
3208
+ const absPath = resolve3(filePath);
2623
3209
  await this.openFile(absPath);
2624
3210
  return this.send("textDocument/definition", {
2625
3211
  textDocument: { uri: `file://${absPath}` },
@@ -2627,7 +3213,7 @@ ${msg}`);
2627
3213
  });
2628
3214
  }
2629
3215
  async references(filePath, line, character, includeDeclaration = true) {
2630
- const absPath = resolve2(filePath);
3216
+ const absPath = resolve3(filePath);
2631
3217
  await this.openFile(absPath);
2632
3218
  return this.send("textDocument/references", {
2633
3219
  textDocument: { uri: `file://${absPath}` },
@@ -2636,7 +3222,7 @@ ${msg}`);
2636
3222
  });
2637
3223
  }
2638
3224
  async documentSymbols(filePath) {
2639
- const absPath = resolve2(filePath);
3225
+ const absPath = resolve3(filePath);
2640
3226
  await this.openFile(absPath);
2641
3227
  return this.send("textDocument/documentSymbol", {
2642
3228
  textDocument: { uri: `file://${absPath}` }
@@ -2646,7 +3232,7 @@ ${msg}`);
2646
3232
  return this.send("workspace/symbol", { query });
2647
3233
  }
2648
3234
  async diagnostics(filePath) {
2649
- const absPath = resolve2(filePath);
3235
+ const absPath = resolve3(filePath);
2650
3236
  const uri = `file://${absPath}`;
2651
3237
  await this.openFile(absPath);
2652
3238
  await new Promise((r) => setTimeout(r, 500));
@@ -2661,7 +3247,7 @@ ${msg}`);
2661
3247
  return { items: this.diagnosticsStore.get(uri) ?? [] };
2662
3248
  }
2663
3249
  async prepareRename(filePath, line, character) {
2664
- const absPath = resolve2(filePath);
3250
+ const absPath = resolve3(filePath);
2665
3251
  await this.openFile(absPath);
2666
3252
  return this.send("textDocument/prepareRename", {
2667
3253
  textDocument: { uri: `file://${absPath}` },
@@ -2669,7 +3255,7 @@ ${msg}`);
2669
3255
  });
2670
3256
  }
2671
3257
  async rename(filePath, line, character, newName) {
2672
- const absPath = resolve2(filePath);
3258
+ const absPath = resolve3(filePath);
2673
3259
  await this.openFile(absPath);
2674
3260
  return this.send("textDocument/rename", {
2675
3261
  textDocument: { uri: `file://${absPath}` },
@@ -2678,7 +3264,7 @@ ${msg}`);
2678
3264
  });
2679
3265
  }
2680
3266
  async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
2681
- const absPath = resolve2(filePath);
3267
+ const absPath = resolve3(filePath);
2682
3268
  await this.openFile(absPath);
2683
3269
  return this.send("textDocument/codeAction", {
2684
3270
  textDocument: { uri: `file://${absPath}` },
@@ -2710,26 +3296,26 @@ ${msg}`);
2710
3296
  }
2711
3297
  }
2712
3298
  // src/tools/lsp/utils.ts
2713
- import { extname as extname2, resolve as resolve3 } from "path";
2714
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
3299
+ import { extname as extname2, resolve as resolve4 } from "path";
3300
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
2715
3301
  function findWorkspaceRoot(filePath) {
2716
- let dir = resolve3(filePath);
2717
- if (!existsSync8(dir) || !__require("fs").statSync(dir).isDirectory()) {
3302
+ let dir = resolve4(filePath);
3303
+ if (!existsSync12(dir) || !__require("fs").statSync(dir).isDirectory()) {
2718
3304
  dir = __require("path").dirname(dir);
2719
3305
  }
2720
3306
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
2721
3307
  while (dir !== "/") {
2722
3308
  for (const marker of markers) {
2723
- if (existsSync8(__require("path").join(dir, marker))) {
3309
+ if (existsSync12(__require("path").join(dir, marker))) {
2724
3310
  return dir;
2725
3311
  }
2726
3312
  }
2727
3313
  dir = __require("path").dirname(dir);
2728
3314
  }
2729
- return __require("path").dirname(resolve3(filePath));
3315
+ return __require("path").dirname(resolve4(filePath));
2730
3316
  }
2731
3317
  async function withLspClient(filePath, fn) {
2732
- const absPath = resolve3(filePath);
3318
+ const absPath = resolve4(filePath);
2733
3319
  const ext = extname2(absPath);
2734
3320
  const server = findServerForExtension(ext);
2735
3321
  if (!server) {
@@ -2878,7 +3464,7 @@ function formatCodeActions(actions) {
2878
3464
  }
2879
3465
  function applyTextEditsToFile(filePath, edits) {
2880
3466
  try {
2881
- let content = readFileSync6(filePath, "utf-8");
3467
+ let content = readFileSync9(filePath, "utf-8");
2882
3468
  const lines = content.split(`
2883
3469
  `);
2884
3470
  const sortedEdits = [...edits].sort((a, b) => {
@@ -2944,7 +3530,7 @@ function applyWorkspaceEdit(edit) {
2944
3530
  try {
2945
3531
  const oldPath = change.oldUri.replace("file://", "");
2946
3532
  const newPath = change.newUri.replace("file://", "");
2947
- const content = readFileSync6(oldPath, "utf-8");
3533
+ const content = readFileSync9(oldPath, "utf-8");
2948
3534
  writeFileSync3(newPath, content, "utf-8");
2949
3535
  __require("fs").unlinkSync(oldPath);
2950
3536
  result.filesModified.push(newPath);
@@ -3725,10 +4311,10 @@ function mergeDefs(...defs) {
3725
4311
  function cloneDef(schema) {
3726
4312
  return mergeDefs(schema._zod.def);
3727
4313
  }
3728
- function getElementAtPath(obj, path2) {
3729
- if (!path2)
4314
+ function getElementAtPath(obj, path3) {
4315
+ if (!path3)
3730
4316
  return obj;
3731
- return path2.reduce((acc, key) => acc?.[key], obj);
4317
+ return path3.reduce((acc, key) => acc?.[key], obj);
3732
4318
  }
3733
4319
  function promiseAllObject(promisesObj) {
3734
4320
  const keys = Object.keys(promisesObj);
@@ -4087,11 +4673,11 @@ function aborted(x, startIndex = 0) {
4087
4673
  }
4088
4674
  return false;
4089
4675
  }
4090
- function prefixIssues(path2, issues) {
4676
+ function prefixIssues(path3, issues) {
4091
4677
  return issues.map((iss) => {
4092
4678
  var _a;
4093
4679
  (_a = iss).path ?? (_a.path = []);
4094
- iss.path.unshift(path2);
4680
+ iss.path.unshift(path3);
4095
4681
  return iss;
4096
4682
  });
4097
4683
  }
@@ -4259,7 +4845,7 @@ function treeifyError(error, _mapper) {
4259
4845
  return issue2.message;
4260
4846
  };
4261
4847
  const result = { errors: [] };
4262
- const processError = (error2, path2 = []) => {
4848
+ const processError = (error2, path3 = []) => {
4263
4849
  var _a, _b;
4264
4850
  for (const issue2 of error2.issues) {
4265
4851
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -4269,7 +4855,7 @@ function treeifyError(error, _mapper) {
4269
4855
  } else if (issue2.code === "invalid_element") {
4270
4856
  processError({ issues: issue2.issues }, issue2.path);
4271
4857
  } else {
4272
- const fullpath = [...path2, ...issue2.path];
4858
+ const fullpath = [...path3, ...issue2.path];
4273
4859
  if (fullpath.length === 0) {
4274
4860
  result.errors.push(mapper(issue2));
4275
4861
  continue;
@@ -4301,8 +4887,8 @@ function treeifyError(error, _mapper) {
4301
4887
  }
4302
4888
  function toDotPath(_path) {
4303
4889
  const segs = [];
4304
- const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4305
- for (const seg of path2) {
4890
+ const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
4891
+ for (const seg of path3) {
4306
4892
  if (typeof seg === "number")
4307
4893
  segs.push(`[${seg}]`);
4308
4894
  else if (typeof seg === "symbol")
@@ -13080,10 +13666,10 @@ function _property(property, schema, params) {
13080
13666
  ...normalizeParams(params)
13081
13667
  });
13082
13668
  }
13083
- function _mime(types2, params) {
13669
+ function _mime(types8, params) {
13084
13670
  return new $ZodCheckMimeType({
13085
13671
  check: "mime_type",
13086
- mime: types2,
13672
+ mime: types8,
13087
13673
  ...normalizeParams(params)
13088
13674
  });
13089
13675
  }
@@ -14993,7 +15579,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
14993
15579
  ZodType.init(inst, def);
14994
15580
  inst.min = (size, params) => inst.check(_minSize(size, params));
14995
15581
  inst.max = (size, params) => inst.check(_maxSize(size, params));
14996
- inst.mime = (types2, params) => inst.check(_mime(Array.isArray(types2) ? types2 : [types2], params));
15582
+ inst.mime = (types8, params) => inst.check(_mime(Array.isArray(types8) ? types8 : [types8], params));
14997
15583
  });
14998
15584
  function file(params) {
14999
15585
  return _file(ZodFile, params);
@@ -15645,14 +16231,14 @@ var lsp_code_action_resolve = tool({
15645
16231
  });
15646
16232
  // src/tools/ast-grep/constants.ts
15647
16233
  import { createRequire as createRequire4 } from "module";
15648
- import { dirname as dirname3, join as join10 } from "path";
15649
- import { existsSync as existsSync10, statSync } from "fs";
16234
+ import { dirname as dirname3, join as join15 } from "path";
16235
+ import { existsSync as existsSync14, statSync as statSync2 } from "fs";
15650
16236
 
15651
16237
  // src/tools/ast-grep/downloader.ts
15652
16238
  var {spawn: spawn4 } = globalThis.Bun;
15653
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
15654
- import { join as join9 } from "path";
15655
- import { homedir as homedir3 } from "os";
16239
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
16240
+ import { join as join14 } from "path";
16241
+ import { homedir as homedir7 } from "os";
15656
16242
  import { createRequire as createRequire3 } from "module";
15657
16243
  var REPO2 = "ast-grep/ast-grep";
15658
16244
  var DEFAULT_VERSION = "0.40.0";
@@ -15677,19 +16263,19 @@ var PLATFORM_MAP2 = {
15677
16263
  function getCacheDir2() {
15678
16264
  if (process.platform === "win32") {
15679
16265
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
15680
- const base2 = localAppData || join9(homedir3(), "AppData", "Local");
15681
- return join9(base2, "oh-my-opencode", "bin");
16266
+ const base2 = localAppData || join14(homedir7(), "AppData", "Local");
16267
+ return join14(base2, "oh-my-opencode", "bin");
15682
16268
  }
15683
16269
  const xdgCache2 = process.env.XDG_CACHE_HOME;
15684
- const base = xdgCache2 || join9(homedir3(), ".cache");
15685
- return join9(base, "oh-my-opencode", "bin");
16270
+ const base = xdgCache2 || join14(homedir7(), ".cache");
16271
+ return join14(base, "oh-my-opencode", "bin");
15686
16272
  }
15687
16273
  function getBinaryName3() {
15688
16274
  return process.platform === "win32" ? "sg.exe" : "sg";
15689
16275
  }
15690
16276
  function getCachedBinaryPath2() {
15691
- const binaryPath = join9(getCacheDir2(), getBinaryName3());
15692
- return existsSync9(binaryPath) ? binaryPath : null;
16277
+ const binaryPath = join14(getCacheDir2(), getBinaryName3());
16278
+ return existsSync13(binaryPath) ? binaryPath : null;
15693
16279
  }
15694
16280
  async function extractZip2(archivePath, destDir) {
15695
16281
  const proc = process.platform === "win32" ? spawn4([
@@ -15715,30 +16301,30 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
15715
16301
  }
15716
16302
  const cacheDir = getCacheDir2();
15717
16303
  const binaryName = getBinaryName3();
15718
- const binaryPath = join9(cacheDir, binaryName);
15719
- if (existsSync9(binaryPath)) {
16304
+ const binaryPath = join14(cacheDir, binaryName);
16305
+ if (existsSync13(binaryPath)) {
15720
16306
  return binaryPath;
15721
16307
  }
15722
- const { arch, os: os2 } = platformInfo;
15723
- const assetName = `app-${arch}-${os2}.zip`;
16308
+ const { arch, os: os3 } = platformInfo;
16309
+ const assetName = `app-${arch}-${os3}.zip`;
15724
16310
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
15725
16311
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
15726
16312
  try {
15727
- if (!existsSync9(cacheDir)) {
16313
+ if (!existsSync13(cacheDir)) {
15728
16314
  mkdirSync4(cacheDir, { recursive: true });
15729
16315
  }
15730
16316
  const response = await fetch(downloadUrl, { redirect: "follow" });
15731
16317
  if (!response.ok) {
15732
16318
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
15733
16319
  }
15734
- const archivePath = join9(cacheDir, assetName);
16320
+ const archivePath = join14(cacheDir, assetName);
15735
16321
  const arrayBuffer = await response.arrayBuffer();
15736
16322
  await Bun.write(archivePath, arrayBuffer);
15737
16323
  await extractZip2(archivePath, cacheDir);
15738
- if (existsSync9(archivePath)) {
16324
+ if (existsSync13(archivePath)) {
15739
16325
  unlinkSync4(archivePath);
15740
16326
  }
15741
- if (process.platform !== "win32" && existsSync9(binaryPath)) {
16327
+ if (process.platform !== "win32" && existsSync13(binaryPath)) {
15742
16328
  chmodSync2(binaryPath, 493);
15743
16329
  }
15744
16330
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -15760,7 +16346,7 @@ async function ensureAstGrepBinary() {
15760
16346
  // src/tools/ast-grep/constants.ts
15761
16347
  function isValidBinary(filePath) {
15762
16348
  try {
15763
- return statSync(filePath).size > 1e4;
16349
+ return statSync2(filePath).size > 1e4;
15764
16350
  } catch {
15765
16351
  return false;
15766
16352
  }
@@ -15789,8 +16375,8 @@ function findSgCliPathSync() {
15789
16375
  const require2 = createRequire4(import.meta.url);
15790
16376
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
15791
16377
  const cliDir = dirname3(cliPkgPath);
15792
- const sgPath = join10(cliDir, binaryName);
15793
- if (existsSync10(sgPath) && isValidBinary(sgPath)) {
16378
+ const sgPath = join15(cliDir, binaryName);
16379
+ if (existsSync14(sgPath) && isValidBinary(sgPath)) {
15794
16380
  return sgPath;
15795
16381
  }
15796
16382
  } catch {}
@@ -15801,17 +16387,17 @@ function findSgCliPathSync() {
15801
16387
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
15802
16388
  const pkgDir = dirname3(pkgPath);
15803
16389
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
15804
- const binaryPath = join10(pkgDir, astGrepName);
15805
- if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
16390
+ const binaryPath = join15(pkgDir, astGrepName);
16391
+ if (existsSync14(binaryPath) && isValidBinary(binaryPath)) {
15806
16392
  return binaryPath;
15807
16393
  }
15808
16394
  } catch {}
15809
16395
  }
15810
16396
  if (process.platform === "darwin") {
15811
16397
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
15812
- for (const path2 of homebrewPaths) {
15813
- if (existsSync10(path2) && isValidBinary(path2)) {
15814
- return path2;
16398
+ for (const path3 of homebrewPaths) {
16399
+ if (existsSync14(path3) && isValidBinary(path3)) {
16400
+ return path3;
15815
16401
  }
15816
16402
  }
15817
16403
  }
@@ -15829,8 +16415,8 @@ function getSgCliPath() {
15829
16415
  }
15830
16416
  return "sg";
15831
16417
  }
15832
- function setSgCliPath(path2) {
15833
- resolvedCliPath2 = path2;
16418
+ function setSgCliPath(path3) {
16419
+ resolvedCliPath2 = path3;
15834
16420
  }
15835
16421
  var SG_CLI_PATH = getSgCliPath();
15836
16422
  var CLI_LANGUAGES = [
@@ -15866,11 +16452,11 @@ var DEFAULT_MAX_MATCHES = 500;
15866
16452
 
15867
16453
  // src/tools/ast-grep/cli.ts
15868
16454
  var {spawn: spawn5 } = globalThis.Bun;
15869
- import { existsSync as existsSync11 } from "fs";
16455
+ import { existsSync as existsSync15 } from "fs";
15870
16456
  var resolvedCliPath3 = null;
15871
16457
  var initPromise2 = null;
15872
16458
  async function getAstGrepPath() {
15873
- if (resolvedCliPath3 !== null && existsSync11(resolvedCliPath3)) {
16459
+ if (resolvedCliPath3 !== null && existsSync15(resolvedCliPath3)) {
15874
16460
  return resolvedCliPath3;
15875
16461
  }
15876
16462
  if (initPromise2) {
@@ -15878,7 +16464,7 @@ async function getAstGrepPath() {
15878
16464
  }
15879
16465
  initPromise2 = (async () => {
15880
16466
  const syncPath = findSgCliPathSync();
15881
- if (syncPath && existsSync11(syncPath)) {
16467
+ if (syncPath && existsSync15(syncPath)) {
15882
16468
  resolvedCliPath3 = syncPath;
15883
16469
  setSgCliPath(syncPath);
15884
16470
  return syncPath;
@@ -15912,7 +16498,7 @@ async function runSg(options) {
15912
16498
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
15913
16499
  args.push(...paths);
15914
16500
  let cliPath = getSgCliPath();
15915
- if (!existsSync11(cliPath) && cliPath !== "sg") {
16501
+ if (!existsSync15(cliPath) && cliPath !== "sg") {
15916
16502
  const downloadedPath = await getAstGrepPath();
15917
16503
  if (downloadedPath) {
15918
16504
  cliPath = downloadedPath;
@@ -16176,8 +16762,8 @@ var ast_grep_replace = tool({
16176
16762
  var {spawn: spawn6 } = globalThis.Bun;
16177
16763
 
16178
16764
  // src/tools/grep/constants.ts
16179
- import { existsSync as existsSync12 } from "fs";
16180
- import { join as join11, dirname as dirname4 } from "path";
16765
+ import { existsSync as existsSync16 } from "fs";
16766
+ import { join as join16, dirname as dirname4 } from "path";
16181
16767
  import { spawnSync } from "child_process";
16182
16768
  var cachedCli = null;
16183
16769
  function findExecutable(name) {
@@ -16198,13 +16784,13 @@ function getOpenCodeBundledRg() {
16198
16784
  const isWindows = process.platform === "win32";
16199
16785
  const rgName = isWindows ? "rg.exe" : "rg";
16200
16786
  const candidates = [
16201
- join11(execDir, rgName),
16202
- join11(execDir, "bin", rgName),
16203
- join11(execDir, "..", "bin", rgName),
16204
- join11(execDir, "..", "libexec", rgName)
16787
+ join16(execDir, rgName),
16788
+ join16(execDir, "bin", rgName),
16789
+ join16(execDir, "..", "bin", rgName),
16790
+ join16(execDir, "..", "libexec", rgName)
16205
16791
  ];
16206
16792
  for (const candidate of candidates) {
16207
- if (existsSync12(candidate)) {
16793
+ if (existsSync16(candidate)) {
16208
16794
  return candidate;
16209
16795
  }
16210
16796
  }
@@ -16601,7 +17187,572 @@ var glob = tool({
16601
17187
  }
16602
17188
  }
16603
17189
  });
17190
+ // src/tools/slashcommand/tools.ts
17191
+ import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
17192
+ import { homedir as homedir8 } from "os";
17193
+ import { join as join18, basename as basename3, dirname as dirname5 } from "path";
17194
+ // src/shared/command-executor.ts
17195
+ import { exec } from "child_process";
17196
+ import { promisify } from "util";
17197
+ var execAsync = promisify(exec);
17198
+ async function executeCommand(command) {
17199
+ try {
17200
+ const { stdout, stderr } = await execAsync(command);
17201
+ const out = stdout?.toString().trim() ?? "";
17202
+ const err = stderr?.toString().trim() ?? "";
17203
+ if (err) {
17204
+ if (out) {
17205
+ return `${out}
17206
+ [stderr: ${err}]`;
17207
+ }
17208
+ return `[stderr: ${err}]`;
17209
+ }
17210
+ return out;
17211
+ } catch (error45) {
17212
+ const e = error45;
17213
+ const stdout = e?.stdout?.toString().trim() ?? "";
17214
+ const stderr = e?.stderr?.toString().trim() ?? "";
17215
+ const errMsg = stderr || e?.message || String(error45);
17216
+ if (stdout) {
17217
+ return `${stdout}
17218
+ [stderr: ${errMsg}]`;
17219
+ }
17220
+ return `[stderr: ${errMsg}]`;
17221
+ }
17222
+ }
17223
+ var COMMAND_PATTERN = /!`([^`]+)`/g;
17224
+ function findCommands(text) {
17225
+ const matches = [];
17226
+ let match;
17227
+ COMMAND_PATTERN.lastIndex = 0;
17228
+ while ((match = COMMAND_PATTERN.exec(text)) !== null) {
17229
+ matches.push({
17230
+ fullMatch: match[0],
17231
+ command: match[1],
17232
+ start: match.index,
17233
+ end: match.index + match[0].length
17234
+ });
17235
+ }
17236
+ return matches;
17237
+ }
17238
+ async function resolveCommandsInText(text, depth = 0, maxDepth = 3) {
17239
+ if (depth >= maxDepth) {
17240
+ return text;
17241
+ }
17242
+ const matches = findCommands(text);
17243
+ if (matches.length === 0) {
17244
+ return text;
17245
+ }
17246
+ const tasks = matches.map((m) => executeCommand(m.command));
17247
+ const results = await Promise.allSettled(tasks);
17248
+ const replacements = new Map;
17249
+ matches.forEach((match, idx) => {
17250
+ const result = results[idx];
17251
+ if (result.status === "rejected") {
17252
+ replacements.set(match.fullMatch, `[error: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}]`);
17253
+ } else {
17254
+ replacements.set(match.fullMatch, result.value);
17255
+ }
17256
+ });
17257
+ let resolved = text;
17258
+ for (const [pattern, replacement] of replacements.entries()) {
17259
+ resolved = resolved.split(pattern).join(replacement);
17260
+ }
17261
+ if (findCommands(resolved).length > 0) {
17262
+ return resolveCommandsInText(resolved, depth + 1, maxDepth);
17263
+ }
17264
+ return resolved;
17265
+ }
17266
+ // src/shared/file-reference-resolver.ts
17267
+ import { existsSync as existsSync17, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
17268
+ import { join as join17, isAbsolute } from "path";
17269
+ var FILE_REFERENCE_PATTERN = /@([^\s@]+)/g;
17270
+ function findFileReferences(text) {
17271
+ const matches = [];
17272
+ let match;
17273
+ FILE_REFERENCE_PATTERN.lastIndex = 0;
17274
+ while ((match = FILE_REFERENCE_PATTERN.exec(text)) !== null) {
17275
+ matches.push({
17276
+ fullMatch: match[0],
17277
+ filePath: match[1],
17278
+ start: match.index,
17279
+ end: match.index + match[0].length
17280
+ });
17281
+ }
17282
+ return matches;
17283
+ }
17284
+ function resolveFilePath(filePath, cwd) {
17285
+ if (isAbsolute(filePath)) {
17286
+ return filePath;
17287
+ }
17288
+ return join17(cwd, filePath);
17289
+ }
17290
+ function readFileContent(resolvedPath) {
17291
+ if (!existsSync17(resolvedPath)) {
17292
+ return `[file not found: ${resolvedPath}]`;
17293
+ }
17294
+ const stat2 = statSync3(resolvedPath);
17295
+ if (stat2.isDirectory()) {
17296
+ return `[cannot read directory: ${resolvedPath}]`;
17297
+ }
17298
+ const content = readFileSync10(resolvedPath, "utf-8");
17299
+ return content;
17300
+ }
17301
+ async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0, maxDepth = 3) {
17302
+ if (depth >= maxDepth) {
17303
+ return text;
17304
+ }
17305
+ const matches = findFileReferences(text);
17306
+ if (matches.length === 0) {
17307
+ return text;
17308
+ }
17309
+ const replacements = new Map;
17310
+ for (const match of matches) {
17311
+ const resolvedPath = resolveFilePath(match.filePath, cwd);
17312
+ const content = readFileContent(resolvedPath);
17313
+ replacements.set(match.fullMatch, content);
17314
+ }
17315
+ let resolved = text;
17316
+ for (const [pattern, replacement] of replacements.entries()) {
17317
+ resolved = resolved.split(pattern).join(replacement);
17318
+ }
17319
+ if (findFileReferences(resolved).length > 0 && depth + 1 < maxDepth) {
17320
+ return resolveFileReferencesInText(resolved, cwd, depth + 1, maxDepth);
17321
+ }
17322
+ return resolved;
17323
+ }
17324
+ // src/tools/slashcommand/tools.ts
17325
+ function discoverCommandsFromDir(commandsDir, scope) {
17326
+ if (!existsSync18(commandsDir)) {
17327
+ return [];
17328
+ }
17329
+ const entries = readdirSync5(commandsDir, { withFileTypes: true });
17330
+ const commands = [];
17331
+ for (const entry of entries) {
17332
+ if (entry.name.startsWith("."))
17333
+ continue;
17334
+ if (!entry.name.endsWith(".md"))
17335
+ continue;
17336
+ if (!entry.isFile())
17337
+ continue;
17338
+ const commandPath = join18(commandsDir, entry.name);
17339
+ const commandName = basename3(entry.name, ".md");
17340
+ try {
17341
+ const content = readFileSync11(commandPath, "utf-8");
17342
+ const { data, body } = parseFrontmatter(content);
17343
+ const metadata = {
17344
+ name: commandName,
17345
+ description: data.description || "",
17346
+ argumentHint: data["argument-hint"],
17347
+ model: sanitizeModelField(data.model),
17348
+ agent: data.agent,
17349
+ subtask: Boolean(data.subtask)
17350
+ };
17351
+ commands.push({
17352
+ name: commandName,
17353
+ path: commandPath,
17354
+ metadata,
17355
+ content: body,
17356
+ scope
17357
+ });
17358
+ } catch {
17359
+ continue;
17360
+ }
17361
+ }
17362
+ return commands;
17363
+ }
17364
+ function discoverCommandsSync() {
17365
+ const userCommandsDir = join18(homedir8(), ".claude", "commands");
17366
+ const projectCommandsDir = join18(process.cwd(), ".claude", "commands");
17367
+ const opencodeGlobalDir = join18(homedir8(), ".config", "opencode", "command");
17368
+ const opencodeProjectDir = join18(process.cwd(), ".opencode", "command");
17369
+ const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
17370
+ const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
17371
+ const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
17372
+ const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project");
17373
+ return [...opencodeProjectCommands, ...projectCommands, ...opencodeGlobalCommands, ...userCommands];
17374
+ }
17375
+ var availableCommands = discoverCommandsSync();
17376
+ var commandListForDescription = availableCommands.map((cmd) => {
17377
+ const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
17378
+ return `- /${cmd.name}${hint}: ${cmd.metadata.description} (${cmd.scope})`;
17379
+ }).join(`
17380
+ `);
17381
+ async function formatLoadedCommand(cmd) {
17382
+ const sections = [];
17383
+ sections.push(`# /${cmd.name} Command
17384
+ `);
17385
+ if (cmd.metadata.description) {
17386
+ sections.push(`**Description**: ${cmd.metadata.description}
17387
+ `);
17388
+ }
17389
+ if (cmd.metadata.argumentHint) {
17390
+ sections.push(`**Usage**: /${cmd.name} ${cmd.metadata.argumentHint}
17391
+ `);
17392
+ }
17393
+ if (cmd.metadata.model) {
17394
+ sections.push(`**Model**: ${cmd.metadata.model}
17395
+ `);
17396
+ }
17397
+ if (cmd.metadata.agent) {
17398
+ sections.push(`**Agent**: ${cmd.metadata.agent}
17399
+ `);
17400
+ }
17401
+ if (cmd.metadata.subtask) {
17402
+ sections.push(`**Subtask**: true
17403
+ `);
17404
+ }
17405
+ sections.push(`**Scope**: ${cmd.scope}
17406
+ `);
17407
+ sections.push(`---
17408
+ `);
17409
+ sections.push(`## Command Instructions
17410
+ `);
17411
+ const commandDir = dirname5(cmd.path);
17412
+ const withFileRefs = await resolveFileReferencesInText(cmd.content, commandDir);
17413
+ const resolvedContent = await resolveCommandsInText(withFileRefs);
17414
+ sections.push(resolvedContent.trim());
17415
+ return sections.join(`
17416
+ `);
17417
+ }
17418
+ function formatCommandList(commands) {
17419
+ if (commands.length === 0) {
17420
+ return "No commands found.";
17421
+ }
17422
+ const lines = [`# Available Commands
17423
+ `];
17424
+ for (const cmd of commands) {
17425
+ const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
17426
+ lines.push(`- **/${cmd.name}${hint}**: ${cmd.metadata.description || "(no description)"} (${cmd.scope})`);
17427
+ }
17428
+ lines.push(`
17429
+ **Total**: ${commands.length} commands`);
17430
+ return lines.join(`
17431
+ `);
17432
+ }
17433
+ var slashcommand = tool({
17434
+ description: `Execute a slash command within the main conversation.
17435
+
17436
+ When you use this tool, the slash command gets expanded to a full prompt that provides detailed instructions on how to complete the task.
17437
+
17438
+ How slash commands work:
17439
+ - Invoke commands using this tool with the command name (without arguments)
17440
+ - The command's prompt will expand and provide detailed instructions
17441
+ - Arguments from user input should be passed separately
17442
+
17443
+ Important:
17444
+ - Only use commands listed in Available Commands below
17445
+ - Do not invoke a command that is already running
17446
+ - **CRITICAL**: When user's message starts with '/' (e.g., "/commit", "/plan"), you MUST immediately invoke this tool with that command. Do NOT attempt to handle the command manually.
17447
+
17448
+ Commands are loaded from (priority order, highest wins):
17449
+ - .opencode/command/ (opencode-project - OpenCode project-specific commands)
17450
+ - ./.claude/commands/ (project - Claude Code project-specific commands)
17451
+ - ~/.config/opencode/command/ (opencode - OpenCode global commands)
17452
+ - ~/.claude/commands/ (user - Claude Code global commands)
17453
+
17454
+ Each command is a markdown file with:
17455
+ - YAML frontmatter: description, argument-hint, model, agent, subtask (optional)
17456
+ - Markdown body: The command instructions/prompt
17457
+ - File references: @path/to/file (relative to command file location)
17458
+ - Shell injection: \`!\`command\`\` (executes and injects output)
17459
+
17460
+ Available Commands:
17461
+ ${commandListForDescription}`,
17462
+ args: {
17463
+ command: tool.schema.string().describe("The slash command to execute (without the leading slash). E.g., 'commit', 'plan', 'execute'.")
17464
+ },
17465
+ async execute(args) {
17466
+ const commands = discoverCommandsSync();
17467
+ if (!args.command) {
17468
+ return formatCommandList(commands) + `
17469
+
17470
+ Provide a command name to execute.`;
17471
+ }
17472
+ const cmdName = args.command.replace(/^\//, "");
17473
+ const exactMatch = commands.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
17474
+ if (exactMatch) {
17475
+ return await formatLoadedCommand(exactMatch);
17476
+ }
17477
+ const partialMatches = commands.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
17478
+ if (partialMatches.length > 0) {
17479
+ const matchList = partialMatches.map((cmd) => `/${cmd.name}`).join(", ");
17480
+ return `No exact match for "/${cmdName}". Did you mean: ${matchList}?
17481
+
17482
+ ` + formatCommandList(commands);
17483
+ }
17484
+ return `Command "/${cmdName}" not found.
17485
+
17486
+ ` + formatCommandList(commands) + `
17487
+
17488
+ Try a different command name.`;
17489
+ }
17490
+ });
17491
+ // src/tools/skill/tools.ts
17492
+ import { existsSync as existsSync19, readdirSync as readdirSync6, statSync as statSync4, readlinkSync as readlinkSync2, readFileSync as readFileSync12 } from "fs";
17493
+ import { homedir as homedir9 } from "os";
17494
+ import { join as join19, resolve as resolve5, basename as basename4 } from "path";
17495
+ function discoverSkillsFromDir(skillsDir, scope) {
17496
+ if (!existsSync19(skillsDir)) {
17497
+ return [];
17498
+ }
17499
+ const entries = readdirSync6(skillsDir, { withFileTypes: true });
17500
+ const skills = [];
17501
+ for (const entry of entries) {
17502
+ if (entry.name.startsWith("."))
17503
+ continue;
17504
+ const skillPath = join19(skillsDir, entry.name);
17505
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
17506
+ let resolvedPath = skillPath;
17507
+ try {
17508
+ const stats = statSync4(skillPath, { throwIfNoEntry: false });
17509
+ if (stats?.isSymbolicLink()) {
17510
+ resolvedPath = resolve5(skillPath, "..", readlinkSync2(skillPath));
17511
+ }
17512
+ } catch {
17513
+ continue;
17514
+ }
17515
+ const skillMdPath = join19(resolvedPath, "SKILL.md");
17516
+ if (!existsSync19(skillMdPath))
17517
+ continue;
17518
+ try {
17519
+ const content = readFileSync12(skillMdPath, "utf-8");
17520
+ const { data } = parseFrontmatter(content);
17521
+ skills.push({
17522
+ name: data.name || entry.name,
17523
+ description: data.description || "",
17524
+ scope
17525
+ });
17526
+ } catch {
17527
+ continue;
17528
+ }
17529
+ }
17530
+ }
17531
+ return skills;
17532
+ }
17533
+ function discoverSkillsSync() {
17534
+ const userSkillsDir = join19(homedir9(), ".claude", "skills");
17535
+ const projectSkillsDir = join19(process.cwd(), ".claude", "skills");
17536
+ const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
17537
+ const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
17538
+ return [...projectSkills, ...userSkills];
17539
+ }
17540
+ var availableSkills = discoverSkillsSync();
17541
+ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.description} (${s.scope})`).join(`
17542
+ `);
17543
+ function resolveSymlink(skillPath) {
17544
+ try {
17545
+ const stats = statSync4(skillPath, { throwIfNoEntry: false });
17546
+ if (stats?.isSymbolicLink()) {
17547
+ return resolve5(skillPath, "..", readlinkSync2(skillPath));
17548
+ }
17549
+ return skillPath;
17550
+ } catch {
17551
+ return skillPath;
17552
+ }
17553
+ }
17554
+ async function parseSkillMd(skillPath) {
17555
+ const resolvedPath = resolveSymlink(skillPath);
17556
+ const skillMdPath = join19(resolvedPath, "SKILL.md");
17557
+ if (!existsSync19(skillMdPath)) {
17558
+ return null;
17559
+ }
17560
+ try {
17561
+ let content = readFileSync12(skillMdPath, "utf-8");
17562
+ content = await resolveCommandsInText(content);
17563
+ const { data, body } = parseFrontmatter(content);
17564
+ const metadata = {
17565
+ name: data.name || basename4(skillPath),
17566
+ description: data.description || "",
17567
+ license: data.license
17568
+ };
17569
+ const referencesDir = join19(resolvedPath, "references");
17570
+ const scriptsDir = join19(resolvedPath, "scripts");
17571
+ const assetsDir = join19(resolvedPath, "assets");
17572
+ const references = existsSync19(referencesDir) ? readdirSync6(referencesDir).filter((f) => !f.startsWith(".")) : [];
17573
+ const scripts = existsSync19(scriptsDir) ? readdirSync6(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
17574
+ const assets = existsSync19(assetsDir) ? readdirSync6(assetsDir).filter((f) => !f.startsWith(".")) : [];
17575
+ return {
17576
+ name: metadata.name,
17577
+ path: resolvedPath,
17578
+ metadata,
17579
+ content: body,
17580
+ references,
17581
+ scripts,
17582
+ assets
17583
+ };
17584
+ } catch {
17585
+ return null;
17586
+ }
17587
+ }
17588
+ async function discoverSkillsFromDirAsync(skillsDir) {
17589
+ if (!existsSync19(skillsDir)) {
17590
+ return [];
17591
+ }
17592
+ const entries = readdirSync6(skillsDir, { withFileTypes: true });
17593
+ const skills = [];
17594
+ for (const entry of entries) {
17595
+ if (entry.name.startsWith("."))
17596
+ continue;
17597
+ const skillPath = join19(skillsDir, entry.name);
17598
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
17599
+ const skillInfo = await parseSkillMd(skillPath);
17600
+ if (skillInfo) {
17601
+ skills.push(skillInfo);
17602
+ }
17603
+ }
17604
+ }
17605
+ return skills;
17606
+ }
17607
+ async function discoverSkills() {
17608
+ const userSkillsDir = join19(homedir9(), ".claude", "skills");
17609
+ const projectSkillsDir = join19(process.cwd(), ".claude", "skills");
17610
+ const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
17611
+ const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
17612
+ return [...projectSkills, ...userSkills];
17613
+ }
17614
+ function findMatchingSkills(skills, query) {
17615
+ const queryLower = query.toLowerCase();
17616
+ const queryTerms = queryLower.split(/\s+/).filter(Boolean);
17617
+ return skills.map((skill) => {
17618
+ let score = 0;
17619
+ const nameLower = skill.metadata.name.toLowerCase();
17620
+ const descLower = skill.metadata.description.toLowerCase();
17621
+ if (nameLower === queryLower)
17622
+ score += 100;
17623
+ if (nameLower.includes(queryLower))
17624
+ score += 50;
17625
+ for (const term of queryTerms) {
17626
+ if (nameLower.includes(term))
17627
+ score += 20;
17628
+ if (descLower.includes(term))
17629
+ score += 10;
17630
+ }
17631
+ return { skill, score };
17632
+ }).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score).map(({ skill }) => skill);
17633
+ }
17634
+ async function loadSkillWithReferences(skill, includeRefs) {
17635
+ const referencesLoaded = [];
17636
+ if (includeRefs && skill.references.length > 0) {
17637
+ for (const ref of skill.references) {
17638
+ const refPath = join19(skill.path, "references", ref);
17639
+ try {
17640
+ let content = readFileSync12(refPath, "utf-8");
17641
+ content = await resolveCommandsInText(content);
17642
+ referencesLoaded.push({ path: ref, content });
17643
+ } catch {}
17644
+ }
17645
+ }
17646
+ return {
17647
+ name: skill.name,
17648
+ metadata: skill.metadata,
17649
+ body: skill.content,
17650
+ referencesLoaded
17651
+ };
17652
+ }
17653
+ function formatSkillList(skills) {
17654
+ if (skills.length === 0) {
17655
+ return "No skills found in ~/.claude/skills/";
17656
+ }
17657
+ const lines = [`# Available Skills
17658
+ `];
17659
+ for (const skill of skills) {
17660
+ lines.push(`- **${skill.metadata.name}**: ${skill.metadata.description || "(no description)"}`);
17661
+ }
17662
+ lines.push(`
17663
+ **Total**: ${skills.length} skills`);
17664
+ return lines.join(`
17665
+ `);
17666
+ }
17667
+ function formatLoadedSkills(loadedSkills) {
17668
+ if (loadedSkills.length === 0) {
17669
+ return "No skills loaded.";
17670
+ }
17671
+ const sections = [`# Loaded Skills
17672
+ `];
17673
+ for (const skill of loadedSkills) {
17674
+ sections.push(`## ${skill.metadata.name}
17675
+ `);
17676
+ sections.push(`**Description**: ${skill.metadata.description || "(no description)"}
17677
+ `);
17678
+ sections.push(`### Skill Instructions
17679
+ `);
17680
+ sections.push(skill.body.trim());
17681
+ if (skill.referencesLoaded.length > 0) {
17682
+ sections.push(`
17683
+ ### Loaded References
17684
+ `);
17685
+ for (const ref of skill.referencesLoaded) {
17686
+ sections.push(`#### ${ref.path}
17687
+ `);
17688
+ sections.push("```");
17689
+ sections.push(ref.content.trim());
17690
+ sections.push("```\n");
17691
+ }
17692
+ }
17693
+ sections.push(`
17694
+ ---
17695
+ `);
17696
+ }
17697
+ const skillNames = loadedSkills.map((s) => s.metadata.name).join(", ");
17698
+ sections.push(`**Skills loaded**: ${skillNames}`);
17699
+ sections.push(`**Total**: ${loadedSkills.length} skill(s)`);
17700
+ sections.push(`
17701
+ Please confirm these skills match your needs before proceeding.`);
17702
+ return sections.join(`
17703
+ `);
17704
+ }
17705
+ var skill = tool({
17706
+ description: `Execute a skill within the main conversation.
16604
17707
 
17708
+ When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
17709
+
17710
+ How to use skills:
17711
+ - Invoke skills using this tool with the skill name only (no arguments)
17712
+ - When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task
17713
+
17714
+ Important:
17715
+ - Only use skills listed in Available Skills below
17716
+ - Do not invoke a skill that is already running
17717
+
17718
+ Skills are loaded from:
17719
+ - ~/.claude/skills/ (user scope - global skills)
17720
+ - ./.claude/skills/ (project scope - project-specific skills)
17721
+
17722
+ Each skill contains:
17723
+ - SKILL.md: Main instructions with YAML frontmatter (name, description)
17724
+ - references/: Documentation files loaded into context as needed
17725
+ - scripts/: Executable code for deterministic operations
17726
+ - assets/: Files used in output (templates, icons, etc.)
17727
+
17728
+ Available Skills:
17729
+ ${skillListForDescription}`,
17730
+ args: {
17731
+ skill: tool.schema.string().describe("The skill name or search query to find and load. Can be exact skill name (e.g., 'python-programmer') or keywords (e.g., 'python', 'plan').")
17732
+ },
17733
+ async execute(args) {
17734
+ const skills = await discoverSkills();
17735
+ if (!args.skill) {
17736
+ return formatSkillList(skills) + `
17737
+
17738
+ Provide a skill name to load.`;
17739
+ }
17740
+ const matchingSkills = findMatchingSkills(skills, args.skill);
17741
+ if (matchingSkills.length === 0) {
17742
+ return `No skills found matching "${args.skill}".
17743
+
17744
+ ` + formatSkillList(skills) + `
17745
+
17746
+ Try a different skill name.`;
17747
+ }
17748
+ const loadedSkills = [];
17749
+ for (const skillInfo of matchingSkills.slice(0, 3)) {
17750
+ const loaded = await loadSkillWithReferences(skillInfo, true);
17751
+ loadedSkills.push(loaded);
17752
+ }
17753
+ return formatLoadedSkills(loadedSkills);
17754
+ }
17755
+ });
16605
17756
  // src/tools/index.ts
16606
17757
  var builtinTools = {
16607
17758
  lsp_hover,
@@ -16618,7 +17769,9 @@ var builtinTools = {
16618
17769
  ast_grep_search,
16619
17770
  ast_grep_replace,
16620
17771
  grep,
16621
- glob
17772
+ glob,
17773
+ slashcommand,
17774
+ skill
16622
17775
  };
16623
17776
 
16624
17777
  // src/mcp/websearch-exa.ts
@@ -16693,24 +17846,21 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
16693
17846
  agents: AgentOverridesSchema.optional()
16694
17847
  });
16695
17848
  // src/index.ts
16696
- import * as fs3 from "fs";
16697
- import * as path2 from "path";
17849
+ import * as fs4 from "fs";
17850
+ import * as path3 from "path";
16698
17851
  function loadPluginConfig(directory) {
16699
17852
  const configPaths = [
16700
- path2.join(directory, "oh-my-opencode.json"),
16701
- path2.join(directory, ".oh-my-opencode.json")
17853
+ path3.join(directory, "oh-my-opencode.json"),
17854
+ path3.join(directory, ".oh-my-opencode.json")
16702
17855
  ];
16703
17856
  for (const configPath of configPaths) {
16704
17857
  try {
16705
- if (fs3.existsSync(configPath)) {
16706
- const content = fs3.readFileSync(configPath, "utf-8");
17858
+ if (fs4.existsSync(configPath)) {
17859
+ const content = fs4.readFileSync(configPath, "utf-8");
16707
17860
  const rawConfig = JSON.parse(content);
16708
17861
  const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
16709
17862
  if (!result.success) {
16710
- console.error(`[oh-my-opencode] Config validation error in ${configPath}:`);
16711
- for (const issue2 of result.error.issues) {
16712
- console.error(` - ${issue2.path.join(".")}: ${issue2.message}`);
16713
- }
17863
+ log(`Config validation error in ${configPath}:`, result.error.issues);
16714
17864
  return {};
16715
17865
  }
16716
17866
  return result.data;
@@ -16727,66 +17877,84 @@ var OhMyOpenCodePlugin = async (ctx) => {
16727
17877
  const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
16728
17878
  const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
16729
17879
  const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
17880
+ const thinkMode = createThinkModeHook();
16730
17881
  updateTerminalTitle({ sessionId: "main" });
16731
17882
  const pluginConfig = loadPluginConfig(ctx.directory);
16732
- let mainSessionID;
16733
- let currentSessionID;
16734
- let currentSessionTitle;
16735
17883
  return {
16736
17884
  tool: builtinTools,
16737
17885
  config: async (config3) => {
16738
- const agents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents);
17886
+ const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents);
17887
+ const userAgents = loadUserAgents();
17888
+ const projectAgents = loadProjectAgents();
16739
17889
  config3.agent = {
16740
17890
  ...config3.agent,
16741
- ...agents
17891
+ ...builtinAgents,
17892
+ ...userAgents,
17893
+ ...projectAgents
16742
17894
  };
16743
17895
  config3.tools = {
16744
17896
  ...config3.tools
16745
17897
  };
17898
+ const mcpResult = await loadMcpConfigs();
16746
17899
  config3.mcp = {
16747
17900
  ...config3.mcp,
16748
- ...createBuiltinMcps(pluginConfig.disabled_mcps)
17901
+ ...createBuiltinMcps(pluginConfig.disabled_mcps),
17902
+ ...mcpResult.servers
17903
+ };
17904
+ const userCommands = loadUserCommands();
17905
+ const opencodeGlobalCommands = loadOpencodeGlobalCommands();
17906
+ const systemCommands = config3.command ?? {};
17907
+ const projectCommands = loadProjectCommands();
17908
+ const opencodeProjectCommands = loadOpencodeProjectCommands();
17909
+ const userSkills = loadUserSkillsAsCommands();
17910
+ const projectSkills = loadProjectSkillsAsCommands();
17911
+ config3.command = {
17912
+ ...userCommands,
17913
+ ...userSkills,
17914
+ ...opencodeGlobalCommands,
17915
+ ...systemCommands,
17916
+ ...projectCommands,
17917
+ ...projectSkills,
17918
+ ...opencodeProjectCommands
16749
17919
  };
16750
17920
  },
16751
17921
  event: async (input) => {
16752
17922
  await todoContinuationEnforcer(input);
16753
17923
  await contextWindowMonitor.event(input);
16754
17924
  await directoryAgentsInjector.event(input);
17925
+ await thinkMode.event(input);
16755
17926
  const { event } = input;
16756
17927
  const props = event.properties;
16757
17928
  if (event.type === "session.created") {
16758
17929
  const sessionInfo = props?.info;
16759
17930
  if (!sessionInfo?.parentID) {
16760
- mainSessionID = sessionInfo?.id;
16761
- currentSessionID = sessionInfo?.id;
16762
- currentSessionTitle = sessionInfo?.title;
17931
+ setMainSession(sessionInfo?.id);
17932
+ setCurrentSession(sessionInfo?.id, sessionInfo?.title);
16763
17933
  updateTerminalTitle({
16764
- sessionId: currentSessionID || "main",
17934
+ sessionId: sessionInfo?.id || "main",
16765
17935
  status: "idle",
16766
17936
  directory: ctx.directory,
16767
- sessionTitle: currentSessionTitle
17937
+ sessionTitle: sessionInfo?.title
16768
17938
  });
16769
17939
  }
16770
17940
  }
16771
17941
  if (event.type === "session.updated") {
16772
17942
  const sessionInfo = props?.info;
16773
17943
  if (!sessionInfo?.parentID) {
16774
- currentSessionID = sessionInfo?.id;
16775
- currentSessionTitle = sessionInfo?.title;
17944
+ setCurrentSession(sessionInfo?.id, sessionInfo?.title);
16776
17945
  updateTerminalTitle({
16777
- sessionId: currentSessionID || "main",
17946
+ sessionId: sessionInfo?.id || "main",
16778
17947
  status: "processing",
16779
17948
  directory: ctx.directory,
16780
- sessionTitle: currentSessionTitle
17949
+ sessionTitle: sessionInfo?.title
16781
17950
  });
16782
17951
  }
16783
17952
  }
16784
17953
  if (event.type === "session.deleted") {
16785
17954
  const sessionInfo = props?.info;
16786
- if (sessionInfo?.id === mainSessionID) {
16787
- mainSessionID = undefined;
16788
- currentSessionID = undefined;
16789
- currentSessionTitle = undefined;
17955
+ if (sessionInfo?.id === getMainSessionID()) {
17956
+ setMainSession(undefined);
17957
+ setCurrentSession(undefined, undefined);
16790
17958
  updateTerminalTitle({
16791
17959
  sessionId: "main",
16792
17960
  status: "idle"
@@ -16804,7 +17972,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
16804
17972
  error: error45
16805
17973
  };
16806
17974
  const recovered = await sessionRecovery.handleSessionRecovery(messageInfo);
16807
- if (recovered && sessionID && sessionID === mainSessionID) {
17975
+ if (recovered && sessionID && sessionID === getMainSessionID()) {
16808
17976
  await ctx.client.session.prompt({
16809
17977
  path: { id: sessionID },
16810
17978
  body: { parts: [{ type: "text", text: "continue" }] },
@@ -16812,36 +17980,36 @@ var OhMyOpenCodePlugin = async (ctx) => {
16812
17980
  }).catch(() => {});
16813
17981
  }
16814
17982
  }
16815
- if (sessionID && sessionID === mainSessionID) {
17983
+ if (sessionID && sessionID === getMainSessionID()) {
16816
17984
  updateTerminalTitle({
16817
17985
  sessionId: sessionID,
16818
17986
  status: "error",
16819
17987
  directory: ctx.directory,
16820
- sessionTitle: currentSessionTitle
17988
+ sessionTitle: getCurrentSessionTitle()
16821
17989
  });
16822
17990
  }
16823
17991
  }
16824
17992
  if (event.type === "session.idle") {
16825
17993
  const sessionID = props?.sessionID;
16826
- if (sessionID && sessionID === mainSessionID) {
17994
+ if (sessionID && sessionID === getMainSessionID()) {
16827
17995
  updateTerminalTitle({
16828
17996
  sessionId: sessionID,
16829
17997
  status: "idle",
16830
17998
  directory: ctx.directory,
16831
- sessionTitle: currentSessionTitle
17999
+ sessionTitle: getCurrentSessionTitle()
16832
18000
  });
16833
18001
  }
16834
18002
  }
16835
18003
  },
16836
18004
  "tool.execute.before": async (input, output) => {
16837
18005
  await commentChecker["tool.execute.before"](input, output);
16838
- if (input.sessionID === mainSessionID) {
18006
+ if (input.sessionID === getMainSessionID()) {
16839
18007
  updateTerminalTitle({
16840
18008
  sessionId: input.sessionID,
16841
18009
  status: "tool",
16842
18010
  currentTool: input.tool,
16843
18011
  directory: ctx.directory,
16844
- sessionTitle: currentSessionTitle
18012
+ sessionTitle: getCurrentSessionTitle()
16845
18013
  });
16846
18014
  }
16847
18015
  },
@@ -16851,12 +18019,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
16851
18019
  await commentChecker["tool.execute.after"](input, output);
16852
18020
  await directoryAgentsInjector["tool.execute.after"](input, output);
16853
18021
  await emptyTaskResponseDetector["tool.execute.after"](input, output);
16854
- if (input.sessionID === mainSessionID) {
18022
+ if (input.sessionID === getMainSessionID()) {
16855
18023
  updateTerminalTitle({
16856
18024
  sessionId: input.sessionID,
16857
18025
  status: "idle",
16858
18026
  directory: ctx.directory,
16859
- sessionTitle: currentSessionTitle
18027
+ sessionTitle: getCurrentSessionTitle()
16860
18028
  });
16861
18029
  }
16862
18030
  }