oh-my-opencode 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1989,10 +1989,166 @@ function createThinkModeHook() {
1989
1989
  }
1990
1990
  };
1991
1991
  }
1992
- // src/features/claude-code-command-loader/loader.ts
1993
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
1992
+ // src/hooks/claude-code-hooks/config.ts
1994
1993
  import { homedir as homedir2 } from "os";
1995
- import { join as join8, basename } from "path";
1994
+ import { join as join8 } from "path";
1995
+ import { existsSync as existsSync7 } from "fs";
1996
+ function normalizeHookMatcher(raw) {
1997
+ return {
1998
+ matcher: raw.matcher ?? raw.pattern ?? "*",
1999
+ hooks: raw.hooks
2000
+ };
2001
+ }
2002
+ function normalizeHooksConfig(raw) {
2003
+ const result = {};
2004
+ const eventTypes = [
2005
+ "PreToolUse",
2006
+ "PostToolUse",
2007
+ "UserPromptSubmit",
2008
+ "Stop"
2009
+ ];
2010
+ for (const eventType of eventTypes) {
2011
+ if (raw[eventType]) {
2012
+ result[eventType] = raw[eventType].map(normalizeHookMatcher);
2013
+ }
2014
+ }
2015
+ return result;
2016
+ }
2017
+ function getClaudeSettingsPaths(customPath) {
2018
+ const home = homedir2();
2019
+ const paths = [
2020
+ join8(home, ".claude", "settings.json"),
2021
+ join8(process.cwd(), ".claude", "settings.json"),
2022
+ join8(process.cwd(), ".claude", "settings.local.json")
2023
+ ];
2024
+ if (customPath && existsSync7(customPath)) {
2025
+ paths.unshift(customPath);
2026
+ }
2027
+ return paths;
2028
+ }
2029
+ function mergeHooksConfig(base, override) {
2030
+ const result = { ...base };
2031
+ const eventTypes = [
2032
+ "PreToolUse",
2033
+ "PostToolUse",
2034
+ "UserPromptSubmit",
2035
+ "Stop"
2036
+ ];
2037
+ for (const eventType of eventTypes) {
2038
+ if (override[eventType]) {
2039
+ result[eventType] = [...base[eventType] || [], ...override[eventType]];
2040
+ }
2041
+ }
2042
+ return result;
2043
+ }
2044
+ async function loadClaudeHooksConfig(customSettingsPath) {
2045
+ const paths = getClaudeSettingsPaths(customSettingsPath);
2046
+ let mergedConfig = {};
2047
+ for (const settingsPath of paths) {
2048
+ if (existsSync7(settingsPath)) {
2049
+ try {
2050
+ const content = await Bun.file(settingsPath).text();
2051
+ const settings = JSON.parse(content);
2052
+ if (settings.hooks) {
2053
+ const normalizedHooks = normalizeHooksConfig(settings.hooks);
2054
+ mergedConfig = mergeHooksConfig(mergedConfig, normalizedHooks);
2055
+ }
2056
+ } catch {
2057
+ continue;
2058
+ }
2059
+ }
2060
+ }
2061
+ return Object.keys(mergedConfig).length > 0 ? mergedConfig : null;
2062
+ }
2063
+
2064
+ // src/hooks/claude-code-hooks/config-loader.ts
2065
+ import { existsSync as existsSync8 } from "fs";
2066
+ import { homedir as homedir3 } from "os";
2067
+ import { join as join10 } from "path";
2068
+
2069
+ // src/shared/logger.ts
2070
+ import * as fs3 from "fs";
2071
+ import * as os2 from "os";
2072
+ import * as path2 from "path";
2073
+ var logFile = path2.join(os2.tmpdir(), "oh-my-opencode.log");
2074
+ function log(message, data) {
2075
+ try {
2076
+ const timestamp = new Date().toISOString();
2077
+ const logEntry = `[${timestamp}] ${message} ${data ? JSON.stringify(data) : ""}
2078
+ `;
2079
+ fs3.appendFileSync(logFile, logEntry);
2080
+ } catch {}
2081
+ }
2082
+
2083
+ // src/hooks/claude-code-hooks/config-loader.ts
2084
+ var USER_CONFIG_PATH = join10(homedir3(), ".config", "opencode", "opencode-cc-plugin.json");
2085
+ function getProjectConfigPath() {
2086
+ return join10(process.cwd(), ".opencode", "opencode-cc-plugin.json");
2087
+ }
2088
+ async function loadConfigFromPath(path3) {
2089
+ if (!existsSync8(path3)) {
2090
+ return null;
2091
+ }
2092
+ try {
2093
+ const content = await Bun.file(path3).text();
2094
+ return JSON.parse(content);
2095
+ } catch (error) {
2096
+ log("Failed to load config", { path: path3, error });
2097
+ return null;
2098
+ }
2099
+ }
2100
+ function mergeDisabledHooks(base, override) {
2101
+ if (!override)
2102
+ return base ?? {};
2103
+ if (!base)
2104
+ return override;
2105
+ return {
2106
+ Stop: override.Stop ?? base.Stop,
2107
+ PreToolUse: override.PreToolUse ?? base.PreToolUse,
2108
+ PostToolUse: override.PostToolUse ?? base.PostToolUse,
2109
+ UserPromptSubmit: override.UserPromptSubmit ?? base.UserPromptSubmit
2110
+ };
2111
+ }
2112
+ async function loadPluginExtendedConfig() {
2113
+ const userConfig = await loadConfigFromPath(USER_CONFIG_PATH);
2114
+ const projectConfig = await loadConfigFromPath(getProjectConfigPath());
2115
+ const merged = {
2116
+ disabledHooks: mergeDisabledHooks(userConfig?.disabledHooks, projectConfig?.disabledHooks)
2117
+ };
2118
+ if (userConfig || projectConfig) {
2119
+ log("Plugin extended config loaded", {
2120
+ userConfigExists: userConfig !== null,
2121
+ projectConfigExists: projectConfig !== null,
2122
+ mergedDisabledHooks: merged.disabledHooks
2123
+ });
2124
+ }
2125
+ return merged;
2126
+ }
2127
+ var regexCache = new Map;
2128
+ function getRegex(pattern) {
2129
+ let regex = regexCache.get(pattern);
2130
+ if (!regex) {
2131
+ try {
2132
+ regex = new RegExp(pattern);
2133
+ regexCache.set(pattern, regex);
2134
+ } catch {
2135
+ regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
2136
+ regexCache.set(pattern, regex);
2137
+ }
2138
+ }
2139
+ return regex;
2140
+ }
2141
+ function isHookCommandDisabled(eventType, command, config) {
2142
+ if (!config?.disabledHooks)
2143
+ return false;
2144
+ const patterns = config.disabledHooks[eventType];
2145
+ if (!patterns || patterns.length === 0)
2146
+ return false;
2147
+ return patterns.some((pattern) => {
2148
+ const regex = getRegex(pattern);
2149
+ return regex.test(command);
2150
+ });
2151
+ }
1996
2152
 
1997
2153
  // src/shared/frontmatter.ts
1998
2154
  function parseFrontmatter(content) {
@@ -2017,31 +2173,1181 @@ function parseFrontmatter(content) {
2017
2173
  data[key] = value;
2018
2174
  }
2019
2175
  }
2020
- return { data, body };
2176
+ return { data, body };
2177
+ }
2178
+ // src/shared/command-executor.ts
2179
+ import { spawn as spawn3 } from "child_process";
2180
+ import { exec } from "child_process";
2181
+ import { promisify } from "util";
2182
+ import { existsSync as existsSync9 } from "fs";
2183
+ var DEFAULT_ZSH_PATHS = ["/bin/zsh", "/usr/bin/zsh", "/usr/local/bin/zsh"];
2184
+ function findZshPath(customZshPath) {
2185
+ if (customZshPath && existsSync9(customZshPath)) {
2186
+ return customZshPath;
2187
+ }
2188
+ for (const path3 of DEFAULT_ZSH_PATHS) {
2189
+ if (existsSync9(path3)) {
2190
+ return path3;
2191
+ }
2192
+ }
2193
+ return null;
2194
+ }
2195
+ var execAsync = promisify(exec);
2196
+ async function executeHookCommand(command, stdin, cwd, options) {
2197
+ const home = process.env.HOME ?? "";
2198
+ let expandedCommand = command.replace(/^~(?=\/|$)/g, home).replace(/\s~(?=\/)/g, ` ${home}`).replace(/\$CLAUDE_PROJECT_DIR/g, cwd).replace(/\$\{CLAUDE_PROJECT_DIR\}/g, cwd);
2199
+ let finalCommand = expandedCommand;
2200
+ if (options?.forceZsh) {
2201
+ const zshPath = options.zshPath || findZshPath();
2202
+ if (zshPath) {
2203
+ const escapedCommand = expandedCommand.replace(/'/g, "'\\''");
2204
+ finalCommand = `${zshPath} -lc '${escapedCommand}'`;
2205
+ }
2206
+ }
2207
+ return new Promise((resolve2) => {
2208
+ const proc = spawn3(finalCommand, {
2209
+ cwd,
2210
+ shell: true,
2211
+ env: { ...process.env, HOME: home, CLAUDE_PROJECT_DIR: cwd }
2212
+ });
2213
+ let stdout = "";
2214
+ let stderr = "";
2215
+ proc.stdout?.on("data", (data) => {
2216
+ stdout += data.toString();
2217
+ });
2218
+ proc.stderr?.on("data", (data) => {
2219
+ stderr += data.toString();
2220
+ });
2221
+ proc.stdin?.write(stdin);
2222
+ proc.stdin?.end();
2223
+ proc.on("close", (code) => {
2224
+ resolve2({
2225
+ exitCode: code ?? 0,
2226
+ stdout: stdout.trim(),
2227
+ stderr: stderr.trim()
2228
+ });
2229
+ });
2230
+ proc.on("error", (err) => {
2231
+ resolve2({
2232
+ exitCode: 1,
2233
+ stderr: err.message
2234
+ });
2235
+ });
2236
+ });
2237
+ }
2238
+ async function executeCommand(command) {
2239
+ try {
2240
+ const { stdout, stderr } = await execAsync(command);
2241
+ const out = stdout?.toString().trim() ?? "";
2242
+ const err = stderr?.toString().trim() ?? "";
2243
+ if (err) {
2244
+ if (out) {
2245
+ return `${out}
2246
+ [stderr: ${err}]`;
2247
+ }
2248
+ return `[stderr: ${err}]`;
2249
+ }
2250
+ return out;
2251
+ } catch (error) {
2252
+ const e = error;
2253
+ const stdout = e?.stdout?.toString().trim() ?? "";
2254
+ const stderr = e?.stderr?.toString().trim() ?? "";
2255
+ const errMsg = stderr || e?.message || String(error);
2256
+ if (stdout) {
2257
+ return `${stdout}
2258
+ [stderr: ${errMsg}]`;
2259
+ }
2260
+ return `[stderr: ${errMsg}]`;
2261
+ }
2262
+ }
2263
+ var COMMAND_PATTERN = /!`([^`]+)`/g;
2264
+ function findCommands(text) {
2265
+ const matches = [];
2266
+ let match;
2267
+ COMMAND_PATTERN.lastIndex = 0;
2268
+ while ((match = COMMAND_PATTERN.exec(text)) !== null) {
2269
+ matches.push({
2270
+ fullMatch: match[0],
2271
+ command: match[1],
2272
+ start: match.index,
2273
+ end: match.index + match[0].length
2274
+ });
2275
+ }
2276
+ return matches;
2277
+ }
2278
+ async function resolveCommandsInText(text, depth = 0, maxDepth = 3) {
2279
+ if (depth >= maxDepth) {
2280
+ return text;
2281
+ }
2282
+ const matches = findCommands(text);
2283
+ if (matches.length === 0) {
2284
+ return text;
2285
+ }
2286
+ const tasks = matches.map((m) => executeCommand(m.command));
2287
+ const results = await Promise.allSettled(tasks);
2288
+ const replacements = new Map;
2289
+ matches.forEach((match, idx) => {
2290
+ const result = results[idx];
2291
+ if (result.status === "rejected") {
2292
+ replacements.set(match.fullMatch, `[error: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}]`);
2293
+ } else {
2294
+ replacements.set(match.fullMatch, result.value);
2295
+ }
2296
+ });
2297
+ let resolved = text;
2298
+ for (const [pattern, replacement] of replacements.entries()) {
2299
+ resolved = resolved.split(pattern).join(replacement);
2300
+ }
2301
+ if (findCommands(resolved).length > 0) {
2302
+ return resolveCommandsInText(resolved, depth + 1, maxDepth);
2303
+ }
2304
+ return resolved;
2305
+ }
2306
+ // 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";
2309
+ var FILE_REFERENCE_PATTERN = /@([^\s@]+)/g;
2310
+ function findFileReferences(text) {
2311
+ const matches = [];
2312
+ let match;
2313
+ FILE_REFERENCE_PATTERN.lastIndex = 0;
2314
+ while ((match = FILE_REFERENCE_PATTERN.exec(text)) !== null) {
2315
+ matches.push({
2316
+ fullMatch: match[0],
2317
+ filePath: match[1],
2318
+ start: match.index,
2319
+ end: match.index + match[0].length
2320
+ });
2321
+ }
2322
+ return matches;
2323
+ }
2324
+ function resolveFilePath(filePath, cwd) {
2325
+ if (isAbsolute(filePath)) {
2326
+ return filePath;
2327
+ }
2328
+ return join11(cwd, filePath);
2329
+ }
2330
+ function readFileContent(resolvedPath) {
2331
+ if (!existsSync10(resolvedPath)) {
2332
+ return `[file not found: ${resolvedPath}]`;
2333
+ }
2334
+ const stat = statSync(resolvedPath);
2335
+ if (stat.isDirectory()) {
2336
+ return `[cannot read directory: ${resolvedPath}]`;
2337
+ }
2338
+ const content = readFileSync4(resolvedPath, "utf-8");
2339
+ return content;
2340
+ }
2341
+ async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0, maxDepth = 3) {
2342
+ if (depth >= maxDepth) {
2343
+ return text;
2344
+ }
2345
+ const matches = findFileReferences(text);
2346
+ if (matches.length === 0) {
2347
+ return text;
2348
+ }
2349
+ const replacements = new Map;
2350
+ for (const match of matches) {
2351
+ const resolvedPath = resolveFilePath(match.filePath, cwd);
2352
+ const content = readFileContent(resolvedPath);
2353
+ replacements.set(match.fullMatch, content);
2354
+ }
2355
+ let resolved = text;
2356
+ for (const [pattern, replacement] of replacements.entries()) {
2357
+ resolved = resolved.split(pattern).join(replacement);
2358
+ }
2359
+ if (findFileReferences(resolved).length > 0 && depth + 1 < maxDepth) {
2360
+ return resolveFileReferencesInText(resolved, cwd, depth + 1, maxDepth);
2361
+ }
2362
+ return resolved;
2363
+ }
2364
+ // src/shared/model-sanitizer.ts
2365
+ function sanitizeModelField(_model) {
2366
+ return;
2367
+ }
2368
+ // src/shared/snake-case.ts
2369
+ function camelToSnake(str) {
2370
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
2371
+ }
2372
+ function isPlainObject(value) {
2373
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2374
+ }
2375
+ function objectToSnakeCase(obj, deep = true) {
2376
+ const result = {};
2377
+ for (const [key, value] of Object.entries(obj)) {
2378
+ const snakeKey = camelToSnake(key);
2379
+ if (deep && isPlainObject(value)) {
2380
+ result[snakeKey] = objectToSnakeCase(value, true);
2381
+ } else if (deep && Array.isArray(value)) {
2382
+ result[snakeKey] = value.map((item) => isPlainObject(item) ? objectToSnakeCase(item, true) : item);
2383
+ } else {
2384
+ result[snakeKey] = value;
2385
+ }
2386
+ }
2387
+ return result;
2388
+ }
2389
+ // src/shared/tool-name.ts
2390
+ var SPECIAL_TOOL_MAPPINGS = {
2391
+ webfetch: "WebFetch",
2392
+ websearch: "WebSearch",
2393
+ todoread: "TodoRead",
2394
+ todowrite: "TodoWrite"
2395
+ };
2396
+ function toPascalCase(str) {
2397
+ return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
2398
+ }
2399
+ function transformToolName(toolName) {
2400
+ const lower = toolName.toLowerCase();
2401
+ if (lower in SPECIAL_TOOL_MAPPINGS) {
2402
+ return SPECIAL_TOOL_MAPPINGS[lower];
2403
+ }
2404
+ if (toolName.includes("-") || toolName.includes("_")) {
2405
+ return toPascalCase(toolName);
2406
+ }
2407
+ return toolName.charAt(0).toUpperCase() + toolName.slice(1);
2408
+ }
2409
+ // src/shared/pattern-matcher.ts
2410
+ function matchesToolMatcher(toolName, matcher) {
2411
+ if (!matcher) {
2412
+ return true;
2413
+ }
2414
+ const patterns = matcher.split("|").map((p) => p.trim());
2415
+ return patterns.some((p) => {
2416
+ if (p.includes("*")) {
2417
+ const regex = new RegExp(`^${p.replace(/\*/g, ".*")}$`, "i");
2418
+ return regex.test(toolName);
2419
+ }
2420
+ return p.toLowerCase() === toolName.toLowerCase();
2421
+ });
2422
+ }
2423
+ function findMatchingHooks(config, eventName, toolName) {
2424
+ const hookMatchers = config[eventName];
2425
+ if (!hookMatchers)
2426
+ return [];
2427
+ return hookMatchers.filter((hookMatcher) => {
2428
+ if (!toolName)
2429
+ return true;
2430
+ return matchesToolMatcher(toolName, hookMatcher.matcher);
2431
+ });
2432
+ }
2433
+ // src/shared/hook-disabled.ts
2434
+ function isHookDisabled(config, hookType) {
2435
+ const { disabledHooks } = config;
2436
+ if (disabledHooks === undefined) {
2437
+ return false;
2438
+ }
2439
+ if (disabledHooks === true) {
2440
+ return true;
2441
+ }
2442
+ if (Array.isArray(disabledHooks)) {
2443
+ return disabledHooks.includes(hookType);
2444
+ }
2445
+ return false;
2446
+ }
2447
+ // src/hooks/claude-code-hooks/plugin-config.ts
2448
+ var DEFAULT_CONFIG = {
2449
+ forceZsh: true,
2450
+ zshPath: "/bin/zsh"
2451
+ };
2452
+
2453
+ // src/hooks/claude-code-hooks/pre-tool-use.ts
2454
+ function buildInputLines(toolInput) {
2455
+ return Object.entries(toolInput).slice(0, 3).map(([key, val]) => {
2456
+ const valStr = String(val).slice(0, 40);
2457
+ return ` ${key}: ${valStr}${String(val).length > 40 ? "..." : ""}`;
2458
+ }).join(`
2459
+ `);
2460
+ }
2461
+ async function executePreToolUseHooks(ctx, config, extendedConfig) {
2462
+ if (!config) {
2463
+ return { decision: "allow" };
2464
+ }
2465
+ const transformedToolName = transformToolName(ctx.toolName);
2466
+ const matchers = findMatchingHooks(config, "PreToolUse", transformedToolName);
2467
+ if (matchers.length === 0) {
2468
+ return { decision: "allow" };
2469
+ }
2470
+ const stdinData = {
2471
+ session_id: ctx.sessionId,
2472
+ transcript_path: ctx.transcriptPath,
2473
+ cwd: ctx.cwd,
2474
+ permission_mode: ctx.permissionMode ?? "bypassPermissions",
2475
+ hook_event_name: "PreToolUse",
2476
+ tool_name: transformedToolName,
2477
+ tool_input: objectToSnakeCase(ctx.toolInput),
2478
+ tool_use_id: ctx.toolUseId,
2479
+ hook_source: "opencode-plugin"
2480
+ };
2481
+ const startTime = Date.now();
2482
+ let firstHookName;
2483
+ const inputLines = buildInputLines(ctx.toolInput);
2484
+ for (const matcher of matchers) {
2485
+ for (const hook of matcher.hooks) {
2486
+ if (hook.type !== "command")
2487
+ continue;
2488
+ if (isHookCommandDisabled("PreToolUse", hook.command, extendedConfig ?? null)) {
2489
+ log("PreToolUse hook command skipped (disabled by config)", { command: hook.command, toolName: ctx.toolName });
2490
+ continue;
2491
+ }
2492
+ const hookName = hook.command.split("/").pop() || hook.command;
2493
+ if (!firstHookName)
2494
+ firstHookName = hookName;
2495
+ const result = await executeHookCommand(hook.command, JSON.stringify(stdinData), ctx.cwd, { forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath });
2496
+ if (result.exitCode === 2) {
2497
+ return {
2498
+ decision: "deny",
2499
+ reason: result.stderr || result.stdout || "Hook blocked the operation",
2500
+ elapsedMs: Date.now() - startTime,
2501
+ hookName: firstHookName,
2502
+ toolName: transformedToolName,
2503
+ inputLines
2504
+ };
2505
+ }
2506
+ if (result.exitCode === 1) {
2507
+ return {
2508
+ decision: "ask",
2509
+ reason: result.stderr || result.stdout,
2510
+ elapsedMs: Date.now() - startTime,
2511
+ hookName: firstHookName,
2512
+ toolName: transformedToolName,
2513
+ inputLines
2514
+ };
2515
+ }
2516
+ if (result.stdout) {
2517
+ try {
2518
+ const output = JSON.parse(result.stdout);
2519
+ let decision;
2520
+ let reason;
2521
+ let modifiedInput;
2522
+ if (output.hookSpecificOutput?.permissionDecision) {
2523
+ decision = output.hookSpecificOutput.permissionDecision;
2524
+ reason = output.hookSpecificOutput.permissionDecisionReason;
2525
+ modifiedInput = output.hookSpecificOutput.updatedInput;
2526
+ } else if (output.decision) {
2527
+ const legacyDecision = output.decision;
2528
+ if (legacyDecision === "approve" || legacyDecision === "allow") {
2529
+ decision = "allow";
2530
+ } else if (legacyDecision === "block" || legacyDecision === "deny") {
2531
+ decision = "deny";
2532
+ } else if (legacyDecision === "ask") {
2533
+ decision = "ask";
2534
+ }
2535
+ reason = output.reason;
2536
+ }
2537
+ const hasCommonFields = output.continue !== undefined || output.stopReason !== undefined || output.suppressOutput !== undefined || output.systemMessage !== undefined;
2538
+ if (decision || hasCommonFields) {
2539
+ return {
2540
+ decision: decision ?? "allow",
2541
+ reason,
2542
+ modifiedInput,
2543
+ elapsedMs: Date.now() - startTime,
2544
+ hookName: firstHookName,
2545
+ toolName: transformedToolName,
2546
+ inputLines,
2547
+ continue: output.continue,
2548
+ stopReason: output.stopReason,
2549
+ suppressOutput: output.suppressOutput,
2550
+ systemMessage: output.systemMessage
2551
+ };
2552
+ }
2553
+ } catch {}
2554
+ }
2555
+ }
2556
+ }
2557
+ return { decision: "allow" };
2558
+ }
2559
+
2560
+ // 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";
2563
+ import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
2564
+ import { randomUUID } from "crypto";
2565
+ var TRANSCRIPT_DIR = join12(homedir4(), ".claude", "transcripts");
2566
+ function getTranscriptPath(sessionId) {
2567
+ return join12(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
2568
+ }
2569
+ function ensureTranscriptDir() {
2570
+ if (!existsSync11(TRANSCRIPT_DIR)) {
2571
+ mkdirSync4(TRANSCRIPT_DIR, { recursive: true });
2572
+ }
2573
+ }
2574
+ function appendTranscriptEntry(sessionId, entry) {
2575
+ ensureTranscriptDir();
2576
+ const path3 = getTranscriptPath(sessionId);
2577
+ const line = JSON.stringify(entry) + `
2578
+ `;
2579
+ appendFileSync5(path3, line);
2580
+ }
2581
+ function recordToolUse(sessionId, toolName, toolInput) {
2582
+ appendTranscriptEntry(sessionId, {
2583
+ type: "tool_use",
2584
+ timestamp: new Date().toISOString(),
2585
+ tool_name: toolName,
2586
+ tool_input: toolInput
2587
+ });
2588
+ }
2589
+ function recordToolResult(sessionId, toolName, toolInput, toolOutput) {
2590
+ appendTranscriptEntry(sessionId, {
2591
+ type: "tool_result",
2592
+ timestamp: new Date().toISOString(),
2593
+ tool_name: toolName,
2594
+ tool_input: toolInput,
2595
+ tool_output: toolOutput
2596
+ });
2597
+ }
2598
+ function recordUserMessage(sessionId, content) {
2599
+ appendTranscriptEntry(sessionId, {
2600
+ type: "user",
2601
+ timestamp: new Date().toISOString(),
2602
+ content
2603
+ });
2604
+ }
2605
+ async function buildTranscriptFromSession(client, sessionId, directory, currentToolName, currentToolInput) {
2606
+ try {
2607
+ const response = await client.session.messages({
2608
+ path: { id: sessionId },
2609
+ query: { directory }
2610
+ });
2611
+ const messages = response["200"] ?? response.data ?? (Array.isArray(response) ? response : []);
2612
+ const entries = [];
2613
+ if (Array.isArray(messages)) {
2614
+ for (const msg of messages) {
2615
+ if (msg.info?.role !== "assistant")
2616
+ continue;
2617
+ for (const part of msg.parts || []) {
2618
+ if (part.type !== "tool")
2619
+ continue;
2620
+ if (part.state?.status !== "completed")
2621
+ continue;
2622
+ if (!part.state?.input)
2623
+ continue;
2624
+ const rawToolName = part.tool;
2625
+ const toolName = transformToolName(rawToolName);
2626
+ const entry = {
2627
+ type: "assistant",
2628
+ message: {
2629
+ role: "assistant",
2630
+ content: [
2631
+ {
2632
+ type: "tool_use",
2633
+ name: toolName,
2634
+ input: part.state.input
2635
+ }
2636
+ ]
2637
+ }
2638
+ };
2639
+ entries.push(JSON.stringify(entry));
2640
+ }
2641
+ }
2642
+ }
2643
+ const currentEntry = {
2644
+ type: "assistant",
2645
+ message: {
2646
+ role: "assistant",
2647
+ content: [
2648
+ {
2649
+ type: "tool_use",
2650
+ name: transformToolName(currentToolName),
2651
+ input: currentToolInput
2652
+ }
2653
+ ]
2654
+ }
2655
+ };
2656
+ entries.push(JSON.stringify(currentEntry));
2657
+ const tempPath = join12(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
2658
+ writeFileSync3(tempPath, entries.join(`
2659
+ `) + `
2660
+ `);
2661
+ return tempPath;
2662
+ } catch {
2663
+ try {
2664
+ const currentEntry = {
2665
+ type: "assistant",
2666
+ message: {
2667
+ role: "assistant",
2668
+ content: [
2669
+ {
2670
+ type: "tool_use",
2671
+ name: transformToolName(currentToolName),
2672
+ input: currentToolInput
2673
+ }
2674
+ ]
2675
+ }
2676
+ };
2677
+ const tempPath = join12(tmpdir2(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
2678
+ writeFileSync3(tempPath, JSON.stringify(currentEntry) + `
2679
+ `);
2680
+ return tempPath;
2681
+ } catch {
2682
+ return null;
2683
+ }
2684
+ }
2685
+ }
2686
+ function deleteTempTranscript(path3) {
2687
+ if (!path3)
2688
+ return;
2689
+ try {
2690
+ unlinkSync4(path3);
2691
+ } catch {}
2692
+ }
2693
+
2694
+ // src/hooks/claude-code-hooks/post-tool-use.ts
2695
+ async function executePostToolUseHooks(ctx, config, extendedConfig) {
2696
+ if (!config) {
2697
+ return { block: false };
2698
+ }
2699
+ const transformedToolName = transformToolName(ctx.toolName);
2700
+ const matchers = findMatchingHooks(config, "PostToolUse", transformedToolName);
2701
+ if (matchers.length === 0) {
2702
+ return { block: false };
2703
+ }
2704
+ let tempTranscriptPath = null;
2705
+ try {
2706
+ if (ctx.client) {
2707
+ tempTranscriptPath = await buildTranscriptFromSession(ctx.client, ctx.sessionId, ctx.cwd, ctx.toolName, ctx.toolInput);
2708
+ }
2709
+ const stdinData = {
2710
+ session_id: ctx.sessionId,
2711
+ transcript_path: tempTranscriptPath ?? ctx.transcriptPath,
2712
+ cwd: ctx.cwd,
2713
+ permission_mode: ctx.permissionMode ?? "bypassPermissions",
2714
+ hook_event_name: "PostToolUse",
2715
+ tool_name: transformedToolName,
2716
+ tool_input: objectToSnakeCase(ctx.toolInput),
2717
+ tool_response: objectToSnakeCase(ctx.toolOutput),
2718
+ tool_use_id: ctx.toolUseId,
2719
+ hook_source: "opencode-plugin"
2720
+ };
2721
+ const messages = [];
2722
+ const warnings = [];
2723
+ let firstHookName;
2724
+ const startTime = Date.now();
2725
+ for (const matcher of matchers) {
2726
+ for (const hook of matcher.hooks) {
2727
+ if (hook.type !== "command")
2728
+ continue;
2729
+ if (isHookCommandDisabled("PostToolUse", hook.command, extendedConfig ?? null)) {
2730
+ log("PostToolUse hook command skipped (disabled by config)", { command: hook.command, toolName: ctx.toolName });
2731
+ continue;
2732
+ }
2733
+ const hookName = hook.command.split("/").pop() || hook.command;
2734
+ if (!firstHookName)
2735
+ firstHookName = hookName;
2736
+ const result = await executeHookCommand(hook.command, JSON.stringify(stdinData), ctx.cwd, { forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath });
2737
+ if (result.stdout) {
2738
+ messages.push(result.stdout);
2739
+ }
2740
+ if (result.exitCode === 2) {
2741
+ if (result.stderr) {
2742
+ warnings.push(`[${hookName}]
2743
+ ${result.stderr.trim()}`);
2744
+ }
2745
+ continue;
2746
+ }
2747
+ if (result.exitCode === 0 && result.stdout) {
2748
+ try {
2749
+ const output = JSON.parse(result.stdout);
2750
+ if (output.decision === "block") {
2751
+ return {
2752
+ block: true,
2753
+ reason: output.reason || result.stderr,
2754
+ message: messages.join(`
2755
+ `),
2756
+ warnings: warnings.length > 0 ? warnings : undefined,
2757
+ elapsedMs: Date.now() - startTime,
2758
+ hookName: firstHookName,
2759
+ toolName: transformedToolName,
2760
+ additionalContext: output.hookSpecificOutput?.additionalContext,
2761
+ continue: output.continue,
2762
+ stopReason: output.stopReason,
2763
+ suppressOutput: output.suppressOutput,
2764
+ systemMessage: output.systemMessage
2765
+ };
2766
+ }
2767
+ if (output.hookSpecificOutput?.additionalContext || output.continue !== undefined || output.systemMessage || output.suppressOutput === true || output.stopReason !== undefined) {
2768
+ return {
2769
+ block: false,
2770
+ message: messages.join(`
2771
+ `),
2772
+ warnings: warnings.length > 0 ? warnings : undefined,
2773
+ elapsedMs: Date.now() - startTime,
2774
+ hookName: firstHookName,
2775
+ toolName: transformedToolName,
2776
+ additionalContext: output.hookSpecificOutput?.additionalContext,
2777
+ continue: output.continue,
2778
+ stopReason: output.stopReason,
2779
+ suppressOutput: output.suppressOutput,
2780
+ systemMessage: output.systemMessage
2781
+ };
2782
+ }
2783
+ } catch {}
2784
+ } else if (result.exitCode !== 0 && result.exitCode !== 2) {
2785
+ try {
2786
+ const output = JSON.parse(result.stdout || "{}");
2787
+ if (output.decision === "block") {
2788
+ return {
2789
+ block: true,
2790
+ reason: output.reason || result.stderr,
2791
+ message: messages.join(`
2792
+ `),
2793
+ warnings: warnings.length > 0 ? warnings : undefined,
2794
+ elapsedMs: Date.now() - startTime,
2795
+ hookName: firstHookName,
2796
+ toolName: transformedToolName,
2797
+ additionalContext: output.hookSpecificOutput?.additionalContext,
2798
+ continue: output.continue,
2799
+ stopReason: output.stopReason,
2800
+ suppressOutput: output.suppressOutput,
2801
+ systemMessage: output.systemMessage
2802
+ };
2803
+ }
2804
+ } catch {}
2805
+ }
2806
+ }
2807
+ }
2808
+ const elapsedMs = Date.now() - startTime;
2809
+ return {
2810
+ block: false,
2811
+ message: messages.length > 0 ? messages.join(`
2812
+ `) : undefined,
2813
+ warnings: warnings.length > 0 ? warnings : undefined,
2814
+ elapsedMs,
2815
+ hookName: firstHookName,
2816
+ toolName: transformedToolName
2817
+ };
2818
+ } finally {
2819
+ deleteTempTranscript(tempTranscriptPath);
2820
+ }
2821
+ }
2822
+
2823
+ // src/hooks/claude-code-hooks/user-prompt-submit.ts
2824
+ var USER_PROMPT_SUBMIT_TAG_OPEN = "<user-prompt-submit-hook>";
2825
+ var USER_PROMPT_SUBMIT_TAG_CLOSE = "</user-prompt-submit-hook>";
2826
+ async function executeUserPromptSubmitHooks(ctx, config, extendedConfig) {
2827
+ const modifiedParts = ctx.parts;
2828
+ const messages = [];
2829
+ if (ctx.parentSessionId) {
2830
+ return { block: false, modifiedParts, messages };
2831
+ }
2832
+ if (ctx.prompt.includes(USER_PROMPT_SUBMIT_TAG_OPEN) && ctx.prompt.includes(USER_PROMPT_SUBMIT_TAG_CLOSE)) {
2833
+ return { block: false, modifiedParts, messages };
2834
+ }
2835
+ if (!config) {
2836
+ return { block: false, modifiedParts, messages };
2837
+ }
2838
+ const matchers = findMatchingHooks(config, "UserPromptSubmit");
2839
+ if (matchers.length === 0) {
2840
+ return { block: false, modifiedParts, messages };
2841
+ }
2842
+ const stdinData = {
2843
+ session_id: ctx.sessionId,
2844
+ cwd: ctx.cwd,
2845
+ permission_mode: ctx.permissionMode ?? "bypassPermissions",
2846
+ hook_event_name: "UserPromptSubmit",
2847
+ prompt: ctx.prompt,
2848
+ session: { id: ctx.sessionId },
2849
+ hook_source: "opencode-plugin"
2850
+ };
2851
+ for (const matcher of matchers) {
2852
+ for (const hook of matcher.hooks) {
2853
+ if (hook.type !== "command")
2854
+ continue;
2855
+ if (isHookCommandDisabled("UserPromptSubmit", hook.command, extendedConfig ?? null)) {
2856
+ log("UserPromptSubmit hook command skipped (disabled by config)", { command: hook.command });
2857
+ continue;
2858
+ }
2859
+ const result = await executeHookCommand(hook.command, JSON.stringify(stdinData), ctx.cwd, { forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath });
2860
+ if (result.stdout) {
2861
+ const output = result.stdout.trim();
2862
+ if (output.startsWith(USER_PROMPT_SUBMIT_TAG_OPEN)) {
2863
+ messages.push(output);
2864
+ } else {
2865
+ messages.push(`${USER_PROMPT_SUBMIT_TAG_OPEN}
2866
+ ${output}
2867
+ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
2868
+ }
2869
+ }
2870
+ if (result.exitCode !== 0) {
2871
+ try {
2872
+ const output = JSON.parse(result.stdout || "{}");
2873
+ if (output.decision === "block") {
2874
+ return {
2875
+ block: true,
2876
+ reason: output.reason || result.stderr,
2877
+ modifiedParts,
2878
+ messages
2879
+ };
2880
+ }
2881
+ } catch {}
2882
+ }
2883
+ }
2884
+ }
2885
+ return { block: false, modifiedParts, messages };
2886
+ }
2887
+
2888
+ // src/hooks/claude-code-hooks/todo.ts
2889
+ import { join as join13 } from "path";
2890
+ import { homedir as homedir5 } from "os";
2891
+ var TODO_DIR = join13(homedir5(), ".claude", "todos");
2892
+ function getTodoPath(sessionId) {
2893
+ return join13(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
2894
+ }
2895
+
2896
+ // src/hooks/claude-code-hooks/stop.ts
2897
+ var stopHookActiveState = new Map;
2898
+ async function executeStopHooks(ctx, config, extendedConfig) {
2899
+ if (ctx.parentSessionId) {
2900
+ return { block: false };
2901
+ }
2902
+ if (!config) {
2903
+ return { block: false };
2904
+ }
2905
+ const matchers = findMatchingHooks(config, "Stop");
2906
+ if (matchers.length === 0) {
2907
+ return { block: false };
2908
+ }
2909
+ const stdinData = {
2910
+ session_id: ctx.sessionId,
2911
+ transcript_path: ctx.transcriptPath,
2912
+ cwd: ctx.cwd,
2913
+ permission_mode: ctx.permissionMode ?? "bypassPermissions",
2914
+ hook_event_name: "Stop",
2915
+ stop_hook_active: stopHookActiveState.get(ctx.sessionId) ?? false,
2916
+ todo_path: getTodoPath(ctx.sessionId),
2917
+ hook_source: "opencode-plugin"
2918
+ };
2919
+ for (const matcher of matchers) {
2920
+ for (const hook of matcher.hooks) {
2921
+ if (hook.type !== "command")
2922
+ continue;
2923
+ if (isHookCommandDisabled("Stop", hook.command, extendedConfig ?? null)) {
2924
+ log("Stop hook command skipped (disabled by config)", { command: hook.command });
2925
+ continue;
2926
+ }
2927
+ const result = await executeHookCommand(hook.command, JSON.stringify(stdinData), ctx.cwd, { forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath });
2928
+ if (result.exitCode === 2) {
2929
+ const reason = result.stderr || result.stdout || "Blocked by stop hook";
2930
+ return {
2931
+ block: true,
2932
+ reason,
2933
+ injectPrompt: reason
2934
+ };
2935
+ }
2936
+ if (result.stdout) {
2937
+ try {
2938
+ const output = JSON.parse(result.stdout);
2939
+ if (output.stop_hook_active !== undefined) {
2940
+ stopHookActiveState.set(ctx.sessionId, output.stop_hook_active);
2941
+ }
2942
+ const isBlock = output.decision === "block";
2943
+ const injectPrompt = output.inject_prompt ?? (isBlock && output.reason ? output.reason : undefined);
2944
+ return {
2945
+ block: isBlock,
2946
+ reason: output.reason,
2947
+ stopHookActive: output.stop_hook_active,
2948
+ permissionMode: output.permission_mode,
2949
+ injectPrompt
2950
+ };
2951
+ } catch {}
2952
+ }
2953
+ }
2954
+ }
2955
+ return { block: false };
2956
+ }
2957
+
2958
+ // src/hooks/claude-code-hooks/tool-input-cache.ts
2959
+ var cache = new Map;
2960
+ var CACHE_TTL = 60000;
2961
+ function cacheToolInput(sessionId, toolName, invocationId, toolInput) {
2962
+ const key = `${sessionId}:${toolName}:${invocationId}`;
2963
+ cache.set(key, { toolInput, timestamp: Date.now() });
2964
+ }
2965
+ function getToolInput(sessionId, toolName, invocationId) {
2966
+ const key = `${sessionId}:${toolName}:${invocationId}`;
2967
+ const entry = cache.get(key);
2968
+ if (!entry)
2969
+ return null;
2970
+ cache.delete(key);
2971
+ if (Date.now() - entry.timestamp > CACHE_TTL)
2972
+ return null;
2973
+ return entry.toolInput;
2974
+ }
2975
+ setInterval(() => {
2976
+ const now = Date.now();
2977
+ for (const [key, entry] of cache.entries()) {
2978
+ if (now - entry.timestamp > CACHE_TTL) {
2979
+ cache.delete(key);
2980
+ }
2981
+ }
2982
+ }, CACHE_TTL);
2983
+
2984
+ // 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";
2987
+
2988
+ // src/features/hook-message-injector/constants.ts
2989
+ import { join as join14 } from "path";
2990
+ 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");
2995
+
2996
+ // src/features/hook-message-injector/injector.ts
2997
+ function findNearestMessageWithFields(messageDir) {
2998
+ try {
2999
+ const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
3000
+ for (const file of files) {
3001
+ try {
3002
+ const content = readFileSync5(join15(messageDir, file), "utf-8");
3003
+ const msg = JSON.parse(content);
3004
+ if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
3005
+ return msg;
3006
+ }
3007
+ } catch {
3008
+ continue;
3009
+ }
3010
+ }
3011
+ } catch {
3012
+ return null;
3013
+ }
3014
+ return null;
3015
+ }
3016
+ function generateMessageId() {
3017
+ const timestamp = Date.now().toString(16);
3018
+ const random = Math.random().toString(36).substring(2, 14);
3019
+ return `msg_${timestamp}${random}`;
3020
+ }
3021
+ function generatePartId2() {
3022
+ const timestamp = Date.now().toString(16);
3023
+ const random = Math.random().toString(36).substring(2, 10);
3024
+ return `prt_${timestamp}${random}`;
3025
+ }
3026
+ function getOrCreateMessageDir(sessionID) {
3027
+ if (!existsSync12(MESSAGE_STORAGE2)) {
3028
+ mkdirSync5(MESSAGE_STORAGE2, { recursive: true });
3029
+ }
3030
+ const directPath = join15(MESSAGE_STORAGE2, sessionID);
3031
+ if (existsSync12(directPath)) {
3032
+ return directPath;
3033
+ }
3034
+ for (const dir of readdirSync2(MESSAGE_STORAGE2)) {
3035
+ const sessionPath = join15(MESSAGE_STORAGE2, dir, sessionID);
3036
+ if (existsSync12(sessionPath)) {
3037
+ return sessionPath;
3038
+ }
3039
+ }
3040
+ mkdirSync5(directPath, { recursive: true });
3041
+ return directPath;
3042
+ }
3043
+ function injectHookMessage(sessionID, hookContent, originalMessage) {
3044
+ const messageDir = getOrCreateMessageDir(sessionID);
3045
+ const needsFallback = !originalMessage.agent || !originalMessage.model?.providerID || !originalMessage.model?.modelID;
3046
+ const fallback = needsFallback ? findNearestMessageWithFields(messageDir) : null;
3047
+ const now = Date.now();
3048
+ const messageID = generateMessageId();
3049
+ const partID = generatePartId2();
3050
+ const resolvedAgent = originalMessage.agent ?? fallback?.agent ?? "general";
3051
+ const resolvedModel = originalMessage.model?.providerID && originalMessage.model?.modelID ? { providerID: originalMessage.model.providerID, modelID: originalMessage.model.modelID } : fallback?.model?.providerID && fallback?.model?.modelID ? { providerID: fallback.model.providerID, modelID: fallback.model.modelID } : undefined;
3052
+ const resolvedTools = originalMessage.tools ?? fallback?.tools;
3053
+ const messageMeta = {
3054
+ id: messageID,
3055
+ sessionID,
3056
+ role: "user",
3057
+ time: {
3058
+ created: now
3059
+ },
3060
+ agent: resolvedAgent,
3061
+ model: resolvedModel,
3062
+ path: originalMessage.path?.cwd ? {
3063
+ cwd: originalMessage.path.cwd,
3064
+ root: originalMessage.path.root ?? "/"
3065
+ } : undefined,
3066
+ tools: resolvedTools
3067
+ };
3068
+ const textPart = {
3069
+ id: partID,
3070
+ type: "text",
3071
+ text: hookContent,
3072
+ synthetic: true,
3073
+ time: {
3074
+ start: now,
3075
+ end: now
3076
+ },
3077
+ messageID,
3078
+ sessionID
3079
+ };
3080
+ 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 });
3085
+ }
3086
+ writeFileSync4(join15(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3087
+ return true;
3088
+ } catch {
3089
+ return false;
3090
+ }
2021
3091
  }
3092
+ // src/hooks/claude-code-hooks/index.ts
3093
+ var sessionFirstMessageProcessed = new Set;
3094
+ var sessionErrorState = new Map;
3095
+ var sessionInterruptState = new Map;
3096
+ function createClaudeCodeHooksHook(ctx, config = {}) {
3097
+ return {
3098
+ "chat.message": async (input, output) => {
3099
+ const interruptState = sessionInterruptState.get(input.sessionID);
3100
+ if (interruptState?.interrupted) {
3101
+ log("chat.message hook skipped - session interrupted", { sessionID: input.sessionID });
3102
+ return;
3103
+ }
3104
+ const claudeConfig = await loadClaudeHooksConfig();
3105
+ const extendedConfig = await loadPluginExtendedConfig();
3106
+ const textParts = output.parts.filter((p) => p.type === "text" && p.text);
3107
+ const prompt = textParts.map((p) => p.text ?? "").join(`
3108
+ `);
3109
+ recordUserMessage(input.sessionID, prompt);
3110
+ const messageParts = textParts.map((p) => ({
3111
+ type: p.type,
3112
+ text: p.text
3113
+ }));
3114
+ const interruptStateBeforeHooks = sessionInterruptState.get(input.sessionID);
3115
+ if (interruptStateBeforeHooks?.interrupted) {
3116
+ log("chat.message hooks skipped - interrupted during preparation", { sessionID: input.sessionID });
3117
+ return;
3118
+ }
3119
+ let parentSessionId;
3120
+ try {
3121
+ const sessionInfo = await ctx.client.session.get({
3122
+ path: { id: input.sessionID }
3123
+ });
3124
+ parentSessionId = sessionInfo.data?.parentID;
3125
+ } catch {}
3126
+ const isFirstMessage = !sessionFirstMessageProcessed.has(input.sessionID);
3127
+ sessionFirstMessageProcessed.add(input.sessionID);
3128
+ if (isFirstMessage) {
3129
+ log("Skipping UserPromptSubmit hooks on first message for title generation", { sessionID: input.sessionID });
3130
+ return;
3131
+ }
3132
+ if (!isHookDisabled(config, "UserPromptSubmit")) {
3133
+ const userPromptCtx = {
3134
+ sessionId: input.sessionID,
3135
+ parentSessionId,
3136
+ prompt,
3137
+ parts: messageParts,
3138
+ cwd: ctx.directory
3139
+ };
3140
+ const result = await executeUserPromptSubmitHooks(userPromptCtx, claudeConfig, extendedConfig);
3141
+ if (result.block) {
3142
+ throw new Error(result.reason ?? "Hook blocked the prompt");
3143
+ }
3144
+ const interruptStateAfterHooks = sessionInterruptState.get(input.sessionID);
3145
+ if (interruptStateAfterHooks?.interrupted) {
3146
+ log("chat.message injection skipped - interrupted during hooks", { sessionID: input.sessionID });
3147
+ return;
3148
+ }
3149
+ if (result.messages.length > 0) {
3150
+ const hookContent = result.messages.join(`
2022
3151
 
2023
- // src/shared/model-sanitizer.ts
2024
- function sanitizeModelField(_model) {
2025
- return;
2026
- }
3152
+ `);
3153
+ const message = output.message;
3154
+ const success = injectHookMessage(input.sessionID, hookContent, {
3155
+ agent: message.agent,
3156
+ model: message.model,
3157
+ path: message.path ?? { cwd: ctx.directory, root: "/" },
3158
+ tools: message.tools
3159
+ });
3160
+ log(success ? "Hook message injected via file system" : "File injection failed", {
3161
+ sessionID: input.sessionID
3162
+ });
3163
+ }
3164
+ }
3165
+ },
3166
+ "tool.execute.before": async (input, output) => {
3167
+ const claudeConfig = await loadClaudeHooksConfig();
3168
+ const extendedConfig = await loadPluginExtendedConfig();
3169
+ recordToolUse(input.sessionID, input.tool, output.args);
3170
+ cacheToolInput(input.sessionID, input.tool, input.callID, output.args);
3171
+ if (!isHookDisabled(config, "PreToolUse")) {
3172
+ const preCtx = {
3173
+ sessionId: input.sessionID,
3174
+ toolName: input.tool,
3175
+ toolInput: output.args,
3176
+ cwd: ctx.directory,
3177
+ toolUseId: input.callID
3178
+ };
3179
+ const result = await executePreToolUseHooks(preCtx, claudeConfig, extendedConfig);
3180
+ if (result.decision === "deny") {
3181
+ ctx.client.tui.showToast({
3182
+ body: {
3183
+ title: "PreToolUse Hook Executed",
3184
+ message: `\u2717 ${result.toolName ?? input.tool} ${result.hookName ?? "hook"}: BLOCKED ${result.elapsedMs ?? 0}ms
3185
+ ${result.inputLines ?? ""}`,
3186
+ variant: "error",
3187
+ duration: 4000
3188
+ }
3189
+ }).catch(() => {});
3190
+ throw new Error(result.reason ?? "Hook blocked the operation");
3191
+ }
3192
+ if (result.modifiedInput) {
3193
+ Object.assign(output.args, result.modifiedInput);
3194
+ }
3195
+ }
3196
+ },
3197
+ "tool.execute.after": async (input, output) => {
3198
+ const claudeConfig = await loadClaudeHooksConfig();
3199
+ const extendedConfig = await loadPluginExtendedConfig();
3200
+ const cachedInput = getToolInput(input.sessionID, input.tool, input.callID) || {};
3201
+ recordToolResult(input.sessionID, input.tool, cachedInput, output.metadata || {});
3202
+ if (!isHookDisabled(config, "PostToolUse")) {
3203
+ const postClient = {
3204
+ session: {
3205
+ messages: (opts) => ctx.client.session.messages(opts)
3206
+ }
3207
+ };
3208
+ const postCtx = {
3209
+ sessionId: input.sessionID,
3210
+ toolName: input.tool,
3211
+ toolInput: cachedInput,
3212
+ toolOutput: {
3213
+ title: input.tool,
3214
+ output: output.output,
3215
+ metadata: output.metadata
3216
+ },
3217
+ cwd: ctx.directory,
3218
+ transcriptPath: getTranscriptPath(input.sessionID),
3219
+ toolUseId: input.callID,
3220
+ client: postClient,
3221
+ permissionMode: "bypassPermissions"
3222
+ };
3223
+ const result = await executePostToolUseHooks(postCtx, claudeConfig, extendedConfig);
3224
+ if (result.block) {
3225
+ ctx.client.tui.showToast({
3226
+ body: {
3227
+ title: "PostToolUse Hook Warning",
3228
+ message: result.reason ?? "Hook returned warning",
3229
+ variant: "warning",
3230
+ duration: 4000
3231
+ }
3232
+ }).catch(() => {});
3233
+ }
3234
+ if (result.warnings && result.warnings.length > 0) {
3235
+ output.output = `${output.output}
3236
+
3237
+ ${result.warnings.join(`
3238
+ `)}`;
3239
+ }
3240
+ if (result.message) {
3241
+ output.output = `${output.output}
2027
3242
 
3243
+ ${result.message}`;
3244
+ }
3245
+ if (result.hookName) {
3246
+ ctx.client.tui.showToast({
3247
+ body: {
3248
+ title: "PostToolUse Hook Executed",
3249
+ message: `\u25B6 ${result.toolName ?? input.tool} ${result.hookName}: ${result.elapsedMs ?? 0}ms`,
3250
+ variant: "success",
3251
+ duration: 2000
3252
+ }
3253
+ }).catch(() => {});
3254
+ }
3255
+ }
3256
+ },
3257
+ event: async (input) => {
3258
+ const { event } = input;
3259
+ if (event.type === "session.error") {
3260
+ const props = event.properties;
3261
+ const sessionID = props?.sessionID;
3262
+ if (sessionID) {
3263
+ sessionErrorState.set(sessionID, {
3264
+ hasError: true,
3265
+ errorMessage: String(props?.error ?? "Unknown error")
3266
+ });
3267
+ }
3268
+ return;
3269
+ }
3270
+ if (event.type === "session.deleted") {
3271
+ const props = event.properties;
3272
+ const sessionInfo = props?.info;
3273
+ if (sessionInfo?.id) {
3274
+ sessionErrorState.delete(sessionInfo.id);
3275
+ sessionInterruptState.delete(sessionInfo.id);
3276
+ sessionFirstMessageProcessed.delete(sessionInfo.id);
3277
+ }
3278
+ return;
3279
+ }
3280
+ if (event.type === "session.idle") {
3281
+ const props = event.properties;
3282
+ const sessionID = props?.sessionID;
3283
+ if (!sessionID)
3284
+ return;
3285
+ const claudeConfig = await loadClaudeHooksConfig();
3286
+ const extendedConfig = await loadPluginExtendedConfig();
3287
+ const errorStateBefore = sessionErrorState.get(sessionID);
3288
+ const endedWithErrorBefore = errorStateBefore?.hasError === true;
3289
+ const interruptStateBefore = sessionInterruptState.get(sessionID);
3290
+ const interruptedBefore = interruptStateBefore?.interrupted === true;
3291
+ let parentSessionId;
3292
+ try {
3293
+ const sessionInfo = await ctx.client.session.get({
3294
+ path: { id: sessionID }
3295
+ });
3296
+ parentSessionId = sessionInfo.data?.parentID;
3297
+ } catch {}
3298
+ if (!isHookDisabled(config, "Stop")) {
3299
+ const stopCtx = {
3300
+ sessionId: sessionID,
3301
+ parentSessionId,
3302
+ cwd: ctx.directory
3303
+ };
3304
+ const stopResult = await executeStopHooks(stopCtx, claudeConfig, extendedConfig);
3305
+ const errorStateAfter = sessionErrorState.get(sessionID);
3306
+ const endedWithErrorAfter = errorStateAfter?.hasError === true;
3307
+ const interruptStateAfter = sessionInterruptState.get(sessionID);
3308
+ const interruptedAfter = interruptStateAfter?.interrupted === true;
3309
+ const shouldBypass = endedWithErrorBefore || endedWithErrorAfter || interruptedBefore || interruptedAfter;
3310
+ if (shouldBypass && stopResult.block) {
3311
+ const interrupted = interruptedBefore || interruptedAfter;
3312
+ const endedWithError = endedWithErrorBefore || endedWithErrorAfter;
3313
+ log("Stop hook block ignored", { sessionID, block: stopResult.block, interrupted, endedWithError });
3314
+ } else if (stopResult.block && stopResult.injectPrompt) {
3315
+ log("Stop hook returned block with inject_prompt", { sessionID });
3316
+ ctx.client.session.prompt({
3317
+ path: { id: sessionID },
3318
+ body: { parts: [{ type: "text", text: stopResult.injectPrompt }] },
3319
+ query: { directory: ctx.directory }
3320
+ }).catch((err) => log("Failed to inject prompt from Stop hook", err));
3321
+ } else if (stopResult.block) {
3322
+ log("Stop hook returned block", { sessionID, reason: stopResult.reason });
3323
+ }
3324
+ }
3325
+ sessionErrorState.delete(sessionID);
3326
+ sessionInterruptState.delete(sessionID);
3327
+ }
3328
+ }
3329
+ };
3330
+ }
2028
3331
  // src/features/claude-code-command-loader/loader.ts
3332
+ import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
3333
+ import { homedir as homedir7 } from "os";
3334
+ import { join as join16, basename } from "path";
2029
3335
  function isMarkdownFile(entry) {
2030
3336
  return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
2031
3337
  }
2032
3338
  function loadCommandsFromDir(commandsDir, scope) {
2033
- if (!existsSync7(commandsDir)) {
3339
+ if (!existsSync13(commandsDir)) {
2034
3340
  return [];
2035
3341
  }
2036
- const entries = readdirSync2(commandsDir, { withFileTypes: true });
3342
+ const entries = readdirSync3(commandsDir, { withFileTypes: true });
2037
3343
  const commands = [];
2038
3344
  for (const entry of entries) {
2039
3345
  if (!isMarkdownFile(entry))
2040
3346
  continue;
2041
- const commandPath = join8(commandsDir, entry.name);
3347
+ const commandPath = join16(commandsDir, entry.name);
2042
3348
  const commandName = basename(entry.name, ".md");
2043
3349
  try {
2044
- const content = readFileSync4(commandPath, "utf-8");
3350
+ const content = readFileSync6(commandPath, "utf-8");
2045
3351
  const { data, body } = parseFrontmatter(content);
2046
3352
  const wrappedTemplate = `<command-instruction>
2047
3353
  ${body.trim()}
@@ -2080,50 +3386,50 @@ function commandsToRecord(commands) {
2080
3386
  return result;
2081
3387
  }
2082
3388
  function loadUserCommands() {
2083
- const userCommandsDir = join8(homedir2(), ".claude", "commands");
3389
+ const userCommandsDir = join16(homedir7(), ".claude", "commands");
2084
3390
  const commands = loadCommandsFromDir(userCommandsDir, "user");
2085
3391
  return commandsToRecord(commands);
2086
3392
  }
2087
3393
  function loadProjectCommands() {
2088
- const projectCommandsDir = join8(process.cwd(), ".claude", "commands");
3394
+ const projectCommandsDir = join16(process.cwd(), ".claude", "commands");
2089
3395
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
2090
3396
  return commandsToRecord(commands);
2091
3397
  }
2092
3398
  function loadOpencodeGlobalCommands() {
2093
- const opencodeCommandsDir = join8(homedir2(), ".config", "opencode", "command");
3399
+ const opencodeCommandsDir = join16(homedir7(), ".config", "opencode", "command");
2094
3400
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
2095
3401
  return commandsToRecord(commands);
2096
3402
  }
2097
3403
  function loadOpencodeProjectCommands() {
2098
- const opencodeProjectDir = join8(process.cwd(), ".opencode", "command");
3404
+ const opencodeProjectDir = join16(process.cwd(), ".opencode", "command");
2099
3405
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
2100
3406
  return commandsToRecord(commands);
2101
3407
  }
2102
3408
  // 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";
3409
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync2, readlinkSync } from "fs";
3410
+ import { homedir as homedir8 } from "os";
3411
+ import { join as join17, resolve as resolve2 } from "path";
2106
3412
  function loadSkillsFromDir(skillsDir, scope) {
2107
- if (!existsSync8(skillsDir)) {
3413
+ if (!existsSync14(skillsDir)) {
2108
3414
  return [];
2109
3415
  }
2110
- const entries = readdirSync3(skillsDir, { withFileTypes: true });
3416
+ const entries = readdirSync4(skillsDir, { withFileTypes: true });
2111
3417
  const skills = [];
2112
3418
  for (const entry of entries) {
2113
3419
  if (entry.name.startsWith("."))
2114
3420
  continue;
2115
- const skillPath = join9(skillsDir, entry.name);
3421
+ const skillPath = join17(skillsDir, entry.name);
2116
3422
  if (!entry.isDirectory() && !entry.isSymbolicLink())
2117
3423
  continue;
2118
3424
  let resolvedPath = skillPath;
2119
- if (statSync(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
3425
+ if (statSync2(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
2120
3426
  resolvedPath = resolve2(skillPath, "..", readlinkSync(skillPath));
2121
3427
  }
2122
- const skillMdPath = join9(resolvedPath, "SKILL.md");
2123
- if (!existsSync8(skillMdPath))
3428
+ const skillMdPath = join17(resolvedPath, "SKILL.md");
3429
+ if (!existsSync14(skillMdPath))
2124
3430
  continue;
2125
3431
  try {
2126
- const content = readFileSync5(skillMdPath, "utf-8");
3432
+ const content = readFileSync7(skillMdPath, "utf-8");
2127
3433
  const { data, body } = parseFrontmatter(content);
2128
3434
  const skillName = data.name || entry.name;
2129
3435
  const originalDescription = data.description || "";
@@ -2154,7 +3460,7 @@ $ARGUMENTS
2154
3460
  return skills;
2155
3461
  }
2156
3462
  function loadUserSkillsAsCommands() {
2157
- const userSkillsDir = join9(homedir3(), ".claude", "skills");
3463
+ const userSkillsDir = join17(homedir8(), ".claude", "skills");
2158
3464
  const skills = loadSkillsFromDir(userSkillsDir, "user");
2159
3465
  return skills.reduce((acc, skill) => {
2160
3466
  acc[skill.name] = skill.definition;
@@ -2162,7 +3468,7 @@ function loadUserSkillsAsCommands() {
2162
3468
  }, {});
2163
3469
  }
2164
3470
  function loadProjectSkillsAsCommands() {
2165
- const projectSkillsDir = join9(process.cwd(), ".claude", "skills");
3471
+ const projectSkillsDir = join17(process.cwd(), ".claude", "skills");
2166
3472
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
2167
3473
  return skills.reduce((acc, skill) => {
2168
3474
  acc[skill.name] = skill.definition;
@@ -2170,9 +3476,9 @@ function loadProjectSkillsAsCommands() {
2170
3476
  }, {});
2171
3477
  }
2172
3478
  // 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";
3479
+ import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
3480
+ import { homedir as homedir9 } from "os";
3481
+ import { join as join18, basename as basename2 } from "path";
2176
3482
  function parseToolsConfig(toolsStr) {
2177
3483
  if (!toolsStr)
2178
3484
  return;
@@ -2189,18 +3495,18 @@ function isMarkdownFile2(entry) {
2189
3495
  return !entry.name.startsWith(".") && entry.name.endsWith(".md") && entry.isFile();
2190
3496
  }
2191
3497
  function loadAgentsFromDir(agentsDir, scope) {
2192
- if (!existsSync9(agentsDir)) {
3498
+ if (!existsSync15(agentsDir)) {
2193
3499
  return [];
2194
3500
  }
2195
- const entries = readdirSync4(agentsDir, { withFileTypes: true });
3501
+ const entries = readdirSync5(agentsDir, { withFileTypes: true });
2196
3502
  const agents = [];
2197
3503
  for (const entry of entries) {
2198
3504
  if (!isMarkdownFile2(entry))
2199
3505
  continue;
2200
- const agentPath = join10(agentsDir, entry.name);
3506
+ const agentPath = join18(agentsDir, entry.name);
2201
3507
  const agentName = basename2(entry.name, ".md");
2202
3508
  try {
2203
- const content = readFileSync6(agentPath, "utf-8");
3509
+ const content = readFileSync8(agentPath, "utf-8");
2204
3510
  const { data, body } = parseFrontmatter(content);
2205
3511
  const name = data.name || agentName;
2206
3512
  const originalDescription = data.description || "";
@@ -2227,7 +3533,7 @@ function loadAgentsFromDir(agentsDir, scope) {
2227
3533
  return agents;
2228
3534
  }
2229
3535
  function loadUserAgents() {
2230
- const userAgentsDir = join10(homedir4(), ".claude", "agents");
3536
+ const userAgentsDir = join18(homedir9(), ".claude", "agents");
2231
3537
  const agents = loadAgentsFromDir(userAgentsDir, "user");
2232
3538
  const result = {};
2233
3539
  for (const agent of agents) {
@@ -2236,7 +3542,7 @@ function loadUserAgents() {
2236
3542
  return result;
2237
3543
  }
2238
3544
  function loadProjectAgents() {
2239
- const projectAgentsDir = join10(process.cwd(), ".claude", "agents");
3545
+ const projectAgentsDir = join18(process.cwd(), ".claude", "agents");
2240
3546
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
2241
3547
  const result = {};
2242
3548
  for (const agent of agents) {
@@ -2245,9 +3551,9 @@ function loadProjectAgents() {
2245
3551
  return result;
2246
3552
  }
2247
3553
  // 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";
3554
+ import { existsSync as existsSync16 } from "fs";
3555
+ import { homedir as homedir10 } from "os";
3556
+ import { join as join19 } from "path";
2251
3557
 
2252
3558
  // src/features/claude-code-mcp-loader/env-expander.ts
2253
3559
  function expandEnvVars(value) {
@@ -2311,32 +3617,18 @@ function transformMcpServer(name, server) {
2311
3617
  return config;
2312
3618
  }
2313
3619
 
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
3620
  // src/features/claude-code-mcp-loader/loader.ts
2329
3621
  function getMcpConfigPaths() {
2330
- const home = homedir5();
3622
+ const home = homedir10();
2331
3623
  const cwd = process.cwd();
2332
3624
  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" }
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" }
2336
3628
  ];
2337
3629
  }
2338
3630
  async function loadMcpConfigFile(filePath) {
2339
- if (!existsSync10(filePath)) {
3631
+ if (!existsSync16(filePath)) {
2340
3632
  return null;
2341
3633
  }
2342
3634
  try {
@@ -2377,10 +3669,10 @@ async function loadMcpConfigs() {
2377
3669
  return { servers, loadedServers };
2378
3670
  }
2379
3671
  // src/features/claude-code-session-state/state.ts
2380
- var sessionErrorState = new Map;
2381
- var sessionInterruptState = new Map;
3672
+ var sessionErrorState2 = new Map;
3673
+ var sessionInterruptState2 = new Map;
2382
3674
  var subagentSessions = new Set;
2383
- var sessionFirstMessageProcessed = new Set;
3675
+ var sessionFirstMessageProcessed2 = new Set;
2384
3676
  var currentSessionID;
2385
3677
  var currentSessionTitle;
2386
3678
  var mainSessionID;
@@ -2622,14 +3914,14 @@ var EXT_TO_LANG = {
2622
3914
  ".tfvars": "terraform"
2623
3915
  };
2624
3916
  // src/tools/lsp/config.ts
2625
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
2626
- import { join as join13 } from "path";
2627
- import { homedir as homedir6 } from "os";
3917
+ import { existsSync as existsSync17, readFileSync as readFileSync9 } from "fs";
3918
+ import { join as join20 } from "path";
3919
+ import { homedir as homedir11 } from "os";
2628
3920
  function loadJsonFile(path3) {
2629
- if (!existsSync11(path3))
3921
+ if (!existsSync17(path3))
2630
3922
  return null;
2631
3923
  try {
2632
- return JSON.parse(readFileSync7(path3, "utf-8"));
3924
+ return JSON.parse(readFileSync9(path3, "utf-8"));
2633
3925
  } catch {
2634
3926
  return null;
2635
3927
  }
@@ -2637,9 +3929,9 @@ function loadJsonFile(path3) {
2637
3929
  function getConfigPaths() {
2638
3930
  const cwd = process.cwd();
2639
3931
  return {
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")
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")
2643
3935
  };
2644
3936
  }
2645
3937
  function loadAllConfigs() {
@@ -2732,7 +4024,7 @@ function isServerInstalled(command) {
2732
4024
  const pathEnv = process.env.PATH || "";
2733
4025
  const paths = pathEnv.split(":");
2734
4026
  for (const p of paths) {
2735
- if (existsSync11(join13(p, cmd))) {
4027
+ if (existsSync17(join20(p, cmd))) {
2736
4028
  return true;
2737
4029
  }
2738
4030
  }
@@ -2781,8 +4073,8 @@ function getAllServers() {
2781
4073
  return result;
2782
4074
  }
2783
4075
  // src/tools/lsp/client.ts
2784
- var {spawn: spawn3 } = globalThis.Bun;
2785
- import { readFileSync as readFileSync8 } from "fs";
4076
+ var {spawn: spawn4 } = globalThis.Bun;
4077
+ import { readFileSync as readFileSync10 } from "fs";
2786
4078
  import { extname, resolve as resolve3 } from "path";
2787
4079
  class LSPServerManager {
2788
4080
  static instance;
@@ -2918,7 +4210,7 @@ class LSPClient {
2918
4210
  this.server = server;
2919
4211
  }
2920
4212
  async start() {
2921
- this.proc = spawn3(this.server.command, {
4213
+ this.proc = spawn4(this.server.command, {
2922
4214
  stdin: "pipe",
2923
4215
  stdout: "pipe",
2924
4216
  stderr: "pipe",
@@ -3182,7 +4474,7 @@ ${msg}`);
3182
4474
  const absPath = resolve3(filePath);
3183
4475
  if (this.openedFiles.has(absPath))
3184
4476
  return;
3185
- const text = readFileSync8(absPath, "utf-8");
4477
+ const text = readFileSync10(absPath, "utf-8");
3186
4478
  const ext = extname(absPath);
3187
4479
  const languageId = getLanguageId(ext);
3188
4480
  this.notify("textDocument/didOpen", {
@@ -3297,16 +4589,16 @@ ${msg}`);
3297
4589
  }
3298
4590
  // src/tools/lsp/utils.ts
3299
4591
  import { extname as extname2, resolve as resolve4 } from "path";
3300
- import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync3 } from "fs";
4592
+ import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
3301
4593
  function findWorkspaceRoot(filePath) {
3302
4594
  let dir = resolve4(filePath);
3303
- if (!existsSync12(dir) || !__require("fs").statSync(dir).isDirectory()) {
4595
+ if (!existsSync18(dir) || !__require("fs").statSync(dir).isDirectory()) {
3304
4596
  dir = __require("path").dirname(dir);
3305
4597
  }
3306
4598
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
3307
4599
  while (dir !== "/") {
3308
4600
  for (const marker of markers) {
3309
- if (existsSync12(__require("path").join(dir, marker))) {
4601
+ if (existsSync18(__require("path").join(dir, marker))) {
3310
4602
  return dir;
3311
4603
  }
3312
4604
  }
@@ -3464,7 +4756,7 @@ function formatCodeActions(actions) {
3464
4756
  }
3465
4757
  function applyTextEditsToFile(filePath, edits) {
3466
4758
  try {
3467
- let content = readFileSync9(filePath, "utf-8");
4759
+ let content = readFileSync11(filePath, "utf-8");
3468
4760
  const lines = content.split(`
3469
4761
  `);
3470
4762
  const sortedEdits = [...edits].sort((a, b) => {
@@ -3489,7 +4781,7 @@ function applyTextEditsToFile(filePath, edits) {
3489
4781
  `));
3490
4782
  }
3491
4783
  }
3492
- writeFileSync3(filePath, lines.join(`
4784
+ writeFileSync5(filePath, lines.join(`
3493
4785
  `), "utf-8");
3494
4786
  return { success: true, editCount: edits.length };
3495
4787
  } catch (err) {
@@ -3520,7 +4812,7 @@ function applyWorkspaceEdit(edit) {
3520
4812
  if (change.kind === "create") {
3521
4813
  try {
3522
4814
  const filePath = change.uri.replace("file://", "");
3523
- writeFileSync3(filePath, "", "utf-8");
4815
+ writeFileSync5(filePath, "", "utf-8");
3524
4816
  result.filesModified.push(filePath);
3525
4817
  } catch (err) {
3526
4818
  result.success = false;
@@ -3530,8 +4822,8 @@ function applyWorkspaceEdit(edit) {
3530
4822
  try {
3531
4823
  const oldPath = change.oldUri.replace("file://", "");
3532
4824
  const newPath = change.newUri.replace("file://", "");
3533
- const content = readFileSync9(oldPath, "utf-8");
3534
- writeFileSync3(newPath, content, "utf-8");
4825
+ const content = readFileSync11(oldPath, "utf-8");
4826
+ writeFileSync5(newPath, content, "utf-8");
3535
4827
  __require("fs").unlinkSync(oldPath);
3536
4828
  result.filesModified.push(newPath);
3537
4829
  } catch (err) {
@@ -4172,7 +5464,7 @@ __export(exports_util, {
4172
5464
  jsonStringifyReplacer: () => jsonStringifyReplacer,
4173
5465
  joinValues: () => joinValues,
4174
5466
  issue: () => issue,
4175
- isPlainObject: () => isPlainObject,
5467
+ isPlainObject: () => isPlainObject2,
4176
5468
  isObject: () => isObject,
4177
5469
  hexToUint8Array: () => hexToUint8Array,
4178
5470
  getSizableOrigin: () => getSizableOrigin,
@@ -4354,7 +5646,7 @@ var allowsEval = cached(() => {
4354
5646
  return false;
4355
5647
  }
4356
5648
  });
4357
- function isPlainObject(o) {
5649
+ function isPlainObject2(o) {
4358
5650
  if (isObject(o) === false)
4359
5651
  return false;
4360
5652
  const ctor = o.constructor;
@@ -4369,7 +5661,7 @@ function isPlainObject(o) {
4369
5661
  return true;
4370
5662
  }
4371
5663
  function shallowClone(o) {
4372
- if (isPlainObject(o))
5664
+ if (isPlainObject2(o))
4373
5665
  return { ...o };
4374
5666
  if (Array.isArray(o))
4375
5667
  return [...o];
@@ -4552,7 +5844,7 @@ function omit(schema, mask) {
4552
5844
  return clone(schema, def);
4553
5845
  }
4554
5846
  function extend(schema, shape) {
4555
- if (!isPlainObject(shape)) {
5847
+ if (!isPlainObject2(shape)) {
4556
5848
  throw new Error("Invalid input to extend: expected a plain object");
4557
5849
  }
4558
5850
  const checks = schema._zod.def.checks;
@@ -4571,7 +5863,7 @@ function extend(schema, shape) {
4571
5863
  return clone(schema, def);
4572
5864
  }
4573
5865
  function safeExtend(schema, shape) {
4574
- if (!isPlainObject(shape)) {
5866
+ if (!isPlainObject2(shape)) {
4575
5867
  throw new Error("Invalid input to safeExtend: expected a plain object");
4576
5868
  }
4577
5869
  const def = {
@@ -6721,7 +8013,7 @@ function mergeValues(a, b) {
6721
8013
  if (a instanceof Date && b instanceof Date && +a === +b) {
6722
8014
  return { valid: true, data: a };
6723
8015
  }
6724
- if (isPlainObject(a) && isPlainObject(b)) {
8016
+ if (isPlainObject2(a) && isPlainObject2(b)) {
6725
8017
  const bKeys = Object.keys(b);
6726
8018
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
6727
8019
  const newObj = { ...a, ...b };
@@ -6851,7 +8143,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
6851
8143
  $ZodType.init(inst, def);
6852
8144
  inst._zod.parse = (payload, ctx) => {
6853
8145
  const input = payload.value;
6854
- if (!isPlainObject(input)) {
8146
+ if (!isPlainObject2(input)) {
6855
8147
  payload.issues.push({
6856
8148
  expected: "record",
6857
8149
  code: "invalid_type",
@@ -16231,14 +17523,14 @@ var lsp_code_action_resolve = tool({
16231
17523
  });
16232
17524
  // src/tools/ast-grep/constants.ts
16233
17525
  import { createRequire as createRequire4 } from "module";
16234
- import { dirname as dirname3, join as join15 } from "path";
16235
- import { existsSync as existsSync14, statSync as statSync2 } from "fs";
17526
+ import { dirname as dirname3, join as join22 } from "path";
17527
+ import { existsSync as existsSync20, statSync as statSync3 } from "fs";
16236
17528
 
16237
17529
  // src/tools/ast-grep/downloader.ts
16238
- var {spawn: spawn4 } = globalThis.Bun;
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";
17530
+ 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";
17533
+ import { homedir as homedir12 } from "os";
16242
17534
  import { createRequire as createRequire3 } from "module";
16243
17535
  var REPO2 = "ast-grep/ast-grep";
16244
17536
  var DEFAULT_VERSION = "0.40.0";
@@ -16263,26 +17555,26 @@ var PLATFORM_MAP2 = {
16263
17555
  function getCacheDir2() {
16264
17556
  if (process.platform === "win32") {
16265
17557
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
16266
- const base2 = localAppData || join14(homedir7(), "AppData", "Local");
16267
- return join14(base2, "oh-my-opencode", "bin");
17558
+ const base2 = localAppData || join21(homedir12(), "AppData", "Local");
17559
+ return join21(base2, "oh-my-opencode", "bin");
16268
17560
  }
16269
17561
  const xdgCache2 = process.env.XDG_CACHE_HOME;
16270
- const base = xdgCache2 || join14(homedir7(), ".cache");
16271
- return join14(base, "oh-my-opencode", "bin");
17562
+ const base = xdgCache2 || join21(homedir12(), ".cache");
17563
+ return join21(base, "oh-my-opencode", "bin");
16272
17564
  }
16273
17565
  function getBinaryName3() {
16274
17566
  return process.platform === "win32" ? "sg.exe" : "sg";
16275
17567
  }
16276
17568
  function getCachedBinaryPath2() {
16277
- const binaryPath = join14(getCacheDir2(), getBinaryName3());
16278
- return existsSync13(binaryPath) ? binaryPath : null;
17569
+ const binaryPath = join21(getCacheDir2(), getBinaryName3());
17570
+ return existsSync19(binaryPath) ? binaryPath : null;
16279
17571
  }
16280
17572
  async function extractZip2(archivePath, destDir) {
16281
- const proc = process.platform === "win32" ? spawn4([
17573
+ const proc = process.platform === "win32" ? spawn5([
16282
17574
  "powershell",
16283
17575
  "-command",
16284
17576
  `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`
16285
- ], { stdout: "pipe", stderr: "pipe" }) : spawn4(["unzip", "-o", archivePath, "-d", destDir], { stdout: "pipe", stderr: "pipe" });
17577
+ ], { stdout: "pipe", stderr: "pipe" }) : spawn5(["unzip", "-o", archivePath, "-d", destDir], { stdout: "pipe", stderr: "pipe" });
16286
17578
  const exitCode = await proc.exited;
16287
17579
  if (exitCode !== 0) {
16288
17580
  const stderr = await new Response(proc.stderr).text();
@@ -16301,8 +17593,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
16301
17593
  }
16302
17594
  const cacheDir = getCacheDir2();
16303
17595
  const binaryName = getBinaryName3();
16304
- const binaryPath = join14(cacheDir, binaryName);
16305
- if (existsSync13(binaryPath)) {
17596
+ const binaryPath = join21(cacheDir, binaryName);
17597
+ if (existsSync19(binaryPath)) {
16306
17598
  return binaryPath;
16307
17599
  }
16308
17600
  const { arch, os: os3 } = platformInfo;
@@ -16310,21 +17602,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
16310
17602
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
16311
17603
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
16312
17604
  try {
16313
- if (!existsSync13(cacheDir)) {
16314
- mkdirSync4(cacheDir, { recursive: true });
17605
+ if (!existsSync19(cacheDir)) {
17606
+ mkdirSync6(cacheDir, { recursive: true });
16315
17607
  }
16316
17608
  const response = await fetch(downloadUrl, { redirect: "follow" });
16317
17609
  if (!response.ok) {
16318
17610
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
16319
17611
  }
16320
- const archivePath = join14(cacheDir, assetName);
17612
+ const archivePath = join21(cacheDir, assetName);
16321
17613
  const arrayBuffer = await response.arrayBuffer();
16322
17614
  await Bun.write(archivePath, arrayBuffer);
16323
17615
  await extractZip2(archivePath, cacheDir);
16324
- if (existsSync13(archivePath)) {
16325
- unlinkSync4(archivePath);
17616
+ if (existsSync19(archivePath)) {
17617
+ unlinkSync5(archivePath);
16326
17618
  }
16327
- if (process.platform !== "win32" && existsSync13(binaryPath)) {
17619
+ if (process.platform !== "win32" && existsSync19(binaryPath)) {
16328
17620
  chmodSync2(binaryPath, 493);
16329
17621
  }
16330
17622
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -16346,7 +17638,7 @@ async function ensureAstGrepBinary() {
16346
17638
  // src/tools/ast-grep/constants.ts
16347
17639
  function isValidBinary(filePath) {
16348
17640
  try {
16349
- return statSync2(filePath).size > 1e4;
17641
+ return statSync3(filePath).size > 1e4;
16350
17642
  } catch {
16351
17643
  return false;
16352
17644
  }
@@ -16375,8 +17667,8 @@ function findSgCliPathSync() {
16375
17667
  const require2 = createRequire4(import.meta.url);
16376
17668
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
16377
17669
  const cliDir = dirname3(cliPkgPath);
16378
- const sgPath = join15(cliDir, binaryName);
16379
- if (existsSync14(sgPath) && isValidBinary(sgPath)) {
17670
+ const sgPath = join22(cliDir, binaryName);
17671
+ if (existsSync20(sgPath) && isValidBinary(sgPath)) {
16380
17672
  return sgPath;
16381
17673
  }
16382
17674
  } catch {}
@@ -16387,8 +17679,8 @@ function findSgCliPathSync() {
16387
17679
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
16388
17680
  const pkgDir = dirname3(pkgPath);
16389
17681
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
16390
- const binaryPath = join15(pkgDir, astGrepName);
16391
- if (existsSync14(binaryPath) && isValidBinary(binaryPath)) {
17682
+ const binaryPath = join22(pkgDir, astGrepName);
17683
+ if (existsSync20(binaryPath) && isValidBinary(binaryPath)) {
16392
17684
  return binaryPath;
16393
17685
  }
16394
17686
  } catch {}
@@ -16396,7 +17688,7 @@ function findSgCliPathSync() {
16396
17688
  if (process.platform === "darwin") {
16397
17689
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
16398
17690
  for (const path3 of homebrewPaths) {
16399
- if (existsSync14(path3) && isValidBinary(path3)) {
17691
+ if (existsSync20(path3) && isValidBinary(path3)) {
16400
17692
  return path3;
16401
17693
  }
16402
17694
  }
@@ -16451,12 +17743,12 @@ var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
16451
17743
  var DEFAULT_MAX_MATCHES = 500;
16452
17744
 
16453
17745
  // src/tools/ast-grep/cli.ts
16454
- var {spawn: spawn5 } = globalThis.Bun;
16455
- import { existsSync as existsSync15 } from "fs";
17746
+ var {spawn: spawn6 } = globalThis.Bun;
17747
+ import { existsSync as existsSync21 } from "fs";
16456
17748
  var resolvedCliPath3 = null;
16457
17749
  var initPromise2 = null;
16458
17750
  async function getAstGrepPath() {
16459
- if (resolvedCliPath3 !== null && existsSync15(resolvedCliPath3)) {
17751
+ if (resolvedCliPath3 !== null && existsSync21(resolvedCliPath3)) {
16460
17752
  return resolvedCliPath3;
16461
17753
  }
16462
17754
  if (initPromise2) {
@@ -16464,7 +17756,7 @@ async function getAstGrepPath() {
16464
17756
  }
16465
17757
  initPromise2 = (async () => {
16466
17758
  const syncPath = findSgCliPathSync();
16467
- if (syncPath && existsSync15(syncPath)) {
17759
+ if (syncPath && existsSync21(syncPath)) {
16468
17760
  resolvedCliPath3 = syncPath;
16469
17761
  setSgCliPath(syncPath);
16470
17762
  return syncPath;
@@ -16498,14 +17790,14 @@ async function runSg(options) {
16498
17790
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
16499
17791
  args.push(...paths);
16500
17792
  let cliPath = getSgCliPath();
16501
- if (!existsSync15(cliPath) && cliPath !== "sg") {
17793
+ if (!existsSync21(cliPath) && cliPath !== "sg") {
16502
17794
  const downloadedPath = await getAstGrepPath();
16503
17795
  if (downloadedPath) {
16504
17796
  cliPath = downloadedPath;
16505
17797
  }
16506
17798
  }
16507
17799
  const timeout = DEFAULT_TIMEOUT_MS;
16508
- const proc = spawn5([cliPath, ...args], {
17800
+ const proc = spawn6([cliPath, ...args], {
16509
17801
  stdout: "pipe",
16510
17802
  stderr: "pipe"
16511
17803
  });
@@ -16759,11 +18051,11 @@ var ast_grep_replace = tool({
16759
18051
  }
16760
18052
  });
16761
18053
  // src/tools/grep/cli.ts
16762
- var {spawn: spawn6 } = globalThis.Bun;
18054
+ var {spawn: spawn7 } = globalThis.Bun;
16763
18055
 
16764
18056
  // src/tools/grep/constants.ts
16765
- import { existsSync as existsSync16 } from "fs";
16766
- import { join as join16, dirname as dirname4 } from "path";
18057
+ import { existsSync as existsSync22 } from "fs";
18058
+ import { join as join23, dirname as dirname4 } from "path";
16767
18059
  import { spawnSync } from "child_process";
16768
18060
  var cachedCli = null;
16769
18061
  function findExecutable(name) {
@@ -16784,13 +18076,13 @@ function getOpenCodeBundledRg() {
16784
18076
  const isWindows = process.platform === "win32";
16785
18077
  const rgName = isWindows ? "rg.exe" : "rg";
16786
18078
  const candidates = [
16787
- join16(execDir, rgName),
16788
- join16(execDir, "bin", rgName),
16789
- join16(execDir, "..", "bin", rgName),
16790
- join16(execDir, "..", "libexec", rgName)
18079
+ join23(execDir, rgName),
18080
+ join23(execDir, "bin", rgName),
18081
+ join23(execDir, "..", "bin", rgName),
18082
+ join23(execDir, "..", "libexec", rgName)
16791
18083
  ];
16792
18084
  for (const candidate of candidates) {
16793
- if (existsSync16(candidate)) {
18085
+ if (existsSync22(candidate)) {
16794
18086
  return candidate;
16795
18087
  }
16796
18088
  }
@@ -16931,7 +18223,7 @@ async function runRg(options) {
16931
18223
  }
16932
18224
  const paths = options.paths?.length ? options.paths : ["."];
16933
18225
  args.push(...paths);
16934
- const proc = spawn6([cli.path, ...args], {
18226
+ const proc = spawn7([cli.path, ...args], {
16935
18227
  stdout: "pipe",
16936
18228
  stderr: "pipe"
16937
18229
  });
@@ -17033,7 +18325,7 @@ var grep = tool({
17033
18325
  });
17034
18326
 
17035
18327
  // src/tools/glob/cli.ts
17036
- var {spawn: spawn7 } = globalThis.Bun;
18328
+ var {spawn: spawn8 } = globalThis.Bun;
17037
18329
 
17038
18330
  // src/tools/glob/constants.ts
17039
18331
  var DEFAULT_TIMEOUT_MS3 = 60000;
@@ -17090,7 +18382,7 @@ async function runRgFiles(options) {
17090
18382
  args.push(...paths);
17091
18383
  }
17092
18384
  const cwd = paths[0] || ".";
17093
- const proc = spawn7([cli.path, ...args], {
18385
+ const proc = spawn8([cli.path, ...args], {
17094
18386
  stdout: "pipe",
17095
18387
  stderr: "pipe",
17096
18388
  cwd: isRg ? undefined : cwd
@@ -17188,145 +18480,14 @@ var glob = tool({
17188
18480
  }
17189
18481
  });
17190
18482
  // 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
18483
+ import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync12 } from "fs";
18484
+ import { homedir as homedir13 } from "os";
18485
+ import { join as join24, basename as basename3, dirname as dirname5 } from "path";
17325
18486
  function discoverCommandsFromDir(commandsDir, scope) {
17326
- if (!existsSync18(commandsDir)) {
18487
+ if (!existsSync23(commandsDir)) {
17327
18488
  return [];
17328
18489
  }
17329
- const entries = readdirSync5(commandsDir, { withFileTypes: true });
18490
+ const entries = readdirSync6(commandsDir, { withFileTypes: true });
17330
18491
  const commands = [];
17331
18492
  for (const entry of entries) {
17332
18493
  if (entry.name.startsWith("."))
@@ -17335,10 +18496,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
17335
18496
  continue;
17336
18497
  if (!entry.isFile())
17337
18498
  continue;
17338
- const commandPath = join18(commandsDir, entry.name);
18499
+ const commandPath = join24(commandsDir, entry.name);
17339
18500
  const commandName = basename3(entry.name, ".md");
17340
18501
  try {
17341
- const content = readFileSync11(commandPath, "utf-8");
18502
+ const content = readFileSync12(commandPath, "utf-8");
17342
18503
  const { data, body } = parseFrontmatter(content);
17343
18504
  const metadata = {
17344
18505
  name: commandName,
@@ -17362,10 +18523,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
17362
18523
  return commands;
17363
18524
  }
17364
18525
  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");
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");
17369
18530
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
17370
18531
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
17371
18532
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -17489,19 +18650,19 @@ Try a different command name.`;
17489
18650
  }
17490
18651
  });
17491
18652
  // 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";
18653
+ import { existsSync as existsSync24, readdirSync as readdirSync7, statSync as statSync4, readlinkSync as readlinkSync2, readFileSync as readFileSync13 } from "fs";
18654
+ import { homedir as homedir14 } from "os";
18655
+ import { join as join25, resolve as resolve5, basename as basename4 } from "path";
17495
18656
  function discoverSkillsFromDir(skillsDir, scope) {
17496
- if (!existsSync19(skillsDir)) {
18657
+ if (!existsSync24(skillsDir)) {
17497
18658
  return [];
17498
18659
  }
17499
- const entries = readdirSync6(skillsDir, { withFileTypes: true });
18660
+ const entries = readdirSync7(skillsDir, { withFileTypes: true });
17500
18661
  const skills = [];
17501
18662
  for (const entry of entries) {
17502
18663
  if (entry.name.startsWith("."))
17503
18664
  continue;
17504
- const skillPath = join19(skillsDir, entry.name);
18665
+ const skillPath = join25(skillsDir, entry.name);
17505
18666
  if (entry.isDirectory() || entry.isSymbolicLink()) {
17506
18667
  let resolvedPath = skillPath;
17507
18668
  try {
@@ -17512,11 +18673,11 @@ function discoverSkillsFromDir(skillsDir, scope) {
17512
18673
  } catch {
17513
18674
  continue;
17514
18675
  }
17515
- const skillMdPath = join19(resolvedPath, "SKILL.md");
17516
- if (!existsSync19(skillMdPath))
18676
+ const skillMdPath = join25(resolvedPath, "SKILL.md");
18677
+ if (!existsSync24(skillMdPath))
17517
18678
  continue;
17518
18679
  try {
17519
- const content = readFileSync12(skillMdPath, "utf-8");
18680
+ const content = readFileSync13(skillMdPath, "utf-8");
17520
18681
  const { data } = parseFrontmatter(content);
17521
18682
  skills.push({
17522
18683
  name: data.name || entry.name,
@@ -17531,8 +18692,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
17531
18692
  return skills;
17532
18693
  }
17533
18694
  function discoverSkillsSync() {
17534
- const userSkillsDir = join19(homedir9(), ".claude", "skills");
17535
- const projectSkillsDir = join19(process.cwd(), ".claude", "skills");
18695
+ const userSkillsDir = join25(homedir14(), ".claude", "skills");
18696
+ const projectSkillsDir = join25(process.cwd(), ".claude", "skills");
17536
18697
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
17537
18698
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
17538
18699
  return [...projectSkills, ...userSkills];
@@ -17553,12 +18714,12 @@ function resolveSymlink(skillPath) {
17553
18714
  }
17554
18715
  async function parseSkillMd(skillPath) {
17555
18716
  const resolvedPath = resolveSymlink(skillPath);
17556
- const skillMdPath = join19(resolvedPath, "SKILL.md");
17557
- if (!existsSync19(skillMdPath)) {
18717
+ const skillMdPath = join25(resolvedPath, "SKILL.md");
18718
+ if (!existsSync24(skillMdPath)) {
17558
18719
  return null;
17559
18720
  }
17560
18721
  try {
17561
- let content = readFileSync12(skillMdPath, "utf-8");
18722
+ let content = readFileSync13(skillMdPath, "utf-8");
17562
18723
  content = await resolveCommandsInText(content);
17563
18724
  const { data, body } = parseFrontmatter(content);
17564
18725
  const metadata = {
@@ -17566,12 +18727,12 @@ async function parseSkillMd(skillPath) {
17566
18727
  description: data.description || "",
17567
18728
  license: data.license
17568
18729
  };
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(".")) : [];
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(".")) : [];
17575
18736
  return {
17576
18737
  name: metadata.name,
17577
18738
  path: resolvedPath,
@@ -17586,15 +18747,15 @@ async function parseSkillMd(skillPath) {
17586
18747
  }
17587
18748
  }
17588
18749
  async function discoverSkillsFromDirAsync(skillsDir) {
17589
- if (!existsSync19(skillsDir)) {
18750
+ if (!existsSync24(skillsDir)) {
17590
18751
  return [];
17591
18752
  }
17592
- const entries = readdirSync6(skillsDir, { withFileTypes: true });
18753
+ const entries = readdirSync7(skillsDir, { withFileTypes: true });
17593
18754
  const skills = [];
17594
18755
  for (const entry of entries) {
17595
18756
  if (entry.name.startsWith("."))
17596
18757
  continue;
17597
- const skillPath = join19(skillsDir, entry.name);
18758
+ const skillPath = join25(skillsDir, entry.name);
17598
18759
  if (entry.isDirectory() || entry.isSymbolicLink()) {
17599
18760
  const skillInfo = await parseSkillMd(skillPath);
17600
18761
  if (skillInfo) {
@@ -17605,8 +18766,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
17605
18766
  return skills;
17606
18767
  }
17607
18768
  async function discoverSkills() {
17608
- const userSkillsDir = join19(homedir9(), ".claude", "skills");
17609
- const projectSkillsDir = join19(process.cwd(), ".claude", "skills");
18769
+ const userSkillsDir = join25(homedir14(), ".claude", "skills");
18770
+ const projectSkillsDir = join25(process.cwd(), ".claude", "skills");
17610
18771
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
17611
18772
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
17612
18773
  return [...projectSkills, ...userSkills];
@@ -17635,9 +18796,9 @@ async function loadSkillWithReferences(skill, includeRefs) {
17635
18796
  const referencesLoaded = [];
17636
18797
  if (includeRefs && skill.references.length > 0) {
17637
18798
  for (const ref of skill.references) {
17638
- const refPath = join19(skill.path, "references", ref);
18799
+ const refPath = join25(skill.path, "references", ref);
17639
18800
  try {
17640
- let content = readFileSync12(refPath, "utf-8");
18801
+ let content = readFileSync13(refPath, "utf-8");
17641
18802
  content = await resolveCommandsInText(content);
17642
18803
  referencesLoaded.push({ path: ref, content });
17643
18804
  } catch {}
@@ -17870,6 +19031,7 @@ function loadPluginConfig(directory) {
17870
19031
  return {};
17871
19032
  }
17872
19033
  var OhMyOpenCodePlugin = async (ctx) => {
19034
+ const pluginConfig = loadPluginConfig(ctx.directory);
17873
19035
  const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx);
17874
19036
  const contextWindowMonitor = createContextWindowMonitorHook(ctx);
17875
19037
  const sessionRecovery = createSessionRecoveryHook(ctx);
@@ -17878,19 +19040,22 @@ var OhMyOpenCodePlugin = async (ctx) => {
17878
19040
  const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
17879
19041
  const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
17880
19042
  const thinkMode = createThinkModeHook();
19043
+ const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {});
17881
19044
  updateTerminalTitle({ sessionId: "main" });
17882
- const pluginConfig = loadPluginConfig(ctx.directory);
17883
19045
  return {
17884
19046
  tool: builtinTools,
19047
+ "chat.message": async (input, output) => {
19048
+ await claudeCodeHooks["chat.message"]?.(input, output);
19049
+ },
17885
19050
  config: async (config3) => {
17886
19051
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents);
17887
19052
  const userAgents = loadUserAgents();
17888
19053
  const projectAgents = loadProjectAgents();
17889
19054
  config3.agent = {
17890
- ...config3.agent,
17891
19055
  ...builtinAgents,
17892
19056
  ...userAgents,
17893
- ...projectAgents
19057
+ ...projectAgents,
19058
+ ...config3.agent
17894
19059
  };
17895
19060
  config3.tools = {
17896
19061
  ...config3.tools
@@ -17919,6 +19084,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
17919
19084
  };
17920
19085
  },
17921
19086
  event: async (input) => {
19087
+ await claudeCodeHooks.event(input);
17922
19088
  await todoContinuationEnforcer(input);
17923
19089
  await contextWindowMonitor.event(input);
17924
19090
  await directoryAgentsInjector.event(input);
@@ -18002,6 +19168,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
18002
19168
  }
18003
19169
  },
18004
19170
  "tool.execute.before": async (input, output) => {
19171
+ await claudeCodeHooks["tool.execute.before"](input, output);
18005
19172
  await commentChecker["tool.execute.before"](input, output);
18006
19173
  if (input.sessionID === getMainSessionID()) {
18007
19174
  updateTerminalTitle({
@@ -18014,6 +19181,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
18014
19181
  }
18015
19182
  },
18016
19183
  "tool.execute.after": async (input, output) => {
19184
+ await claudeCodeHooks["tool.execute.after"](input, output);
18017
19185
  await grepOutputTruncator["tool.execute.after"](input, output);
18018
19186
  await contextWindowMonitor["tool.execute.after"](input, output);
18019
19187
  await commentChecker["tool.execute.after"](input, output);