oh-my-opencode 2.7.3 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.ja.md +20 -10
  2. package/README.ko.md +20 -10
  3. package/README.md +16 -6
  4. package/README.zh-cn.md +20 -10
  5. package/dist/cli/ast-grep-napi.linux-x64-gnu-jfv8414z.node +0 -0
  6. package/dist/cli/ast-grep-napi.linux-x64-musl-8cj2e5cf.node +0 -0
  7. package/dist/cli/doctor/checks/auth.d.ts +7 -0
  8. package/dist/cli/doctor/checks/config.d.ts +8 -0
  9. package/dist/cli/doctor/checks/dependencies.d.ts +8 -0
  10. package/dist/cli/doctor/checks/dependencies.test.d.ts +1 -0
  11. package/dist/cli/doctor/checks/index.d.ts +10 -0
  12. package/dist/cli/doctor/checks/lsp.d.ts +8 -0
  13. package/dist/cli/doctor/checks/lsp.test.d.ts +1 -0
  14. package/dist/cli/doctor/checks/mcp.d.ts +6 -0
  15. package/dist/cli/doctor/checks/mcp.test.d.ts +1 -0
  16. package/dist/cli/doctor/checks/opencode.d.ts +10 -0
  17. package/dist/cli/doctor/checks/opencode.test.d.ts +1 -0
  18. package/dist/cli/doctor/checks/plugin.d.ts +4 -0
  19. package/dist/cli/doctor/checks/plugin.test.d.ts +1 -0
  20. package/dist/cli/doctor/checks/version.d.ts +4 -0
  21. package/dist/cli/doctor/checks/version.test.d.ts +1 -0
  22. package/dist/cli/doctor/constants.d.ts +39 -0
  23. package/dist/cli/doctor/formatter.d.ts +12 -0
  24. package/dist/cli/doctor/formatter.test.d.ts +1 -0
  25. package/dist/cli/doctor/index.d.ts +5 -0
  26. package/dist/cli/doctor/runner.d.ts +7 -0
  27. package/dist/cli/doctor/runner.test.d.ts +1 -0
  28. package/dist/cli/doctor/types.d.ts +91 -0
  29. package/dist/cli/index.js +14180 -76
  30. package/dist/config/index.d.ts +2 -2
  31. package/dist/config/schema.d.ts +98 -6
  32. package/dist/features/builtin-commands/templates/ralph-loop.d.ts +2 -0
  33. package/dist/features/builtin-commands/types.d.ts +1 -1
  34. package/dist/features/builtin-skills/index.d.ts +2 -0
  35. package/dist/features/builtin-skills/skills.d.ts +2 -0
  36. package/dist/features/builtin-skills/types.d.ts +13 -0
  37. package/dist/features/{claude-code-skill-loader → opencode-skill-loader}/index.d.ts +1 -0
  38. package/dist/features/opencode-skill-loader/loader.d.ts +41 -0
  39. package/dist/features/opencode-skill-loader/merger.d.ts +7 -0
  40. package/dist/features/opencode-skill-loader/types.d.ts +25 -0
  41. package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/executor.d.ts +1 -1
  42. package/dist/hooks/anthropic-context-window-limit-recovery/executor.test.d.ts +1 -0
  43. package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/index.d.ts +3 -2
  44. package/dist/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.d.ts +1 -0
  45. package/dist/hooks/index.d.ts +2 -1
  46. package/dist/hooks/ralph-loop/constants.d.ts +5 -0
  47. package/dist/hooks/ralph-loop/index.d.ts +20 -0
  48. package/dist/hooks/ralph-loop/index.test.d.ts +1 -0
  49. package/dist/hooks/ralph-loop/storage.d.ts +6 -0
  50. package/dist/hooks/ralph-loop/types.d.ts +13 -0
  51. package/dist/hooks/think-mode/index.test.d.ts +1 -0
  52. package/dist/hooks/think-mode/switcher.d.ts +54 -1
  53. package/dist/hooks/think-mode/switcher.test.d.ts +1 -0
  54. package/dist/index.js +1290 -356
  55. package/dist/tools/index.d.ts +1 -0
  56. package/dist/tools/interactive-bash/constants.d.ts +1 -1
  57. package/dist/tools/look-at/constants.d.ts +1 -1
  58. package/dist/tools/lsp/utils.d.ts +1 -0
  59. package/dist/tools/skill/constants.d.ts +3 -0
  60. package/dist/tools/skill/index.d.ts +3 -0
  61. package/dist/tools/skill/tools.d.ts +10 -0
  62. package/dist/tools/skill/types.d.ts +20 -0
  63. package/dist/tools/slashcommand/types.d.ts +3 -3
  64. package/package.json +1 -1
  65. package/dist/features/claude-code-skill-loader/loader.d.ts +0 -3
  66. package/dist/features/claude-code-skill-loader/types.d.ts +0 -13
  67. /package/dist/{hooks/anthropic-auto-compact/executor.test.d.ts → cli/doctor/checks/auth.test.d.ts} +0 -0
  68. /package/dist/{hooks/anthropic-auto-compact/pruning-deduplication.test.d.ts → cli/doctor/checks/config.test.d.ts} +0 -0
  69. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/parser.d.ts +0 -0
  70. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-deduplication.d.ts +0 -0
  71. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-executor.d.ts +0 -0
  72. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-purge-errors.d.ts +0 -0
  73. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-storage.d.ts +0 -0
  74. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-supersede.d.ts +0 -0
  75. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-types.d.ts +0 -0
  76. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/storage.d.ts +0 -0
  77. /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/types.d.ts +0 -0
package/dist/index.js CHANGED
@@ -6026,7 +6026,7 @@ var TRUNCATABLE_TOOLS = [
6026
6026
  ];
6027
6027
  function createToolOutputTruncatorHook(ctx, options) {
6028
6028
  const truncator = createDynamicTruncator(ctx);
6029
- const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? true;
6029
+ const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? false;
6030
6030
  const toolExecuteAfter = async (input, output) => {
6031
6031
  if (!truncateAll && !TRUNCATABLE_TOOLS.includes(input.tool))
6032
6032
  return;
@@ -6406,7 +6406,7 @@ function createEmptyTaskResponseDetectorHook(_ctx) {
6406
6406
  }
6407
6407
  };
6408
6408
  }
6409
- // src/hooks/anthropic-auto-compact/parser.ts
6409
+ // src/hooks/anthropic-context-window-limit-recovery/parser.ts
6410
6410
  var TOKEN_LIMIT_PATTERNS = [
6411
6411
  /(\d+)\s*tokens?\s*>\s*(\d+)\s*maximum/i,
6412
6412
  /prompt.*?(\d+).*?tokens.*?exceeds.*?(\d+)/i,
@@ -6571,7 +6571,7 @@ function parseAnthropicTokenLimitError(err) {
6571
6571
  return null;
6572
6572
  }
6573
6573
 
6574
- // src/hooks/anthropic-auto-compact/types.ts
6574
+ // src/hooks/anthropic-context-window-limit-recovery/types.ts
6575
6575
  var RETRY_CONFIG = {
6576
6576
  maxAttempts: 2,
6577
6577
  initialDelayMs: 2000,
@@ -6589,17 +6589,17 @@ var TRUNCATE_CONFIG = {
6589
6589
  charsPerToken: 4
6590
6590
  };
6591
6591
 
6592
- // src/hooks/anthropic-auto-compact/pruning-deduplication.ts
6592
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts
6593
6593
  import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
6594
6594
  import { join as join20 } from "path";
6595
6595
 
6596
- // src/hooks/anthropic-auto-compact/pruning-types.ts
6596
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts
6597
6597
  var CHARS_PER_TOKEN = 4;
6598
6598
  function estimateTokens2(text) {
6599
6599
  return Math.ceil(text.length / CHARS_PER_TOKEN);
6600
6600
  }
6601
6601
 
6602
- // src/hooks/anthropic-auto-compact/pruning-deduplication.ts
6602
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts
6603
6603
  function createToolSignature(toolName, input) {
6604
6604
  const sortedInput = sortObject(input);
6605
6605
  return `${toolName}::${JSON.stringify(sortedInput)}`;
@@ -6734,7 +6734,7 @@ function findToolOutput(messages, callID) {
6734
6734
  return null;
6735
6735
  }
6736
6736
 
6737
- // src/hooks/anthropic-auto-compact/pruning-supersede.ts
6737
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-supersede.ts
6738
6738
  import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
6739
6739
  import { join as join21 } from "path";
6740
6740
  function getMessageDir4(sessionID) {
@@ -6896,7 +6896,7 @@ function findToolInput(messages, callID) {
6896
6896
  return null;
6897
6897
  }
6898
6898
 
6899
- // src/hooks/anthropic-auto-compact/pruning-purge-errors.ts
6899
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-purge-errors.ts
6900
6900
  import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
6901
6901
  import { join as join22 } from "path";
6902
6902
  function getMessageDir5(sessionID) {
@@ -7001,7 +7001,7 @@ function executePurgeErrors(sessionID, state2, config, protectedTools) {
7001
7001
  return prunedCount;
7002
7002
  }
7003
7003
 
7004
- // src/hooks/anthropic-auto-compact/pruning-storage.ts
7004
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-storage.ts
7005
7005
  import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
7006
7006
  import { join as join23 } from "path";
7007
7007
  function getMessageDir6(sessionID) {
@@ -7070,7 +7070,7 @@ async function applyPruning(sessionID, state2) {
7070
7070
  return totalTokensSaved;
7071
7071
  }
7072
7072
 
7073
- // src/hooks/anthropic-auto-compact/pruning-executor.ts
7073
+ // src/hooks/anthropic-context-window-limit-recovery/pruning-executor.ts
7074
7074
  var DEFAULT_PROTECTED_TOOLS = new Set([
7075
7075
  "task",
7076
7076
  "todowrite",
@@ -7151,7 +7151,7 @@ async function executeDynamicContextPruning(sessionID, config, client) {
7151
7151
  return result;
7152
7152
  }
7153
7153
 
7154
- // src/hooks/anthropic-auto-compact/storage.ts
7154
+ // src/hooks/anthropic-context-window-limit-recovery/storage.ts
7155
7155
  import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
7156
7156
  import { join as join24 } from "path";
7157
7157
  var OPENCODE_STORAGE5 = getOpenCodeStorageDir();
@@ -7293,7 +7293,7 @@ function truncateUntilTargetTokens(sessionID, currentTokens, maxTokens, targetRa
7293
7293
  };
7294
7294
  }
7295
7295
 
7296
- // src/hooks/anthropic-auto-compact/executor.ts
7296
+ // src/hooks/anthropic-context-window-limit-recovery/executor.ts
7297
7297
  var PLACEHOLDER_TEXT = "[user interrupted]";
7298
7298
  function getOrCreateRetryState(autoCompactState, sessionID) {
7299
7299
  let state2 = autoCompactState.retryStateBySession.get(sessionID);
@@ -7492,7 +7492,7 @@ async function fixEmptyMessages(sessionID, autoCompactState, client, messageInde
7492
7492
  }
7493
7493
  return fixed;
7494
7494
  }
7495
- async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
7495
+ async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental, dcpForCompaction) {
7496
7496
  if (autoCompactState.compactionInProgress.has(sessionID)) {
7497
7497
  await client.tui.showToast({
7498
7498
  body: {
@@ -7509,14 +7509,14 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
7509
7509
  const errorData = autoCompactState.errorDataBySession.get(sessionID);
7510
7510
  const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
7511
7511
  const dcpState = getOrCreateDcpState(autoCompactState, sessionID);
7512
- if (experimental?.dcp_for_compaction && !dcpState.attempted && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
7512
+ if (dcpForCompaction !== false && !dcpState.attempted && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
7513
7513
  dcpState.attempted = true;
7514
7514
  log("[auto-compact] DCP triggered FIRST on token limit error", {
7515
7515
  sessionID,
7516
7516
  currentTokens: errorData.currentTokens,
7517
7517
  maxTokens: errorData.maxTokens
7518
7518
  });
7519
- const dcpConfig = experimental.dynamic_context_pruning ?? {
7519
+ const dcpConfig = experimental?.dynamic_context_pruning ?? {
7520
7520
  enabled: true,
7521
7521
  notification: "detailed",
7522
7522
  protected_tools: ["task", "todowrite", "todoread", "lsp_rename", "lsp_code_action_resolve"]
@@ -7677,7 +7677,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
7677
7677
  const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
7678
7678
  if (fixed) {
7679
7679
  setTimeout(() => {
7680
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
7680
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental, dcpForCompaction);
7681
7681
  }, 500);
7682
7682
  return;
7683
7683
  }
@@ -7733,7 +7733,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
7733
7733
  const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
7734
7734
  const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
7735
7735
  setTimeout(() => {
7736
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
7736
+ executeCompact(sessionID, msg, autoCompactState, client, directory, experimental, dcpForCompaction);
7737
7737
  }, cappedDelay);
7738
7738
  return;
7739
7739
  }
@@ -7812,8 +7812,8 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
7812
7812
  }
7813
7813
  }
7814
7814
 
7815
- // src/hooks/anthropic-auto-compact/index.ts
7816
- function createAutoCompactState() {
7815
+ // src/hooks/anthropic-context-window-limit-recovery/index.ts
7816
+ function createRecoveryState() {
7817
7817
  return {
7818
7818
  pendingCompact: new Set,
7819
7819
  errorDataBySession: new Map,
@@ -7825,9 +7825,10 @@ function createAutoCompactState() {
7825
7825
  compactionInProgress: new Set
7826
7826
  };
7827
7827
  }
7828
- function createAnthropicAutoCompactHook(ctx, options) {
7829
- const autoCompactState = createAutoCompactState();
7828
+ function createAnthropicContextWindowLimitRecoveryHook(ctx, options) {
7829
+ const autoCompactState = createRecoveryState();
7830
7830
  const experimental = options?.experimental;
7831
+ const dcpForCompaction = options?.dcpForCompaction;
7831
7832
  const eventHandler = async ({ event }) => {
7832
7833
  const props = event.properties;
7833
7834
  if (event.type === "session.deleted") {
@@ -7869,7 +7870,7 @@ function createAnthropicAutoCompactHook(ctx, options) {
7869
7870
  }
7870
7871
  }).catch(() => {});
7871
7872
  setTimeout(() => {
7872
- executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
7873
+ executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental, dcpForCompaction);
7873
7874
  }, 300);
7874
7875
  }
7875
7876
  return;
@@ -7912,7 +7913,7 @@ function createAnthropicAutoCompactHook(ctx, options) {
7912
7913
  duration: 3000
7913
7914
  }
7914
7915
  }).catch(() => {});
7915
- await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
7916
+ await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental, dcpForCompaction);
7916
7917
  }
7917
7918
  };
7918
7919
  return {
@@ -7957,7 +7958,7 @@ function createPreemptiveCompactionHook(ctx, options) {
7957
7958
  const experimental = options?.experimental;
7958
7959
  const onBeforeSummarize = options?.onBeforeSummarize;
7959
7960
  const getModelLimit = options?.getModelLimit;
7960
- const enabled = experimental?.preemptive_compaction !== false;
7961
+ const enabled = experimental?.preemptive_compaction === true;
7961
7962
  const threshold = experimental?.preemptive_compaction_threshold ?? DEFAULT_THRESHOLD;
7962
7963
  if (!enabled) {
7963
7964
  return { event: async () => {} };
@@ -8263,43 +8264,45 @@ function extractPromptText(parts) {
8263
8264
  }
8264
8265
 
8265
8266
  // src/hooks/think-mode/switcher.ts
8267
+ function normalizeModelID(modelID) {
8268
+ return modelID.replace(/\.(\d+)/g, "-$1");
8269
+ }
8270
+ function resolveProvider(providerID, modelID) {
8271
+ if (providerID === "github-copilot") {
8272
+ const modelLower = modelID.toLowerCase();
8273
+ if (modelLower.includes("claude"))
8274
+ return "anthropic";
8275
+ if (modelLower.includes("gemini"))
8276
+ return "google";
8277
+ if (modelLower.includes("gpt") || modelLower.includes("o1") || modelLower.includes("o3")) {
8278
+ return "openai";
8279
+ }
8280
+ }
8281
+ return providerID;
8282
+ }
8266
8283
  var HIGH_VARIANT_MAP = {
8267
8284
  "claude-sonnet-4-5": "claude-sonnet-4-5-high",
8268
8285
  "claude-opus-4-5": "claude-opus-4-5-high",
8269
8286
  "gemini-3-pro": "gemini-3-pro-high",
8270
8287
  "gemini-3-pro-low": "gemini-3-pro-high",
8288
+ "gemini-3-pro-preview": "gemini-3-pro-preview-high",
8289
+ "gemini-3-flash": "gemini-3-flash-high",
8290
+ "gemini-3-flash-preview": "gemini-3-flash-preview-high",
8271
8291
  "gpt-5": "gpt-5-high",
8272
8292
  "gpt-5-mini": "gpt-5-mini-high",
8273
8293
  "gpt-5-nano": "gpt-5-nano-high",
8274
8294
  "gpt-5-pro": "gpt-5-pro-high",
8275
8295
  "gpt-5-chat-latest": "gpt-5-chat-latest-high",
8276
- "gpt-5.1": "gpt-5.1-high",
8277
- "gpt-5.1-chat-latest": "gpt-5.1-chat-latest-high",
8278
- "gpt-5.1-codex": "gpt-5.1-codex-high",
8279
- "gpt-5.1-codex-mini": "gpt-5.1-codex-mini-high",
8280
- "gpt-5.1-codex-max": "gpt-5.1-codex-max-high",
8281
- "gpt-5.2": "gpt-5.2-high",
8282
- "gpt-5.2-chat-latest": "gpt-5.2-chat-latest-high",
8283
- "gpt-5.2-pro": "gpt-5.2-pro-high"
8296
+ "gpt-5-1": "gpt-5-1-high",
8297
+ "gpt-5-1-chat-latest": "gpt-5-1-chat-latest-high",
8298
+ "gpt-5-1-codex": "gpt-5-1-codex-high",
8299
+ "gpt-5-1-codex-mini": "gpt-5-1-codex-mini-high",
8300
+ "gpt-5-1-codex-max": "gpt-5-1-codex-max-high",
8301
+ "gpt-5-2": "gpt-5-2-high",
8302
+ "gpt-5-2-chat-latest": "gpt-5-2-chat-latest-high",
8303
+ "gpt-5-2-pro": "gpt-5-2-pro-high"
8284
8304
  };
8285
- var ALREADY_HIGH = new Set([
8286
- "claude-sonnet-4-5-high",
8287
- "claude-opus-4-5-high",
8288
- "gemini-3-pro-high",
8289
- "gpt-5-high",
8290
- "gpt-5-mini-high",
8291
- "gpt-5-nano-high",
8292
- "gpt-5-pro-high",
8293
- "gpt-5-chat-latest-high",
8294
- "gpt-5.1-high",
8295
- "gpt-5.1-chat-latest-high",
8296
- "gpt-5.1-codex-high",
8297
- "gpt-5.1-codex-mini-high",
8298
- "gpt-5.1-codex-max-high",
8299
- "gpt-5.2-high",
8300
- "gpt-5.2-chat-latest-high",
8301
- "gpt-5.2-pro-high"
8302
- ]);
8305
+ var ALREADY_HIGH = new Set(Object.values(HIGH_VARIANT_MAP));
8303
8306
  var THINKING_CONFIGS = {
8304
8307
  anthropic: {
8305
8308
  thinking: {
@@ -8332,33 +8335,44 @@ var THINKING_CONFIGS = {
8332
8335
  }
8333
8336
  }
8334
8337
  }
8338
+ },
8339
+ openai: {
8340
+ reasoning_effort: "high"
8335
8341
  }
8336
8342
  };
8337
8343
  var THINKING_CAPABLE_MODELS = {
8338
8344
  anthropic: ["claude-sonnet-4", "claude-opus-4", "claude-3"],
8339
8345
  "amazon-bedrock": ["claude", "anthropic"],
8340
8346
  google: ["gemini-2", "gemini-3"],
8341
- "google-vertex": ["gemini-2", "gemini-3"]
8347
+ "google-vertex": ["gemini-2", "gemini-3"],
8348
+ openai: ["gpt-5", "o1", "o3"]
8342
8349
  };
8343
8350
  function getHighVariant(modelID) {
8344
- if (ALREADY_HIGH.has(modelID)) {
8351
+ const normalized = normalizeModelID(modelID);
8352
+ if (ALREADY_HIGH.has(normalized)) {
8345
8353
  return null;
8346
8354
  }
8347
- return HIGH_VARIANT_MAP[modelID] ?? null;
8355
+ return HIGH_VARIANT_MAP[normalized] ?? null;
8348
8356
  }
8349
8357
  function isAlreadyHighVariant(modelID) {
8350
- return ALREADY_HIGH.has(modelID) || modelID.endsWith("-high");
8358
+ const normalized = normalizeModelID(modelID);
8359
+ return ALREADY_HIGH.has(normalized) || normalized.endsWith("-high");
8360
+ }
8361
+ function isThinkingProvider(provider) {
8362
+ return provider in THINKING_CONFIGS;
8351
8363
  }
8352
8364
  function getThinkingConfig(providerID, modelID) {
8353
- if (isAlreadyHighVariant(modelID)) {
8365
+ const normalized = normalizeModelID(modelID);
8366
+ if (isAlreadyHighVariant(normalized)) {
8354
8367
  return null;
8355
8368
  }
8356
- const config = THINKING_CONFIGS[providerID];
8357
- const capablePatterns = THINKING_CAPABLE_MODELS[providerID];
8358
- if (!config || !capablePatterns) {
8369
+ const resolvedProvider = resolveProvider(providerID, modelID);
8370
+ if (!isThinkingProvider(resolvedProvider)) {
8359
8371
  return null;
8360
8372
  }
8361
- const modelLower = modelID.toLowerCase();
8373
+ const config = THINKING_CONFIGS[resolvedProvider];
8374
+ const capablePatterns = THINKING_CAPABLE_MODELS[resolvedProvider];
8375
+ const modelLower = normalized.toLowerCase();
8362
8376
  const isCapable = capablePatterns.some((pattern) => modelLower.includes(pattern.toLowerCase()));
8363
8377
  return isCapable ? config : null;
8364
8378
  }
@@ -11125,6 +11139,297 @@ function createThinkingBlockValidatorHook() {
11125
11139
  }
11126
11140
  };
11127
11141
  }
11142
+ // src/hooks/ralph-loop/index.ts
11143
+ import { existsSync as existsSync31, readFileSync as readFileSync21 } from "fs";
11144
+
11145
+ // src/hooks/ralph-loop/storage.ts
11146
+ import { existsSync as existsSync30, readFileSync as readFileSync20, writeFileSync as writeFileSync13, unlinkSync as unlinkSync9, mkdirSync as mkdirSync10 } from "fs";
11147
+ import { dirname as dirname6, join as join40 } from "path";
11148
+
11149
+ // src/hooks/ralph-loop/constants.ts
11150
+ var HOOK_NAME3 = "ralph-loop";
11151
+ var DEFAULT_STATE_FILE = ".sisyphus/ralph-loop.local.md";
11152
+ var DEFAULT_MAX_ITERATIONS = 100;
11153
+ var DEFAULT_COMPLETION_PROMISE = "DONE";
11154
+
11155
+ // src/hooks/ralph-loop/storage.ts
11156
+ function getStateFilePath(directory, customPath) {
11157
+ return customPath ? join40(directory, customPath) : join40(directory, DEFAULT_STATE_FILE);
11158
+ }
11159
+ function readState(directory, customPath) {
11160
+ const filePath = getStateFilePath(directory, customPath);
11161
+ if (!existsSync30(filePath)) {
11162
+ return null;
11163
+ }
11164
+ try {
11165
+ const content = readFileSync20(filePath, "utf-8");
11166
+ const { data, body } = parseFrontmatter(content);
11167
+ const active = data.active;
11168
+ const iteration = data.iteration;
11169
+ if (active === undefined || iteration === undefined) {
11170
+ return null;
11171
+ }
11172
+ const isActive = active === true || active === "true";
11173
+ const iterationNum = typeof iteration === "number" ? iteration : Number(iteration);
11174
+ if (isNaN(iterationNum)) {
11175
+ return null;
11176
+ }
11177
+ const stripQuotes = (val) => {
11178
+ const str = String(val ?? "");
11179
+ return str.replace(/^["']|["']$/g, "");
11180
+ };
11181
+ return {
11182
+ active: isActive,
11183
+ iteration: iterationNum,
11184
+ max_iterations: Number(data.max_iterations) || DEFAULT_MAX_ITERATIONS,
11185
+ completion_promise: stripQuotes(data.completion_promise) || DEFAULT_COMPLETION_PROMISE,
11186
+ started_at: stripQuotes(data.started_at) || new Date().toISOString(),
11187
+ prompt: body.trim(),
11188
+ session_id: data.session_id ? stripQuotes(data.session_id) : undefined
11189
+ };
11190
+ } catch {
11191
+ return null;
11192
+ }
11193
+ }
11194
+ function writeState(directory, state2, customPath) {
11195
+ const filePath = getStateFilePath(directory, customPath);
11196
+ try {
11197
+ const dir = dirname6(filePath);
11198
+ if (!existsSync30(dir)) {
11199
+ mkdirSync10(dir, { recursive: true });
11200
+ }
11201
+ const sessionIdLine = state2.session_id ? `session_id: "${state2.session_id}"
11202
+ ` : "";
11203
+ const content = `---
11204
+ active: ${state2.active}
11205
+ iteration: ${state2.iteration}
11206
+ max_iterations: ${state2.max_iterations}
11207
+ completion_promise: "${state2.completion_promise}"
11208
+ started_at: "${state2.started_at}"
11209
+ ${sessionIdLine}---
11210
+ ${state2.prompt}
11211
+ `;
11212
+ writeFileSync13(filePath, content, "utf-8");
11213
+ return true;
11214
+ } catch {
11215
+ return false;
11216
+ }
11217
+ }
11218
+ function clearState(directory, customPath) {
11219
+ const filePath = getStateFilePath(directory, customPath);
11220
+ try {
11221
+ if (existsSync30(filePath)) {
11222
+ unlinkSync9(filePath);
11223
+ }
11224
+ return true;
11225
+ } catch {
11226
+ return false;
11227
+ }
11228
+ }
11229
+ function incrementIteration(directory, customPath) {
11230
+ const state2 = readState(directory, customPath);
11231
+ if (!state2)
11232
+ return null;
11233
+ state2.iteration += 1;
11234
+ if (writeState(directory, state2, customPath)) {
11235
+ return state2;
11236
+ }
11237
+ return null;
11238
+ }
11239
+
11240
+ // src/hooks/ralph-loop/index.ts
11241
+ var CONTINUATION_PROMPT2 = `[RALPH LOOP - ITERATION {{ITERATION}}/{{MAX}}]
11242
+
11243
+ Your previous attempt did not output the completion promise. Continue working on the task.
11244
+
11245
+ IMPORTANT:
11246
+ - Review your progress so far
11247
+ - Continue from where you left off
11248
+ - When FULLY complete, output: <promise>{{PROMISE}}</promise>
11249
+ - Do not stop until the task is truly done
11250
+
11251
+ Original task:
11252
+ {{PROMPT}}`;
11253
+ function createRalphLoopHook(ctx, options) {
11254
+ const sessions = new Map;
11255
+ const config = options?.config;
11256
+ const stateDir = config?.state_dir;
11257
+ function getSessionState(sessionID) {
11258
+ let state2 = sessions.get(sessionID);
11259
+ if (!state2) {
11260
+ state2 = {};
11261
+ sessions.set(sessionID, state2);
11262
+ }
11263
+ return state2;
11264
+ }
11265
+ function detectCompletionPromise(transcriptPath, promise) {
11266
+ if (!transcriptPath)
11267
+ return false;
11268
+ try {
11269
+ if (!existsSync31(transcriptPath))
11270
+ return false;
11271
+ const content = readFileSync21(transcriptPath, "utf-8");
11272
+ const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
11273
+ return pattern.test(content);
11274
+ } catch {
11275
+ return false;
11276
+ }
11277
+ }
11278
+ function escapeRegex(str) {
11279
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11280
+ }
11281
+ const startLoop = (sessionID, prompt, loopOptions) => {
11282
+ const state2 = {
11283
+ active: true,
11284
+ iteration: 1,
11285
+ max_iterations: loopOptions?.maxIterations ?? config?.default_max_iterations ?? DEFAULT_MAX_ITERATIONS,
11286
+ completion_promise: loopOptions?.completionPromise ?? DEFAULT_COMPLETION_PROMISE,
11287
+ started_at: new Date().toISOString(),
11288
+ prompt,
11289
+ session_id: sessionID
11290
+ };
11291
+ const success = writeState(ctx.directory, state2, stateDir);
11292
+ if (success) {
11293
+ log(`[${HOOK_NAME3}] Loop started`, {
11294
+ sessionID,
11295
+ maxIterations: state2.max_iterations,
11296
+ completionPromise: state2.completion_promise
11297
+ });
11298
+ }
11299
+ return success;
11300
+ };
11301
+ const cancelLoop = (sessionID) => {
11302
+ const state2 = readState(ctx.directory, stateDir);
11303
+ if (!state2 || state2.session_id !== sessionID) {
11304
+ return false;
11305
+ }
11306
+ const success = clearState(ctx.directory, stateDir);
11307
+ if (success) {
11308
+ log(`[${HOOK_NAME3}] Loop cancelled`, { sessionID, iteration: state2.iteration });
11309
+ }
11310
+ return success;
11311
+ };
11312
+ const getState = () => {
11313
+ return readState(ctx.directory, stateDir);
11314
+ };
11315
+ const event = async ({
11316
+ event: event2
11317
+ }) => {
11318
+ const props = event2.properties;
11319
+ if (event2.type === "session.idle") {
11320
+ const sessionID = props?.sessionID;
11321
+ if (!sessionID)
11322
+ return;
11323
+ const sessionState = getSessionState(sessionID);
11324
+ if (sessionState.isRecovering) {
11325
+ log(`[${HOOK_NAME3}] Skipped: in recovery`, { sessionID });
11326
+ return;
11327
+ }
11328
+ const state2 = readState(ctx.directory, stateDir);
11329
+ if (!state2 || !state2.active) {
11330
+ return;
11331
+ }
11332
+ if (state2.session_id && state2.session_id !== sessionID) {
11333
+ return;
11334
+ }
11335
+ const transcriptPath = props?.transcriptPath;
11336
+ if (detectCompletionPromise(transcriptPath, state2.completion_promise)) {
11337
+ log(`[${HOOK_NAME3}] Completion detected!`, {
11338
+ sessionID,
11339
+ iteration: state2.iteration,
11340
+ promise: state2.completion_promise
11341
+ });
11342
+ clearState(ctx.directory, stateDir);
11343
+ await ctx.client.tui.showToast({
11344
+ body: {
11345
+ title: "Ralph Loop Complete!",
11346
+ message: `Task completed after ${state2.iteration} iteration(s)`,
11347
+ variant: "success",
11348
+ duration: 5000
11349
+ }
11350
+ }).catch(() => {});
11351
+ return;
11352
+ }
11353
+ if (state2.iteration >= state2.max_iterations) {
11354
+ log(`[${HOOK_NAME3}] Max iterations reached`, {
11355
+ sessionID,
11356
+ iteration: state2.iteration,
11357
+ max: state2.max_iterations
11358
+ });
11359
+ clearState(ctx.directory, stateDir);
11360
+ await ctx.client.tui.showToast({
11361
+ body: {
11362
+ title: "Ralph Loop Stopped",
11363
+ message: `Max iterations (${state2.max_iterations}) reached without completion`,
11364
+ variant: "warning",
11365
+ duration: 5000
11366
+ }
11367
+ }).catch(() => {});
11368
+ return;
11369
+ }
11370
+ const newState = incrementIteration(ctx.directory, stateDir);
11371
+ if (!newState) {
11372
+ log(`[${HOOK_NAME3}] Failed to increment iteration`, { sessionID });
11373
+ return;
11374
+ }
11375
+ log(`[${HOOK_NAME3}] Continuing loop`, {
11376
+ sessionID,
11377
+ iteration: newState.iteration,
11378
+ max: newState.max_iterations
11379
+ });
11380
+ const continuationPrompt = CONTINUATION_PROMPT2.replace("{{ITERATION}}", String(newState.iteration)).replace("{{MAX}}", String(newState.max_iterations)).replace("{{PROMISE}}", newState.completion_promise).replace("{{PROMPT}}", newState.prompt);
11381
+ await ctx.client.tui.showToast({
11382
+ body: {
11383
+ title: "Ralph Loop",
11384
+ message: `Iteration ${newState.iteration}/${newState.max_iterations}`,
11385
+ variant: "info",
11386
+ duration: 2000
11387
+ }
11388
+ }).catch(() => {});
11389
+ try {
11390
+ await ctx.client.session.prompt({
11391
+ path: { id: sessionID },
11392
+ body: {
11393
+ parts: [{ type: "text", text: continuationPrompt }]
11394
+ },
11395
+ query: { directory: ctx.directory }
11396
+ });
11397
+ } catch (err) {
11398
+ log(`[${HOOK_NAME3}] Failed to inject continuation`, {
11399
+ sessionID,
11400
+ error: String(err)
11401
+ });
11402
+ }
11403
+ }
11404
+ if (event2.type === "session.deleted") {
11405
+ const sessionInfo = props?.info;
11406
+ if (sessionInfo?.id) {
11407
+ const state2 = readState(ctx.directory, stateDir);
11408
+ if (state2?.session_id === sessionInfo.id) {
11409
+ clearState(ctx.directory, stateDir);
11410
+ log(`[${HOOK_NAME3}] Session deleted, loop cleared`, { sessionID: sessionInfo.id });
11411
+ }
11412
+ sessions.delete(sessionInfo.id);
11413
+ }
11414
+ }
11415
+ if (event2.type === "session.error") {
11416
+ const sessionID = props?.sessionID;
11417
+ if (sessionID) {
11418
+ const sessionState = getSessionState(sessionID);
11419
+ sessionState.isRecovering = true;
11420
+ setTimeout(() => {
11421
+ sessionState.isRecovering = false;
11422
+ }, 5000);
11423
+ }
11424
+ }
11425
+ };
11426
+ return {
11427
+ event,
11428
+ startLoop,
11429
+ cancelLoop,
11430
+ getState
11431
+ };
11432
+ }
11128
11433
  // src/auth/antigravity/constants.ts
11129
11434
  var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
11130
11435
  var ANTIGRAVITY_CLIENT_SECRET = "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";
@@ -12955,10 +13260,10 @@ async function createGoogleAntigravityAuthPlugin({
12955
13260
  };
12956
13261
  }
12957
13262
  // src/features/claude-code-command-loader/loader.ts
12958
- import { existsSync as existsSync30, readdirSync as readdirSync11, readFileSync as readFileSync20 } from "fs";
12959
- import { join as join40, basename } from "path";
13263
+ import { existsSync as existsSync32, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
13264
+ import { join as join41, basename } from "path";
12960
13265
  function loadCommandsFromDir(commandsDir, scope) {
12961
- if (!existsSync30(commandsDir)) {
13266
+ if (!existsSync32(commandsDir)) {
12962
13267
  return [];
12963
13268
  }
12964
13269
  const entries = readdirSync11(commandsDir, { withFileTypes: true });
@@ -12966,10 +13271,10 @@ function loadCommandsFromDir(commandsDir, scope) {
12966
13271
  for (const entry of entries) {
12967
13272
  if (!isMarkdownFile(entry))
12968
13273
  continue;
12969
- const commandPath = join40(commandsDir, entry.name);
13274
+ const commandPath = join41(commandsDir, entry.name);
12970
13275
  const commandName = basename(entry.name, ".md");
12971
13276
  try {
12972
- const content = readFileSync20(commandPath, "utf-8");
13277
+ const content = readFileSync22(commandPath, "utf-8");
12973
13278
  const { data, body } = parseFrontmatter(content);
12974
13279
  const wrappedTemplate = `<command-instruction>
12975
13280
  ${body.trim()}
@@ -13009,23 +13314,23 @@ function commandsToRecord(commands) {
13009
13314
  return result;
13010
13315
  }
13011
13316
  function loadUserCommands() {
13012
- const userCommandsDir = join40(getClaudeConfigDir(), "commands");
13317
+ const userCommandsDir = join41(getClaudeConfigDir(), "commands");
13013
13318
  const commands = loadCommandsFromDir(userCommandsDir, "user");
13014
13319
  return commandsToRecord(commands);
13015
13320
  }
13016
13321
  function loadProjectCommands() {
13017
- const projectCommandsDir = join40(process.cwd(), ".claude", "commands");
13322
+ const projectCommandsDir = join41(process.cwd(), ".claude", "commands");
13018
13323
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
13019
13324
  return commandsToRecord(commands);
13020
13325
  }
13021
13326
  function loadOpencodeGlobalCommands() {
13022
13327
  const { homedir: homedir9 } = __require("os");
13023
- const opencodeCommandsDir = join40(homedir9(), ".config", "opencode", "command");
13328
+ const opencodeCommandsDir = join41(homedir9(), ".config", "opencode", "command");
13024
13329
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
13025
13330
  return commandsToRecord(commands);
13026
13331
  }
13027
13332
  function loadOpencodeProjectCommands() {
13028
- const opencodeProjectDir = join40(process.cwd(), ".opencode", "command");
13333
+ const opencodeProjectDir = join41(process.cwd(), ".opencode", "command");
13029
13334
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
13030
13335
  return commandsToRecord(commands);
13031
13336
  }
@@ -13425,6 +13730,45 @@ Hierarchy:
13425
13730
  - **LSP without fallback**: Always have explore agent backup if LSP unavailable
13426
13731
  - **Over-referencing**: Don't trace refs for EVERY symbol - focus on exports only`;
13427
13732
 
13733
+ // src/features/builtin-commands/templates/ralph-loop.ts
13734
+ var RALPH_LOOP_TEMPLATE = `You are starting a Ralph Loop - a self-referential development loop that runs until task completion.
13735
+
13736
+ ## How Ralph Loop Works
13737
+
13738
+ 1. You will work on the task continuously
13739
+ 2. When you believe the task is FULLY complete, output: \`<promise>{{COMPLETION_PROMISE}}</promise>\`
13740
+ 3. If you don't output the promise, the loop will automatically inject another prompt to continue
13741
+ 4. Maximum iterations: Configurable (default 100)
13742
+
13743
+ ## Rules
13744
+
13745
+ - Focus on completing the task fully, not partially
13746
+ - Don't output the completion promise until the task is truly done
13747
+ - Each iteration should make meaningful progress toward the goal
13748
+ - If stuck, try different approaches
13749
+ - Use todos to track your progress
13750
+
13751
+ ## Exit Conditions
13752
+
13753
+ 1. **Completion**: Output \`<promise>DONE</promise>\` (or custom promise text) when fully complete
13754
+ 2. **Max Iterations**: Loop stops automatically at limit
13755
+ 3. **Cancel**: User runs \`/cancel-ralph\` command
13756
+
13757
+ ## Your Task
13758
+
13759
+ Parse the arguments below and begin working on the task. The format is:
13760
+ \`"task description" [--completion-promise=TEXT] [--max-iterations=N]\`
13761
+
13762
+ Default completion promise is "DONE" and default max iterations is 100.`;
13763
+ var CANCEL_RALPH_TEMPLATE = `Cancel the currently active Ralph Loop.
13764
+
13765
+ This will:
13766
+ 1. Stop the loop from continuing
13767
+ 2. Clear the loop state file
13768
+ 3. Allow the session to end normally
13769
+
13770
+ Check if a loop is active and cancel it. Inform the user of the result.`;
13771
+
13428
13772
  // src/features/builtin-commands/commands.ts
13429
13773
  var BUILTIN_COMMAND_DEFINITIONS = {
13430
13774
  "init-deep": {
@@ -13437,6 +13781,23 @@ ${INIT_DEEP_TEMPLATE}
13437
13781
  $ARGUMENTS
13438
13782
  </user-request>`,
13439
13783
  argumentHint: "[--create-new] [--max-depth=N]"
13784
+ },
13785
+ "ralph-loop": {
13786
+ description: "(builtin) Start self-referential development loop until completion",
13787
+ template: `<command-instruction>
13788
+ ${RALPH_LOOP_TEMPLATE}
13789
+ </command-instruction>
13790
+
13791
+ <user-task>
13792
+ $ARGUMENTS
13793
+ </user-task>`,
13794
+ argumentHint: '"task description" [--completion-promise=TEXT] [--max-iterations=N]'
13795
+ },
13796
+ "cancel-ralph": {
13797
+ description: "(builtin) Cancel active Ralph Loop",
13798
+ template: `<command-instruction>
13799
+ ${CANCEL_RALPH_TEMPLATE}
13800
+ </command-instruction>`
13440
13801
  }
13441
13802
  };
13442
13803
  function loadBuiltinCommands(disabledCommands) {
@@ -13452,9 +13813,373 @@ function loadBuiltinCommands(disabledCommands) {
13452
13813
  }
13453
13814
  return commands;
13454
13815
  }
13816
+ // src/features/opencode-skill-loader/loader.ts
13817
+ import { existsSync as existsSync33, readdirSync as readdirSync12, readFileSync as readFileSync23 } from "fs";
13818
+ import { join as join42, basename as basename2 } from "path";
13819
+ import { homedir as homedir9 } from "os";
13820
+ function parseAllowedTools(allowedTools) {
13821
+ if (!allowedTools)
13822
+ return;
13823
+ return allowedTools.split(/\s+/).filter(Boolean);
13824
+ }
13825
+ function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
13826
+ try {
13827
+ const content = readFileSync23(skillPath, "utf-8");
13828
+ const { data, body } = parseFrontmatter(content);
13829
+ const skillName = data.name || defaultName;
13830
+ const originalDescription = data.description || "";
13831
+ const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
13832
+ const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
13833
+ const wrappedTemplate = `<skill-instruction>
13834
+ Base directory for this skill: ${resolvedPath}/
13835
+ File references (@path) in this skill are relative to this directory.
13836
+
13837
+ ${body.trim()}
13838
+ </skill-instruction>
13839
+
13840
+ <user-request>
13841
+ $ARGUMENTS
13842
+ </user-request>`;
13843
+ const definition = {
13844
+ name: skillName,
13845
+ description: formattedDescription,
13846
+ template: wrappedTemplate,
13847
+ model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
13848
+ agent: data.agent,
13849
+ subtask: data.subtask,
13850
+ argumentHint: data["argument-hint"]
13851
+ };
13852
+ return {
13853
+ name: skillName,
13854
+ path: skillPath,
13855
+ resolvedPath,
13856
+ definition,
13857
+ scope,
13858
+ license: data.license,
13859
+ compatibility: data.compatibility,
13860
+ metadata: data.metadata,
13861
+ allowedTools: parseAllowedTools(data["allowed-tools"])
13862
+ };
13863
+ } catch {
13864
+ return null;
13865
+ }
13866
+ }
13867
+ function loadSkillsFromDir(skillsDir, scope) {
13868
+ if (!existsSync33(skillsDir)) {
13869
+ return [];
13870
+ }
13871
+ const entries = readdirSync12(skillsDir, { withFileTypes: true });
13872
+ const skills = [];
13873
+ for (const entry of entries) {
13874
+ if (entry.name.startsWith("."))
13875
+ continue;
13876
+ const entryPath = join42(skillsDir, entry.name);
13877
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
13878
+ const resolvedPath = resolveSymlink(entryPath);
13879
+ const dirName = entry.name;
13880
+ const skillMdPath = join42(resolvedPath, "SKILL.md");
13881
+ if (existsSync33(skillMdPath)) {
13882
+ const skill = loadSkillFromPath(skillMdPath, resolvedPath, dirName, scope);
13883
+ if (skill)
13884
+ skills.push(skill);
13885
+ continue;
13886
+ }
13887
+ const namedSkillMdPath = join42(resolvedPath, `${dirName}.md`);
13888
+ if (existsSync33(namedSkillMdPath)) {
13889
+ const skill = loadSkillFromPath(namedSkillMdPath, resolvedPath, dirName, scope);
13890
+ if (skill)
13891
+ skills.push(skill);
13892
+ continue;
13893
+ }
13894
+ continue;
13895
+ }
13896
+ if (isMarkdownFile(entry)) {
13897
+ const skillName = basename2(entry.name, ".md");
13898
+ const skill = loadSkillFromPath(entryPath, skillsDir, skillName, scope);
13899
+ if (skill)
13900
+ skills.push(skill);
13901
+ }
13902
+ }
13903
+ return skills;
13904
+ }
13905
+ function skillsToRecord(skills) {
13906
+ const result = {};
13907
+ for (const skill of skills) {
13908
+ result[skill.name] = skill.definition;
13909
+ }
13910
+ return result;
13911
+ }
13912
+ function loadUserSkills() {
13913
+ const userSkillsDir = join42(getClaudeConfigDir(), "skills");
13914
+ const skills = loadSkillsFromDir(userSkillsDir, "user");
13915
+ return skillsToRecord(skills);
13916
+ }
13917
+ function loadProjectSkills() {
13918
+ const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
13919
+ const skills = loadSkillsFromDir(projectSkillsDir, "project");
13920
+ return skillsToRecord(skills);
13921
+ }
13922
+ function loadOpencodeGlobalSkills() {
13923
+ const opencodeSkillsDir = join42(homedir9(), ".config", "opencode", "skill");
13924
+ const skills = loadSkillsFromDir(opencodeSkillsDir, "opencode");
13925
+ return skillsToRecord(skills);
13926
+ }
13927
+ function loadOpencodeProjectSkills() {
13928
+ const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
13929
+ const skills = loadSkillsFromDir(opencodeProjectDir, "opencode-project");
13930
+ return skillsToRecord(skills);
13931
+ }
13932
+ function discoverAllSkills() {
13933
+ const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
13934
+ const projectDir = join42(process.cwd(), ".claude", "skills");
13935
+ const opencodeGlobalDir = join42(homedir9(), ".config", "opencode", "skill");
13936
+ const userDir = join42(getClaudeConfigDir(), "skills");
13937
+ const opencodeProjectSkills = loadSkillsFromDir(opencodeProjectDir, "opencode-project");
13938
+ const projectSkills = loadSkillsFromDir(projectDir, "project");
13939
+ const opencodeGlobalSkills = loadSkillsFromDir(opencodeGlobalDir, "opencode");
13940
+ const userSkills = loadSkillsFromDir(userDir, "user");
13941
+ return [...opencodeProjectSkills, ...projectSkills, ...opencodeGlobalSkills, ...userSkills];
13942
+ }
13943
+ function discoverSkills(options = {}) {
13944
+ const { includeClaudeCodePaths = true } = options;
13945
+ const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
13946
+ const opencodeGlobalDir = join42(homedir9(), ".config", "opencode", "skill");
13947
+ const opencodeProjectSkills = loadSkillsFromDir(opencodeProjectDir, "opencode-project");
13948
+ const opencodeGlobalSkills = loadSkillsFromDir(opencodeGlobalDir, "opencode");
13949
+ if (!includeClaudeCodePaths) {
13950
+ return [...opencodeProjectSkills, ...opencodeGlobalSkills];
13951
+ }
13952
+ const projectDir = join42(process.cwd(), ".claude", "skills");
13953
+ const userDir = join42(getClaudeConfigDir(), "skills");
13954
+ const projectSkills = loadSkillsFromDir(projectDir, "project");
13955
+ const userSkills = loadSkillsFromDir(userDir, "user");
13956
+ return [...opencodeProjectSkills, ...projectSkills, ...opencodeGlobalSkills, ...userSkills];
13957
+ }
13958
+ function discoverUserClaudeSkills() {
13959
+ const userSkillsDir = join42(getClaudeConfigDir(), "skills");
13960
+ return loadSkillsFromDir(userSkillsDir, "user");
13961
+ }
13962
+ function discoverProjectClaudeSkills() {
13963
+ const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
13964
+ return loadSkillsFromDir(projectSkillsDir, "project");
13965
+ }
13966
+ function discoverOpencodeGlobalSkills() {
13967
+ const opencodeSkillsDir = join42(homedir9(), ".config", "opencode", "skill");
13968
+ return loadSkillsFromDir(opencodeSkillsDir, "opencode");
13969
+ }
13970
+ function discoverOpencodeProjectSkills() {
13971
+ const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
13972
+ return loadSkillsFromDir(opencodeProjectDir, "opencode-project");
13973
+ }
13974
+ // src/features/opencode-skill-loader/merger.ts
13975
+ import { readFileSync as readFileSync24, existsSync as existsSync34 } from "fs";
13976
+ import { dirname as dirname8, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
13977
+ import { homedir as homedir10 } from "os";
13978
+ var SCOPE_PRIORITY = {
13979
+ builtin: 1,
13980
+ config: 2,
13981
+ user: 3,
13982
+ opencode: 4,
13983
+ project: 5,
13984
+ "opencode-project": 6
13985
+ };
13986
+ function builtinToLoaded(builtin) {
13987
+ const definition = {
13988
+ name: builtin.name,
13989
+ description: `(builtin - Skill) ${builtin.description}`,
13990
+ template: builtin.template,
13991
+ model: builtin.model,
13992
+ agent: builtin.agent,
13993
+ subtask: builtin.subtask,
13994
+ argumentHint: builtin.argumentHint
13995
+ };
13996
+ return {
13997
+ name: builtin.name,
13998
+ definition,
13999
+ scope: "builtin",
14000
+ license: builtin.license,
14001
+ compatibility: builtin.compatibility,
14002
+ metadata: builtin.metadata,
14003
+ allowedTools: builtin.allowedTools
14004
+ };
14005
+ }
14006
+ function resolveFilePath2(from, configDir) {
14007
+ let filePath = from;
14008
+ if (filePath.startsWith("{file:") && filePath.endsWith("}")) {
14009
+ filePath = filePath.slice(6, -1);
14010
+ }
14011
+ if (filePath.startsWith("~/")) {
14012
+ return resolve5(homedir10(), filePath.slice(2));
14013
+ }
14014
+ if (isAbsolute2(filePath)) {
14015
+ return filePath;
14016
+ }
14017
+ const baseDir = configDir || process.cwd();
14018
+ return resolve5(baseDir, filePath);
14019
+ }
14020
+ function loadSkillFromFile(filePath) {
14021
+ try {
14022
+ if (!existsSync34(filePath))
14023
+ return null;
14024
+ const content = readFileSync24(filePath, "utf-8");
14025
+ const { data, body } = parseFrontmatter(content);
14026
+ return { template: body, metadata: data };
14027
+ } catch {
14028
+ return null;
14029
+ }
14030
+ }
14031
+ function configEntryToLoaded(name, entry, configDir) {
14032
+ let template = entry.template || "";
14033
+ let fileMetadata = {};
14034
+ if (entry.from) {
14035
+ const filePath = resolveFilePath2(entry.from, configDir);
14036
+ const loaded = loadSkillFromFile(filePath);
14037
+ if (loaded) {
14038
+ template = loaded.template;
14039
+ fileMetadata = loaded.metadata;
14040
+ } else {
14041
+ return null;
14042
+ }
14043
+ }
14044
+ if (!template && !entry.from) {
14045
+ return null;
14046
+ }
14047
+ const description = entry.description || fileMetadata.description || "";
14048
+ const resolvedPath = entry.from ? dirname8(resolveFilePath2(entry.from, configDir)) : configDir || process.cwd();
14049
+ const wrappedTemplate = `<skill-instruction>
14050
+ Base directory for this skill: ${resolvedPath}/
14051
+ File references (@path) in this skill are relative to this directory.
14052
+
14053
+ ${template.trim()}
14054
+ </skill-instruction>
14055
+
14056
+ <user-request>
14057
+ $ARGUMENTS
14058
+ </user-request>`;
14059
+ const definition = {
14060
+ name,
14061
+ description: `(config - Skill) ${description}`,
14062
+ template: wrappedTemplate,
14063
+ model: sanitizeModelField(entry.model || fileMetadata.model, "opencode"),
14064
+ agent: entry.agent || fileMetadata.agent,
14065
+ subtask: entry.subtask ?? fileMetadata.subtask,
14066
+ argumentHint: entry["argument-hint"] || fileMetadata["argument-hint"]
14067
+ };
14068
+ const allowedTools = entry["allowed-tools"] || (fileMetadata["allowed-tools"] ? fileMetadata["allowed-tools"].split(/\s+/).filter(Boolean) : undefined);
14069
+ return {
14070
+ name,
14071
+ path: entry.from ? resolveFilePath2(entry.from, configDir) : undefined,
14072
+ resolvedPath,
14073
+ definition,
14074
+ scope: "config",
14075
+ license: entry.license || fileMetadata.license,
14076
+ compatibility: entry.compatibility || fileMetadata.compatibility,
14077
+ metadata: entry.metadata || fileMetadata.metadata,
14078
+ allowedTools
14079
+ };
14080
+ }
14081
+ function normalizeConfig(config) {
14082
+ if (!config) {
14083
+ return { sources: [], enable: [], disable: [], entries: {} };
14084
+ }
14085
+ if (Array.isArray(config)) {
14086
+ return { sources: [], enable: config, disable: [], entries: {} };
14087
+ }
14088
+ const { sources = [], enable = [], disable = [], ...entries } = config;
14089
+ return { sources, enable, disable, entries };
14090
+ }
14091
+ function mergeSkillDefinitions(base, patch) {
14092
+ const mergedMetadata = base.metadata || patch.metadata ? deepMerge(base.metadata || {}, patch.metadata || {}) : undefined;
14093
+ const mergedTools = base.allowedTools || patch["allowed-tools"] ? [...base.allowedTools || [], ...patch["allowed-tools"] || []] : undefined;
14094
+ const description = patch.description || base.definition.description?.replace(/^\([^)]+\) /, "");
14095
+ return {
14096
+ ...base,
14097
+ definition: {
14098
+ ...base.definition,
14099
+ description: `(${base.scope} - Skill) ${description}`,
14100
+ model: patch.model || base.definition.model,
14101
+ agent: patch.agent || base.definition.agent,
14102
+ subtask: patch.subtask ?? base.definition.subtask,
14103
+ argumentHint: patch["argument-hint"] || base.definition.argumentHint
14104
+ },
14105
+ license: patch.license || base.license,
14106
+ compatibility: patch.compatibility || base.compatibility,
14107
+ metadata: mergedMetadata,
14108
+ allowedTools: mergedTools ? [...new Set(mergedTools)] : undefined
14109
+ };
14110
+ }
14111
+ function mergeSkills(builtinSkills, config, userClaudeSkills, userOpencodeSkills, projectClaudeSkills, projectOpencodeSkills, options = {}) {
14112
+ const skillMap = new Map;
14113
+ for (const builtin of builtinSkills) {
14114
+ const loaded = builtinToLoaded(builtin);
14115
+ skillMap.set(loaded.name, loaded);
14116
+ }
14117
+ const normalizedConfig = normalizeConfig(config);
14118
+ for (const [name, entry] of Object.entries(normalizedConfig.entries)) {
14119
+ if (entry === false)
14120
+ continue;
14121
+ if (entry === true)
14122
+ continue;
14123
+ if (entry.disable)
14124
+ continue;
14125
+ const loaded = configEntryToLoaded(name, entry, options.configDir);
14126
+ if (loaded) {
14127
+ const existing = skillMap.get(name);
14128
+ if (existing && !entry.template && !entry.from) {
14129
+ skillMap.set(name, mergeSkillDefinitions(existing, entry));
14130
+ } else {
14131
+ skillMap.set(name, loaded);
14132
+ }
14133
+ }
14134
+ }
14135
+ const fileSystemSkills = [
14136
+ ...userClaudeSkills,
14137
+ ...userOpencodeSkills,
14138
+ ...projectClaudeSkills,
14139
+ ...projectOpencodeSkills
14140
+ ];
14141
+ for (const skill of fileSystemSkills) {
14142
+ const existing = skillMap.get(skill.name);
14143
+ if (!existing || SCOPE_PRIORITY[skill.scope] > SCOPE_PRIORITY[existing.scope]) {
14144
+ skillMap.set(skill.name, skill);
14145
+ }
14146
+ }
14147
+ for (const [name, entry] of Object.entries(normalizedConfig.entries)) {
14148
+ if (entry === true)
14149
+ continue;
14150
+ if (entry === false) {
14151
+ skillMap.delete(name);
14152
+ continue;
14153
+ }
14154
+ if (entry.disable) {
14155
+ skillMap.delete(name);
14156
+ continue;
14157
+ }
14158
+ const existing = skillMap.get(name);
14159
+ if (existing && !entry.template && !entry.from) {
14160
+ skillMap.set(name, mergeSkillDefinitions(existing, entry));
14161
+ }
14162
+ }
14163
+ for (const name of normalizedConfig.disable) {
14164
+ skillMap.delete(name);
14165
+ }
14166
+ if (normalizedConfig.enable.length > 0) {
14167
+ const enableSet = new Set(normalizedConfig.enable);
14168
+ for (const name of skillMap.keys()) {
14169
+ if (!enableSet.has(name)) {
14170
+ skillMap.delete(name);
14171
+ }
14172
+ }
14173
+ }
14174
+ return Array.from(skillMap.values());
14175
+ }
14176
+ // src/features/builtin-skills/skills.ts
14177
+ function createBuiltinSkills() {
14178
+ return [];
14179
+ }
13455
14180
  // src/features/claude-code-agent-loader/loader.ts
13456
- import { existsSync as existsSync31, readdirSync as readdirSync12, readFileSync as readFileSync21 } from "fs";
13457
- import { join as join41, basename as basename2 } from "path";
14181
+ import { existsSync as existsSync35, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
14182
+ import { join as join43, basename as basename3 } from "path";
13458
14183
  function parseToolsConfig(toolsStr) {
13459
14184
  if (!toolsStr)
13460
14185
  return;
@@ -13468,18 +14193,18 @@ function parseToolsConfig(toolsStr) {
13468
14193
  return result;
13469
14194
  }
13470
14195
  function loadAgentsFromDir(agentsDir, scope) {
13471
- if (!existsSync31(agentsDir)) {
14196
+ if (!existsSync35(agentsDir)) {
13472
14197
  return [];
13473
14198
  }
13474
- const entries = readdirSync12(agentsDir, { withFileTypes: true });
14199
+ const entries = readdirSync13(agentsDir, { withFileTypes: true });
13475
14200
  const agents = [];
13476
14201
  for (const entry of entries) {
13477
14202
  if (!isMarkdownFile(entry))
13478
14203
  continue;
13479
- const agentPath = join41(agentsDir, entry.name);
13480
- const agentName = basename2(entry.name, ".md");
14204
+ const agentPath = join43(agentsDir, entry.name);
14205
+ const agentName = basename3(entry.name, ".md");
13481
14206
  try {
13482
- const content = readFileSync21(agentPath, "utf-8");
14207
+ const content = readFileSync25(agentPath, "utf-8");
13483
14208
  const { data, body } = parseFrontmatter(content);
13484
14209
  const name = data.name || agentName;
13485
14210
  const originalDescription = data.description || "";
@@ -13506,7 +14231,7 @@ function loadAgentsFromDir(agentsDir, scope) {
13506
14231
  return agents;
13507
14232
  }
13508
14233
  function loadUserAgents() {
13509
- const userAgentsDir = join41(getClaudeConfigDir(), "agents");
14234
+ const userAgentsDir = join43(getClaudeConfigDir(), "agents");
13510
14235
  const agents = loadAgentsFromDir(userAgentsDir, "user");
13511
14236
  const result = {};
13512
14237
  for (const agent of agents) {
@@ -13515,7 +14240,7 @@ function loadUserAgents() {
13515
14240
  return result;
13516
14241
  }
13517
14242
  function loadProjectAgents() {
13518
- const projectAgentsDir = join41(process.cwd(), ".claude", "agents");
14243
+ const projectAgentsDir = join43(process.cwd(), ".claude", "agents");
13519
14244
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
13520
14245
  const result = {};
13521
14246
  for (const agent of agents) {
@@ -13524,8 +14249,8 @@ function loadProjectAgents() {
13524
14249
  return result;
13525
14250
  }
13526
14251
  // src/features/claude-code-mcp-loader/loader.ts
13527
- import { existsSync as existsSync32 } from "fs";
13528
- import { join as join42 } from "path";
14252
+ import { existsSync as existsSync36 } from "fs";
14253
+ import { join as join44 } from "path";
13529
14254
 
13530
14255
  // src/features/claude-code-mcp-loader/env-expander.ts
13531
14256
  function expandEnvVars(value) {
@@ -13594,13 +14319,13 @@ function getMcpConfigPaths() {
13594
14319
  const claudeConfigDir = getClaudeConfigDir();
13595
14320
  const cwd = process.cwd();
13596
14321
  return [
13597
- { path: join42(claudeConfigDir, ".mcp.json"), scope: "user" },
13598
- { path: join42(cwd, ".mcp.json"), scope: "project" },
13599
- { path: join42(cwd, ".claude", ".mcp.json"), scope: "local" }
14322
+ { path: join44(claudeConfigDir, ".mcp.json"), scope: "user" },
14323
+ { path: join44(cwd, ".mcp.json"), scope: "project" },
14324
+ { path: join44(cwd, ".claude", ".mcp.json"), scope: "local" }
13600
14325
  ];
13601
14326
  }
13602
14327
  async function loadMcpConfigFile(filePath) {
13603
- if (!existsSync32(filePath)) {
14328
+ if (!existsSync36(filePath)) {
13604
14329
  return null;
13605
14330
  }
13606
14331
  try {
@@ -13641,18 +14366,18 @@ async function loadMcpConfigs() {
13641
14366
  return { servers, loadedServers };
13642
14367
  }
13643
14368
  // src/features/claude-code-plugin-loader/loader.ts
13644
- import { existsSync as existsSync33, readdirSync as readdirSync13, readFileSync as readFileSync22 } from "fs";
13645
- import { homedir as homedir9 } from "os";
13646
- import { join as join43, basename as basename3 } from "path";
14369
+ import { existsSync as existsSync37, readdirSync as readdirSync14, readFileSync as readFileSync26 } from "fs";
14370
+ import { homedir as homedir11 } from "os";
14371
+ import { join as join45, basename as basename4 } from "path";
13647
14372
  var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
13648
14373
  function getPluginsBaseDir() {
13649
14374
  if (process.env.CLAUDE_PLUGINS_HOME) {
13650
14375
  return process.env.CLAUDE_PLUGINS_HOME;
13651
14376
  }
13652
- return join43(homedir9(), ".claude", "plugins");
14377
+ return join45(homedir11(), ".claude", "plugins");
13653
14378
  }
13654
14379
  function getInstalledPluginsPath() {
13655
- return join43(getPluginsBaseDir(), "installed_plugins.json");
14380
+ return join45(getPluginsBaseDir(), "installed_plugins.json");
13656
14381
  }
13657
14382
  function resolvePluginPath(path7, pluginRoot) {
13658
14383
  return path7.replace(CLAUDE_PLUGIN_ROOT_VAR, pluginRoot);
@@ -13677,11 +14402,11 @@ function resolvePluginPaths(obj, pluginRoot) {
13677
14402
  }
13678
14403
  function loadInstalledPlugins() {
13679
14404
  const dbPath = getInstalledPluginsPath();
13680
- if (!existsSync33(dbPath)) {
14405
+ if (!existsSync37(dbPath)) {
13681
14406
  return null;
13682
14407
  }
13683
14408
  try {
13684
- const content = readFileSync22(dbPath, "utf-8");
14409
+ const content = readFileSync26(dbPath, "utf-8");
13685
14410
  return JSON.parse(content);
13686
14411
  } catch (error) {
13687
14412
  log("Failed to load installed plugins database", error);
@@ -13692,15 +14417,15 @@ function getClaudeSettingsPath() {
13692
14417
  if (process.env.CLAUDE_SETTINGS_PATH) {
13693
14418
  return process.env.CLAUDE_SETTINGS_PATH;
13694
14419
  }
13695
- return join43(homedir9(), ".claude", "settings.json");
14420
+ return join45(homedir11(), ".claude", "settings.json");
13696
14421
  }
13697
14422
  function loadClaudeSettings() {
13698
14423
  const settingsPath = getClaudeSettingsPath();
13699
- if (!existsSync33(settingsPath)) {
14424
+ if (!existsSync37(settingsPath)) {
13700
14425
  return null;
13701
14426
  }
13702
14427
  try {
13703
- const content = readFileSync22(settingsPath, "utf-8");
14428
+ const content = readFileSync26(settingsPath, "utf-8");
13704
14429
  return JSON.parse(content);
13705
14430
  } catch (error) {
13706
14431
  log("Failed to load Claude settings", error);
@@ -13708,12 +14433,12 @@ function loadClaudeSettings() {
13708
14433
  }
13709
14434
  }
13710
14435
  function loadPluginManifest(installPath) {
13711
- const manifestPath = join43(installPath, ".claude-plugin", "plugin.json");
13712
- if (!existsSync33(manifestPath)) {
14436
+ const manifestPath = join45(installPath, ".claude-plugin", "plugin.json");
14437
+ if (!existsSync37(manifestPath)) {
13713
14438
  return null;
13714
14439
  }
13715
14440
  try {
13716
- const content = readFileSync22(manifestPath, "utf-8");
14441
+ const content = readFileSync26(manifestPath, "utf-8");
13717
14442
  return JSON.parse(content);
13718
14443
  } catch (error) {
13719
14444
  log(`Failed to load plugin manifest from ${manifestPath}`, error);
@@ -13760,7 +14485,7 @@ function discoverInstalledPlugins(options) {
13760
14485
  continue;
13761
14486
  }
13762
14487
  const { installPath, scope, version } = installation;
13763
- if (!existsSync33(installPath)) {
14488
+ if (!existsSync37(installPath)) {
13764
14489
  errors.push({
13765
14490
  pluginKey,
13766
14491
  installPath,
@@ -13778,21 +14503,21 @@ function discoverInstalledPlugins(options) {
13778
14503
  pluginKey,
13779
14504
  manifest: manifest ?? undefined
13780
14505
  };
13781
- if (existsSync33(join43(installPath, "commands"))) {
13782
- loadedPlugin.commandsDir = join43(installPath, "commands");
14506
+ if (existsSync37(join45(installPath, "commands"))) {
14507
+ loadedPlugin.commandsDir = join45(installPath, "commands");
13783
14508
  }
13784
- if (existsSync33(join43(installPath, "agents"))) {
13785
- loadedPlugin.agentsDir = join43(installPath, "agents");
14509
+ if (existsSync37(join45(installPath, "agents"))) {
14510
+ loadedPlugin.agentsDir = join45(installPath, "agents");
13786
14511
  }
13787
- if (existsSync33(join43(installPath, "skills"))) {
13788
- loadedPlugin.skillsDir = join43(installPath, "skills");
14512
+ if (existsSync37(join45(installPath, "skills"))) {
14513
+ loadedPlugin.skillsDir = join45(installPath, "skills");
13789
14514
  }
13790
- const hooksPath = join43(installPath, "hooks", "hooks.json");
13791
- if (existsSync33(hooksPath)) {
14515
+ const hooksPath = join45(installPath, "hooks", "hooks.json");
14516
+ if (existsSync37(hooksPath)) {
13792
14517
  loadedPlugin.hooksPath = hooksPath;
13793
14518
  }
13794
- const mcpPath = join43(installPath, ".mcp.json");
13795
- if (existsSync33(mcpPath)) {
14519
+ const mcpPath = join45(installPath, ".mcp.json");
14520
+ if (existsSync37(mcpPath)) {
13796
14521
  loadedPlugin.mcpPath = mcpPath;
13797
14522
  }
13798
14523
  plugins.push(loadedPlugin);
@@ -13803,17 +14528,17 @@ function discoverInstalledPlugins(options) {
13803
14528
  function loadPluginCommands(plugins) {
13804
14529
  const commands2 = {};
13805
14530
  for (const plugin2 of plugins) {
13806
- if (!plugin2.commandsDir || !existsSync33(plugin2.commandsDir))
14531
+ if (!plugin2.commandsDir || !existsSync37(plugin2.commandsDir))
13807
14532
  continue;
13808
- const entries = readdirSync13(plugin2.commandsDir, { withFileTypes: true });
14533
+ const entries = readdirSync14(plugin2.commandsDir, { withFileTypes: true });
13809
14534
  for (const entry of entries) {
13810
14535
  if (!isMarkdownFile(entry))
13811
14536
  continue;
13812
- const commandPath = join43(plugin2.commandsDir, entry.name);
13813
- const commandName = basename3(entry.name, ".md");
14537
+ const commandPath = join45(plugin2.commandsDir, entry.name);
14538
+ const commandName = basename4(entry.name, ".md");
13814
14539
  const namespacedName = `${plugin2.name}:${commandName}`;
13815
14540
  try {
13816
- const content = readFileSync22(commandPath, "utf-8");
14541
+ const content = readFileSync26(commandPath, "utf-8");
13817
14542
  const { data, body } = parseFrontmatter(content);
13818
14543
  const wrappedTemplate = `<command-instruction>
13819
14544
  ${body.trim()}
@@ -13843,21 +14568,21 @@ $ARGUMENTS
13843
14568
  function loadPluginSkillsAsCommands(plugins) {
13844
14569
  const skills = {};
13845
14570
  for (const plugin2 of plugins) {
13846
- if (!plugin2.skillsDir || !existsSync33(plugin2.skillsDir))
14571
+ if (!plugin2.skillsDir || !existsSync37(plugin2.skillsDir))
13847
14572
  continue;
13848
- const entries = readdirSync13(plugin2.skillsDir, { withFileTypes: true });
14573
+ const entries = readdirSync14(plugin2.skillsDir, { withFileTypes: true });
13849
14574
  for (const entry of entries) {
13850
14575
  if (entry.name.startsWith("."))
13851
14576
  continue;
13852
- const skillPath = join43(plugin2.skillsDir, entry.name);
14577
+ const skillPath = join45(plugin2.skillsDir, entry.name);
13853
14578
  if (!entry.isDirectory() && !entry.isSymbolicLink())
13854
14579
  continue;
13855
14580
  const resolvedPath = resolveSymlink(skillPath);
13856
- const skillMdPath = join43(resolvedPath, "SKILL.md");
13857
- if (!existsSync33(skillMdPath))
14581
+ const skillMdPath = join45(resolvedPath, "SKILL.md");
14582
+ if (!existsSync37(skillMdPath))
13858
14583
  continue;
13859
14584
  try {
13860
- const content = readFileSync22(skillMdPath, "utf-8");
14585
+ const content = readFileSync26(skillMdPath, "utf-8");
13861
14586
  const { data, body } = parseFrontmatter(content);
13862
14587
  const skillName = data.name || entry.name;
13863
14588
  const namespacedName = `${plugin2.name}:${skillName}`;
@@ -13902,17 +14627,17 @@ function parseToolsConfig2(toolsStr) {
13902
14627
  function loadPluginAgents(plugins) {
13903
14628
  const agents = {};
13904
14629
  for (const plugin2 of plugins) {
13905
- if (!plugin2.agentsDir || !existsSync33(plugin2.agentsDir))
14630
+ if (!plugin2.agentsDir || !existsSync37(plugin2.agentsDir))
13906
14631
  continue;
13907
- const entries = readdirSync13(plugin2.agentsDir, { withFileTypes: true });
14632
+ const entries = readdirSync14(plugin2.agentsDir, { withFileTypes: true });
13908
14633
  for (const entry of entries) {
13909
14634
  if (!isMarkdownFile(entry))
13910
14635
  continue;
13911
- const agentPath = join43(plugin2.agentsDir, entry.name);
13912
- const agentName = basename3(entry.name, ".md");
14636
+ const agentPath = join45(plugin2.agentsDir, entry.name);
14637
+ const agentName = basename4(entry.name, ".md");
13913
14638
  const namespacedName = `${plugin2.name}:${agentName}`;
13914
14639
  try {
13915
- const content = readFileSync22(agentPath, "utf-8");
14640
+ const content = readFileSync26(agentPath, "utf-8");
13916
14641
  const { data, body } = parseFrontmatter(content);
13917
14642
  const name = data.name || agentName;
13918
14643
  const originalDescription = data.description || "";
@@ -13938,7 +14663,7 @@ function loadPluginAgents(plugins) {
13938
14663
  async function loadPluginMcpServers(plugins) {
13939
14664
  const servers = {};
13940
14665
  for (const plugin2 of plugins) {
13941
- if (!plugin2.mcpPath || !existsSync33(plugin2.mcpPath))
14666
+ if (!plugin2.mcpPath || !existsSync37(plugin2.mcpPath))
13942
14667
  continue;
13943
14668
  try {
13944
14669
  const content = await Bun.file(plugin2.mcpPath).text();
@@ -13970,10 +14695,10 @@ async function loadPluginMcpServers(plugins) {
13970
14695
  function loadPluginHooksConfigs(plugins) {
13971
14696
  const configs = [];
13972
14697
  for (const plugin2 of plugins) {
13973
- if (!plugin2.hooksPath || !existsSync33(plugin2.hooksPath))
14698
+ if (!plugin2.hooksPath || !existsSync37(plugin2.hooksPath))
13974
14699
  continue;
13975
14700
  try {
13976
- const content = readFileSync22(plugin2.hooksPath, "utf-8");
14701
+ const content = readFileSync26(plugin2.hooksPath, "utf-8");
13977
14702
  let config = JSON.parse(content);
13978
14703
  config = resolvePluginPaths(config, plugin2.installPath);
13979
14704
  configs.push(config);
@@ -14327,14 +15052,14 @@ var EXT_TO_LANG = {
14327
15052
  ".gql": "graphql"
14328
15053
  };
14329
15054
  // src/tools/lsp/config.ts
14330
- import { existsSync as existsSync34, readFileSync as readFileSync23 } from "fs";
14331
- import { join as join44 } from "path";
14332
- import { homedir as homedir10 } from "os";
15055
+ import { existsSync as existsSync38, readFileSync as readFileSync27 } from "fs";
15056
+ import { join as join46 } from "path";
15057
+ import { homedir as homedir12 } from "os";
14333
15058
  function loadJsonFile(path7) {
14334
- if (!existsSync34(path7))
15059
+ if (!existsSync38(path7))
14335
15060
  return null;
14336
15061
  try {
14337
- return JSON.parse(readFileSync23(path7, "utf-8"));
15062
+ return JSON.parse(readFileSync27(path7, "utf-8"));
14338
15063
  } catch {
14339
15064
  return null;
14340
15065
  }
@@ -14342,9 +15067,9 @@ function loadJsonFile(path7) {
14342
15067
  function getConfigPaths2() {
14343
15068
  const cwd = process.cwd();
14344
15069
  return {
14345
- project: join44(cwd, ".opencode", "oh-my-opencode.json"),
14346
- user: join44(homedir10(), ".config", "opencode", "oh-my-opencode.json"),
14347
- opencode: join44(homedir10(), ".config", "opencode", "opencode.json")
15070
+ project: join46(cwd, ".opencode", "oh-my-opencode.json"),
15071
+ user: join46(homedir12(), ".config", "opencode", "oh-my-opencode.json"),
15072
+ opencode: join46(homedir12(), ".config", "opencode", "opencode.json")
14348
15073
  };
14349
15074
  }
14350
15075
  function loadAllConfigs() {
@@ -14457,7 +15182,7 @@ function isServerInstalled(command) {
14457
15182
  return false;
14458
15183
  const cmd = command[0];
14459
15184
  if (cmd.includes("/") || cmd.includes("\\")) {
14460
- if (existsSync34(cmd))
15185
+ if (existsSync38(cmd))
14461
15186
  return true;
14462
15187
  }
14463
15188
  const isWindows2 = process.platform === "win32";
@@ -14466,21 +15191,21 @@ function isServerInstalled(command) {
14466
15191
  const pathSeparator = isWindows2 ? ";" : ":";
14467
15192
  const paths = pathEnv.split(pathSeparator);
14468
15193
  for (const p of paths) {
14469
- if (existsSync34(join44(p, cmd)) || existsSync34(join44(p, cmd + ext))) {
15194
+ if (existsSync38(join46(p, cmd)) || existsSync38(join46(p, cmd + ext))) {
14470
15195
  return true;
14471
15196
  }
14472
15197
  }
14473
15198
  const cwd = process.cwd();
14474
15199
  const additionalPaths = [
14475
- join44(cwd, "node_modules", ".bin", cmd),
14476
- join44(cwd, "node_modules", ".bin", cmd + ext),
14477
- join44(homedir10(), ".config", "opencode", "bin", cmd),
14478
- join44(homedir10(), ".config", "opencode", "bin", cmd + ext),
14479
- join44(homedir10(), ".config", "opencode", "node_modules", ".bin", cmd),
14480
- join44(homedir10(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
15200
+ join46(cwd, "node_modules", ".bin", cmd),
15201
+ join46(cwd, "node_modules", ".bin", cmd + ext),
15202
+ join46(homedir12(), ".config", "opencode", "bin", cmd),
15203
+ join46(homedir12(), ".config", "opencode", "bin", cmd + ext),
15204
+ join46(homedir12(), ".config", "opencode", "node_modules", ".bin", cmd),
15205
+ join46(homedir12(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
14481
15206
  ];
14482
15207
  for (const p of additionalPaths) {
14483
- if (existsSync34(p)) {
15208
+ if (existsSync38(p)) {
14484
15209
  return true;
14485
15210
  }
14486
15211
  }
@@ -14533,8 +15258,8 @@ function getAllServers() {
14533
15258
  }
14534
15259
  // src/tools/lsp/client.ts
14535
15260
  var {spawn: spawn5 } = globalThis.Bun;
14536
- import { readFileSync as readFileSync24 } from "fs";
14537
- import { extname, resolve as resolve5 } from "path";
15261
+ import { readFileSync as readFileSync28 } from "fs";
15262
+ import { extname, resolve as resolve6 } from "path";
14538
15263
  class LSPServerManager {
14539
15264
  static instance;
14540
15265
  clients = new Map;
@@ -14714,7 +15439,7 @@ class LSPClient {
14714
15439
  }
14715
15440
  this.startReading();
14716
15441
  this.startStderrReading();
14717
- await new Promise((resolve6) => setTimeout(resolve6, 100));
15442
+ await new Promise((resolve7) => setTimeout(resolve7, 100));
14718
15443
  if (this.proc.exitCode !== null) {
14719
15444
  const stderr = this.stderrBuffer.join(`
14720
15445
  `);
@@ -14851,8 +15576,8 @@ stderr: ${stderr}` : ""));
14851
15576
  \r
14852
15577
  `;
14853
15578
  this.proc.stdin.write(header + msg);
14854
- return new Promise((resolve6, reject) => {
14855
- this.pending.set(id, { resolve: resolve6, reject });
15579
+ return new Promise((resolve7, reject) => {
15580
+ this.pending.set(id, { resolve: resolve7, reject });
14856
15581
  setTimeout(() => {
14857
15582
  if (this.pending.has(id)) {
14858
15583
  this.pending.delete(id);
@@ -14960,10 +15685,10 @@ ${msg}`);
14960
15685
  await new Promise((r) => setTimeout(r, 300));
14961
15686
  }
14962
15687
  async openFile(filePath) {
14963
- const absPath = resolve5(filePath);
15688
+ const absPath = resolve6(filePath);
14964
15689
  if (this.openedFiles.has(absPath))
14965
15690
  return;
14966
- const text = readFileSync24(absPath, "utf-8");
15691
+ const text = readFileSync28(absPath, "utf-8");
14967
15692
  const ext = extname(absPath);
14968
15693
  const languageId = getLanguageId(ext);
14969
15694
  this.notify("textDocument/didOpen", {
@@ -14978,7 +15703,7 @@ ${msg}`);
14978
15703
  await new Promise((r) => setTimeout(r, 1000));
14979
15704
  }
14980
15705
  async hover(filePath, line, character) {
14981
- const absPath = resolve5(filePath);
15706
+ const absPath = resolve6(filePath);
14982
15707
  await this.openFile(absPath);
14983
15708
  return this.send("textDocument/hover", {
14984
15709
  textDocument: { uri: `file://${absPath}` },
@@ -14986,7 +15711,7 @@ ${msg}`);
14986
15711
  });
14987
15712
  }
14988
15713
  async definition(filePath, line, character) {
14989
- const absPath = resolve5(filePath);
15714
+ const absPath = resolve6(filePath);
14990
15715
  await this.openFile(absPath);
14991
15716
  return this.send("textDocument/definition", {
14992
15717
  textDocument: { uri: `file://${absPath}` },
@@ -14994,7 +15719,7 @@ ${msg}`);
14994
15719
  });
14995
15720
  }
14996
15721
  async references(filePath, line, character, includeDeclaration = true) {
14997
- const absPath = resolve5(filePath);
15722
+ const absPath = resolve6(filePath);
14998
15723
  await this.openFile(absPath);
14999
15724
  return this.send("textDocument/references", {
15000
15725
  textDocument: { uri: `file://${absPath}` },
@@ -15003,7 +15728,7 @@ ${msg}`);
15003
15728
  });
15004
15729
  }
15005
15730
  async documentSymbols(filePath) {
15006
- const absPath = resolve5(filePath);
15731
+ const absPath = resolve6(filePath);
15007
15732
  await this.openFile(absPath);
15008
15733
  return this.send("textDocument/documentSymbol", {
15009
15734
  textDocument: { uri: `file://${absPath}` }
@@ -15013,7 +15738,7 @@ ${msg}`);
15013
15738
  return this.send("workspace/symbol", { query });
15014
15739
  }
15015
15740
  async diagnostics(filePath) {
15016
- const absPath = resolve5(filePath);
15741
+ const absPath = resolve6(filePath);
15017
15742
  const uri = `file://${absPath}`;
15018
15743
  await this.openFile(absPath);
15019
15744
  await new Promise((r) => setTimeout(r, 500));
@@ -15028,7 +15753,7 @@ ${msg}`);
15028
15753
  return { items: this.diagnosticsStore.get(uri) ?? [] };
15029
15754
  }
15030
15755
  async prepareRename(filePath, line, character) {
15031
- const absPath = resolve5(filePath);
15756
+ const absPath = resolve6(filePath);
15032
15757
  await this.openFile(absPath);
15033
15758
  return this.send("textDocument/prepareRename", {
15034
15759
  textDocument: { uri: `file://${absPath}` },
@@ -15036,7 +15761,7 @@ ${msg}`);
15036
15761
  });
15037
15762
  }
15038
15763
  async rename(filePath, line, character, newName) {
15039
- const absPath = resolve5(filePath);
15764
+ const absPath = resolve6(filePath);
15040
15765
  await this.openFile(absPath);
15041
15766
  return this.send("textDocument/rename", {
15042
15767
  textDocument: { uri: `file://${absPath}` },
@@ -15045,7 +15770,7 @@ ${msg}`);
15045
15770
  });
15046
15771
  }
15047
15772
  async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
15048
- const absPath = resolve5(filePath);
15773
+ const absPath = resolve6(filePath);
15049
15774
  await this.openFile(absPath);
15050
15775
  return this.send("textDocument/codeAction", {
15051
15776
  textDocument: { uri: `file://${absPath}` },
@@ -15077,23 +15802,27 @@ ${msg}`);
15077
15802
  }
15078
15803
  }
15079
15804
  // src/tools/lsp/utils.ts
15080
- import { extname as extname2, resolve as resolve6 } from "path";
15081
- import { existsSync as existsSync35, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "fs";
15805
+ import { extname as extname2, resolve as resolve7 } from "path";
15806
+ import { fileURLToPath as fileURLToPath2 } from "url";
15807
+ import { existsSync as existsSync39, readFileSync as readFileSync29, writeFileSync as writeFileSync14 } from "fs";
15082
15808
  function findWorkspaceRoot(filePath) {
15083
- let dir = resolve6(filePath);
15084
- if (!existsSync35(dir) || !__require("fs").statSync(dir).isDirectory()) {
15809
+ let dir = resolve7(filePath);
15810
+ if (!existsSync39(dir) || !__require("fs").statSync(dir).isDirectory()) {
15085
15811
  dir = __require("path").dirname(dir);
15086
15812
  }
15087
15813
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
15088
15814
  while (dir !== "/") {
15089
15815
  for (const marker of markers) {
15090
- if (existsSync35(__require("path").join(dir, marker))) {
15816
+ if (existsSync39(__require("path").join(dir, marker))) {
15091
15817
  return dir;
15092
15818
  }
15093
15819
  }
15094
15820
  dir = __require("path").dirname(dir);
15095
15821
  }
15096
- return __require("path").dirname(resolve6(filePath));
15822
+ return __require("path").dirname(resolve7(filePath));
15823
+ }
15824
+ function uriToPath(uri) {
15825
+ return fileURLToPath2(uri);
15097
15826
  }
15098
15827
  function formatServerLookupError(result) {
15099
15828
  if (result.status === "not_installed") {
@@ -15125,13 +15854,12 @@ function formatServerLookupError(result) {
15125
15854
  ` "command": ["my-lsp", "--stdio"],`,
15126
15855
  ` "extensions": ["${result.extension}"]`,
15127
15856
  ` }`,
15128
- ` }`,
15129
- ` }`
15857
+ ` }`
15130
15858
  ].join(`
15131
15859
  `);
15132
15860
  }
15133
15861
  async function withLspClient(filePath, fn) {
15134
- const absPath = resolve6(filePath);
15862
+ const absPath = resolve7(filePath);
15135
15863
  const ext = extname2(absPath);
15136
15864
  const result = findServerForExtension(ext);
15137
15865
  if (result.status !== "found") {
@@ -15173,12 +15901,12 @@ function formatHoverResult(result) {
15173
15901
  }
15174
15902
  function formatLocation(loc) {
15175
15903
  if ("targetUri" in loc) {
15176
- const uri2 = loc.targetUri.replace("file://", "");
15904
+ const uri2 = uriToPath(loc.targetUri);
15177
15905
  const line2 = loc.targetRange.start.line + 1;
15178
15906
  const char2 = loc.targetRange.start.character;
15179
15907
  return `${uri2}:${line2}:${char2}`;
15180
15908
  }
15181
- const uri = loc.uri.replace("file://", "");
15909
+ const uri = uriToPath(loc.uri);
15182
15910
  const line = loc.range.start.line + 1;
15183
15911
  const char = loc.range.start.character;
15184
15912
  return `${uri}:${line}:${char}`;
@@ -15281,7 +16009,7 @@ function formatCodeActions(actions) {
15281
16009
  }
15282
16010
  function applyTextEditsToFile(filePath, edits) {
15283
16011
  try {
15284
- let content = readFileSync25(filePath, "utf-8");
16012
+ let content = readFileSync29(filePath, "utf-8");
15285
16013
  const lines = content.split(`
15286
16014
  `);
15287
16015
  const sortedEdits = [...edits].sort((a, b) => {
@@ -15306,7 +16034,7 @@ function applyTextEditsToFile(filePath, edits) {
15306
16034
  `));
15307
16035
  }
15308
16036
  }
15309
- writeFileSync13(filePath, lines.join(`
16037
+ writeFileSync14(filePath, lines.join(`
15310
16038
  `), "utf-8");
15311
16039
  return { success: true, editCount: edits.length };
15312
16040
  } catch (err) {
@@ -15320,7 +16048,7 @@ function applyWorkspaceEdit(edit) {
15320
16048
  const result = { success: true, filesModified: [], totalEdits: 0, errors: [] };
15321
16049
  if (edit.changes) {
15322
16050
  for (const [uri, edits] of Object.entries(edit.changes)) {
15323
- const filePath = uri.replace("file://", "");
16051
+ const filePath = uriToPath(uri);
15324
16052
  const applyResult = applyTextEditsToFile(filePath, edits);
15325
16053
  if (applyResult.success) {
15326
16054
  result.filesModified.push(filePath);
@@ -15336,8 +16064,8 @@ function applyWorkspaceEdit(edit) {
15336
16064
  if ("kind" in change) {
15337
16065
  if (change.kind === "create") {
15338
16066
  try {
15339
- const filePath = change.uri.replace("file://", "");
15340
- writeFileSync13(filePath, "", "utf-8");
16067
+ const filePath = uriToPath(change.uri);
16068
+ writeFileSync14(filePath, "", "utf-8");
15341
16069
  result.filesModified.push(filePath);
15342
16070
  } catch (err) {
15343
16071
  result.success = false;
@@ -15345,10 +16073,10 @@ function applyWorkspaceEdit(edit) {
15345
16073
  }
15346
16074
  } else if (change.kind === "rename") {
15347
16075
  try {
15348
- const oldPath = change.oldUri.replace("file://", "");
15349
- const newPath = change.newUri.replace("file://", "");
15350
- const content = readFileSync25(oldPath, "utf-8");
15351
- writeFileSync13(newPath, content, "utf-8");
16076
+ const oldPath = uriToPath(change.oldUri);
16077
+ const newPath = uriToPath(change.newUri);
16078
+ const content = readFileSync29(oldPath, "utf-8");
16079
+ writeFileSync14(newPath, content, "utf-8");
15352
16080
  __require("fs").unlinkSync(oldPath);
15353
16081
  result.filesModified.push(newPath);
15354
16082
  } catch (err) {
@@ -15357,7 +16085,7 @@ function applyWorkspaceEdit(edit) {
15357
16085
  }
15358
16086
  } else if (change.kind === "delete") {
15359
16087
  try {
15360
- const filePath = change.uri.replace("file://", "");
16088
+ const filePath = uriToPath(change.uri);
15361
16089
  __require("fs").unlinkSync(filePath);
15362
16090
  result.filesModified.push(filePath);
15363
16091
  } catch (err) {
@@ -15366,7 +16094,7 @@ function applyWorkspaceEdit(edit) {
15366
16094
  }
15367
16095
  }
15368
16096
  } else {
15369
- const filePath = change.textDocument.uri.replace("file://", "");
16097
+ const filePath = uriToPath(change.textDocument.uri);
15370
16098
  const applyResult = applyTextEditsToFile(filePath, change.edits);
15371
16099
  if (applyResult.success) {
15372
16100
  result.filesModified.push(filePath);
@@ -25483,10 +26211,10 @@ function _property(property, schema, params) {
25483
26211
  ...normalizeParams(params)
25484
26212
  });
25485
26213
  }
25486
- function _mime(types11, params) {
26214
+ function _mime(types14, params) {
25487
26215
  return new $ZodCheckMimeType({
25488
26216
  check: "mime_type",
25489
- mime: types11,
26217
+ mime: types14,
25490
26218
  ...normalizeParams(params)
25491
26219
  });
25492
26220
  }
@@ -27396,7 +28124,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
27396
28124
  ZodType.init(inst, def);
27397
28125
  inst.min = (size, params) => inst.check(_minSize(size, params));
27398
28126
  inst.max = (size, params) => inst.check(_maxSize(size, params));
27399
- inst.mime = (types11, params) => inst.check(_mime(Array.isArray(types11) ? types11 : [types11], params));
28127
+ inst.mime = (types14, params) => inst.check(_mime(Array.isArray(types14) ? types14 : [types14], params));
27400
28128
  });
27401
28129
  function file(params) {
27402
28130
  return _file(ZodFile, params);
@@ -28048,14 +28776,14 @@ var lsp_code_action_resolve = tool({
28048
28776
  });
28049
28777
  // src/tools/ast-grep/constants.ts
28050
28778
  import { createRequire as createRequire4 } from "module";
28051
- import { dirname as dirname6, join as join46 } from "path";
28052
- import { existsSync as existsSync37, statSync as statSync4 } from "fs";
28779
+ import { dirname as dirname9, join as join48 } from "path";
28780
+ import { existsSync as existsSync41, statSync as statSync4 } from "fs";
28053
28781
 
28054
28782
  // src/tools/ast-grep/downloader.ts
28055
28783
  var {spawn: spawn6 } = globalThis.Bun;
28056
- import { existsSync as existsSync36, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
28057
- import { join as join45 } from "path";
28058
- import { homedir as homedir11 } from "os";
28784
+ import { existsSync as existsSync40, mkdirSync as mkdirSync11, chmodSync as chmodSync2, unlinkSync as unlinkSync10 } from "fs";
28785
+ import { join as join47 } from "path";
28786
+ import { homedir as homedir13 } from "os";
28059
28787
  import { createRequire as createRequire3 } from "module";
28060
28788
  var REPO2 = "ast-grep/ast-grep";
28061
28789
  var DEFAULT_VERSION = "0.40.0";
@@ -28080,19 +28808,19 @@ var PLATFORM_MAP2 = {
28080
28808
  function getCacheDir3() {
28081
28809
  if (process.platform === "win32") {
28082
28810
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
28083
- const base2 = localAppData || join45(homedir11(), "AppData", "Local");
28084
- return join45(base2, "oh-my-opencode", "bin");
28811
+ const base2 = localAppData || join47(homedir13(), "AppData", "Local");
28812
+ return join47(base2, "oh-my-opencode", "bin");
28085
28813
  }
28086
28814
  const xdgCache = process.env.XDG_CACHE_HOME;
28087
- const base = xdgCache || join45(homedir11(), ".cache");
28088
- return join45(base, "oh-my-opencode", "bin");
28815
+ const base = xdgCache || join47(homedir13(), ".cache");
28816
+ return join47(base, "oh-my-opencode", "bin");
28089
28817
  }
28090
28818
  function getBinaryName3() {
28091
28819
  return process.platform === "win32" ? "sg.exe" : "sg";
28092
28820
  }
28093
28821
  function getCachedBinaryPath2() {
28094
- const binaryPath = join45(getCacheDir3(), getBinaryName3());
28095
- return existsSync36(binaryPath) ? binaryPath : null;
28822
+ const binaryPath = join47(getCacheDir3(), getBinaryName3());
28823
+ return existsSync40(binaryPath) ? binaryPath : null;
28096
28824
  }
28097
28825
  async function extractZip2(archivePath, destDir) {
28098
28826
  const proc = process.platform === "win32" ? spawn6([
@@ -28118,8 +28846,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
28118
28846
  }
28119
28847
  const cacheDir = getCacheDir3();
28120
28848
  const binaryName = getBinaryName3();
28121
- const binaryPath = join45(cacheDir, binaryName);
28122
- if (existsSync36(binaryPath)) {
28849
+ const binaryPath = join47(cacheDir, binaryName);
28850
+ if (existsSync40(binaryPath)) {
28123
28851
  return binaryPath;
28124
28852
  }
28125
28853
  const { arch, os: os5 } = platformInfo;
@@ -28127,21 +28855,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
28127
28855
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
28128
28856
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
28129
28857
  try {
28130
- if (!existsSync36(cacheDir)) {
28131
- mkdirSync10(cacheDir, { recursive: true });
28858
+ if (!existsSync40(cacheDir)) {
28859
+ mkdirSync11(cacheDir, { recursive: true });
28132
28860
  }
28133
28861
  const response2 = await fetch(downloadUrl, { redirect: "follow" });
28134
28862
  if (!response2.ok) {
28135
28863
  throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
28136
28864
  }
28137
- const archivePath = join45(cacheDir, assetName);
28865
+ const archivePath = join47(cacheDir, assetName);
28138
28866
  const arrayBuffer = await response2.arrayBuffer();
28139
28867
  await Bun.write(archivePath, arrayBuffer);
28140
28868
  await extractZip2(archivePath, cacheDir);
28141
- if (existsSync36(archivePath)) {
28142
- unlinkSync9(archivePath);
28869
+ if (existsSync40(archivePath)) {
28870
+ unlinkSync10(archivePath);
28143
28871
  }
28144
- if (process.platform !== "win32" && existsSync36(binaryPath)) {
28872
+ if (process.platform !== "win32" && existsSync40(binaryPath)) {
28145
28873
  chmodSync2(binaryPath, 493);
28146
28874
  }
28147
28875
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -28191,9 +28919,9 @@ function findSgCliPathSync() {
28191
28919
  try {
28192
28920
  const require2 = createRequire4(import.meta.url);
28193
28921
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
28194
- const cliDir = dirname6(cliPkgPath);
28195
- const sgPath = join46(cliDir, binaryName);
28196
- if (existsSync37(sgPath) && isValidBinary(sgPath)) {
28922
+ const cliDir = dirname9(cliPkgPath);
28923
+ const sgPath = join48(cliDir, binaryName);
28924
+ if (existsSync41(sgPath) && isValidBinary(sgPath)) {
28197
28925
  return sgPath;
28198
28926
  }
28199
28927
  } catch {}
@@ -28202,10 +28930,10 @@ function findSgCliPathSync() {
28202
28930
  try {
28203
28931
  const require2 = createRequire4(import.meta.url);
28204
28932
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
28205
- const pkgDir = dirname6(pkgPath);
28933
+ const pkgDir = dirname9(pkgPath);
28206
28934
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
28207
- const binaryPath = join46(pkgDir, astGrepName);
28208
- if (existsSync37(binaryPath) && isValidBinary(binaryPath)) {
28935
+ const binaryPath = join48(pkgDir, astGrepName);
28936
+ if (existsSync41(binaryPath) && isValidBinary(binaryPath)) {
28209
28937
  return binaryPath;
28210
28938
  }
28211
28939
  } catch {}
@@ -28213,7 +28941,7 @@ function findSgCliPathSync() {
28213
28941
  if (process.platform === "darwin") {
28214
28942
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
28215
28943
  for (const path7 of homebrewPaths) {
28216
- if (existsSync37(path7) && isValidBinary(path7)) {
28944
+ if (existsSync41(path7) && isValidBinary(path7)) {
28217
28945
  return path7;
28218
28946
  }
28219
28947
  }
@@ -28268,11 +28996,11 @@ var DEFAULT_MAX_MATCHES = 500;
28268
28996
 
28269
28997
  // src/tools/ast-grep/cli.ts
28270
28998
  var {spawn: spawn7 } = globalThis.Bun;
28271
- import { existsSync as existsSync38 } from "fs";
28999
+ import { existsSync as existsSync42 } from "fs";
28272
29000
  var resolvedCliPath3 = null;
28273
29001
  var initPromise2 = null;
28274
29002
  async function getAstGrepPath() {
28275
- if (resolvedCliPath3 !== null && existsSync38(resolvedCliPath3)) {
29003
+ if (resolvedCliPath3 !== null && existsSync42(resolvedCliPath3)) {
28276
29004
  return resolvedCliPath3;
28277
29005
  }
28278
29006
  if (initPromise2) {
@@ -28280,7 +29008,7 @@ async function getAstGrepPath() {
28280
29008
  }
28281
29009
  initPromise2 = (async () => {
28282
29010
  const syncPath = findSgCliPathSync();
28283
- if (syncPath && existsSync38(syncPath)) {
29011
+ if (syncPath && existsSync42(syncPath)) {
28284
29012
  resolvedCliPath3 = syncPath;
28285
29013
  setSgCliPath(syncPath);
28286
29014
  return syncPath;
@@ -28314,7 +29042,7 @@ async function runSg(options) {
28314
29042
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
28315
29043
  args.push(...paths);
28316
29044
  let cliPath = getSgCliPath();
28317
- if (!existsSync38(cliPath) && cliPath !== "sg") {
29045
+ if (!existsSync42(cliPath) && cliPath !== "sg") {
28318
29046
  const downloadedPath = await getAstGrepPath();
28319
29047
  if (downloadedPath) {
28320
29048
  cliPath = downloadedPath;
@@ -28578,20 +29306,20 @@ var ast_grep_replace = tool({
28578
29306
  var {spawn: spawn9 } = globalThis.Bun;
28579
29307
 
28580
29308
  // src/tools/grep/constants.ts
28581
- import { existsSync as existsSync40 } from "fs";
28582
- import { join as join48, dirname as dirname7 } from "path";
29309
+ import { existsSync as existsSync44 } from "fs";
29310
+ import { join as join50, dirname as dirname10 } from "path";
28583
29311
  import { spawnSync } from "child_process";
28584
29312
 
28585
29313
  // src/tools/grep/downloader.ts
28586
- import { existsSync as existsSync39, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync14 } from "fs";
28587
- import { join as join47 } from "path";
29314
+ import { existsSync as existsSync43, mkdirSync as mkdirSync12, chmodSync as chmodSync3, unlinkSync as unlinkSync11, readdirSync as readdirSync15 } from "fs";
29315
+ import { join as join49 } from "path";
28588
29316
  var {spawn: spawn8 } = globalThis.Bun;
28589
29317
  function findFileRecursive(dir, filename) {
28590
29318
  try {
28591
- const entries = readdirSync14(dir, { withFileTypes: true, recursive: true });
29319
+ const entries = readdirSync15(dir, { withFileTypes: true, recursive: true });
28592
29320
  for (const entry of entries) {
28593
29321
  if (entry.isFile() && entry.name === filename) {
28594
- return join47(entry.parentPath ?? dir, entry.name);
29322
+ return join49(entry.parentPath ?? dir, entry.name);
28595
29323
  }
28596
29324
  }
28597
29325
  } catch {
@@ -28612,11 +29340,11 @@ function getPlatformKey() {
28612
29340
  }
28613
29341
  function getInstallDir() {
28614
29342
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
28615
- return join47(homeDir, ".cache", "oh-my-opencode", "bin");
29343
+ return join49(homeDir, ".cache", "oh-my-opencode", "bin");
28616
29344
  }
28617
29345
  function getRgPath() {
28618
29346
  const isWindows2 = process.platform === "win32";
28619
- return join47(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
29347
+ return join49(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
28620
29348
  }
28621
29349
  async function downloadFile(url2, destPath) {
28622
29350
  const response2 = await fetch(url2);
@@ -28653,7 +29381,7 @@ async function extractZipWindows(archivePath, destDir) {
28653
29381
  }
28654
29382
  const foundPath = findFileRecursive(destDir, "rg.exe");
28655
29383
  if (foundPath) {
28656
- const destPath = join47(destDir, "rg.exe");
29384
+ const destPath = join49(destDir, "rg.exe");
28657
29385
  if (foundPath !== destPath) {
28658
29386
  const { renameSync } = await import("fs");
28659
29387
  renameSync(foundPath, destPath);
@@ -28671,7 +29399,7 @@ async function extractZipUnix(archivePath, destDir) {
28671
29399
  }
28672
29400
  const foundPath = findFileRecursive(destDir, "rg");
28673
29401
  if (foundPath) {
28674
- const destPath = join47(destDir, "rg");
29402
+ const destPath = join49(destDir, "rg");
28675
29403
  if (foundPath !== destPath) {
28676
29404
  const { renameSync } = await import("fs");
28677
29405
  renameSync(foundPath, destPath);
@@ -28693,13 +29421,13 @@ async function downloadAndInstallRipgrep() {
28693
29421
  }
28694
29422
  const installDir = getInstallDir();
28695
29423
  const rgPath = getRgPath();
28696
- if (existsSync39(rgPath)) {
29424
+ if (existsSync43(rgPath)) {
28697
29425
  return rgPath;
28698
29426
  }
28699
- mkdirSync11(installDir, { recursive: true });
29427
+ mkdirSync12(installDir, { recursive: true });
28700
29428
  const filename = `ripgrep-${RG_VERSION}-${config3.platform}.${config3.extension}`;
28701
29429
  const url2 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
28702
- const archivePath = join47(installDir, filename);
29430
+ const archivePath = join49(installDir, filename);
28703
29431
  try {
28704
29432
  await downloadFile(url2, archivePath);
28705
29433
  if (config3.extension === "tar.gz") {
@@ -28710,21 +29438,21 @@ async function downloadAndInstallRipgrep() {
28710
29438
  if (process.platform !== "win32") {
28711
29439
  chmodSync3(rgPath, 493);
28712
29440
  }
28713
- if (!existsSync39(rgPath)) {
29441
+ if (!existsSync43(rgPath)) {
28714
29442
  throw new Error("ripgrep binary not found after extraction");
28715
29443
  }
28716
29444
  return rgPath;
28717
29445
  } finally {
28718
- if (existsSync39(archivePath)) {
29446
+ if (existsSync43(archivePath)) {
28719
29447
  try {
28720
- unlinkSync10(archivePath);
29448
+ unlinkSync11(archivePath);
28721
29449
  } catch {}
28722
29450
  }
28723
29451
  }
28724
29452
  }
28725
29453
  function getInstalledRipgrepPath() {
28726
29454
  const rgPath = getRgPath();
28727
- return existsSync39(rgPath) ? rgPath : null;
29455
+ return existsSync43(rgPath) ? rgPath : null;
28728
29456
  }
28729
29457
 
28730
29458
  // src/tools/grep/constants.ts
@@ -28744,18 +29472,18 @@ function findExecutable(name) {
28744
29472
  }
28745
29473
  function getOpenCodeBundledRg() {
28746
29474
  const execPath = process.execPath;
28747
- const execDir = dirname7(execPath);
29475
+ const execDir = dirname10(execPath);
28748
29476
  const isWindows2 = process.platform === "win32";
28749
29477
  const rgName = isWindows2 ? "rg.exe" : "rg";
28750
29478
  const candidates = [
28751
- join48(getDataDir(), "opencode", "bin", rgName),
28752
- join48(execDir, rgName),
28753
- join48(execDir, "bin", rgName),
28754
- join48(execDir, "..", "bin", rgName),
28755
- join48(execDir, "..", "libexec", rgName)
29479
+ join50(getDataDir(), "opencode", "bin", rgName),
29480
+ join50(execDir, rgName),
29481
+ join50(execDir, "bin", rgName),
29482
+ join50(execDir, "..", "bin", rgName),
29483
+ join50(execDir, "..", "libexec", rgName)
28756
29484
  ];
28757
29485
  for (const candidate of candidates) {
28758
- if (existsSync40(candidate)) {
29486
+ if (existsSync44(candidate)) {
28759
29487
  return candidate;
28760
29488
  }
28761
29489
  }
@@ -29208,21 +29936,21 @@ var glob = tool({
29208
29936
  }
29209
29937
  });
29210
29938
  // src/tools/slashcommand/tools.ts
29211
- import { existsSync as existsSync41, readdirSync as readdirSync15, readFileSync as readFileSync26 } from "fs";
29212
- import { join as join49, basename as basename4, dirname as dirname8 } from "path";
29939
+ import { existsSync as existsSync45, readdirSync as readdirSync16, readFileSync as readFileSync30 } from "fs";
29940
+ import { join as join51, basename as basename5, dirname as dirname11 } from "path";
29213
29941
  function discoverCommandsFromDir(commandsDir, scope) {
29214
- if (!existsSync41(commandsDir)) {
29942
+ if (!existsSync45(commandsDir)) {
29215
29943
  return [];
29216
29944
  }
29217
- const entries = readdirSync15(commandsDir, { withFileTypes: true });
29945
+ const entries = readdirSync16(commandsDir, { withFileTypes: true });
29218
29946
  const commands2 = [];
29219
29947
  for (const entry of entries) {
29220
29948
  if (!isMarkdownFile(entry))
29221
29949
  continue;
29222
- const commandPath = join49(commandsDir, entry.name);
29223
- const commandName = basename4(entry.name, ".md");
29950
+ const commandPath = join51(commandsDir, entry.name);
29951
+ const commandName = basename5(entry.name, ".md");
29224
29952
  try {
29225
- const content = readFileSync26(commandPath, "utf-8");
29953
+ const content = readFileSync30(commandPath, "utf-8");
29226
29954
  const { data, body } = parseFrontmatter(content);
29227
29955
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
29228
29956
  const metadata = {
@@ -29247,19 +29975,40 @@ function discoverCommandsFromDir(commandsDir, scope) {
29247
29975
  return commands2;
29248
29976
  }
29249
29977
  function discoverCommandsSync() {
29250
- const { homedir: homedir12 } = __require("os");
29251
- const userCommandsDir = join49(getClaudeConfigDir(), "commands");
29252
- const projectCommandsDir = join49(process.cwd(), ".claude", "commands");
29253
- const opencodeGlobalDir = join49(homedir12(), ".config", "opencode", "command");
29254
- const opencodeProjectDir = join49(process.cwd(), ".opencode", "command");
29978
+ const { homedir: homedir14 } = __require("os");
29979
+ const userCommandsDir = join51(getClaudeConfigDir(), "commands");
29980
+ const projectCommandsDir = join51(process.cwd(), ".claude", "commands");
29981
+ const opencodeGlobalDir = join51(homedir14(), ".config", "opencode", "command");
29982
+ const opencodeProjectDir = join51(process.cwd(), ".opencode", "command");
29255
29983
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
29256
29984
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
29257
29985
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
29258
29986
  const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project");
29259
29987
  return [...opencodeProjectCommands, ...projectCommands, ...opencodeGlobalCommands, ...userCommands];
29260
29988
  }
29989
+ function skillToCommandInfo(skill) {
29990
+ return {
29991
+ name: skill.name,
29992
+ path: skill.path,
29993
+ metadata: {
29994
+ name: skill.name,
29995
+ description: skill.definition.description || "",
29996
+ argumentHint: skill.definition.argumentHint,
29997
+ model: skill.definition.model,
29998
+ agent: skill.definition.agent,
29999
+ subtask: skill.definition.subtask
30000
+ },
30001
+ content: skill.definition.template,
30002
+ scope: skill.scope
30003
+ };
30004
+ }
29261
30005
  var availableCommands = discoverCommandsSync();
29262
- var commandListForDescription = availableCommands.map((cmd) => {
30006
+ var availableSkills = discoverAllSkills();
30007
+ var availableItems = [
30008
+ ...availableCommands,
30009
+ ...availableSkills.map(skillToCommandInfo)
30010
+ ];
30011
+ var commandListForDescription = availableItems.map((cmd) => {
29263
30012
  const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
29264
30013
  return `- /${cmd.name}${hint}: ${cmd.metadata.description} (${cmd.scope})`;
29265
30014
  }).join(`
@@ -29294,93 +30043,78 @@ async function formatLoadedCommand(cmd) {
29294
30043
  `);
29295
30044
  sections.push(`## Command Instructions
29296
30045
  `);
29297
- const commandDir = dirname8(cmd.path);
29298
- const withFileRefs = await resolveFileReferencesInText(cmd.content, commandDir);
30046
+ const commandDir = cmd.path ? dirname11(cmd.path) : process.cwd();
30047
+ const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
29299
30048
  const resolvedContent = await resolveCommandsInText(withFileRefs);
29300
30049
  sections.push(resolvedContent.trim());
29301
30050
  return sections.join(`
29302
30051
  `);
29303
30052
  }
29304
- function formatCommandList(commands2) {
29305
- if (commands2.length === 0) {
29306
- return "No commands found.";
30053
+ function formatCommandList(items) {
30054
+ if (items.length === 0) {
30055
+ return "No commands or skills found.";
29307
30056
  }
29308
- const lines = [`# Available Commands
30057
+ const lines = [`# Available Commands & Skills
29309
30058
  `];
29310
- for (const cmd of commands2) {
30059
+ for (const cmd of items) {
29311
30060
  const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
29312
30061
  lines.push(`- **/${cmd.name}${hint}**: ${cmd.metadata.description || "(no description)"} (${cmd.scope})`);
29313
30062
  }
29314
30063
  lines.push(`
29315
- **Total**: ${commands2.length} commands`);
30064
+ **Total**: ${items.length} items`);
29316
30065
  return lines.join(`
29317
30066
  `);
29318
30067
  }
29319
30068
  var slashcommand = tool({
29320
- description: `Execute a slash command within the main conversation.
29321
-
29322
- When you use this tool, the slash command gets expanded to a full prompt that provides detailed instructions on how to complete the task.
29323
-
29324
- How slash commands work:
29325
- - Invoke commands using this tool with the command name (without arguments)
29326
- - The command's prompt will expand and provide detailed instructions
29327
- - Arguments from user input should be passed separately
29328
-
29329
- Important:
29330
- - Only use commands listed in Available Commands below
29331
- - Do not invoke a command that is already running
29332
- - **CRITICAL**: When user's message starts with '/' (e.g., "/commit", "/plan"), you MUST immediately invoke this tool with that command. Do NOT attempt to handle the command manually.
29333
-
29334
- Commands are loaded from (priority order, highest wins):
29335
- - .opencode/command/ (opencode-project - OpenCode project-specific commands)
29336
- - ./.claude/commands/ (project - Claude Code project-specific commands)
29337
- - ~/.config/opencode/command/ (opencode - OpenCode global commands)
29338
- - $CLAUDE_CONFIG_DIR/commands/ or ~/.claude/commands/ (user - Claude Code global commands)
29339
-
29340
- Each command is a markdown file with:
29341
- - YAML frontmatter: description, argument-hint, model, agent, subtask (optional)
29342
- - Markdown body: The command instructions/prompt
29343
- - File references: @path/to/file (relative to command file location)
29344
- - Shell injection: \`!\`command\`\` (executes and injects output)
29345
-
29346
- Available Commands:
29347
- ${commandListForDescription}`,
30069
+ description: `Load a skill to get detailed instructions for a specific task.
30070
+
30071
+ Skills provide specialized knowledge and step-by-step guidance.
30072
+ Use this when a task matches an available skill's description.
30073
+
30074
+ <available_skills>
30075
+ ${commandListForDescription}
30076
+ </available_skills>`,
29348
30077
  args: {
29349
30078
  command: tool.schema.string().describe("The slash command to execute (without the leading slash). E.g., 'commit', 'plan', 'execute'.")
29350
30079
  },
29351
30080
  async execute(args) {
29352
30081
  const commands2 = discoverCommandsSync();
30082
+ const skills = discoverAllSkills();
30083
+ const allItems = [
30084
+ ...commands2,
30085
+ ...skills.map(skillToCommandInfo)
30086
+ ];
29353
30087
  if (!args.command) {
29354
- return formatCommandList(commands2) + `
30088
+ return formatCommandList(allItems) + `
29355
30089
 
29356
- Provide a command name to execute.`;
30090
+ Provide a command or skill name to execute.`;
29357
30091
  }
29358
30092
  const cmdName = args.command.replace(/^\//, "");
29359
- const exactMatch = commands2.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
30093
+ const exactMatch = allItems.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
29360
30094
  if (exactMatch) {
29361
30095
  return await formatLoadedCommand(exactMatch);
29362
30096
  }
29363
- const partialMatches = commands2.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
30097
+ const partialMatches = allItems.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
29364
30098
  if (partialMatches.length > 0) {
29365
30099
  const matchList = partialMatches.map((cmd) => `/${cmd.name}`).join(", ");
29366
30100
  return `No exact match for "/${cmdName}". Did you mean: ${matchList}?
29367
30101
 
29368
- ` + formatCommandList(commands2);
30102
+ ` + formatCommandList(allItems);
29369
30103
  }
29370
- return `Command "/${cmdName}" not found.
30104
+ return `Command or skill "/${cmdName}" not found.
29371
30105
 
29372
- ` + formatCommandList(commands2) + `
30106
+ ` + formatCommandList(allItems) + `
29373
30107
 
29374
- Try a different command name.`;
30108
+ Try a different name.`;
29375
30109
  }
29376
30110
  });
29377
30111
  // src/tools/session-manager/constants.ts
29378
- import { join as join50 } from "path";
30112
+ import { join as join52 } from "path";
29379
30113
  var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
29380
- var MESSAGE_STORAGE4 = join50(OPENCODE_STORAGE9, "message");
29381
- var PART_STORAGE4 = join50(OPENCODE_STORAGE9, "part");
29382
- var TODO_DIR2 = join50(getClaudeConfigDir(), "todos");
29383
- var TRANSCRIPT_DIR2 = join50(getClaudeConfigDir(), "transcripts");
30114
+ var MESSAGE_STORAGE4 = join52(OPENCODE_STORAGE9, "message");
30115
+ var PART_STORAGE4 = join52(OPENCODE_STORAGE9, "part");
30116
+ var TODO_DIR2 = join52(getClaudeConfigDir(), "todos");
30117
+ var TRANSCRIPT_DIR2 = join52(getClaudeConfigDir(), "transcripts");
29384
30118
  var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
29385
30119
 
29386
30120
  Returns a list of available session IDs with metadata including message count, date range, and agents used.
@@ -29453,11 +30187,11 @@ Has Todos: Yes (12 items, 8 completed)
29453
30187
  Has Transcript: Yes (234 entries)`;
29454
30188
 
29455
30189
  // src/tools/session-manager/storage.ts
29456
- import { existsSync as existsSync42, readdirSync as readdirSync16 } from "fs";
30190
+ import { existsSync as existsSync46, readdirSync as readdirSync17 } from "fs";
29457
30191
  import { readdir, readFile } from "fs/promises";
29458
- import { join as join51 } from "path";
30192
+ import { join as join53 } from "path";
29459
30193
  async function getAllSessions() {
29460
- if (!existsSync42(MESSAGE_STORAGE4))
30194
+ if (!existsSync46(MESSAGE_STORAGE4))
29461
30195
  return [];
29462
30196
  const sessions = [];
29463
30197
  async function scanDirectory(dir) {
@@ -29465,7 +30199,7 @@ async function getAllSessions() {
29465
30199
  const entries = await readdir(dir, { withFileTypes: true });
29466
30200
  for (const entry of entries) {
29467
30201
  if (entry.isDirectory()) {
29468
- const sessionPath = join51(dir, entry.name);
30202
+ const sessionPath = join53(dir, entry.name);
29469
30203
  const files = await readdir(sessionPath);
29470
30204
  if (files.some((f) => f.endsWith(".json"))) {
29471
30205
  sessions.push(entry.name);
@@ -29482,16 +30216,16 @@ async function getAllSessions() {
29482
30216
  return [...new Set(sessions)];
29483
30217
  }
29484
30218
  function getMessageDir9(sessionID) {
29485
- if (!existsSync42(MESSAGE_STORAGE4))
30219
+ if (!existsSync46(MESSAGE_STORAGE4))
29486
30220
  return "";
29487
- const directPath = join51(MESSAGE_STORAGE4, sessionID);
29488
- if (existsSync42(directPath)) {
30221
+ const directPath = join53(MESSAGE_STORAGE4, sessionID);
30222
+ if (existsSync46(directPath)) {
29489
30223
  return directPath;
29490
30224
  }
29491
30225
  try {
29492
- for (const dir of readdirSync16(MESSAGE_STORAGE4)) {
29493
- const sessionPath = join51(MESSAGE_STORAGE4, dir, sessionID);
29494
- if (existsSync42(sessionPath)) {
30226
+ for (const dir of readdirSync17(MESSAGE_STORAGE4)) {
30227
+ const sessionPath = join53(MESSAGE_STORAGE4, dir, sessionID);
30228
+ if (existsSync46(sessionPath)) {
29495
30229
  return sessionPath;
29496
30230
  }
29497
30231
  }
@@ -29505,7 +30239,7 @@ function sessionExists(sessionID) {
29505
30239
  }
29506
30240
  async function readSessionMessages(sessionID) {
29507
30241
  const messageDir = getMessageDir9(sessionID);
29508
- if (!messageDir || !existsSync42(messageDir))
30242
+ if (!messageDir || !existsSync46(messageDir))
29509
30243
  return [];
29510
30244
  const messages = [];
29511
30245
  try {
@@ -29514,7 +30248,7 @@ async function readSessionMessages(sessionID) {
29514
30248
  if (!file2.endsWith(".json"))
29515
30249
  continue;
29516
30250
  try {
29517
- const content = await readFile(join51(messageDir, file2), "utf-8");
30251
+ const content = await readFile(join53(messageDir, file2), "utf-8");
29518
30252
  const meta = JSON.parse(content);
29519
30253
  const parts = await readParts2(meta.id);
29520
30254
  messages.push({
@@ -29540,8 +30274,8 @@ async function readSessionMessages(sessionID) {
29540
30274
  });
29541
30275
  }
29542
30276
  async function readParts2(messageID) {
29543
- const partDir = join51(PART_STORAGE4, messageID);
29544
- if (!existsSync42(partDir))
30277
+ const partDir = join53(PART_STORAGE4, messageID);
30278
+ if (!existsSync46(partDir))
29545
30279
  return [];
29546
30280
  const parts = [];
29547
30281
  try {
@@ -29550,7 +30284,7 @@ async function readParts2(messageID) {
29550
30284
  if (!file2.endsWith(".json"))
29551
30285
  continue;
29552
30286
  try {
29553
- const content = await readFile(join51(partDir, file2), "utf-8");
30287
+ const content = await readFile(join53(partDir, file2), "utf-8");
29554
30288
  parts.push(JSON.parse(content));
29555
30289
  } catch {
29556
30290
  continue;
@@ -29562,14 +30296,14 @@ async function readParts2(messageID) {
29562
30296
  return parts.sort((a, b) => a.id.localeCompare(b.id));
29563
30297
  }
29564
30298
  async function readSessionTodos(sessionID) {
29565
- if (!existsSync42(TODO_DIR2))
30299
+ if (!existsSync46(TODO_DIR2))
29566
30300
  return [];
29567
30301
  try {
29568
30302
  const allFiles = await readdir(TODO_DIR2);
29569
30303
  const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
29570
30304
  for (const file2 of todoFiles) {
29571
30305
  try {
29572
- const content = await readFile(join51(TODO_DIR2, file2), "utf-8");
30306
+ const content = await readFile(join53(TODO_DIR2, file2), "utf-8");
29573
30307
  const data = JSON.parse(content);
29574
30308
  if (Array.isArray(data)) {
29575
30309
  return data.map((item) => ({
@@ -29589,10 +30323,10 @@ async function readSessionTodos(sessionID) {
29589
30323
  return [];
29590
30324
  }
29591
30325
  async function readSessionTranscript(sessionID) {
29592
- if (!existsSync42(TRANSCRIPT_DIR2))
30326
+ if (!existsSync46(TRANSCRIPT_DIR2))
29593
30327
  return 0;
29594
- const transcriptFile = join51(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
29595
- if (!existsSync42(transcriptFile))
30328
+ const transcriptFile = join53(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
30329
+ if (!existsSync46(transcriptFile))
29596
30330
  return 0;
29597
30331
  try {
29598
30332
  const content = await readFile(transcriptFile, "utf-8");
@@ -29913,6 +30647,8 @@ var BLOCKED_TMUX_SUBCOMMANDS = [
29913
30647
  ];
29914
30648
  var INTERACTIVE_BASH_DESCRIPTION = `Execute tmux commands. Use "omo-{name}" session pattern.
29915
30649
 
30650
+ For: server processes, long-running tasks, background jobs, interactive CLI tools.
30651
+
29916
30652
  Blocked (use bash instead): capture-pane, save-buffer, show-buffer, pipe-pane.`;
29917
30653
 
29918
30654
  // src/tools/interactive-bash/utils.ts
@@ -30050,9 +30786,91 @@ var interactive_bash = tool({
30050
30786
  }
30051
30787
  }
30052
30788
  });
30789
+ // src/tools/skill/constants.ts
30790
+ var TOOL_DESCRIPTION_NO_SKILLS = "Load a skill to get detailed instructions for a specific task. No skills are currently available.";
30791
+ var TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a specific task.
30792
+
30793
+ Skills provide specialized knowledge and step-by-step guidance.
30794
+ Use this when a task matches an available skill's description.`;
30795
+ // src/tools/skill/tools.ts
30796
+ import { dirname as dirname12 } from "path";
30797
+ import { readFileSync as readFileSync31 } from "fs";
30798
+ function loadedSkillToInfo(skill) {
30799
+ return {
30800
+ name: skill.name,
30801
+ description: skill.definition.description || "",
30802
+ location: skill.path,
30803
+ scope: skill.scope,
30804
+ license: skill.license,
30805
+ compatibility: skill.compatibility,
30806
+ metadata: skill.metadata,
30807
+ allowedTools: skill.allowedTools
30808
+ };
30809
+ }
30810
+ function formatSkillsXml(skills) {
30811
+ if (skills.length === 0)
30812
+ return "";
30813
+ const skillsXml = skills.map((skill) => {
30814
+ const lines = [
30815
+ " <skill>",
30816
+ ` <name>${skill.name}</name>`,
30817
+ ` <description>${skill.description}</description>`
30818
+ ];
30819
+ if (skill.compatibility) {
30820
+ lines.push(` <compatibility>${skill.compatibility}</compatibility>`);
30821
+ }
30822
+ lines.push(" </skill>");
30823
+ return lines.join(`
30824
+ `);
30825
+ }).join(`
30826
+ `);
30827
+ return `
30828
+
30829
+ <available_skills>
30830
+ ${skillsXml}
30831
+ </available_skills>`;
30832
+ }
30833
+ function extractSkillBody(skill) {
30834
+ if (skill.path) {
30835
+ const content = readFileSync31(skill.path, "utf-8");
30836
+ const { body } = parseFrontmatter(content);
30837
+ return body.trim();
30838
+ }
30839
+ const templateMatch = skill.definition.template?.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
30840
+ return templateMatch ? templateMatch[1].trim() : skill.definition.template || "";
30841
+ }
30842
+ function createSkillTool(options = {}) {
30843
+ const skills = options.skills ?? discoverSkills({ includeClaudeCodePaths: !options.opencodeOnly });
30844
+ const skillInfos = skills.map(loadedSkillToInfo);
30845
+ const description = skillInfos.length === 0 ? TOOL_DESCRIPTION_NO_SKILLS : TOOL_DESCRIPTION_PREFIX + formatSkillsXml(skillInfos);
30846
+ return tool({
30847
+ description,
30848
+ args: {
30849
+ name: tool.schema.string().describe("The skill identifier from available_skills (e.g., 'code-review')")
30850
+ },
30851
+ async execute(args) {
30852
+ const skill = options.skills ? skills.find((s) => s.name === args.name) : skills.find((s) => s.name === args.name);
30853
+ if (!skill) {
30854
+ const available = skills.map((s) => s.name).join(", ");
30855
+ throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`);
30856
+ }
30857
+ const body = extractSkillBody(skill);
30858
+ const dir = skill.path ? dirname12(skill.path) : skill.resolvedPath || process.cwd();
30859
+ return [
30860
+ `## Skill: ${skill.name}`,
30861
+ "",
30862
+ `**Base directory**: ${dir}`,
30863
+ "",
30864
+ body
30865
+ ].join(`
30866
+ `);
30867
+ }
30868
+ });
30869
+ }
30870
+ var skill = createSkillTool();
30053
30871
  // src/tools/background-task/tools.ts
30054
- import { existsSync as existsSync43, readdirSync as readdirSync17 } from "fs";
30055
- import { join as join52 } from "path";
30872
+ import { existsSync as existsSync47, readdirSync as readdirSync18 } from "fs";
30873
+ import { join as join54 } from "path";
30056
30874
 
30057
30875
  // src/tools/background-task/constants.ts
30058
30876
  var BACKGROUND_TASK_DESCRIPTION = `Run agent task in background. Returns task_id immediately; notifies on completion.
@@ -30063,14 +30881,14 @@ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=
30063
30881
 
30064
30882
  // src/tools/background-task/tools.ts
30065
30883
  function getMessageDir10(sessionID) {
30066
- if (!existsSync43(MESSAGE_STORAGE))
30884
+ if (!existsSync47(MESSAGE_STORAGE))
30067
30885
  return null;
30068
- const directPath = join52(MESSAGE_STORAGE, sessionID);
30069
- if (existsSync43(directPath))
30886
+ const directPath = join54(MESSAGE_STORAGE, sessionID);
30887
+ if (existsSync47(directPath))
30070
30888
  return directPath;
30071
- for (const dir of readdirSync17(MESSAGE_STORAGE)) {
30072
- const sessionPath = join52(MESSAGE_STORAGE, dir, sessionID);
30073
- if (existsSync43(sessionPath))
30889
+ for (const dir of readdirSync18(MESSAGE_STORAGE)) {
30890
+ const sessionPath = join54(MESSAGE_STORAGE, dir, sessionID);
30891
+ if (existsSync47(sessionPath))
30074
30892
  return sessionPath;
30075
30893
  }
30076
30894
  return null;
@@ -30137,7 +30955,7 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
30137
30955
  });
30138
30956
  }
30139
30957
  function delay(ms) {
30140
- return new Promise((resolve7) => setTimeout(resolve7, ms));
30958
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
30141
30959
  }
30142
30960
  function truncateText(text, maxLength) {
30143
30961
  if (text.length <= maxLength)
@@ -30512,9 +31330,9 @@ session_id: ${sessionID}
30512
31330
  }
30513
31331
  // src/tools/look-at/constants.ts
30514
31332
  var MULTIMODAL_LOOKER_AGENT = "multimodal-looker";
30515
- var LOOK_AT_DESCRIPTION = `Analyze media files (PDFs, images, diagrams) via Gemini 2.5 Flash in separate context. Saves main context tokens.`;
31333
+ var LOOK_AT_DESCRIPTION = `Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents.`;
30516
31334
  // src/tools/look-at/tools.ts
30517
- import { extname as extname3, basename as basename5 } from "path";
31335
+ import { extname as extname3, basename as basename6 } from "path";
30518
31336
  import { pathToFileURL } from "url";
30519
31337
  function inferMimeType(filePath) {
30520
31338
  const ext = extname3(filePath).toLowerCase();
@@ -30522,20 +31340,34 @@ function inferMimeType(filePath) {
30522
31340
  ".jpg": "image/jpeg",
30523
31341
  ".jpeg": "image/jpeg",
30524
31342
  ".png": "image/png",
30525
- ".gif": "image/gif",
30526
31343
  ".webp": "image/webp",
30527
- ".svg": "image/svg+xml",
30528
- ".bmp": "image/bmp",
30529
- ".ico": "image/x-icon",
31344
+ ".heic": "image/heic",
31345
+ ".heif": "image/heif",
31346
+ ".mp4": "video/mp4",
31347
+ ".mpeg": "video/mpeg",
31348
+ ".mpg": "video/mpeg",
31349
+ ".mov": "video/mov",
31350
+ ".avi": "video/avi",
31351
+ ".flv": "video/x-flv",
31352
+ ".webm": "video/webm",
31353
+ ".wmv": "video/wmv",
31354
+ ".3gpp": "video/3gpp",
31355
+ ".3gp": "video/3gpp",
31356
+ ".wav": "audio/wav",
31357
+ ".mp3": "audio/mp3",
31358
+ ".aiff": "audio/aiff",
31359
+ ".aac": "audio/aac",
31360
+ ".ogg": "audio/ogg",
31361
+ ".flac": "audio/flac",
30530
31362
  ".pdf": "application/pdf",
30531
31363
  ".txt": "text/plain",
30532
- ".md": "text/markdown",
31364
+ ".csv": "text/csv",
31365
+ ".md": "text/md",
31366
+ ".html": "text/html",
30533
31367
  ".json": "application/json",
30534
31368
  ".xml": "application/xml",
30535
- ".html": "text/html",
30536
- ".css": "text/css",
30537
31369
  ".js": "text/javascript",
30538
- ".ts": "text/typescript"
31370
+ ".py": "text/x-python"
30539
31371
  };
30540
31372
  return mimeTypes[ext] || "application/octet-stream";
30541
31373
  }
@@ -30549,7 +31381,7 @@ function createLookAt(ctx) {
30549
31381
  async execute(args, toolContext) {
30550
31382
  log(`[look_at] Analyzing file: ${args.file_path}, goal: ${args.goal}`);
30551
31383
  const mimeType = inferMimeType(args.file_path);
30552
- const filename = basename5(args.file_path);
31384
+ const filename = basename6(args.file_path);
30553
31385
  const prompt = `Analyze this file and extract the requested information.
30554
31386
 
30555
31387
  Goal: ${args.goal}
@@ -30642,17 +31474,17 @@ var builtinTools = {
30642
31474
  session_info
30643
31475
  };
30644
31476
  // src/features/background-agent/manager.ts
30645
- import { existsSync as existsSync44, readdirSync as readdirSync18 } from "fs";
30646
- import { join as join53 } from "path";
31477
+ import { existsSync as existsSync48, readdirSync as readdirSync19 } from "fs";
31478
+ import { join as join55 } from "path";
30647
31479
  function getMessageDir11(sessionID) {
30648
- if (!existsSync44(MESSAGE_STORAGE))
31480
+ if (!existsSync48(MESSAGE_STORAGE))
30649
31481
  return null;
30650
- const directPath = join53(MESSAGE_STORAGE, sessionID);
30651
- if (existsSync44(directPath))
31482
+ const directPath = join55(MESSAGE_STORAGE, sessionID);
31483
+ if (existsSync48(directPath))
30652
31484
  return directPath;
30653
- for (const dir of readdirSync18(MESSAGE_STORAGE)) {
30654
- const sessionPath = join53(MESSAGE_STORAGE, dir, sessionID);
30655
- if (existsSync44(sessionPath))
31485
+ for (const dir of readdirSync19(MESSAGE_STORAGE)) {
31486
+ const sessionPath = join55(MESSAGE_STORAGE, dir, sessionID);
31487
+ if (existsSync48(sessionPath))
30656
31488
  return sessionPath;
30657
31489
  }
30658
31490
  return null;
@@ -31086,12 +31918,11 @@ var HookNameSchema = exports_external.enum([
31086
31918
  "session-notification",
31087
31919
  "comment-checker",
31088
31920
  "grep-output-truncator",
31089
- "tool-output-truncator",
31090
31921
  "directory-agents-injector",
31091
31922
  "directory-readme-injector",
31092
31923
  "empty-task-response-detector",
31093
31924
  "think-mode",
31094
- "anthropic-auto-compact",
31925
+ "anthropic-context-window-limit-recovery",
31095
31926
  "rules-injector",
31096
31927
  "background-notification",
31097
31928
  "auto-update-checker",
@@ -31101,7 +31932,8 @@ var HookNameSchema = exports_external.enum([
31101
31932
  "non-interactive-env",
31102
31933
  "interactive-bash-session",
31103
31934
  "empty-message-sanitizer",
31104
- "thinking-block-validator"
31935
+ "thinking-block-validator",
31936
+ "ralph-loop"
31105
31937
  ]);
31106
31938
  var BuiltinCommandNameSchema = exports_external.enum([
31107
31939
  "init-deep"
@@ -31184,12 +32016,52 @@ var DynamicContextPruningConfigSchema = exports_external.object({
31184
32016
  var ExperimentalConfigSchema = exports_external.object({
31185
32017
  aggressive_truncation: exports_external.boolean().optional(),
31186
32018
  auto_resume: exports_external.boolean().optional(),
32019
+ tool_output_truncator: exports_external.boolean().optional(),
31187
32020
  preemptive_compaction: exports_external.boolean().optional(),
31188
32021
  preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
31189
- truncate_all_tool_outputs: exports_external.boolean().default(true),
32022
+ truncate_all_tool_outputs: exports_external.boolean().optional(),
31190
32023
  dynamic_context_pruning: DynamicContextPruningConfigSchema.optional(),
31191
32024
  dcp_for_compaction: exports_external.boolean().optional()
31192
32025
  });
32026
+ var SkillSourceSchema = exports_external.union([
32027
+ exports_external.string(),
32028
+ exports_external.object({
32029
+ path: exports_external.string(),
32030
+ recursive: exports_external.boolean().optional(),
32031
+ glob: exports_external.string().optional()
32032
+ })
32033
+ ]);
32034
+ var SkillDefinitionSchema = exports_external.object({
32035
+ description: exports_external.string().optional(),
32036
+ template: exports_external.string().optional(),
32037
+ from: exports_external.string().optional(),
32038
+ model: exports_external.string().optional(),
32039
+ agent: exports_external.string().optional(),
32040
+ subtask: exports_external.boolean().optional(),
32041
+ "argument-hint": exports_external.string().optional(),
32042
+ license: exports_external.string().optional(),
32043
+ compatibility: exports_external.string().optional(),
32044
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
32045
+ "allowed-tools": exports_external.array(exports_external.string()).optional(),
32046
+ disable: exports_external.boolean().optional()
32047
+ });
32048
+ var SkillEntrySchema = exports_external.union([
32049
+ exports_external.boolean(),
32050
+ SkillDefinitionSchema
32051
+ ]);
32052
+ var SkillsConfigSchema = exports_external.union([
32053
+ exports_external.array(exports_external.string()),
32054
+ exports_external.record(exports_external.string(), SkillEntrySchema).and(exports_external.object({
32055
+ sources: exports_external.array(SkillSourceSchema).optional(),
32056
+ enable: exports_external.array(exports_external.string()).optional(),
32057
+ disable: exports_external.array(exports_external.string()).optional()
32058
+ }).partial())
32059
+ ]);
32060
+ var RalphLoopConfigSchema = exports_external.object({
32061
+ enabled: exports_external.boolean().default(false),
32062
+ default_max_iterations: exports_external.number().min(1).max(1000).default(100),
32063
+ state_dir: exports_external.string().optional()
32064
+ });
31193
32065
  var OhMyOpenCodeConfigSchema = exports_external.object({
31194
32066
  $schema: exports_external.string().optional(),
31195
32067
  disabled_mcps: exports_external.array(McpNameSchema).optional(),
@@ -31202,7 +32074,9 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
31202
32074
  sisyphus_agent: SisyphusAgentConfigSchema.optional(),
31203
32075
  comment_checker: CommentCheckerConfigSchema.optional(),
31204
32076
  experimental: ExperimentalConfigSchema.optional(),
31205
- auto_update: exports_external.boolean().optional()
32077
+ auto_update: exports_external.boolean().optional(),
32078
+ skills: SkillsConfigSchema.optional(),
32079
+ ralph_loop: RalphLoopConfigSchema.optional()
31206
32080
  });
31207
32081
  // src/agents/plan-prompt.ts
31208
32082
  var PLAN_SYSTEM_PROMPT = `<system-reminder>
@@ -31426,7 +32300,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
31426
32300
  const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
31427
32301
  const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
31428
32302
  const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks(pluginConfig.comment_checker) : null;
31429
- const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
32303
+ const toolOutputTruncator = pluginConfig.experimental?.tool_output_truncator === true ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
31430
32304
  const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
31431
32305
  const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
31432
32306
  const emptyTaskResponseDetector = isHookEnabled("empty-task-response-detector") ? createEmptyTaskResponseDetectorHook(ctx) : null;
@@ -31434,7 +32308,10 @@ var OhMyOpenCodePlugin = async (ctx) => {
31434
32308
  const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {
31435
32309
  disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
31436
32310
  });
31437
- const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx, { experimental: pluginConfig.experimental }) : null;
32311
+ const anthropicContextWindowLimitRecovery = isHookEnabled("anthropic-context-window-limit-recovery") ? createAnthropicContextWindowLimitRecoveryHook(ctx, {
32312
+ experimental: pluginConfig.experimental,
32313
+ dcpForCompaction: pluginConfig.experimental?.dcp_for_compaction
32314
+ }) : null;
31438
32315
  const compactionContextInjector = createCompactionContextInjector();
31439
32316
  const preemptiveCompaction = createPreemptiveCompactionHook(ctx, {
31440
32317
  experimental: pluginConfig.experimental,
@@ -31453,6 +32330,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
31453
32330
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
31454
32331
  const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
31455
32332
  const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
32333
+ const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, { config: pluginConfig.ralph_loop }) : null;
31456
32334
  const backgroundManager = new BackgroundManager(ctx);
31457
32335
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
31458
32336
  if (sessionRecovery && todoContinuationEnforcer) {
@@ -31463,6 +32341,10 @@ var OhMyOpenCodePlugin = async (ctx) => {
31463
32341
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
31464
32342
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
31465
32343
  const lookAt = createLookAt(ctx);
32344
+ const builtinSkills = createBuiltinSkills();
32345
+ const includeClaudeSkills = pluginConfig.claude_code?.skills !== false;
32346
+ const mergedSkills = mergeSkills(builtinSkills, pluginConfig.skills, includeClaudeSkills ? discoverUserClaudeSkills() : [], discoverOpencodeGlobalSkills(), includeClaudeSkills ? discoverProjectClaudeSkills() : [], discoverOpencodeProjectSkills());
32347
+ const skillTool = createSkillTool({ skills: mergedSkills });
31466
32348
  const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
31467
32349
  const tmuxAvailable = await getTmuxPath();
31468
32350
  return {
@@ -31472,11 +32354,35 @@ var OhMyOpenCodePlugin = async (ctx) => {
31472
32354
  ...backgroundTools,
31473
32355
  call_omo_agent: callOmoAgent,
31474
32356
  look_at: lookAt,
32357
+ skill: skillTool,
31475
32358
  ...tmuxAvailable ? { interactive_bash } : {}
31476
32359
  },
31477
32360
  "chat.message": async (input, output) => {
31478
32361
  await claudeCodeHooks["chat.message"]?.(input, output);
31479
32362
  await keywordDetector?.["chat.message"]?.(input, output);
32363
+ if (ralphLoop) {
32364
+ const parts = output.parts;
32365
+ const promptText = parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
32366
+ `).trim() || "";
32367
+ const isRalphLoopTemplate = promptText.includes("You are starting a Ralph Loop") && promptText.includes("<user-task>");
32368
+ const isCancelRalphTemplate = promptText.includes("Cancel the currently active Ralph Loop");
32369
+ if (isRalphLoopTemplate) {
32370
+ const taskMatch = promptText.match(/<user-task>\s*([\s\S]*?)\s*<\/user-task>/i);
32371
+ const rawTask = taskMatch?.[1]?.trim() || "";
32372
+ const quotedMatch = rawTask.match(/^["'](.+?)["']/);
32373
+ const prompt = quotedMatch?.[1] || rawTask.split(/\s+--/)[0]?.trim() || "Complete the task as instructed";
32374
+ const maxIterMatch = rawTask.match(/--max-iterations=(\d+)/i);
32375
+ const promiseMatch = rawTask.match(/--completion-promise=["']?([^"'\s]+)["']?/i);
32376
+ log("[ralph-loop] Starting loop from chat.message", { sessionID: input.sessionID, prompt });
32377
+ ralphLoop.startLoop(input.sessionID, prompt, {
32378
+ maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
32379
+ completionPromise: promiseMatch?.[1]
32380
+ });
32381
+ } else if (isCancelRalphTemplate) {
32382
+ log("[ralph-loop] Cancelling loop from chat.message", { sessionID: input.sessionID });
32383
+ ralphLoop.cancelLoop(input.sessionID);
32384
+ }
32385
+ }
31480
32386
  },
31481
32387
  "experimental.chat.messages.transform": async (input, output) => {
31482
32388
  await thinkingBlockValidator?.["experimental.chat.messages.transform"]?.(input, output);
@@ -31611,14 +32517,23 @@ var OhMyOpenCodePlugin = async (ctx) => {
31611
32517
  const systemCommands = config3.command ?? {};
31612
32518
  const projectCommands = pluginConfig.claude_code?.commands ?? true ? loadProjectCommands() : {};
31613
32519
  const opencodeProjectCommands = loadOpencodeProjectCommands();
32520
+ const userSkills = pluginConfig.claude_code?.skills ?? true ? loadUserSkills() : {};
32521
+ const projectSkills = pluginConfig.claude_code?.skills ?? true ? loadProjectSkills() : {};
32522
+ const opencodeGlobalSkills = loadOpencodeGlobalSkills();
32523
+ const opencodeProjectSkills = loadOpencodeProjectSkills();
31614
32524
  config3.command = {
31615
32525
  ...builtinCommands,
31616
32526
  ...userCommands,
32527
+ ...userSkills,
31617
32528
  ...opencodeGlobalCommands,
32529
+ ...opencodeGlobalSkills,
31618
32530
  ...systemCommands,
31619
32531
  ...projectCommands,
32532
+ ...projectSkills,
31620
32533
  ...opencodeProjectCommands,
31621
- ...pluginComponents.commands
32534
+ ...opencodeProjectSkills,
32535
+ ...pluginComponents.commands,
32536
+ ...pluginComponents.skills
31622
32537
  };
31623
32538
  },
31624
32539
  event: async (input) => {
@@ -31632,10 +32547,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
31632
32547
  await directoryReadmeInjector?.event(input);
31633
32548
  await rulesInjector?.event(input);
31634
32549
  await thinkMode?.event(input);
31635
- await anthropicAutoCompact?.event(input);
32550
+ await anthropicContextWindowLimitRecovery?.event(input);
31636
32551
  await preemptiveCompaction?.event(input);
31637
32552
  await agentUsageReminder?.event(input);
31638
32553
  await interactiveBashSession?.event(input);
32554
+ await ralphLoop?.event(input);
31639
32555
  const { event } = input;
31640
32556
  const props = event.properties;
31641
32557
  if (event.type === "session.created") {
@@ -31688,6 +32604,24 @@ var OhMyOpenCodePlugin = async (ctx) => {
31688
32604
  ...isExploreOrLibrarian ? { call_omo_agent: false } : {}
31689
32605
  };
31690
32606
  }
32607
+ if (ralphLoop && input.tool === "slashcommand") {
32608
+ const args = output.args;
32609
+ const command = args?.command?.replace(/^\//, "").toLowerCase();
32610
+ const sessionID = input.sessionID || getMainSessionID();
32611
+ if (command === "ralph-loop" && sessionID) {
32612
+ const rawArgs = args?.command?.replace(/^\/?(ralph-loop)\s*/i, "") || "";
32613
+ const taskMatch = rawArgs.match(/^["'](.+?)["']/);
32614
+ const prompt = taskMatch?.[1] || rawArgs.split(/\s+--/)[0]?.trim() || "Complete the task as instructed";
32615
+ const maxIterMatch = rawArgs.match(/--max-iterations=(\d+)/i);
32616
+ const promiseMatch = rawArgs.match(/--completion-promise=["']?([^"'\s]+)["']?/i);
32617
+ ralphLoop.startLoop(sessionID, prompt, {
32618
+ maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
32619
+ completionPromise: promiseMatch?.[1]
32620
+ });
32621
+ } else if (command === "cancel-ralph" && sessionID) {
32622
+ ralphLoop.cancelLoop(sessionID);
32623
+ }
32624
+ }
31691
32625
  },
31692
32626
  "tool.execute.after": async (input, output) => {
31693
32627
  await claudeCodeHooks["tool.execute.after"](input, output);