oh-my-opencode 0.3.0 → 0.3.2

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/dist/index.js CHANGED
@@ -633,6 +633,7 @@ function createTodoContinuationEnforcer(ctx) {
633
633
  const remindedSessions = new Set;
634
634
  const interruptedSessions = new Set;
635
635
  const errorSessions = new Set;
636
+ const pendingTimers = new Map;
636
637
  return async ({ event }) => {
637
638
  const props = event.properties;
638
639
  if (event.type === "session.error") {
@@ -642,6 +643,11 @@ function createTodoContinuationEnforcer(ctx) {
642
643
  if (detectInterrupt(props?.error)) {
643
644
  interruptedSessions.add(sessionID);
644
645
  }
646
+ const timer = pendingTimers.get(sessionID);
647
+ if (timer) {
648
+ clearTimeout(timer);
649
+ pendingTimers.delete(sessionID);
650
+ }
645
651
  }
646
652
  return;
647
653
  }
@@ -649,61 +655,73 @@ function createTodoContinuationEnforcer(ctx) {
649
655
  const sessionID = props?.sessionID;
650
656
  if (!sessionID)
651
657
  return;
652
- await new Promise((resolve) => setTimeout(resolve, 150));
653
- const shouldBypass = interruptedSessions.has(sessionID) || errorSessions.has(sessionID);
654
- interruptedSessions.delete(sessionID);
655
- errorSessions.delete(sessionID);
656
- if (shouldBypass) {
657
- return;
658
- }
659
- if (remindedSessions.has(sessionID)) {
660
- return;
661
- }
662
- let todos = [];
663
- try {
664
- const response = await ctx.client.session.todo({
665
- path: { id: sessionID }
666
- });
667
- todos = response.data ?? response;
668
- } catch {
669
- return;
670
- }
671
- if (!todos || todos.length === 0) {
672
- return;
673
- }
674
- const incomplete = todos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
675
- if (incomplete.length === 0) {
676
- return;
677
- }
678
- remindedSessions.add(sessionID);
679
- if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID)) {
680
- remindedSessions.delete(sessionID);
681
- return;
682
- }
683
- try {
684
- await ctx.client.session.prompt({
685
- path: { id: sessionID },
686
- body: {
687
- parts: [
688
- {
689
- type: "text",
690
- text: `${CONTINUATION_PROMPT}
658
+ const existingTimer = pendingTimers.get(sessionID);
659
+ if (existingTimer) {
660
+ clearTimeout(existingTimer);
661
+ }
662
+ const timer = setTimeout(async () => {
663
+ pendingTimers.delete(sessionID);
664
+ const shouldBypass = interruptedSessions.has(sessionID) || errorSessions.has(sessionID);
665
+ interruptedSessions.delete(sessionID);
666
+ errorSessions.delete(sessionID);
667
+ if (shouldBypass) {
668
+ return;
669
+ }
670
+ if (remindedSessions.has(sessionID)) {
671
+ return;
672
+ }
673
+ let todos = [];
674
+ try {
675
+ const response = await ctx.client.session.todo({
676
+ path: { id: sessionID }
677
+ });
678
+ todos = response.data ?? response;
679
+ } catch {
680
+ return;
681
+ }
682
+ if (!todos || todos.length === 0) {
683
+ return;
684
+ }
685
+ const incomplete = todos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
686
+ if (incomplete.length === 0) {
687
+ return;
688
+ }
689
+ remindedSessions.add(sessionID);
690
+ if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID)) {
691
+ remindedSessions.delete(sessionID);
692
+ return;
693
+ }
694
+ try {
695
+ await ctx.client.session.prompt({
696
+ path: { id: sessionID },
697
+ body: {
698
+ parts: [
699
+ {
700
+ type: "text",
701
+ text: `${CONTINUATION_PROMPT}
691
702
 
692
703
  [Status: ${todos.length - incomplete.length}/${todos.length} completed, ${incomplete.length} remaining]`
693
- }
694
- ]
695
- },
696
- query: { directory: ctx.directory }
697
- });
698
- } catch {
699
- remindedSessions.delete(sessionID);
700
- }
704
+ }
705
+ ]
706
+ },
707
+ query: { directory: ctx.directory }
708
+ });
709
+ } catch {
710
+ remindedSessions.delete(sessionID);
711
+ }
712
+ }, 200);
713
+ pendingTimers.set(sessionID, timer);
701
714
  }
702
715
  if (event.type === "message.updated") {
703
716
  const info = props?.info;
704
717
  const sessionID = info?.sessionID;
705
718
  if (sessionID && info?.role === "user") {
706
719
  remindedSessions.delete(sessionID);
720
+ const timer = pendingTimers.get(sessionID);
721
+ if (timer) {
722
+ clearTimeout(timer);
723
+ pendingTimers.delete(sessionID);
724
+ }
707
725
  }
708
726
  }
709
727
  if (event.type === "session.deleted") {
@@ -712,6 +730,11 @@ function createTodoContinuationEnforcer(ctx) {
712
730
  remindedSessions.delete(sessionInfo.id);
713
731
  interruptedSessions.delete(sessionInfo.id);
714
732
  errorSessions.delete(sessionInfo.id);
733
+ const timer = pendingTimers.get(sessionInfo.id);
734
+ if (timer) {
735
+ clearTimeout(timer);
736
+ pendingTimers.delete(sessionInfo.id);
737
+ }
715
738
  }
716
739
  }
717
740
  };
@@ -723,16 +746,8 @@ var CONTEXT_WARNING_THRESHOLD = 0.7;
723
746
  var CONTEXT_REMINDER = `[SYSTEM REMINDER - 1M Context Window]
724
747
 
725
748
  You are using Anthropic Claude with 1M context window.
726
- Current usage has exceeded 75%.
727
-
728
- RECOMMENDATIONS:
729
- - Consider compacting the session if available
730
- - Break complex tasks into smaller, focused sessions
731
- - Be concise in your responses
732
- - Avoid redundant file reads
733
-
734
- You have access to 1M tokens - use them wisely. Do NOT rush or skip tasks.
735
- Complete your work thoroughly despite the context usage warning.`;
749
+ You have plenty of context remaining - do NOT rush or skip tasks.
750
+ Complete your work thoroughly and methodically.`;
736
751
  function createContextWindowMonitorHook(ctx) {
737
752
  const remindedSessions = new Set;
738
753
  const toolExecuteAfter = async (input, output) => {
@@ -854,7 +869,13 @@ function readMessages(sessionID) {
854
869
  continue;
855
870
  }
856
871
  }
857
- return messages.sort((a, b) => a.id.localeCompare(b.id));
872
+ return messages.sort((a, b) => {
873
+ const aTime = a.time?.created ?? 0;
874
+ const bTime = b.time?.created ?? 0;
875
+ if (aTime !== bTime)
876
+ return aTime - bTime;
877
+ return a.id.localeCompare(b.id);
878
+ });
858
879
  }
859
880
  function readParts(messageID) {
860
881
  const partDir = join2(PART_STORAGE, messageID);
@@ -918,19 +939,26 @@ function injectTextPart(sessionID, messageID, text) {
918
939
  function findEmptyMessages(sessionID) {
919
940
  const messages = readMessages(sessionID);
920
941
  const emptyIds = [];
921
- for (let i = 0;i < messages.length; i++) {
922
- const msg = messages[i];
942
+ for (const msg of messages) {
923
943
  if (msg.role !== "assistant")
924
944
  continue;
925
- const isLastMessage = i === messages.length - 1;
926
- if (isLastMessage)
927
- continue;
928
945
  if (!messageHasContent(msg.id)) {
929
946
  emptyIds.push(msg.id);
930
947
  }
931
948
  }
932
949
  return emptyIds;
933
950
  }
951
+ function findEmptyMessageByIndex(sessionID, targetIndex) {
952
+ const messages = readMessages(sessionID);
953
+ if (targetIndex < 0 || targetIndex >= messages.length)
954
+ return null;
955
+ const targetMsg = messages[targetIndex];
956
+ if (targetMsg.role !== "assistant")
957
+ return null;
958
+ if (messageHasContent(targetMsg.id))
959
+ return null;
960
+ return targetMsg.id;
961
+ }
934
962
  function findMessagesWithThinkingBlocks(sessionID) {
935
963
  const messages = readMessages(sessionID);
936
964
  const result = [];
@@ -1025,6 +1053,11 @@ function getErrorMessage(error) {
1025
1053
  const errorObj = error;
1026
1054
  return (errorObj.data?.message || errorObj.error?.message || errorObj.message || "").toLowerCase();
1027
1055
  }
1056
+ function extractMessageIndex(error) {
1057
+ const message = getErrorMessage(error);
1058
+ const match = message.match(/messages\.(\d+)/);
1059
+ return match ? parseInt(match[1], 10) : null;
1060
+ }
1028
1061
  function detectErrorType(error) {
1029
1062
  const message = getErrorMessage(error);
1030
1063
  if (message.includes("tool_use") && message.includes("tool_result")) {
@@ -1091,14 +1124,21 @@ async function recoverThinkingDisabledViolation(_client, sessionID, _failedAssis
1091
1124
  }
1092
1125
  return anySuccess;
1093
1126
  }
1094
- async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory) {
1095
- const emptyMessageIDs = findEmptyMessages(sessionID);
1096
- if (emptyMessageIDs.length === 0) {
1097
- const fallbackID = failedAssistantMsg.info?.id;
1098
- if (!fallbackID)
1099
- return false;
1100
- return injectTextPart(sessionID, fallbackID, "(interrupted)");
1127
+ async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory, error) {
1128
+ const targetIndex = extractMessageIndex(error);
1129
+ const failedID = failedAssistantMsg.info?.id;
1130
+ if (targetIndex !== null) {
1131
+ const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex);
1132
+ if (targetMessageID) {
1133
+ return injectTextPart(sessionID, targetMessageID, "(interrupted)");
1134
+ }
1101
1135
  }
1136
+ if (failedID) {
1137
+ if (injectTextPart(sessionID, failedID, "(interrupted)")) {
1138
+ return true;
1139
+ }
1140
+ }
1141
+ const emptyMessageIDs = findEmptyMessages(sessionID);
1102
1142
  let anySuccess = false;
1103
1143
  for (const messageID of emptyMessageIDs) {
1104
1144
  if (injectTextPart(sessionID, messageID, "(interrupted)")) {
@@ -1171,10 +1211,11 @@ function createSessionRecoveryHook(ctx) {
1171
1211
  } else if (errorType === "thinking_disabled_violation") {
1172
1212
  success = await recoverThinkingDisabledViolation(ctx.client, sessionID, failedMsg);
1173
1213
  } else if (errorType === "empty_content_message") {
1174
- success = await recoverEmptyContentMessage(ctx.client, sessionID, failedMsg, ctx.directory);
1214
+ success = await recoverEmptyContentMessage(ctx.client, sessionID, failedMsg, ctx.directory, info.error);
1175
1215
  }
1176
1216
  return success;
1177
- } catch {
1217
+ } catch (err) {
1218
+ console.error("[session-recovery] Recovery failed:", err);
1178
1219
  return false;
1179
1220
  } finally {
1180
1221
  processingErrors.delete(assistantMsgID);
@@ -1790,6 +1831,147 @@ ${content}`;
1790
1831
  event: eventHandler
1791
1832
  };
1792
1833
  }
1834
+ // src/hooks/directory-readme-injector/index.ts
1835
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
1836
+ import { dirname as dirname3, join as join10, resolve as resolve2 } from "path";
1837
+
1838
+ // src/hooks/directory-readme-injector/storage.ts
1839
+ import {
1840
+ existsSync as existsSync7,
1841
+ mkdirSync as mkdirSync4,
1842
+ readFileSync as readFileSync4,
1843
+ writeFileSync as writeFileSync3,
1844
+ unlinkSync as unlinkSync4
1845
+ } from "fs";
1846
+ import { join as join9 } from "path";
1847
+
1848
+ // src/hooks/directory-readme-injector/constants.ts
1849
+ import { join as join8 } from "path";
1850
+ var OPENCODE_STORAGE3 = join8(xdgData ?? "", "opencode", "storage");
1851
+ var README_INJECTOR_STORAGE = join8(OPENCODE_STORAGE3, "directory-readme");
1852
+ var README_FILENAME = "README.md";
1853
+
1854
+ // src/hooks/directory-readme-injector/storage.ts
1855
+ function getStoragePath2(sessionID) {
1856
+ return join9(README_INJECTOR_STORAGE, `${sessionID}.json`);
1857
+ }
1858
+ function loadInjectedPaths2(sessionID) {
1859
+ const filePath = getStoragePath2(sessionID);
1860
+ if (!existsSync7(filePath))
1861
+ return new Set;
1862
+ try {
1863
+ const content = readFileSync4(filePath, "utf-8");
1864
+ const data = JSON.parse(content);
1865
+ return new Set(data.injectedPaths);
1866
+ } catch {
1867
+ return new Set;
1868
+ }
1869
+ }
1870
+ function saveInjectedPaths2(sessionID, paths) {
1871
+ if (!existsSync7(README_INJECTOR_STORAGE)) {
1872
+ mkdirSync4(README_INJECTOR_STORAGE, { recursive: true });
1873
+ }
1874
+ const data = {
1875
+ sessionID,
1876
+ injectedPaths: [...paths],
1877
+ updatedAt: Date.now()
1878
+ };
1879
+ writeFileSync3(getStoragePath2(sessionID), JSON.stringify(data, null, 2));
1880
+ }
1881
+ function clearInjectedPaths2(sessionID) {
1882
+ const filePath = getStoragePath2(sessionID);
1883
+ if (existsSync7(filePath)) {
1884
+ unlinkSync4(filePath);
1885
+ }
1886
+ }
1887
+
1888
+ // src/hooks/directory-readme-injector/index.ts
1889
+ function createDirectoryReadmeInjectorHook(ctx) {
1890
+ const sessionCaches = new Map;
1891
+ function getSessionCache(sessionID) {
1892
+ if (!sessionCaches.has(sessionID)) {
1893
+ sessionCaches.set(sessionID, loadInjectedPaths2(sessionID));
1894
+ }
1895
+ return sessionCaches.get(sessionID);
1896
+ }
1897
+ function resolveFilePath(title) {
1898
+ if (!title)
1899
+ return null;
1900
+ if (title.startsWith("/"))
1901
+ return title;
1902
+ return resolve2(ctx.directory, title);
1903
+ }
1904
+ function findReadmeMdUp(startDir) {
1905
+ const found = [];
1906
+ let current = startDir;
1907
+ while (true) {
1908
+ const readmePath = join10(current, README_FILENAME);
1909
+ if (existsSync8(readmePath)) {
1910
+ found.push(readmePath);
1911
+ }
1912
+ if (current === ctx.directory)
1913
+ break;
1914
+ const parent = dirname3(current);
1915
+ if (parent === current)
1916
+ break;
1917
+ if (!parent.startsWith(ctx.directory))
1918
+ break;
1919
+ current = parent;
1920
+ }
1921
+ return found.reverse();
1922
+ }
1923
+ const toolExecuteAfter = async (input, output) => {
1924
+ if (input.tool.toLowerCase() !== "read")
1925
+ return;
1926
+ const filePath = resolveFilePath(output.title);
1927
+ if (!filePath)
1928
+ return;
1929
+ const dir = dirname3(filePath);
1930
+ const cache = getSessionCache(input.sessionID);
1931
+ const readmePaths = findReadmeMdUp(dir);
1932
+ const toInject = [];
1933
+ for (const readmePath of readmePaths) {
1934
+ const readmeDir = dirname3(readmePath);
1935
+ if (cache.has(readmeDir))
1936
+ continue;
1937
+ try {
1938
+ const content = readFileSync5(readmePath, "utf-8");
1939
+ toInject.push({ path: readmePath, content });
1940
+ cache.add(readmeDir);
1941
+ } catch {}
1942
+ }
1943
+ if (toInject.length === 0)
1944
+ return;
1945
+ for (const { path: path2, content } of toInject) {
1946
+ output.output += `
1947
+
1948
+ [Project README: ${path2}]
1949
+ ${content}`;
1950
+ }
1951
+ saveInjectedPaths2(input.sessionID, cache);
1952
+ };
1953
+ const eventHandler = async ({ event }) => {
1954
+ const props = event.properties;
1955
+ if (event.type === "session.deleted") {
1956
+ const sessionInfo = props?.info;
1957
+ if (sessionInfo?.id) {
1958
+ sessionCaches.delete(sessionInfo.id);
1959
+ clearInjectedPaths2(sessionInfo.id);
1960
+ }
1961
+ }
1962
+ if (event.type === "session.compacted") {
1963
+ const sessionID = props?.sessionID ?? props?.info?.id;
1964
+ if (sessionID) {
1965
+ sessionCaches.delete(sessionID);
1966
+ clearInjectedPaths2(sessionID);
1967
+ }
1968
+ }
1969
+ };
1970
+ return {
1971
+ "tool.execute.after": toolExecuteAfter,
1972
+ event: eventHandler
1973
+ };
1974
+ }
1793
1975
  // src/hooks/empty-task-response-detector.ts
1794
1976
  var EMPTY_RESPONSE_WARNING = `[Task Empty Response Warning]
1795
1977
 
@@ -1811,6 +1993,280 @@ function createEmptyTaskResponseDetectorHook(_ctx) {
1811
1993
  }
1812
1994
  };
1813
1995
  }
1996
+ // src/hooks/anthropic-auto-compact/parser.ts
1997
+ var TOKEN_LIMIT_PATTERNS = [
1998
+ /(\d+)\s*tokens?\s*>\s*(\d+)\s*maximum/i,
1999
+ /prompt.*?(\d+).*?tokens.*?exceeds.*?(\d+)/i,
2000
+ /(\d+).*?tokens.*?limit.*?(\d+)/i,
2001
+ /context.*?length.*?(\d+).*?maximum.*?(\d+)/i,
2002
+ /max.*?context.*?(\d+).*?but.*?(\d+)/i
2003
+ ];
2004
+ var TOKEN_LIMIT_KEYWORDS = [
2005
+ "prompt is too long",
2006
+ "is too long",
2007
+ "context_length_exceeded",
2008
+ "max_tokens",
2009
+ "token limit",
2010
+ "context length",
2011
+ "too many tokens"
2012
+ ];
2013
+ function extractTokensFromMessage(message) {
2014
+ for (const pattern of TOKEN_LIMIT_PATTERNS) {
2015
+ const match = message.match(pattern);
2016
+ if (match) {
2017
+ const num1 = parseInt(match[1], 10);
2018
+ const num2 = parseInt(match[2], 10);
2019
+ return num1 > num2 ? { current: num1, max: num2 } : { current: num2, max: num1 };
2020
+ }
2021
+ }
2022
+ return null;
2023
+ }
2024
+ function isTokenLimitError(text) {
2025
+ const lower = text.toLowerCase();
2026
+ return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
2027
+ }
2028
+ function parseAnthropicTokenLimitError(err) {
2029
+ if (typeof err === "string") {
2030
+ if (isTokenLimitError(err)) {
2031
+ const tokens = extractTokensFromMessage(err);
2032
+ return {
2033
+ currentTokens: tokens?.current ?? 0,
2034
+ maxTokens: tokens?.max ?? 0,
2035
+ errorType: "token_limit_exceeded_string"
2036
+ };
2037
+ }
2038
+ return null;
2039
+ }
2040
+ if (!err || typeof err !== "object")
2041
+ return null;
2042
+ const errObj = err;
2043
+ const dataObj = errObj.data;
2044
+ const responseBody = dataObj?.responseBody;
2045
+ const errorMessage = errObj.message;
2046
+ const errorData = errObj.error;
2047
+ const nestedError = errorData?.error;
2048
+ const textSources = [];
2049
+ if (typeof responseBody === "string")
2050
+ textSources.push(responseBody);
2051
+ if (typeof errorMessage === "string")
2052
+ textSources.push(errorMessage);
2053
+ if (typeof errorData?.message === "string")
2054
+ textSources.push(errorData.message);
2055
+ if (typeof errObj.body === "string")
2056
+ textSources.push(errObj.body);
2057
+ if (typeof errObj.details === "string")
2058
+ textSources.push(errObj.details);
2059
+ if (typeof errObj.reason === "string")
2060
+ textSources.push(errObj.reason);
2061
+ if (typeof errObj.description === "string")
2062
+ textSources.push(errObj.description);
2063
+ if (typeof nestedError?.message === "string")
2064
+ textSources.push(nestedError.message);
2065
+ if (typeof dataObj?.message === "string")
2066
+ textSources.push(dataObj.message);
2067
+ if (typeof dataObj?.error === "string")
2068
+ textSources.push(dataObj.error);
2069
+ if (textSources.length === 0) {
2070
+ try {
2071
+ const jsonStr = JSON.stringify(errObj);
2072
+ if (isTokenLimitError(jsonStr)) {
2073
+ textSources.push(jsonStr);
2074
+ }
2075
+ } catch {}
2076
+ }
2077
+ const combinedText = textSources.join(" ");
2078
+ if (!isTokenLimitError(combinedText))
2079
+ return null;
2080
+ if (typeof responseBody === "string") {
2081
+ try {
2082
+ const jsonPatterns = [
2083
+ /data:\s*(\{[\s\S]*?\})\s*$/m,
2084
+ /(\{"type"\s*:\s*"error"[\s\S]*?\})/,
2085
+ /(\{[\s\S]*?"error"[\s\S]*?\})/
2086
+ ];
2087
+ for (const pattern of jsonPatterns) {
2088
+ const dataMatch = responseBody.match(pattern);
2089
+ if (dataMatch) {
2090
+ try {
2091
+ const jsonData = JSON.parse(dataMatch[1]);
2092
+ const message = jsonData.error?.message || "";
2093
+ const tokens = extractTokensFromMessage(message);
2094
+ if (tokens) {
2095
+ return {
2096
+ currentTokens: tokens.current,
2097
+ maxTokens: tokens.max,
2098
+ requestId: jsonData.request_id,
2099
+ errorType: jsonData.error?.type || "token_limit_exceeded"
2100
+ };
2101
+ }
2102
+ } catch {}
2103
+ }
2104
+ }
2105
+ const bedrockJson = JSON.parse(responseBody);
2106
+ if (typeof bedrockJson.message === "string" && isTokenLimitError(bedrockJson.message)) {
2107
+ return {
2108
+ currentTokens: 0,
2109
+ maxTokens: 0,
2110
+ errorType: "bedrock_input_too_long"
2111
+ };
2112
+ }
2113
+ } catch {}
2114
+ }
2115
+ for (const text of textSources) {
2116
+ const tokens = extractTokensFromMessage(text);
2117
+ if (tokens) {
2118
+ return {
2119
+ currentTokens: tokens.current,
2120
+ maxTokens: tokens.max,
2121
+ errorType: "token_limit_exceeded"
2122
+ };
2123
+ }
2124
+ }
2125
+ if (isTokenLimitError(combinedText)) {
2126
+ return {
2127
+ currentTokens: 0,
2128
+ maxTokens: 0,
2129
+ errorType: "token_limit_exceeded_unknown"
2130
+ };
2131
+ }
2132
+ return null;
2133
+ }
2134
+
2135
+ // src/hooks/anthropic-auto-compact/executor.ts
2136
+ async function getLastAssistant(sessionID, client, directory) {
2137
+ try {
2138
+ const resp = await client.session.messages({
2139
+ path: { id: sessionID },
2140
+ query: { directory }
2141
+ });
2142
+ const data = resp.data;
2143
+ if (!Array.isArray(data))
2144
+ return null;
2145
+ const reversed = [...data].reverse();
2146
+ const last = reversed.find((m) => {
2147
+ const msg = m;
2148
+ const info = msg.info;
2149
+ return info?.role === "assistant";
2150
+ });
2151
+ if (!last)
2152
+ return null;
2153
+ return last.info ?? null;
2154
+ } catch {
2155
+ return null;
2156
+ }
2157
+ }
2158
+ async function executeCompact(sessionID, msg, autoCompactState, client, directory) {
2159
+ try {
2160
+ const providerID = msg.providerID;
2161
+ const modelID = msg.modelID;
2162
+ if (providerID && modelID) {
2163
+ await client.session.summarize({
2164
+ path: { id: sessionID },
2165
+ body: { providerID, modelID },
2166
+ query: { directory }
2167
+ });
2168
+ setTimeout(async () => {
2169
+ try {
2170
+ await client.tui.submitPrompt({ query: { directory } });
2171
+ } catch {}
2172
+ }, 500);
2173
+ }
2174
+ autoCompactState.pendingCompact.delete(sessionID);
2175
+ autoCompactState.errorDataBySession.delete(sessionID);
2176
+ } catch {}
2177
+ }
2178
+
2179
+ // src/hooks/anthropic-auto-compact/index.ts
2180
+ function createAutoCompactState() {
2181
+ return {
2182
+ pendingCompact: new Set,
2183
+ errorDataBySession: new Map
2184
+ };
2185
+ }
2186
+ function createAnthropicAutoCompactHook(ctx) {
2187
+ const autoCompactState = createAutoCompactState();
2188
+ const eventHandler = async ({ event }) => {
2189
+ const props = event.properties;
2190
+ if (event.type === "session.deleted") {
2191
+ const sessionInfo = props?.info;
2192
+ if (sessionInfo?.id) {
2193
+ autoCompactState.pendingCompact.delete(sessionInfo.id);
2194
+ autoCompactState.errorDataBySession.delete(sessionInfo.id);
2195
+ }
2196
+ return;
2197
+ }
2198
+ if (event.type === "session.error") {
2199
+ const sessionID = props?.sessionID;
2200
+ if (!sessionID)
2201
+ return;
2202
+ const parsed = parseAnthropicTokenLimitError(props?.error);
2203
+ if (parsed) {
2204
+ autoCompactState.pendingCompact.add(sessionID);
2205
+ autoCompactState.errorDataBySession.set(sessionID, parsed);
2206
+ }
2207
+ return;
2208
+ }
2209
+ if (event.type === "message.updated") {
2210
+ const info = props?.info;
2211
+ const sessionID = info?.sessionID;
2212
+ if (sessionID && info?.role === "assistant" && info.error) {
2213
+ const parsed = parseAnthropicTokenLimitError(info.error);
2214
+ if (parsed) {
2215
+ parsed.providerID = info.providerID;
2216
+ parsed.modelID = info.modelID;
2217
+ autoCompactState.pendingCompact.add(sessionID);
2218
+ autoCompactState.errorDataBySession.set(sessionID, parsed);
2219
+ }
2220
+ }
2221
+ return;
2222
+ }
2223
+ if (event.type === "session.idle") {
2224
+ const sessionID = props?.sessionID;
2225
+ if (!sessionID)
2226
+ return;
2227
+ if (!autoCompactState.pendingCompact.has(sessionID))
2228
+ return;
2229
+ const errorData = autoCompactState.errorDataBySession.get(sessionID);
2230
+ if (errorData?.providerID && errorData?.modelID) {
2231
+ await ctx.client.tui.showToast({
2232
+ body: {
2233
+ title: "Auto Compact",
2234
+ message: "Token limit exceeded. Summarizing session...",
2235
+ variant: "warning",
2236
+ duration: 3000
2237
+ }
2238
+ }).catch(() => {});
2239
+ await executeCompact(sessionID, { providerID: errorData.providerID, modelID: errorData.modelID }, autoCompactState, ctx.client, ctx.directory);
2240
+ return;
2241
+ }
2242
+ const lastAssistant = await getLastAssistant(sessionID, ctx.client, ctx.directory);
2243
+ if (!lastAssistant) {
2244
+ autoCompactState.pendingCompact.delete(sessionID);
2245
+ return;
2246
+ }
2247
+ if (lastAssistant.summary === true) {
2248
+ autoCompactState.pendingCompact.delete(sessionID);
2249
+ return;
2250
+ }
2251
+ if (!lastAssistant.modelID || !lastAssistant.providerID) {
2252
+ autoCompactState.pendingCompact.delete(sessionID);
2253
+ return;
2254
+ }
2255
+ await ctx.client.tui.showToast({
2256
+ body: {
2257
+ title: "Auto Compact",
2258
+ message: "Token limit exceeded. Summarizing session...",
2259
+ variant: "warning",
2260
+ duration: 3000
2261
+ }
2262
+ }).catch(() => {});
2263
+ await executeCompact(sessionID, lastAssistant, autoCompactState, ctx.client, ctx.directory);
2264
+ }
2265
+ };
2266
+ return {
2267
+ event: eventHandler
2268
+ };
2269
+ }
1814
2270
  // src/hooks/think-mode/detector.ts
1815
2271
  var ENGLISH_PATTERNS = [/\bultrathink\b/i, /\bthink\b/i];
1816
2272
  var MULTILINGUAL_KEYWORDS = [
@@ -1991,8 +2447,8 @@ function createThinkModeHook() {
1991
2447
  }
1992
2448
  // src/hooks/claude-code-hooks/config.ts
1993
2449
  import { homedir as homedir2 } from "os";
1994
- import { join as join8 } from "path";
1995
- import { existsSync as existsSync7 } from "fs";
2450
+ import { join as join11 } from "path";
2451
+ import { existsSync as existsSync9 } from "fs";
1996
2452
  function normalizeHookMatcher(raw) {
1997
2453
  return {
1998
2454
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -2017,11 +2473,11 @@ function normalizeHooksConfig(raw) {
2017
2473
  function getClaudeSettingsPaths(customPath) {
2018
2474
  const home = homedir2();
2019
2475
  const paths = [
2020
- join8(home, ".claude", "settings.json"),
2021
- join8(process.cwd(), ".claude", "settings.json"),
2022
- join8(process.cwd(), ".claude", "settings.local.json")
2476
+ join11(home, ".claude", "settings.json"),
2477
+ join11(process.cwd(), ".claude", "settings.json"),
2478
+ join11(process.cwd(), ".claude", "settings.local.json")
2023
2479
  ];
2024
- if (customPath && existsSync7(customPath)) {
2480
+ if (customPath && existsSync9(customPath)) {
2025
2481
  paths.unshift(customPath);
2026
2482
  }
2027
2483
  return paths;
@@ -2045,7 +2501,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
2045
2501
  const paths = getClaudeSettingsPaths(customSettingsPath);
2046
2502
  let mergedConfig = {};
2047
2503
  for (const settingsPath of paths) {
2048
- if (existsSync7(settingsPath)) {
2504
+ if (existsSync9(settingsPath)) {
2049
2505
  try {
2050
2506
  const content = await Bun.file(settingsPath).text();
2051
2507
  const settings = JSON.parse(content);
@@ -2062,9 +2518,9 @@ async function loadClaudeHooksConfig(customSettingsPath) {
2062
2518
  }
2063
2519
 
2064
2520
  // src/hooks/claude-code-hooks/config-loader.ts
2065
- import { existsSync as existsSync8 } from "fs";
2521
+ import { existsSync as existsSync10 } from "fs";
2066
2522
  import { homedir as homedir3 } from "os";
2067
- import { join as join10 } from "path";
2523
+ import { join as join13 } from "path";
2068
2524
 
2069
2525
  // src/shared/logger.ts
2070
2526
  import * as fs3 from "fs";
@@ -2081,12 +2537,12 @@ function log(message, data) {
2081
2537
  }
2082
2538
 
2083
2539
  // src/hooks/claude-code-hooks/config-loader.ts
2084
- var USER_CONFIG_PATH = join10(homedir3(), ".config", "opencode", "opencode-cc-plugin.json");
2540
+ var USER_CONFIG_PATH = join13(homedir3(), ".config", "opencode", "opencode-cc-plugin.json");
2085
2541
  function getProjectConfigPath() {
2086
- return join10(process.cwd(), ".opencode", "opencode-cc-plugin.json");
2542
+ return join13(process.cwd(), ".opencode", "opencode-cc-plugin.json");
2087
2543
  }
2088
2544
  async function loadConfigFromPath(path3) {
2089
- if (!existsSync8(path3)) {
2545
+ if (!existsSync10(path3)) {
2090
2546
  return null;
2091
2547
  }
2092
2548
  try {
@@ -2179,14 +2635,14 @@ function parseFrontmatter(content) {
2179
2635
  import { spawn as spawn3 } from "child_process";
2180
2636
  import { exec } from "child_process";
2181
2637
  import { promisify } from "util";
2182
- import { existsSync as existsSync9 } from "fs";
2638
+ import { existsSync as existsSync11 } from "fs";
2183
2639
  var DEFAULT_ZSH_PATHS = ["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh"];
2184
2640
  function findZshPath(customZshPath) {
2185
- if (customZshPath && existsSync9(customZshPath)) {
2641
+ if (customZshPath && existsSync11(customZshPath)) {
2186
2642
  return customZshPath;
2187
2643
  }
2188
2644
  for (const path3 of DEFAULT_ZSH_PATHS) {
2189
- if (existsSync9(path3)) {
2645
+ if (existsSync11(path3)) {
2190
2646
  return path3;
2191
2647
  }
2192
2648
  }
@@ -2204,7 +2660,7 @@ async function executeHookCommand(command, stdin, cwd, options) {
2204
2660
  finalCommand = `${zshPath} -lc '${escapedCommand}'`;
2205
2661
  }
2206
2662
  }
2207
- return new Promise((resolve2) => {
2663
+ return new Promise((resolve3) => {
2208
2664
  const proc = spawn3(finalCommand, {
2209
2665
  cwd,
2210
2666
  shell: true,
@@ -2221,14 +2677,14 @@ async function executeHookCommand(command, stdin, cwd, options) {
2221
2677
  proc.stdin?.write(stdin);
2222
2678
  proc.stdin?.end();
2223
2679
  proc.on("close", (code) => {
2224
- resolve2({
2680
+ resolve3({
2225
2681
  exitCode: code ?? 0,
2226
2682
  stdout: stdout.trim(),
2227
2683
  stderr: stderr.trim()
2228
2684
  });
2229
2685
  });
2230
2686
  proc.on("error", (err) => {
2231
- resolve2({
2687
+ resolve3({
2232
2688
  exitCode: 1,
2233
2689
  stderr: err.message
2234
2690
  });
@@ -2304,8 +2760,8 @@ async function resolveCommandsInText(text, depth = 0, maxDepth = 3) {
2304
2760
  return resolved;
2305
2761
  }
2306
2762
  // src/shared/file-reference-resolver.ts
2307
- import { existsSync as existsSync10, readFileSync as readFileSync4, statSync } from "fs";
2308
- import { join as join11, isAbsolute } from "path";
2763
+ import { existsSync as existsSync12, readFileSync as readFileSync6, statSync } from "fs";
2764
+ import { join as join14, isAbsolute } from "path";
2309
2765
  var FILE_REFERENCE_PATTERN = /@([^\s@]+)/g;
2310
2766
  function findFileReferences(text) {
2311
2767
  const matches = [];
@@ -2325,17 +2781,17 @@ function resolveFilePath(filePath, cwd) {
2325
2781
  if (isAbsolute(filePath)) {
2326
2782
  return filePath;
2327
2783
  }
2328
- return join11(cwd, filePath);
2784
+ return join14(cwd, filePath);
2329
2785
  }
2330
2786
  function readFileContent(resolvedPath) {
2331
- if (!existsSync10(resolvedPath)) {
2787
+ if (!existsSync12(resolvedPath)) {
2332
2788
  return `[file not found: ${resolvedPath}]`;
2333
2789
  }
2334
2790
  const stat = statSync(resolvedPath);
2335
2791
  if (stat.isDirectory()) {
2336
2792
  return `[cannot read directory: ${resolvedPath}]`;
2337
2793
  }
2338
- const content = readFileSync4(resolvedPath, "utf-8");
2794
+ const content = readFileSync6(resolvedPath, "utf-8");
2339
2795
  return content;
2340
2796
  }
2341
2797
  async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0, maxDepth = 3) {
@@ -2558,17 +3014,17 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
2558
3014
  }
2559
3015
 
2560
3016
  // src/hooks/claude-code-hooks/transcript.ts
2561
- import { join as join12 } from "path";
2562
- import { mkdirSync as mkdirSync4, appendFileSync as appendFileSync5, existsSync as existsSync11, writeFileSync as writeFileSync3, unlinkSync as unlinkSync4 } from "fs";
3017
+ import { join as join15 } from "path";
3018
+ import { mkdirSync as mkdirSync5, appendFileSync as appendFileSync5, existsSync as existsSync13, writeFileSync as writeFileSync4, unlinkSync as unlinkSync5 } from "fs";
2563
3019
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
2564
3020
  import { randomUUID } from "crypto";
2565
- var TRANSCRIPT_DIR = join12(homedir4(), ".claude", "transcripts");
3021
+ var TRANSCRIPT_DIR = join15(homedir4(), ".claude", "transcripts");
2566
3022
  function getTranscriptPath(sessionId) {
2567
- return join12(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
3023
+ return join15(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
2568
3024
  }
2569
3025
  function ensureTranscriptDir() {
2570
- if (!existsSync11(TRANSCRIPT_DIR)) {
2571
- mkdirSync4(TRANSCRIPT_DIR, { recursive: true });
3026
+ if (!existsSync13(TRANSCRIPT_DIR)) {
3027
+ mkdirSync5(TRANSCRIPT_DIR, { recursive: true });
2572
3028
  }
2573
3029
  }
2574
3030
  function appendTranscriptEntry(sessionId, entry) {
@@ -2654,8 +3110,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
2654
3110
  }
2655
3111
  };
2656
3112
  entries.push(JSON.stringify(currentEntry));
2657
- const tempPath = join12(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
2658
- writeFileSync3(tempPath, entries.join(`
3113
+ const tempPath = join15(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
3114
+ writeFileSync4(tempPath, entries.join(`
2659
3115
  `) + `
2660
3116
  `);
2661
3117
  return tempPath;
@@ -2674,8 +3130,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
2674
3130
  ]
2675
3131
  }
2676
3132
  };
2677
- const tempPath = join12(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
2678
- writeFileSync3(tempPath, JSON.stringify(currentEntry) + `
3133
+ const tempPath = join15(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
3134
+ writeFileSync4(tempPath, JSON.stringify(currentEntry) + `
2679
3135
  `);
2680
3136
  return tempPath;
2681
3137
  } catch {
@@ -2687,7 +3143,7 @@ function deleteTempTranscript(path3) {
2687
3143
  if (!path3)
2688
3144
  return;
2689
3145
  try {
2690
- unlinkSync4(path3);
3146
+ unlinkSync5(path3);
2691
3147
  } catch {}
2692
3148
  }
2693
3149
 
@@ -2886,11 +3342,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
2886
3342
  }
2887
3343
 
2888
3344
  // src/hooks/claude-code-hooks/todo.ts
2889
- import { join as join13 } from "path";
3345
+ import { join as join16 } from "path";
2890
3346
  import { homedir as homedir5 } from "os";
2891
- var TODO_DIR = join13(homedir5(), ".claude", "todos");
3347
+ var TODO_DIR = join16(homedir5(), ".claude", "todos");
2892
3348
  function getTodoPath(sessionId) {
2893
- return join13(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
3349
+ return join16(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
2894
3350
  }
2895
3351
 
2896
3352
  // src/hooks/claude-code-hooks/stop.ts
@@ -2982,16 +3438,16 @@ setInterval(() => {
2982
3438
  }, CACHE_TTL);
2983
3439
 
2984
3440
  // src/features/hook-message-injector/injector.ts
2985
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
2986
- import { join as join15 } from "path";
3441
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
3442
+ import { join as join18 } from "path";
2987
3443
 
2988
3444
  // src/features/hook-message-injector/constants.ts
2989
- import { join as join14 } from "path";
3445
+ import { join as join17 } from "path";
2990
3446
  import { homedir as homedir6 } from "os";
2991
- var xdgData2 = process.env.XDG_DATA_HOME || join14(homedir6(), ".local", "share");
2992
- var OPENCODE_STORAGE3 = join14(xdgData2, "opencode", "storage");
2993
- var MESSAGE_STORAGE2 = join14(OPENCODE_STORAGE3, "message");
2994
- var PART_STORAGE2 = join14(OPENCODE_STORAGE3, "part");
3447
+ var xdgData2 = process.env.XDG_DATA_HOME || join17(homedir6(), ".local", "share");
3448
+ var OPENCODE_STORAGE4 = join17(xdgData2, "opencode", "storage");
3449
+ var MESSAGE_STORAGE2 = join17(OPENCODE_STORAGE4, "message");
3450
+ var PART_STORAGE2 = join17(OPENCODE_STORAGE4, "part");
2995
3451
 
2996
3452
  // src/features/hook-message-injector/injector.ts
2997
3453
  function findNearestMessageWithFields(messageDir) {
@@ -2999,7 +3455,7 @@ function findNearestMessageWithFields(messageDir) {
2999
3455
  const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
3000
3456
  for (const file of files) {
3001
3457
  try {
3002
- const content = readFileSync5(join15(messageDir, file), "utf-8");
3458
+ const content = readFileSync7(join18(messageDir, file), "utf-8");
3003
3459
  const msg = JSON.parse(content);
3004
3460
  if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
3005
3461
  return msg;
@@ -3024,20 +3480,20 @@ function generatePartId2() {
3024
3480
  return `prt_${timestamp}${random}`;
3025
3481
  }
3026
3482
  function getOrCreateMessageDir(sessionID) {
3027
- if (!existsSync12(MESSAGE_STORAGE2)) {
3028
- mkdirSync5(MESSAGE_STORAGE2, { recursive: true });
3483
+ if (!existsSync14(MESSAGE_STORAGE2)) {
3484
+ mkdirSync6(MESSAGE_STORAGE2, { recursive: true });
3029
3485
  }
3030
- const directPath = join15(MESSAGE_STORAGE2, sessionID);
3031
- if (existsSync12(directPath)) {
3486
+ const directPath = join18(MESSAGE_STORAGE2, sessionID);
3487
+ if (existsSync14(directPath)) {
3032
3488
  return directPath;
3033
3489
  }
3034
3490
  for (const dir of readdirSync2(MESSAGE_STORAGE2)) {
3035
- const sessionPath = join15(MESSAGE_STORAGE2, dir, sessionID);
3036
- if (existsSync12(sessionPath)) {
3491
+ const sessionPath = join18(MESSAGE_STORAGE2, dir, sessionID);
3492
+ if (existsSync14(sessionPath)) {
3037
3493
  return sessionPath;
3038
3494
  }
3039
3495
  }
3040
- mkdirSync5(directPath, { recursive: true });
3496
+ mkdirSync6(directPath, { recursive: true });
3041
3497
  return directPath;
3042
3498
  }
3043
3499
  function injectHookMessage(sessionID, hookContent, originalMessage) {
@@ -3078,12 +3534,12 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3078
3534
  sessionID
3079
3535
  };
3080
3536
  try {
3081
- writeFileSync4(join15(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3082
- const partDir = join15(PART_STORAGE2, messageID);
3083
- if (!existsSync12(partDir)) {
3084
- mkdirSync5(partDir, { recursive: true });
3537
+ writeFileSync5(join18(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3538
+ const partDir = join18(PART_STORAGE2, messageID);
3539
+ if (!existsSync14(partDir)) {
3540
+ mkdirSync6(partDir, { recursive: true });
3085
3541
  }
3086
- writeFileSync4(join15(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3542
+ writeFileSync5(join18(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3087
3543
  return true;
3088
3544
  } catch {
3089
3545
  return false;
@@ -3329,14 +3785,14 @@ ${result.message}`;
3329
3785
  };
3330
3786
  }
3331
3787
  // src/features/claude-code-command-loader/loader.ts
3332
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
3788
+ import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
3333
3789
  import { homedir as homedir7 } from "os";
3334
- import { join as join16, basename } from "path";
3790
+ import { join as join19, basename } from "path";
3335
3791
  function isMarkdownFile(entry) {
3336
3792
  return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
3337
3793
  }
3338
3794
  function loadCommandsFromDir(commandsDir, scope) {
3339
- if (!existsSync13(commandsDir)) {
3795
+ if (!existsSync15(commandsDir)) {
3340
3796
  return [];
3341
3797
  }
3342
3798
  const entries = readdirSync3(commandsDir, { withFileTypes: true });
@@ -3344,10 +3800,10 @@ function loadCommandsFromDir(commandsDir, scope) {
3344
3800
  for (const entry of entries) {
3345
3801
  if (!isMarkdownFile(entry))
3346
3802
  continue;
3347
- const commandPath = join16(commandsDir, entry.name);
3803
+ const commandPath = join19(commandsDir, entry.name);
3348
3804
  const commandName = basename(entry.name, ".md");
3349
3805
  try {
3350
- const content = readFileSync6(commandPath, "utf-8");
3806
+ const content = readFileSync8(commandPath, "utf-8");
3351
3807
  const { data, body } = parseFrontmatter(content);
3352
3808
  const wrappedTemplate = `<command-instruction>
3353
3809
  ${body.trim()}
@@ -3386,31 +3842,31 @@ function commandsToRecord(commands) {
3386
3842
  return result;
3387
3843
  }
3388
3844
  function loadUserCommands() {
3389
- const userCommandsDir = join16(homedir7(), ".claude", "commands");
3845
+ const userCommandsDir = join19(homedir7(), ".claude", "commands");
3390
3846
  const commands = loadCommandsFromDir(userCommandsDir, "user");
3391
3847
  return commandsToRecord(commands);
3392
3848
  }
3393
3849
  function loadProjectCommands() {
3394
- const projectCommandsDir = join16(process.cwd(), ".claude", "commands");
3850
+ const projectCommandsDir = join19(process.cwd(), ".claude", "commands");
3395
3851
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
3396
3852
  return commandsToRecord(commands);
3397
3853
  }
3398
3854
  function loadOpencodeGlobalCommands() {
3399
- const opencodeCommandsDir = join16(homedir7(), ".config", "opencode", "command");
3855
+ const opencodeCommandsDir = join19(homedir7(), ".config", "opencode", "command");
3400
3856
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
3401
3857
  return commandsToRecord(commands);
3402
3858
  }
3403
3859
  function loadOpencodeProjectCommands() {
3404
- const opencodeProjectDir = join16(process.cwd(), ".opencode", "command");
3860
+ const opencodeProjectDir = join19(process.cwd(), ".opencode", "command");
3405
3861
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
3406
3862
  return commandsToRecord(commands);
3407
3863
  }
3408
3864
  // src/features/claude-code-skill-loader/loader.ts
3409
- import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync2, readlinkSync } from "fs";
3865
+ import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync2, readlinkSync } from "fs";
3410
3866
  import { homedir as homedir8 } from "os";
3411
- import { join as join17, resolve as resolve2 } from "path";
3867
+ import { join as join20, resolve as resolve3 } from "path";
3412
3868
  function loadSkillsFromDir(skillsDir, scope) {
3413
- if (!existsSync14(skillsDir)) {
3869
+ if (!existsSync16(skillsDir)) {
3414
3870
  return [];
3415
3871
  }
3416
3872
  const entries = readdirSync4(skillsDir, { withFileTypes: true });
@@ -3418,18 +3874,18 @@ function loadSkillsFromDir(skillsDir, scope) {
3418
3874
  for (const entry of entries) {
3419
3875
  if (entry.name.startsWith("."))
3420
3876
  continue;
3421
- const skillPath = join17(skillsDir, entry.name);
3877
+ const skillPath = join20(skillsDir, entry.name);
3422
3878
  if (!entry.isDirectory() && !entry.isSymbolicLink())
3423
3879
  continue;
3424
3880
  let resolvedPath = skillPath;
3425
3881
  if (statSync2(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
3426
- resolvedPath = resolve2(skillPath, "..", readlinkSync(skillPath));
3882
+ resolvedPath = resolve3(skillPath, "..", readlinkSync(skillPath));
3427
3883
  }
3428
- const skillMdPath = join17(resolvedPath, "SKILL.md");
3429
- if (!existsSync14(skillMdPath))
3884
+ const skillMdPath = join20(resolvedPath, "SKILL.md");
3885
+ if (!existsSync16(skillMdPath))
3430
3886
  continue;
3431
3887
  try {
3432
- const content = readFileSync7(skillMdPath, "utf-8");
3888
+ const content = readFileSync9(skillMdPath, "utf-8");
3433
3889
  const { data, body } = parseFrontmatter(content);
3434
3890
  const skillName = data.name || entry.name;
3435
3891
  const originalDescription = data.description || "";
@@ -3460,7 +3916,7 @@ $ARGUMENTS
3460
3916
  return skills;
3461
3917
  }
3462
3918
  function loadUserSkillsAsCommands() {
3463
- const userSkillsDir = join17(homedir8(), ".claude", "skills");
3919
+ const userSkillsDir = join20(homedir8(), ".claude", "skills");
3464
3920
  const skills = loadSkillsFromDir(userSkillsDir, "user");
3465
3921
  return skills.reduce((acc, skill) => {
3466
3922
  acc[skill.name] = skill.definition;
@@ -3468,7 +3924,7 @@ function loadUserSkillsAsCommands() {
3468
3924
  }, {});
3469
3925
  }
3470
3926
  function loadProjectSkillsAsCommands() {
3471
- const projectSkillsDir = join17(process.cwd(), ".claude", "skills");
3927
+ const projectSkillsDir = join20(process.cwd(), ".claude", "skills");
3472
3928
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
3473
3929
  return skills.reduce((acc, skill) => {
3474
3930
  acc[skill.name] = skill.definition;
@@ -3476,9 +3932,9 @@ function loadProjectSkillsAsCommands() {
3476
3932
  }, {});
3477
3933
  }
3478
3934
  // src/features/claude-code-agent-loader/loader.ts
3479
- import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
3935
+ import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
3480
3936
  import { homedir as homedir9 } from "os";
3481
- import { join as join18, basename as basename2 } from "path";
3937
+ import { join as join21, basename as basename2 } from "path";
3482
3938
  function parseToolsConfig(toolsStr) {
3483
3939
  if (!toolsStr)
3484
3940
  return;
@@ -3495,7 +3951,7 @@ function isMarkdownFile2(entry) {
3495
3951
  return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
3496
3952
  }
3497
3953
  function loadAgentsFromDir(agentsDir, scope) {
3498
- if (!existsSync15(agentsDir)) {
3954
+ if (!existsSync17(agentsDir)) {
3499
3955
  return [];
3500
3956
  }
3501
3957
  const entries = readdirSync5(agentsDir, { withFileTypes: true });
@@ -3503,10 +3959,10 @@ function loadAgentsFromDir(agentsDir, scope) {
3503
3959
  for (const entry of entries) {
3504
3960
  if (!isMarkdownFile2(entry))
3505
3961
  continue;
3506
- const agentPath = join18(agentsDir, entry.name);
3962
+ const agentPath = join21(agentsDir, entry.name);
3507
3963
  const agentName = basename2(entry.name, ".md");
3508
3964
  try {
3509
- const content = readFileSync8(agentPath, "utf-8");
3965
+ const content = readFileSync10(agentPath, "utf-8");
3510
3966
  const { data, body } = parseFrontmatter(content);
3511
3967
  const name = data.name || agentName;
3512
3968
  const originalDescription = data.description || "";
@@ -3533,7 +3989,7 @@ function loadAgentsFromDir(agentsDir, scope) {
3533
3989
  return agents;
3534
3990
  }
3535
3991
  function loadUserAgents() {
3536
- const userAgentsDir = join18(homedir9(), ".claude", "agents");
3992
+ const userAgentsDir = join21(homedir9(), ".claude", "agents");
3537
3993
  const agents = loadAgentsFromDir(userAgentsDir, "user");
3538
3994
  const result = {};
3539
3995
  for (const agent of agents) {
@@ -3542,7 +3998,7 @@ function loadUserAgents() {
3542
3998
  return result;
3543
3999
  }
3544
4000
  function loadProjectAgents() {
3545
- const projectAgentsDir = join18(process.cwd(), ".claude", "agents");
4001
+ const projectAgentsDir = join21(process.cwd(), ".claude", "agents");
3546
4002
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
3547
4003
  const result = {};
3548
4004
  for (const agent of agents) {
@@ -3551,9 +4007,9 @@ function loadProjectAgents() {
3551
4007
  return result;
3552
4008
  }
3553
4009
  // src/features/claude-code-mcp-loader/loader.ts
3554
- import { existsSync as existsSync16 } from "fs";
4010
+ import { existsSync as existsSync18 } from "fs";
3555
4011
  import { homedir as homedir10 } from "os";
3556
- import { join as join19 } from "path";
4012
+ import { join as join22 } from "path";
3557
4013
 
3558
4014
  // src/features/claude-code-mcp-loader/env-expander.ts
3559
4015
  function expandEnvVars(value) {
@@ -3622,13 +4078,13 @@ function getMcpConfigPaths() {
3622
4078
  const home = homedir10();
3623
4079
  const cwd = process.cwd();
3624
4080
  return [
3625
- { path: join19(home, ".claude", ".mcp.json"), scope: "user" },
3626
- { path: join19(cwd, ".mcp.json"), scope: "project" },
3627
- { path: join19(cwd, ".claude", ".mcp.json"), scope: "local" }
4081
+ { path: join22(home, ".claude", ".mcp.json"), scope: "user" },
4082
+ { path: join22(cwd, ".mcp.json"), scope: "project" },
4083
+ { path: join22(cwd, ".claude", ".mcp.json"), scope: "local" }
3628
4084
  ];
3629
4085
  }
3630
4086
  async function loadMcpConfigFile(filePath) {
3631
- if (!existsSync16(filePath)) {
4087
+ if (!existsSync18(filePath)) {
3632
4088
  return null;
3633
4089
  }
3634
4090
  try {
@@ -3914,14 +4370,14 @@ var EXT_TO_LANG = {
3914
4370
  ".tfvars": "terraform"
3915
4371
  };
3916
4372
  // src/tools/lsp/config.ts
3917
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "fs";
3918
- import { join as join20 } from "path";
4373
+ import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
4374
+ import { join as join23 } from "path";
3919
4375
  import { homedir as homedir11 } from "os";
3920
4376
  function loadJsonFile(path3) {
3921
- if (!existsSync17(path3))
4377
+ if (!existsSync19(path3))
3922
4378
  return null;
3923
4379
  try {
3924
- return JSON.parse(readFileSync9(path3, "utf-8"));
4380
+ return JSON.parse(readFileSync11(path3, "utf-8"));
3925
4381
  } catch {
3926
4382
  return null;
3927
4383
  }
@@ -3929,9 +4385,9 @@ function loadJsonFile(path3) {
3929
4385
  function getConfigPaths() {
3930
4386
  const cwd = process.cwd();
3931
4387
  return {
3932
- project: join20(cwd, ".opencode", "oh-my-opencode.json"),
3933
- user: join20(homedir11(), ".config", "opencode", "oh-my-opencode.json"),
3934
- opencode: join20(homedir11(), ".config", "opencode", "opencode.json")
4388
+ project: join23(cwd, ".opencode", "oh-my-opencode.json"),
4389
+ user: join23(homedir11(), ".config", "opencode", "oh-my-opencode.json"),
4390
+ opencode: join23(homedir11(), ".config", "opencode", "opencode.json")
3935
4391
  };
3936
4392
  }
3937
4393
  function loadAllConfigs() {
@@ -4024,7 +4480,7 @@ function isServerInstalled(command) {
4024
4480
  const pathEnv = process.env.PATH || "";
4025
4481
  const paths = pathEnv.split(":");
4026
4482
  for (const p of paths) {
4027
- if (existsSync17(join20(p, cmd))) {
4483
+ if (existsSync19(join23(p, cmd))) {
4028
4484
  return true;
4029
4485
  }
4030
4486
  }
@@ -4074,8 +4530,8 @@ function getAllServers() {
4074
4530
  }
4075
4531
  // src/tools/lsp/client.ts
4076
4532
  var {spawn: spawn4 } = globalThis.Bun;
4077
- import { readFileSync as readFileSync10 } from "fs";
4078
- import { extname, resolve as resolve3 } from "path";
4533
+ import { readFileSync as readFileSync12 } from "fs";
4534
+ import { extname, resolve as resolve4 } from "path";
4079
4535
  class LSPServerManager {
4080
4536
  static instance;
4081
4537
  clients = new Map;
@@ -4225,7 +4681,7 @@ class LSPClient {
4225
4681
  }
4226
4682
  this.startReading();
4227
4683
  this.startStderrReading();
4228
- await new Promise((resolve4) => setTimeout(resolve4, 100));
4684
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
4229
4685
  if (this.proc.exitCode !== null) {
4230
4686
  const stderr = this.stderrBuffer.join(`
4231
4687
  `);
@@ -4362,8 +4818,8 @@ stderr: ${stderr}` : ""));
4362
4818
  \r
4363
4819
  `;
4364
4820
  this.proc.stdin.write(header + msg);
4365
- return new Promise((resolve4, reject) => {
4366
- this.pending.set(id, { resolve: resolve4, reject });
4821
+ return new Promise((resolve5, reject) => {
4822
+ this.pending.set(id, { resolve: resolve5, reject });
4367
4823
  setTimeout(() => {
4368
4824
  if (this.pending.has(id)) {
4369
4825
  this.pending.delete(id);
@@ -4471,10 +4927,10 @@ ${msg}`);
4471
4927
  await new Promise((r) => setTimeout(r, 300));
4472
4928
  }
4473
4929
  async openFile(filePath) {
4474
- const absPath = resolve3(filePath);
4930
+ const absPath = resolve4(filePath);
4475
4931
  if (this.openedFiles.has(absPath))
4476
4932
  return;
4477
- const text = readFileSync10(absPath, "utf-8");
4933
+ const text = readFileSync12(absPath, "utf-8");
4478
4934
  const ext = extname(absPath);
4479
4935
  const languageId = getLanguageId(ext);
4480
4936
  this.notify("textDocument/didOpen", {
@@ -4489,7 +4945,7 @@ ${msg}`);
4489
4945
  await new Promise((r) => setTimeout(r, 1000));
4490
4946
  }
4491
4947
  async hover(filePath, line, character) {
4492
- const absPath = resolve3(filePath);
4948
+ const absPath = resolve4(filePath);
4493
4949
  await this.openFile(absPath);
4494
4950
  return this.send("textDocument/hover", {
4495
4951
  textDocument: { uri: `file://${absPath}` },
@@ -4497,7 +4953,7 @@ ${msg}`);
4497
4953
  });
4498
4954
  }
4499
4955
  async definition(filePath, line, character) {
4500
- const absPath = resolve3(filePath);
4956
+ const absPath = resolve4(filePath);
4501
4957
  await this.openFile(absPath);
4502
4958
  return this.send("textDocument/definition", {
4503
4959
  textDocument: { uri: `file://${absPath}` },
@@ -4505,7 +4961,7 @@ ${msg}`);
4505
4961
  });
4506
4962
  }
4507
4963
  async references(filePath, line, character, includeDeclaration = true) {
4508
- const absPath = resolve3(filePath);
4964
+ const absPath = resolve4(filePath);
4509
4965
  await this.openFile(absPath);
4510
4966
  return this.send("textDocument/references", {
4511
4967
  textDocument: { uri: `file://${absPath}` },
@@ -4514,7 +4970,7 @@ ${msg}`);
4514
4970
  });
4515
4971
  }
4516
4972
  async documentSymbols(filePath) {
4517
- const absPath = resolve3(filePath);
4973
+ const absPath = resolve4(filePath);
4518
4974
  await this.openFile(absPath);
4519
4975
  return this.send("textDocument/documentSymbol", {
4520
4976
  textDocument: { uri: `file://${absPath}` }
@@ -4524,7 +4980,7 @@ ${msg}`);
4524
4980
  return this.send("workspace/symbol", { query });
4525
4981
  }
4526
4982
  async diagnostics(filePath) {
4527
- const absPath = resolve3(filePath);
4983
+ const absPath = resolve4(filePath);
4528
4984
  const uri = `file://${absPath}`;
4529
4985
  await this.openFile(absPath);
4530
4986
  await new Promise((r) => setTimeout(r, 500));
@@ -4539,7 +4995,7 @@ ${msg}`);
4539
4995
  return { items: this.diagnosticsStore.get(uri) ?? [] };
4540
4996
  }
4541
4997
  async prepareRename(filePath, line, character) {
4542
- const absPath = resolve3(filePath);
4998
+ const absPath = resolve4(filePath);
4543
4999
  await this.openFile(absPath);
4544
5000
  return this.send("textDocument/prepareRename", {
4545
5001
  textDocument: { uri: `file://${absPath}` },
@@ -4547,7 +5003,7 @@ ${msg}`);
4547
5003
  });
4548
5004
  }
4549
5005
  async rename(filePath, line, character, newName) {
4550
- const absPath = resolve3(filePath);
5006
+ const absPath = resolve4(filePath);
4551
5007
  await this.openFile(absPath);
4552
5008
  return this.send("textDocument/rename", {
4553
5009
  textDocument: { uri: `file://${absPath}` },
@@ -4556,7 +5012,7 @@ ${msg}`);
4556
5012
  });
4557
5013
  }
4558
5014
  async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
4559
- const absPath = resolve3(filePath);
5015
+ const absPath = resolve4(filePath);
4560
5016
  await this.openFile(absPath);
4561
5017
  return this.send("textDocument/codeAction", {
4562
5018
  textDocument: { uri: `file://${absPath}` },
@@ -4588,26 +5044,26 @@ ${msg}`);
4588
5044
  }
4589
5045
  }
4590
5046
  // src/tools/lsp/utils.ts
4591
- import { extname as extname2, resolve as resolve4 } from "path";
4592
- import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
5047
+ import { extname as extname2, resolve as resolve5 } from "path";
5048
+ import { existsSync as existsSync20, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
4593
5049
  function findWorkspaceRoot(filePath) {
4594
- let dir = resolve4(filePath);
4595
- if (!existsSync18(dir) || !__require("fs").statSync(dir).isDirectory()) {
5050
+ let dir = resolve5(filePath);
5051
+ if (!existsSync20(dir) || !__require("fs").statSync(dir).isDirectory()) {
4596
5052
  dir = __require("path").dirname(dir);
4597
5053
  }
4598
5054
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
4599
5055
  while (dir !== "/") {
4600
5056
  for (const marker of markers) {
4601
- if (existsSync18(__require("path").join(dir, marker))) {
5057
+ if (existsSync20(__require("path").join(dir, marker))) {
4602
5058
  return dir;
4603
5059
  }
4604
5060
  }
4605
5061
  dir = __require("path").dirname(dir);
4606
5062
  }
4607
- return __require("path").dirname(resolve4(filePath));
5063
+ return __require("path").dirname(resolve5(filePath));
4608
5064
  }
4609
5065
  async function withLspClient(filePath, fn) {
4610
- const absPath = resolve4(filePath);
5066
+ const absPath = resolve5(filePath);
4611
5067
  const ext = extname2(absPath);
4612
5068
  const server = findServerForExtension(ext);
4613
5069
  if (!server) {
@@ -4756,7 +5212,7 @@ function formatCodeActions(actions) {
4756
5212
  }
4757
5213
  function applyTextEditsToFile(filePath, edits) {
4758
5214
  try {
4759
- let content = readFileSync11(filePath, "utf-8");
5215
+ let content = readFileSync13(filePath, "utf-8");
4760
5216
  const lines = content.split(`
4761
5217
  `);
4762
5218
  const sortedEdits = [...edits].sort((a, b) => {
@@ -4781,7 +5237,7 @@ function applyTextEditsToFile(filePath, edits) {
4781
5237
  `));
4782
5238
  }
4783
5239
  }
4784
- writeFileSync5(filePath, lines.join(`
5240
+ writeFileSync6(filePath, lines.join(`
4785
5241
  `), "utf-8");
4786
5242
  return { success: true, editCount: edits.length };
4787
5243
  } catch (err) {
@@ -4812,7 +5268,7 @@ function applyWorkspaceEdit(edit) {
4812
5268
  if (change.kind === "create") {
4813
5269
  try {
4814
5270
  const filePath = change.uri.replace("file://", "");
4815
- writeFileSync5(filePath, "", "utf-8");
5271
+ writeFileSync6(filePath, "", "utf-8");
4816
5272
  result.filesModified.push(filePath);
4817
5273
  } catch (err) {
4818
5274
  result.success = false;
@@ -4822,8 +5278,8 @@ function applyWorkspaceEdit(edit) {
4822
5278
  try {
4823
5279
  const oldPath = change.oldUri.replace("file://", "");
4824
5280
  const newPath = change.newUri.replace("file://", "");
4825
- const content = readFileSync11(oldPath, "utf-8");
4826
- writeFileSync5(newPath, content, "utf-8");
5281
+ const content = readFileSync13(oldPath, "utf-8");
5282
+ writeFileSync6(newPath, content, "utf-8");
4827
5283
  __require("fs").unlinkSync(oldPath);
4828
5284
  result.filesModified.push(newPath);
4829
5285
  } catch (err) {
@@ -17523,13 +17979,13 @@ var lsp_code_action_resolve = tool({
17523
17979
  });
17524
17980
  // src/tools/ast-grep/constants.ts
17525
17981
  import { createRequire as createRequire4 } from "module";
17526
- import { dirname as dirname3, join as join22 } from "path";
17527
- import { existsSync as existsSync20, statSync as statSync3 } from "fs";
17982
+ import { dirname as dirname4, join as join25 } from "path";
17983
+ import { existsSync as existsSync22, statSync as statSync3 } from "fs";
17528
17984
 
17529
17985
  // src/tools/ast-grep/downloader.ts
17530
17986
  var {spawn: spawn5 } = globalThis.Bun;
17531
- import { existsSync as existsSync19, mkdirSync as mkdirSync6, chmodSync as chmodSync2, unlinkSync as unlinkSync5 } from "fs";
17532
- import { join as join21 } from "path";
17987
+ import { existsSync as existsSync21, mkdirSync as mkdirSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync6 } from "fs";
17988
+ import { join as join24 } from "path";
17533
17989
  import { homedir as homedir12 } from "os";
17534
17990
  import { createRequire as createRequire3 } from "module";
17535
17991
  var REPO2 = "ast-grep/ast-grep";
@@ -17555,19 +18011,19 @@ var PLATFORM_MAP2 = {
17555
18011
  function getCacheDir2() {
17556
18012
  if (process.platform === "win32") {
17557
18013
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
17558
- const base2 = localAppData || join21(homedir12(), "AppData", "Local");
17559
- return join21(base2, "oh-my-opencode", "bin");
18014
+ const base2 = localAppData || join24(homedir12(), "AppData", "Local");
18015
+ return join24(base2, "oh-my-opencode", "bin");
17560
18016
  }
17561
18017
  const xdgCache2 = process.env.XDG_CACHE_HOME;
17562
- const base = xdgCache2 || join21(homedir12(), ".cache");
17563
- return join21(base, "oh-my-opencode", "bin");
18018
+ const base = xdgCache2 || join24(homedir12(), ".cache");
18019
+ return join24(base, "oh-my-opencode", "bin");
17564
18020
  }
17565
18021
  function getBinaryName3() {
17566
18022
  return process.platform === "win32" ? "sg.exe" : "sg";
17567
18023
  }
17568
18024
  function getCachedBinaryPath2() {
17569
- const binaryPath = join21(getCacheDir2(), getBinaryName3());
17570
- return existsSync19(binaryPath) ? binaryPath : null;
18025
+ const binaryPath = join24(getCacheDir2(), getBinaryName3());
18026
+ return existsSync21(binaryPath) ? binaryPath : null;
17571
18027
  }
17572
18028
  async function extractZip2(archivePath, destDir) {
17573
18029
  const proc = process.platform === "win32" ? spawn5([
@@ -17593,8 +18049,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
17593
18049
  }
17594
18050
  const cacheDir = getCacheDir2();
17595
18051
  const binaryName = getBinaryName3();
17596
- const binaryPath = join21(cacheDir, binaryName);
17597
- if (existsSync19(binaryPath)) {
18052
+ const binaryPath = join24(cacheDir, binaryName);
18053
+ if (existsSync21(binaryPath)) {
17598
18054
  return binaryPath;
17599
18055
  }
17600
18056
  const { arch, os: os3 } = platformInfo;
@@ -17602,21 +18058,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
17602
18058
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
17603
18059
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
17604
18060
  try {
17605
- if (!existsSync19(cacheDir)) {
17606
- mkdirSync6(cacheDir, { recursive: true });
18061
+ if (!existsSync21(cacheDir)) {
18062
+ mkdirSync7(cacheDir, { recursive: true });
17607
18063
  }
17608
18064
  const response = await fetch(downloadUrl, { redirect: "follow" });
17609
18065
  if (!response.ok) {
17610
18066
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
17611
18067
  }
17612
- const archivePath = join21(cacheDir, assetName);
18068
+ const archivePath = join24(cacheDir, assetName);
17613
18069
  const arrayBuffer = await response.arrayBuffer();
17614
18070
  await Bun.write(archivePath, arrayBuffer);
17615
18071
  await extractZip2(archivePath, cacheDir);
17616
- if (existsSync19(archivePath)) {
17617
- unlinkSync5(archivePath);
18072
+ if (existsSync21(archivePath)) {
18073
+ unlinkSync6(archivePath);
17618
18074
  }
17619
- if (process.platform !== "win32" && existsSync19(binaryPath)) {
18075
+ if (process.platform !== "win32" && existsSync21(binaryPath)) {
17620
18076
  chmodSync2(binaryPath, 493);
17621
18077
  }
17622
18078
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -17666,9 +18122,9 @@ function findSgCliPathSync() {
17666
18122
  try {
17667
18123
  const require2 = createRequire4(import.meta.url);
17668
18124
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
17669
- const cliDir = dirname3(cliPkgPath);
17670
- const sgPath = join22(cliDir, binaryName);
17671
- if (existsSync20(sgPath) && isValidBinary(sgPath)) {
18125
+ const cliDir = dirname4(cliPkgPath);
18126
+ const sgPath = join25(cliDir, binaryName);
18127
+ if (existsSync22(sgPath) && isValidBinary(sgPath)) {
17672
18128
  return sgPath;
17673
18129
  }
17674
18130
  } catch {}
@@ -17677,10 +18133,10 @@ function findSgCliPathSync() {
17677
18133
  try {
17678
18134
  const require2 = createRequire4(import.meta.url);
17679
18135
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
17680
- const pkgDir = dirname3(pkgPath);
18136
+ const pkgDir = dirname4(pkgPath);
17681
18137
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
17682
- const binaryPath = join22(pkgDir, astGrepName);
17683
- if (existsSync20(binaryPath) && isValidBinary(binaryPath)) {
18138
+ const binaryPath = join25(pkgDir, astGrepName);
18139
+ if (existsSync22(binaryPath) && isValidBinary(binaryPath)) {
17684
18140
  return binaryPath;
17685
18141
  }
17686
18142
  } catch {}
@@ -17688,7 +18144,7 @@ function findSgCliPathSync() {
17688
18144
  if (process.platform === "darwin") {
17689
18145
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
17690
18146
  for (const path3 of homebrewPaths) {
17691
- if (existsSync20(path3) && isValidBinary(path3)) {
18147
+ if (existsSync22(path3) && isValidBinary(path3)) {
17692
18148
  return path3;
17693
18149
  }
17694
18150
  }
@@ -17744,11 +18200,11 @@ var DEFAULT_MAX_MATCHES = 500;
17744
18200
 
17745
18201
  // src/tools/ast-grep/cli.ts
17746
18202
  var {spawn: spawn6 } = globalThis.Bun;
17747
- import { existsSync as existsSync21 } from "fs";
18203
+ import { existsSync as existsSync23 } from "fs";
17748
18204
  var resolvedCliPath3 = null;
17749
18205
  var initPromise2 = null;
17750
18206
  async function getAstGrepPath() {
17751
- if (resolvedCliPath3 !== null && existsSync21(resolvedCliPath3)) {
18207
+ if (resolvedCliPath3 !== null && existsSync23(resolvedCliPath3)) {
17752
18208
  return resolvedCliPath3;
17753
18209
  }
17754
18210
  if (initPromise2) {
@@ -17756,7 +18212,7 @@ async function getAstGrepPath() {
17756
18212
  }
17757
18213
  initPromise2 = (async () => {
17758
18214
  const syncPath = findSgCliPathSync();
17759
- if (syncPath && existsSync21(syncPath)) {
18215
+ if (syncPath && existsSync23(syncPath)) {
17760
18216
  resolvedCliPath3 = syncPath;
17761
18217
  setSgCliPath(syncPath);
17762
18218
  return syncPath;
@@ -17790,7 +18246,7 @@ async function runSg(options) {
17790
18246
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
17791
18247
  args.push(...paths);
17792
18248
  let cliPath = getSgCliPath();
17793
- if (!existsSync21(cliPath) && cliPath !== "sg") {
18249
+ if (!existsSync23(cliPath) && cliPath !== "sg") {
17794
18250
  const downloadedPath = await getAstGrepPath();
17795
18251
  if (downloadedPath) {
17796
18252
  cliPath = downloadedPath;
@@ -18054,8 +18510,8 @@ var ast_grep_replace = tool({
18054
18510
  var {spawn: spawn7 } = globalThis.Bun;
18055
18511
 
18056
18512
  // src/tools/grep/constants.ts
18057
- import { existsSync as existsSync22 } from "fs";
18058
- import { join as join23, dirname as dirname4 } from "path";
18513
+ import { existsSync as existsSync24 } from "fs";
18514
+ import { join as join26, dirname as dirname5 } from "path";
18059
18515
  import { spawnSync } from "child_process";
18060
18516
  var cachedCli = null;
18061
18517
  function findExecutable(name) {
@@ -18072,17 +18528,17 @@ function findExecutable(name) {
18072
18528
  }
18073
18529
  function getOpenCodeBundledRg() {
18074
18530
  const execPath = process.execPath;
18075
- const execDir = dirname4(execPath);
18531
+ const execDir = dirname5(execPath);
18076
18532
  const isWindows = process.platform === "win32";
18077
18533
  const rgName = isWindows ? "rg.exe" : "rg";
18078
18534
  const candidates = [
18079
- join23(execDir, rgName),
18080
- join23(execDir, "bin", rgName),
18081
- join23(execDir, "..", "bin", rgName),
18082
- join23(execDir, "..", "libexec", rgName)
18535
+ join26(execDir, rgName),
18536
+ join26(execDir, "bin", rgName),
18537
+ join26(execDir, "..", "bin", rgName),
18538
+ join26(execDir, "..", "libexec", rgName)
18083
18539
  ];
18084
18540
  for (const candidate of candidates) {
18085
- if (existsSync22(candidate)) {
18541
+ if (existsSync24(candidate)) {
18086
18542
  return candidate;
18087
18543
  }
18088
18544
  }
@@ -18480,11 +18936,11 @@ var glob = tool({
18480
18936
  }
18481
18937
  });
18482
18938
  // src/tools/slashcommand/tools.ts
18483
- import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "fs";
18939
+ import { existsSync as existsSync25, readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
18484
18940
  import { homedir as homedir13 } from "os";
18485
- import { join as join24, basename as basename3, dirname as dirname5 } from "path";
18941
+ import { join as join27, basename as basename3, dirname as dirname6 } from "path";
18486
18942
  function discoverCommandsFromDir(commandsDir, scope) {
18487
- if (!existsSync23(commandsDir)) {
18943
+ if (!existsSync25(commandsDir)) {
18488
18944
  return [];
18489
18945
  }
18490
18946
  const entries = readdirSync6(commandsDir, { withFileTypes: true });
@@ -18496,10 +18952,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
18496
18952
  continue;
18497
18953
  if (!entry.isFile())
18498
18954
  continue;
18499
- const commandPath = join24(commandsDir, entry.name);
18955
+ const commandPath = join27(commandsDir, entry.name);
18500
18956
  const commandName = basename3(entry.name, ".md");
18501
18957
  try {
18502
- const content = readFileSync12(commandPath, "utf-8");
18958
+ const content = readFileSync14(commandPath, "utf-8");
18503
18959
  const { data, body } = parseFrontmatter(content);
18504
18960
  const metadata = {
18505
18961
  name: commandName,
@@ -18523,10 +18979,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
18523
18979
  return commands;
18524
18980
  }
18525
18981
  function discoverCommandsSync() {
18526
- const userCommandsDir = join24(homedir13(), ".claude", "commands");
18527
- const projectCommandsDir = join24(process.cwd(), ".claude", "commands");
18528
- const opencodeGlobalDir = join24(homedir13(), ".config", "opencode", "command");
18529
- const opencodeProjectDir = join24(process.cwd(), ".opencode", "command");
18982
+ const userCommandsDir = join27(homedir13(), ".claude", "commands");
18983
+ const projectCommandsDir = join27(process.cwd(), ".claude", "commands");
18984
+ const opencodeGlobalDir = join27(homedir13(), ".config", "opencode", "command");
18985
+ const opencodeProjectDir = join27(process.cwd(), ".opencode", "command");
18530
18986
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
18531
18987
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
18532
18988
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -18569,7 +19025,7 @@ async function formatLoadedCommand(cmd) {
18569
19025
  `);
18570
19026
  sections.push(`## Command Instructions
18571
19027
  `);
18572
- const commandDir = dirname5(cmd.path);
19028
+ const commandDir = dirname6(cmd.path);
18573
19029
  const withFileRefs = await resolveFileReferencesInText(cmd.content, commandDir);
18574
19030
  const resolvedContent = await resolveCommandsInText(withFileRefs);
18575
19031
  sections.push(resolvedContent.trim());
@@ -18649,12 +19105,29 @@ Provide a command name to execute.`;
18649
19105
  Try a different command name.`;
18650
19106
  }
18651
19107
  });
19108
+ // src/tools/skill/types.ts
19109
+ var SkillFrontmatterSchema = exports_external.object({
19110
+ name: exports_external.string().regex(/^[a-z0-9-]+$/, "Name must be lowercase alphanumeric with hyphens only").min(1, "Name cannot be empty"),
19111
+ description: exports_external.string().min(20, "Description must be at least 20 characters for discoverability"),
19112
+ license: exports_external.string().optional(),
19113
+ "allowed-tools": exports_external.array(exports_external.string()).optional(),
19114
+ metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
19115
+ });
18652
19116
  // src/tools/skill/tools.ts
18653
- import { existsSync as existsSync24, readdirSync as readdirSync7, statSync as statSync4, readlinkSync as readlinkSync2, readFileSync as readFileSync13 } from "fs";
19117
+ import { existsSync as existsSync26, readdirSync as readdirSync7, statSync as statSync4, readlinkSync as readlinkSync2, readFileSync as readFileSync15 } from "fs";
18654
19118
  import { homedir as homedir14 } from "os";
18655
- import { join as join25, resolve as resolve5, basename as basename4 } from "path";
19119
+ import { join as join28, resolve as resolve6, basename as basename4 } from "path";
19120
+ function parseSkillFrontmatter(data) {
19121
+ return {
19122
+ name: typeof data.name === "string" ? data.name : "",
19123
+ description: typeof data.description === "string" ? data.description : "",
19124
+ license: typeof data.license === "string" ? data.license : undefined,
19125
+ "allowed-tools": Array.isArray(data["allowed-tools"]) ? data["allowed-tools"] : undefined,
19126
+ metadata: typeof data.metadata === "object" && data.metadata !== null ? data.metadata : undefined
19127
+ };
19128
+ }
18656
19129
  function discoverSkillsFromDir(skillsDir, scope) {
18657
- if (!existsSync24(skillsDir)) {
19130
+ if (!existsSync26(skillsDir)) {
18658
19131
  return [];
18659
19132
  }
18660
19133
  const entries = readdirSync7(skillsDir, { withFileTypes: true });
@@ -18662,22 +19135,22 @@ function discoverSkillsFromDir(skillsDir, scope) {
18662
19135
  for (const entry of entries) {
18663
19136
  if (entry.name.startsWith("."))
18664
19137
  continue;
18665
- const skillPath = join25(skillsDir, entry.name);
19138
+ const skillPath = join28(skillsDir, entry.name);
18666
19139
  if (entry.isDirectory() || entry.isSymbolicLink()) {
18667
19140
  let resolvedPath = skillPath;
18668
19141
  try {
18669
19142
  const stats = statSync4(skillPath, { throwIfNoEntry: false });
18670
19143
  if (stats?.isSymbolicLink()) {
18671
- resolvedPath = resolve5(skillPath, "..", readlinkSync2(skillPath));
19144
+ resolvedPath = resolve6(skillPath, "..", readlinkSync2(skillPath));
18672
19145
  }
18673
19146
  } catch {
18674
19147
  continue;
18675
19148
  }
18676
- const skillMdPath = join25(resolvedPath, "SKILL.md");
18677
- if (!existsSync24(skillMdPath))
19149
+ const skillMdPath = join28(resolvedPath, "SKILL.md");
19150
+ if (!existsSync26(skillMdPath))
18678
19151
  continue;
18679
19152
  try {
18680
- const content = readFileSync13(skillMdPath, "utf-8");
19153
+ const content = readFileSync15(skillMdPath, "utf-8");
18681
19154
  const { data } = parseFrontmatter(content);
18682
19155
  skills.push({
18683
19156
  name: data.name || entry.name,
@@ -18692,8 +19165,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
18692
19165
  return skills;
18693
19166
  }
18694
19167
  function discoverSkillsSync() {
18695
- const userSkillsDir = join25(homedir14(), ".claude", "skills");
18696
- const projectSkillsDir = join25(process.cwd(), ".claude", "skills");
19168
+ const userSkillsDir = join28(homedir14(), ".claude", "skills");
19169
+ const projectSkillsDir = join28(process.cwd(), ".claude", "skills");
18697
19170
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
18698
19171
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
18699
19172
  return [...projectSkills, ...userSkills];
@@ -18705,7 +19178,7 @@ function resolveSymlink(skillPath) {
18705
19178
  try {
18706
19179
  const stats = statSync4(skillPath, { throwIfNoEntry: false });
18707
19180
  if (stats?.isSymbolicLink()) {
18708
- return resolve5(skillPath, "..", readlinkSync2(skillPath));
19181
+ return resolve6(skillPath, "..", readlinkSync2(skillPath));
18709
19182
  }
18710
19183
  return skillPath;
18711
19184
  } catch {
@@ -18714,28 +19187,32 @@ function resolveSymlink(skillPath) {
18714
19187
  }
18715
19188
  async function parseSkillMd(skillPath) {
18716
19189
  const resolvedPath = resolveSymlink(skillPath);
18717
- const skillMdPath = join25(resolvedPath, "SKILL.md");
18718
- if (!existsSync24(skillMdPath)) {
19190
+ const skillMdPath = join28(resolvedPath, "SKILL.md");
19191
+ if (!existsSync26(skillMdPath)) {
18719
19192
  return null;
18720
19193
  }
18721
19194
  try {
18722
- let content = readFileSync13(skillMdPath, "utf-8");
19195
+ let content = readFileSync15(skillMdPath, "utf-8");
18723
19196
  content = await resolveCommandsInText(content);
18724
19197
  const { data, body } = parseFrontmatter(content);
19198
+ const frontmatter2 = parseSkillFrontmatter(data);
18725
19199
  const metadata = {
18726
- name: data.name || basename4(skillPath),
18727
- description: data.description || "",
18728
- license: data.license
19200
+ name: frontmatter2.name || basename4(skillPath),
19201
+ description: frontmatter2.description,
19202
+ license: frontmatter2.license,
19203
+ allowedTools: frontmatter2["allowed-tools"],
19204
+ metadata: frontmatter2.metadata
18729
19205
  };
18730
- const referencesDir = join25(resolvedPath, "references");
18731
- const scriptsDir = join25(resolvedPath, "scripts");
18732
- const assetsDir = join25(resolvedPath, "assets");
18733
- const references = existsSync24(referencesDir) ? readdirSync7(referencesDir).filter((f) => !f.startsWith(".")) : [];
18734
- const scripts = existsSync24(scriptsDir) ? readdirSync7(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
18735
- const assets = existsSync24(assetsDir) ? readdirSync7(assetsDir).filter((f) => !f.startsWith(".")) : [];
19206
+ const referencesDir = join28(resolvedPath, "references");
19207
+ const scriptsDir = join28(resolvedPath, "scripts");
19208
+ const assetsDir = join28(resolvedPath, "assets");
19209
+ const references = existsSync26(referencesDir) ? readdirSync7(referencesDir).filter((f) => !f.startsWith(".")) : [];
19210
+ const scripts = existsSync26(scriptsDir) ? readdirSync7(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
19211
+ const assets = existsSync26(assetsDir) ? readdirSync7(assetsDir).filter((f) => !f.startsWith(".")) : [];
18736
19212
  return {
18737
19213
  name: metadata.name,
18738
19214
  path: resolvedPath,
19215
+ basePath: resolvedPath,
18739
19216
  metadata,
18740
19217
  content: body,
18741
19218
  references,
@@ -18747,7 +19224,7 @@ async function parseSkillMd(skillPath) {
18747
19224
  }
18748
19225
  }
18749
19226
  async function discoverSkillsFromDirAsync(skillsDir) {
18750
- if (!existsSync24(skillsDir)) {
19227
+ if (!existsSync26(skillsDir)) {
18751
19228
  return [];
18752
19229
  }
18753
19230
  const entries = readdirSync7(skillsDir, { withFileTypes: true });
@@ -18755,7 +19232,7 @@ async function discoverSkillsFromDirAsync(skillsDir) {
18755
19232
  for (const entry of entries) {
18756
19233
  if (entry.name.startsWith("."))
18757
19234
  continue;
18758
- const skillPath = join25(skillsDir, entry.name);
19235
+ const skillPath = join28(skillsDir, entry.name);
18759
19236
  if (entry.isDirectory() || entry.isSymbolicLink()) {
18760
19237
  const skillInfo = await parseSkillMd(skillPath);
18761
19238
  if (skillInfo) {
@@ -18766,8 +19243,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
18766
19243
  return skills;
18767
19244
  }
18768
19245
  async function discoverSkills() {
18769
- const userSkillsDir = join25(homedir14(), ".claude", "skills");
18770
- const projectSkillsDir = join25(process.cwd(), ".claude", "skills");
19246
+ const userSkillsDir = join28(homedir14(), ".claude", "skills");
19247
+ const projectSkillsDir = join28(process.cwd(), ".claude", "skills");
18771
19248
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
18772
19249
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
18773
19250
  return [...projectSkills, ...userSkills];
@@ -18796,9 +19273,9 @@ async function loadSkillWithReferences(skill, includeRefs) {
18796
19273
  const referencesLoaded = [];
18797
19274
  if (includeRefs && skill.references.length > 0) {
18798
19275
  for (const ref of skill.references) {
18799
- const refPath = join25(skill.path, "references", ref);
19276
+ const refPath = join28(skill.path, "references", ref);
18800
19277
  try {
18801
- let content = readFileSync13(refPath, "utf-8");
19278
+ let content = readFileSync15(refPath, "utf-8");
18802
19279
  content = await resolveCommandsInText(content);
18803
19280
  referencesLoaded.push({ path: ref, content });
18804
19281
  } catch {}
@@ -18807,6 +19284,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
18807
19284
  return {
18808
19285
  name: skill.name,
18809
19286
  metadata: skill.metadata,
19287
+ basePath: skill.basePath,
18810
19288
  body: skill.content,
18811
19289
  referencesLoaded
18812
19290
  };
@@ -18829,62 +19307,34 @@ function formatLoadedSkills(loadedSkills) {
18829
19307
  if (loadedSkills.length === 0) {
18830
19308
  return "No skills loaded.";
18831
19309
  }
18832
- const sections = [`# Loaded Skills
18833
- `];
18834
- for (const skill of loadedSkills) {
18835
- sections.push(`## ${skill.metadata.name}
18836
- `);
18837
- sections.push(`**Description**: ${skill.metadata.description || "(no description)"}
18838
- `);
18839
- sections.push(`### Skill Instructions
18840
- `);
18841
- sections.push(skill.body.trim());
18842
- if (skill.referencesLoaded.length > 0) {
18843
- sections.push(`
19310
+ const skill = loadedSkills[0];
19311
+ const sections = [];
19312
+ sections.push(`Base directory for this skill: ${skill.basePath}/`);
19313
+ sections.push("");
19314
+ sections.push(skill.body.trim());
19315
+ if (skill.referencesLoaded.length > 0) {
19316
+ sections.push(`
19317
+ ---
18844
19318
  ### Loaded References
18845
19319
  `);
18846
- for (const ref of skill.referencesLoaded) {
18847
- sections.push(`#### ${ref.path}
19320
+ for (const ref of skill.referencesLoaded) {
19321
+ sections.push(`#### ${ref.path}
18848
19322
  `);
18849
- sections.push("```");
18850
- sections.push(ref.content.trim());
18851
- sections.push("```\n");
18852
- }
19323
+ sections.push("```");
19324
+ sections.push(ref.content.trim());
19325
+ sections.push("```\n");
18853
19326
  }
18854
- sections.push(`
18855
- ---
18856
- `);
18857
19327
  }
18858
- const skillNames = loadedSkills.map((s) => s.metadata.name).join(", ");
18859
- sections.push(`**Skills loaded**: ${skillNames}`);
18860
- sections.push(`**Total**: ${loadedSkills.length} skill(s)`);
18861
19328
  sections.push(`
18862
- Please confirm these skills match your needs before proceeding.`);
19329
+ ---
19330
+ **Launched skill**: ${skill.metadata.name}`);
18863
19331
  return sections.join(`
18864
19332
  `);
18865
19333
  }
18866
19334
  var skill = tool({
18867
19335
  description: `Execute a skill within the main conversation.
18868
19336
 
18869
- 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.
18870
-
18871
- How to use skills:
18872
- - Invoke skills using this tool with the skill name only (no arguments)
18873
- - When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task
18874
-
18875
- Important:
18876
- - Only use skills listed in Available Skills below
18877
- - Do not invoke a skill that is already running
18878
-
18879
- Skills are loaded from:
18880
- - ~/.claude/skills/ (user scope - global skills)
18881
- - ./.claude/skills/ (project scope - project-specific skills)
18882
-
18883
- Each skill contains:
18884
- - SKILL.md: Main instructions with YAML frontmatter (name, description)
18885
- - references/: Documentation files loaded into context as needed
18886
- - scripts/: Executable code for deterministic operations
18887
- - assets/: Files used in output (templates, icons, etc.)
19337
+ When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task.
18888
19338
 
18889
19339
  Available Skills:
18890
19340
  ${skillListForDescription}`,
@@ -19009,26 +19459,62 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
19009
19459
  // src/index.ts
19010
19460
  import * as fs4 from "fs";
19011
19461
  import * as path3 from "path";
19462
+ import * as os3 from "os";
19463
+ function loadConfigFromPath2(configPath) {
19464
+ try {
19465
+ if (fs4.existsSync(configPath)) {
19466
+ const content = fs4.readFileSync(configPath, "utf-8");
19467
+ const rawConfig = JSON.parse(content);
19468
+ const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
19469
+ if (!result.success) {
19470
+ log(`Config validation error in ${configPath}:`, result.error.issues);
19471
+ return null;
19472
+ }
19473
+ log(`Config loaded from ${configPath}`, { agents: result.data.agents });
19474
+ return result.data;
19475
+ }
19476
+ } catch (err) {
19477
+ log(`Error loading config from ${configPath}:`, err);
19478
+ }
19479
+ return null;
19480
+ }
19481
+ function mergeConfigs(base, override) {
19482
+ return {
19483
+ ...base,
19484
+ ...override,
19485
+ agents: override.agents !== undefined ? { ...base.agents ?? {}, ...override.agents } : base.agents,
19486
+ disabled_agents: [
19487
+ ...new Set([...base.disabled_agents ?? [], ...override.disabled_agents ?? []])
19488
+ ],
19489
+ disabled_mcps: [
19490
+ ...new Set([...base.disabled_mcps ?? [], ...override.disabled_mcps ?? []])
19491
+ ]
19492
+ };
19493
+ }
19012
19494
  function loadPluginConfig(directory) {
19013
- const configPaths = [
19014
- path3.join(directory, "oh-my-opencode.json"),
19015
- path3.join(directory, ".oh-my-opencode.json")
19495
+ const userConfigPaths = [
19496
+ path3.join(os3.homedir(), ".config", "opencode", "oh-my-opencode.json")
19016
19497
  ];
19017
- for (const configPath of configPaths) {
19018
- try {
19019
- if (fs4.existsSync(configPath)) {
19020
- const content = fs4.readFileSync(configPath, "utf-8");
19021
- const rawConfig = JSON.parse(content);
19022
- const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
19023
- if (!result.success) {
19024
- log(`Config validation error in ${configPath}:`, result.error.issues);
19025
- return {};
19026
- }
19027
- return result.data;
19028
- }
19029
- } catch {}
19498
+ const projectConfigPaths = [
19499
+ path3.join(directory, ".opencode", "oh-my-opencode.json")
19500
+ ];
19501
+ let config3 = {};
19502
+ for (const configPath of userConfigPaths) {
19503
+ const userConfig = loadConfigFromPath2(configPath);
19504
+ if (userConfig) {
19505
+ config3 = userConfig;
19506
+ break;
19507
+ }
19508
+ }
19509
+ for (const configPath of projectConfigPaths) {
19510
+ const projectConfig = loadConfigFromPath2(configPath);
19511
+ if (projectConfig) {
19512
+ config3 = mergeConfigs(config3, projectConfig);
19513
+ break;
19514
+ }
19030
19515
  }
19031
- return {};
19516
+ log("Final merged config", { agents: config3.agents, disabled_agents: config3.disabled_agents, disabled_mcps: config3.disabled_mcps });
19517
+ return config3;
19032
19518
  }
19033
19519
  var OhMyOpenCodePlugin = async (ctx) => {
19034
19520
  const pluginConfig = loadPluginConfig(ctx.directory);
@@ -19038,9 +19524,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
19038
19524
  const commentChecker = createCommentCheckerHooks();
19039
19525
  const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
19040
19526
  const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
19527
+ const directoryReadmeInjector = createDirectoryReadmeInjectorHook(ctx);
19041
19528
  const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
19042
19529
  const thinkMode = createThinkModeHook();
19043
19530
  const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {});
19531
+ const anthropicAutoCompact = createAnthropicAutoCompactHook(ctx);
19044
19532
  updateTerminalTitle({ sessionId: "main" });
19045
19533
  return {
19046
19534
  tool: builtinTools,
@@ -19088,7 +19576,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
19088
19576
  await todoContinuationEnforcer(input);
19089
19577
  await contextWindowMonitor.event(input);
19090
19578
  await directoryAgentsInjector.event(input);
19579
+ await directoryReadmeInjector.event(input);
19091
19580
  await thinkMode.event(input);
19581
+ await anthropicAutoCompact.event(input);
19092
19582
  const { event } = input;
19093
19583
  const props = event.properties;
19094
19584
  if (event.type === "session.created") {
@@ -19186,6 +19676,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
19186
19676
  await contextWindowMonitor["tool.execute.after"](input, output);
19187
19677
  await commentChecker["tool.execute.after"](input, output);
19188
19678
  await directoryAgentsInjector["tool.execute.after"](input, output);
19679
+ await directoryReadmeInjector["tool.execute.after"](input, output);
19189
19680
  await emptyTaskResponseDetector["tool.execute.after"](input, output);
19190
19681
  if (input.sessionID === getMainSessionID()) {
19191
19682
  updateTerminalTitle({