oh-my-opencode 0.1.31 → 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.
- package/README.ko.md +13 -0
- package/README.md +24 -0
- package/dist/features/claude-code-agent-loader/index.d.ts +2 -0
- package/dist/features/claude-code-agent-loader/loader.d.ts +3 -0
- package/dist/features/claude-code-agent-loader/types.d.ts +14 -0
- package/dist/features/claude-code-command-loader/index.d.ts +2 -0
- package/dist/features/claude-code-command-loader/loader.d.ts +5 -0
- package/dist/features/claude-code-command-loader/types.d.ts +23 -0
- package/dist/features/claude-code-mcp-loader/env-expander.d.ts +2 -0
- package/dist/features/claude-code-mcp-loader/index.d.ts +10 -0
- package/dist/features/claude-code-mcp-loader/loader.d.ts +3 -0
- package/dist/features/claude-code-mcp-loader/transformer.d.ts +2 -0
- package/dist/features/claude-code-mcp-loader/types.d.ts +35 -0
- package/dist/features/claude-code-session-state/detector.d.ts +1 -0
- package/dist/features/claude-code-session-state/index.d.ts +3 -0
- package/dist/features/claude-code-session-state/state.d.ts +13 -0
- package/dist/features/claude-code-session-state/types.d.ts +7 -0
- package/dist/features/claude-code-skill-loader/index.d.ts +2 -0
- package/dist/features/claude-code-skill-loader/loader.d.ts +3 -0
- package/dist/features/claude-code-skill-loader/types.d.ts +13 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/think-mode/detector.d.ts +5 -0
- package/dist/hooks/think-mode/index.d.ts +14 -0
- package/dist/hooks/think-mode/switcher.d.ts +4 -0
- package/dist/hooks/think-mode/types.d.ts +20 -0
- package/dist/index.js +1293 -125
- package/dist/shared/command-executor.d.ts +21 -0
- package/dist/shared/file-reference-resolver.d.ts +1 -0
- package/dist/shared/frontmatter.d.ts +5 -0
- package/dist/shared/index.d.ts +5 -0
- package/dist/shared/logger.d.ts +2 -0
- package/dist/shared/model-sanitizer.d.ts +11 -0
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/skill/index.d.ts +2 -0
- package/dist/tools/skill/tools.d.ts +9 -0
- package/dist/tools/skill/types.d.ts +24 -0
- package/dist/tools/slashcommand/index.d.ts +2 -0
- package/dist/tools/slashcommand/tools.d.ts +9 -0
- package/dist/tools/slashcommand/types.d.ts +16 -0
- package/package.json +2 -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
|
|
2040
|
-
import { join as
|
|
2041
|
-
import { homedir as
|
|
2042
|
-
function loadJsonFile(
|
|
2043
|
-
if (!
|
|
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(
|
|
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:
|
|
2055
|
-
user:
|
|
2056
|
-
opencode:
|
|
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 (
|
|
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
|
|
2200
|
-
import { extname, resolve as
|
|
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((
|
|
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((
|
|
2488
|
-
this.pending.set(id, { resolve:
|
|
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 =
|
|
3182
|
+
const absPath = resolve3(filePath);
|
|
2597
3183
|
if (this.openedFiles.has(absPath))
|
|
2598
3184
|
return;
|
|
2599
|
-
const text =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2714
|
-
import { existsSync as
|
|
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 =
|
|
2717
|
-
if (!
|
|
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 (
|
|
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(
|
|
3315
|
+
return __require("path").dirname(resolve4(filePath));
|
|
2730
3316
|
}
|
|
2731
3317
|
async function withLspClient(filePath, fn) {
|
|
2732
|
-
const absPath =
|
|
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 =
|
|
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 =
|
|
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,
|
|
3729
|
-
if (!
|
|
4314
|
+
function getElementAtPath(obj, path3) {
|
|
4315
|
+
if (!path3)
|
|
3730
4316
|
return obj;
|
|
3731
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
4305
|
-
for (const seg of
|
|
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(
|
|
13669
|
+
function _mime(types8, params) {
|
|
13084
13670
|
return new $ZodCheckMimeType({
|
|
13085
13671
|
check: "mime_type",
|
|
13086
|
-
mime:
|
|
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 = (
|
|
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
|
|
15649
|
-
import { existsSync as
|
|
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
|
|
15654
|
-
import { join as
|
|
15655
|
-
import { homedir as
|
|
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 ||
|
|
15681
|
-
return
|
|
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 ||
|
|
15685
|
-
return
|
|
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 =
|
|
15692
|
-
return
|
|
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 =
|
|
15719
|
-
if (
|
|
16304
|
+
const binaryPath = join14(cacheDir, binaryName);
|
|
16305
|
+
if (existsSync13(binaryPath)) {
|
|
15720
16306
|
return binaryPath;
|
|
15721
16307
|
}
|
|
15722
|
-
const { arch, os:
|
|
15723
|
-
const assetName = `app-${arch}-${
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
16324
|
+
if (existsSync13(archivePath)) {
|
|
15739
16325
|
unlinkSync4(archivePath);
|
|
15740
16326
|
}
|
|
15741
|
-
if (process.platform !== "win32" &&
|
|
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
|
|
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 =
|
|
15793
|
-
if (
|
|
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 =
|
|
15805
|
-
if (
|
|
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
|
|
15813
|
-
if (
|
|
15814
|
-
return
|
|
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(
|
|
15833
|
-
resolvedCliPath2 =
|
|
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
|
|
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 &&
|
|
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 &&
|
|
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 (!
|
|
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
|
|
16180
|
-
import { join as
|
|
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
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
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 (
|
|
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
|
|
16697
|
-
import * as
|
|
17849
|
+
import * as fs4 from "fs";
|
|
17850
|
+
import * as path3 from "path";
|
|
16698
17851
|
function loadPluginConfig(directory) {
|
|
16699
17852
|
const configPaths = [
|
|
16700
|
-
|
|
16701
|
-
|
|
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 (
|
|
16706
|
-
const content =
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
16761
|
-
|
|
16762
|
-
currentSessionTitle = sessionInfo?.title;
|
|
17931
|
+
setMainSession(sessionInfo?.id);
|
|
17932
|
+
setCurrentSession(sessionInfo?.id, sessionInfo?.title);
|
|
16763
17933
|
updateTerminalTitle({
|
|
16764
|
-
sessionId:
|
|
17934
|
+
sessionId: sessionInfo?.id || "main",
|
|
16765
17935
|
status: "idle",
|
|
16766
17936
|
directory: ctx.directory,
|
|
16767
|
-
sessionTitle:
|
|
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
|
-
|
|
16775
|
-
currentSessionTitle = sessionInfo?.title;
|
|
17944
|
+
setCurrentSession(sessionInfo?.id, sessionInfo?.title);
|
|
16776
17945
|
updateTerminalTitle({
|
|
16777
|
-
sessionId:
|
|
17946
|
+
sessionId: sessionInfo?.id || "main",
|
|
16778
17947
|
status: "processing",
|
|
16779
17948
|
directory: ctx.directory,
|
|
16780
|
-
sessionTitle:
|
|
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 ===
|
|
16787
|
-
|
|
16788
|
-
|
|
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 ===
|
|
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 ===
|
|
17983
|
+
if (sessionID && sessionID === getMainSessionID()) {
|
|
16816
17984
|
updateTerminalTitle({
|
|
16817
17985
|
sessionId: sessionID,
|
|
16818
17986
|
status: "error",
|
|
16819
17987
|
directory: ctx.directory,
|
|
16820
|
-
sessionTitle:
|
|
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 ===
|
|
17994
|
+
if (sessionID && sessionID === getMainSessionID()) {
|
|
16827
17995
|
updateTerminalTitle({
|
|
16828
17996
|
sessionId: sessionID,
|
|
16829
17997
|
status: "idle",
|
|
16830
17998
|
directory: ctx.directory,
|
|
16831
|
-
sessionTitle:
|
|
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 ===
|
|
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:
|
|
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 ===
|
|
18022
|
+
if (input.sessionID === getMainSessionID()) {
|
|
16855
18023
|
updateTerminalTitle({
|
|
16856
18024
|
sessionId: input.sessionID,
|
|
16857
18025
|
status: "idle",
|
|
16858
18026
|
directory: ctx.directory,
|
|
16859
|
-
sessionTitle:
|
|
18027
|
+
sessionTitle: getCurrentSessionTitle()
|
|
16860
18028
|
});
|
|
16861
18029
|
}
|
|
16862
18030
|
}
|