@omnidev-ai/cli 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1091 -340
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1833,47 +1833,23 @@ function resolveCapabilityRoot(content, capabilityPath) {
1833
1833
  }
1834
1834
  return result;
1835
1835
  }
1836
- function resolveCapabilityRootInConfig(config, capabilityPath) {
1837
- const result = {};
1838
- if (config.description !== undefined) {
1839
- result.description = config.description;
1836
+ function resolveCapabilityRootInValue(value, capabilityPath) {
1837
+ if (typeof value === "string") {
1838
+ return resolveCapabilityRoot(value, capabilityPath);
1840
1839
  }
1841
- const events = [
1842
- "PreToolUse",
1843
- "PostToolUse",
1844
- "PermissionRequest",
1845
- "UserPromptSubmit",
1846
- "Stop",
1847
- "SubagentStop",
1848
- "Notification",
1849
- "SessionStart",
1850
- "SessionEnd",
1851
- "PreCompact"
1852
- ];
1853
- for (const event of events) {
1854
- const matchers = config[event];
1855
- if (matchers) {
1856
- result[event] = matchers.map((matcher) => ({
1857
- ...matcher,
1858
- hooks: matcher.hooks.map((hook) => {
1859
- if (hook.type === "command") {
1860
- return {
1861
- ...hook,
1862
- command: resolveCapabilityRoot(hook.command, capabilityPath)
1863
- };
1864
- }
1865
- if (hook.type === "prompt") {
1866
- return {
1867
- ...hook,
1868
- prompt: resolveCapabilityRoot(hook.prompt, capabilityPath)
1869
- };
1870
- }
1871
- return hook;
1872
- })
1873
- }));
1874
- }
1840
+ if (Array.isArray(value)) {
1841
+ return value.map((entry) => resolveCapabilityRootInValue(entry, capabilityPath));
1875
1842
  }
1876
- return result;
1843
+ if (value && typeof value === "object") {
1844
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
1845
+ key,
1846
+ resolveCapabilityRootInValue(entry, capabilityPath)
1847
+ ]));
1848
+ }
1849
+ return value;
1850
+ }
1851
+ function resolveCapabilityRootInConfig(config, capabilityPath) {
1852
+ return resolveCapabilityRootInValue(config, capabilityPath);
1877
1853
  }
1878
1854
  var REVERSE_MAPPINGS;
1879
1855
  var init_variables = __esm(() => {
@@ -2183,6 +2159,55 @@ function loadHooksFromCapability(capabilityPath, options) {
2183
2159
  }
2184
2160
  return loadJsonHooksFiles(capabilityPath, hooksJsonInDir, hooksJsonAtRoot, hooksDir, opts);
2185
2161
  }
2162
+ function splitHooksTomlConfig(parsed) {
2163
+ const topLevelIssues = [];
2164
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2165
+ return {
2166
+ sharedConfig: parsed,
2167
+ providerConfigs: undefined,
2168
+ topLevelIssues
2169
+ };
2170
+ }
2171
+ const parsedObj = parsed;
2172
+ const sharedConfig = {};
2173
+ const providerConfigs = {};
2174
+ for (const [key, value] of Object.entries(parsedObj)) {
2175
+ if (key === "description" || HOOK_EVENTS.includes(key)) {
2176
+ sharedConfig[key] = value;
2177
+ continue;
2178
+ }
2179
+ if (PROVIDER_SECTION_KEYS.includes(key)) {
2180
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2181
+ providerConfigs[key] = value;
2182
+ } else {
2183
+ topLevelIssues.push({
2184
+ severity: "error",
2185
+ code: "HOOKS_INVALID_TOML",
2186
+ message: `[${key}] must be a table`
2187
+ });
2188
+ }
2189
+ continue;
2190
+ }
2191
+ topLevelIssues.push({
2192
+ severity: "error",
2193
+ code: "HOOKS_UNKNOWN_EVENT",
2194
+ message: `Unknown hook event: "${key}"`,
2195
+ suggestion: `Valid events are: ${HOOK_EVENTS.join(", ")}, plus [claude] and [codex] sections`
2196
+ });
2197
+ }
2198
+ return {
2199
+ sharedConfig,
2200
+ providerConfigs: Object.keys(providerConfigs).length > 0 ? providerConfigs : undefined,
2201
+ topLevelIssues
2202
+ };
2203
+ }
2204
+ function prefixValidationIssues(issues, sectionLabel) {
2205
+ return issues.map((issue) => ({
2206
+ ...issue,
2207
+ message: `[${sectionLabel}] ${issue.message}`,
2208
+ ...issue.suggestion ? { suggestion: `[${sectionLabel}] ${issue.suggestion}` } : {}
2209
+ }));
2210
+ }
2186
2211
  function loadTomlHooks(capabilityPath, configPath, hooksDir, opts) {
2187
2212
  let rawContent;
2188
2213
  try {
@@ -2234,25 +2259,56 @@ function loadTomlHooks(capabilityPath, configPath, hooksDir, opts) {
2234
2259
  loadError: `Invalid TOML: ${error instanceof Error ? error.message : String(error)}`
2235
2260
  };
2236
2261
  }
2262
+ const {
2263
+ sharedConfig,
2264
+ providerConfigs: rawProviderConfigs,
2265
+ topLevelIssues
2266
+ } = splitHooksTomlConfig(parsed);
2237
2267
  let validation;
2238
2268
  if (opts.validate) {
2239
- validation = validateHooksConfig(parsed, {
2269
+ const sharedValidation = validateHooksConfig(sharedConfig, {
2240
2270
  basePath: hooksDir,
2241
2271
  checkScripts: opts.checkScripts ?? false
2242
2272
  });
2273
+ const providerErrors = [];
2274
+ const providerWarnings = [];
2275
+ for (const [provider, providerConfig] of Object.entries(rawProviderConfigs ?? {})) {
2276
+ const providerValidation = validateHooksConfig(providerConfig, {
2277
+ basePath: hooksDir,
2278
+ checkScripts: opts.checkScripts ?? false
2279
+ });
2280
+ providerErrors.push(...prefixValidationIssues(providerValidation.errors, provider));
2281
+ providerWarnings.push(...prefixValidationIssues(providerValidation.warnings, provider));
2282
+ }
2283
+ validation = {
2284
+ valid: sharedValidation.valid && topLevelIssues.length === 0 && providerErrors.length === 0,
2285
+ errors: [...topLevelIssues, ...sharedValidation.errors, ...providerErrors],
2286
+ warnings: [...sharedValidation.warnings, ...providerWarnings]
2287
+ };
2243
2288
  } else {
2244
2289
  validation = createEmptyValidationResult();
2245
2290
  }
2246
- let config = validation.valid ? parsed : createEmptyHooksConfig();
2291
+ let config = validation.valid ? sharedConfig : createEmptyHooksConfig();
2292
+ let providerConfigs = validation.valid ? rawProviderConfigs : undefined;
2247
2293
  if (opts.resolveCapabilityRoot && validation.valid) {
2248
2294
  config = resolveCapabilityRootInConfig(config, capabilityPath);
2295
+ if (providerConfigs) {
2296
+ providerConfigs = Object.fromEntries(Object.entries(providerConfigs).map(([provider, providerConfig]) => [
2297
+ provider,
2298
+ resolveCapabilityRootInValue(providerConfig, capabilityPath)
2299
+ ]));
2300
+ }
2249
2301
  }
2250
- return {
2302
+ const result = {
2251
2303
  config,
2252
2304
  validation,
2253
2305
  found: true,
2254
2306
  configPath
2255
2307
  };
2308
+ if (providerConfigs) {
2309
+ result.providerConfigs = providerConfigs;
2310
+ }
2311
+ return result;
2256
2312
  }
2257
2313
  function loadJsonHooksFiles(capabilityPath, hooksJsonInDir, hooksJsonAtRoot, hooksDir, opts) {
2258
2314
  const configs = [];
@@ -2348,6 +2404,7 @@ function loadCapabilityHooks(capabilityName, capabilityPath, options) {
2348
2404
  capabilityName,
2349
2405
  capabilityPath,
2350
2406
  config: result.config,
2407
+ ...result.providerConfigs ? { providerConfigs: result.providerConfigs } : {},
2351
2408
  validation: result.validation
2352
2409
  };
2353
2410
  }
@@ -2363,6 +2420,7 @@ function getHooksDirectory(capabilityPath) {
2363
2420
  function getHooksConfigPath(capabilityPath) {
2364
2421
  return join3(capabilityPath, HOOKS_DIRECTORY, HOOKS_CONFIG_FILENAME);
2365
2422
  }
2423
+ var PROVIDER_SECTION_KEYS;
2366
2424
  var init_loader = __esm(() => {
2367
2425
  init_dist();
2368
2426
  init_constants();
@@ -2370,6 +2428,7 @@ var init_loader = __esm(() => {
2370
2428
  init_variables();
2371
2429
  init_json_loader();
2372
2430
  init_constants();
2431
+ PROVIDER_SECTION_KEYS = ["claude", "codex"];
2373
2432
  });
2374
2433
 
2375
2434
  // ../core/src/providers.ts
@@ -2638,7 +2697,10 @@ async function loadSkills(capabilityPath, capabilityId, variables) {
2638
2697
  const skillPath = join6(dir, entry.name, "SKILL.md");
2639
2698
  if (existsSync8(skillPath)) {
2640
2699
  const skill = await parseSkillFile(skillPath, capabilityId, resolvedVariables, `skill file ${skillPath}`);
2641
- skills.push(skill);
2700
+ skills.push({
2701
+ ...skill,
2702
+ sourcePath: join6(dir, entry.name)
2703
+ });
2642
2704
  }
2643
2705
  }
2644
2706
  }
@@ -2665,75 +2727,235 @@ import { readFile as readFile6 } from "node:fs/promises";
2665
2727
  import { basename as basename4, join as join7 } from "node:path";
2666
2728
  async function loadSubagents(capabilityPath, capabilityId) {
2667
2729
  const subagents = [];
2668
- const possibleDirNames = ["subagents", "agents", "agent", "subagent"];
2669
- for (const dirName of possibleDirNames) {
2730
+ for (const dirName of POSSIBLE_DIR_NAMES) {
2670
2731
  const dir = join7(capabilityPath, dirName);
2671
2732
  if (!existsSync9(dir)) {
2672
2733
  continue;
2673
2734
  }
2735
+ const rootSubagent = await parseSubagentDirectory(dir, capabilityId);
2736
+ if (rootSubagent) {
2737
+ subagents.push(rootSubagent);
2738
+ }
2674
2739
  const entries = readdirSync5(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
2675
2740
  for (const entry of entries) {
2676
2741
  if (entry.isDirectory()) {
2677
- let subagentPath = join7(dir, entry.name, "SUBAGENT.md");
2678
- if (!existsSync9(subagentPath)) {
2679
- subagentPath = join7(dir, entry.name, "AGENT.md");
2680
- }
2681
- if (existsSync9(subagentPath)) {
2682
- const subagent = await parseSubagentFile(subagentPath, capabilityId);
2742
+ const subagent = await parseSubagentDirectory(join7(dir, entry.name), capabilityId);
2743
+ if (subagent) {
2683
2744
  subagents.push(subagent);
2684
2745
  }
2685
2746
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
2747
+ if (PROMPT_FILE_NAMES.includes(entry.name) || LEGACY_AGENT_FILES.includes(entry.name)) {
2748
+ continue;
2749
+ }
2686
2750
  const subagentPath = join7(dir, entry.name);
2687
- const subagent = await parseSubagentFile(subagentPath, capabilityId);
2688
- subagents.push(subagent);
2751
+ const content = await readFile6(subagentPath, "utf-8");
2752
+ subagents.push(parseLegacySubagentMarkdown(content, capabilityId, subagentPath, basename4(subagentPath, ".md")));
2689
2753
  }
2690
2754
  }
2691
2755
  }
2692
2756
  return subagents;
2693
2757
  }
2694
- async function parseSubagentFile(filePath, capabilityId) {
2695
- const content = await readFile6(filePath, "utf-8");
2758
+ function parseLegacySubagentMarkdown(content, capabilityId, sourceLabel, inferredName) {
2696
2759
  const parsed = parseFrontmatterWithMarkdown(content);
2697
2760
  if (!parsed) {
2698
- throw new Error(`Invalid SUBAGENT.md format at ${filePath}: missing YAML frontmatter`);
2761
+ throw new Error(`Invalid SUBAGENT.md format at ${sourceLabel}: missing YAML frontmatter`);
2699
2762
  }
2700
2763
  const frontmatter = parsed.frontmatter;
2701
- const systemPrompt = parsed.markdown;
2702
- const inferredName = basename4(filePath, ".md").replace(/^SUBAGENT$/i, "").replace(/^AGENT$/i, "");
2703
- const name = frontmatter.name || inferredName;
2764
+ const name = frontmatter.name || inferredName?.replace(/^SUBAGENT$/i, "").replace(/^AGENT$/i, "") || undefined;
2704
2765
  if (!name || !frontmatter.description) {
2705
- throw new Error(`Invalid SUBAGENT.md at ${filePath}: name and description required`);
2766
+ throw new Error(`Invalid SUBAGENT.md at ${sourceLabel}: name and description required`);
2706
2767
  }
2707
- const result = {
2708
- name,
2709
- description: frontmatter.description,
2710
- systemPrompt: systemPrompt.trim(),
2711
- capabilityId
2712
- };
2768
+ const claude = {};
2713
2769
  if (frontmatter.tools) {
2714
- result.tools = parseCommaSeparatedList(frontmatter.tools);
2770
+ claude.tools = parseCommaSeparatedList(frontmatter.tools);
2715
2771
  }
2716
2772
  if (frontmatter.disallowedTools) {
2717
- result.disallowedTools = parseCommaSeparatedList(frontmatter.disallowedTools);
2773
+ claude.disallowedTools = parseCommaSeparatedList(frontmatter.disallowedTools);
2718
2774
  }
2719
2775
  if (frontmatter.model) {
2720
- result.model = frontmatter.model;
2776
+ claude.model = frontmatter.model;
2721
2777
  }
2722
2778
  if (frontmatter.permissionMode) {
2723
- result.permissionMode = frontmatter.permissionMode;
2779
+ claude.permissionMode = frontmatter.permissionMode;
2724
2780
  }
2725
2781
  if (frontmatter.skills) {
2726
- result.skills = parseCommaSeparatedList(frontmatter.skills);
2782
+ claude.skills = parseCommaSeparatedList(frontmatter.skills);
2727
2783
  }
2728
2784
  if (frontmatter.hooks) {
2729
- result.hooks = frontmatter.hooks;
2785
+ claude.hooks = frontmatter.hooks;
2730
2786
  }
2731
- return result;
2787
+ const subagent = {
2788
+ name,
2789
+ description: frontmatter.description,
2790
+ systemPrompt: parsed.markdown.trim(),
2791
+ capabilityId
2792
+ };
2793
+ if (hasClaudeConfig(claude)) {
2794
+ subagent.claude = claude;
2795
+ }
2796
+ return withClaudeCompatibilityAliases(subagent);
2797
+ }
2798
+ function parseSubagentManifest(agentTomlContent, promptContent, capabilityId, sourceLabel) {
2799
+ let parsed;
2800
+ try {
2801
+ parsed = parse(agentTomlContent);
2802
+ } catch (error) {
2803
+ const message = error instanceof Error ? error.message : String(error);
2804
+ throw new Error(`Invalid agent.toml at ${sourceLabel}: ${message}`);
2805
+ }
2806
+ if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
2807
+ throw new Error(`Invalid agent.toml at ${sourceLabel}: name is required`);
2808
+ }
2809
+ if (typeof parsed.description !== "string" || parsed.description.trim().length === 0) {
2810
+ throw new Error(`Invalid agent.toml at ${sourceLabel}: description is required`);
2811
+ }
2812
+ const claude = parseClaudeConfig(parsed.claude, sourceLabel);
2813
+ const codex = parseCodexConfig(parsed.codex, sourceLabel);
2814
+ const subagent = {
2815
+ name: parsed.name.trim(),
2816
+ description: parsed.description.trim(),
2817
+ systemPrompt: promptContent.trim(),
2818
+ capabilityId
2819
+ };
2820
+ if (claude) {
2821
+ subagent.claude = claude;
2822
+ }
2823
+ if (codex) {
2824
+ subagent.codex = codex;
2825
+ }
2826
+ return withClaudeCompatibilityAliases(subagent);
2827
+ }
2828
+ async function parseSubagentDirectory(dirPath, capabilityId) {
2829
+ const manifestPath = findFirstExisting(dirPath, MANIFEST_FILE_NAMES);
2830
+ const promptPath = findFirstExisting(dirPath, PROMPT_FILE_NAMES);
2831
+ if (manifestPath || promptPath) {
2832
+ if (!manifestPath || !promptPath) {
2833
+ throw new Error(`Invalid subagent directory at ${dirPath}: agent.toml and prompt.md must both be present`);
2834
+ }
2835
+ const [agentTomlContent, promptContent] = await Promise.all([
2836
+ readFile6(manifestPath, "utf-8"),
2837
+ readFile6(promptPath, "utf-8")
2838
+ ]);
2839
+ return parseSubagentManifest(agentTomlContent, promptContent, capabilityId, manifestPath);
2840
+ }
2841
+ const legacyPath = findFirstExisting(dirPath, LEGACY_AGENT_FILES);
2842
+ if (!legacyPath) {
2843
+ return null;
2844
+ }
2845
+ const content = await readFile6(legacyPath, "utf-8");
2846
+ return parseLegacySubagentMarkdown(content, capabilityId, legacyPath);
2847
+ }
2848
+ function findFirstExisting(dirPath, fileNames) {
2849
+ for (const fileName of fileNames) {
2850
+ const filePath = join7(dirPath, fileName);
2851
+ if (existsSync9(filePath)) {
2852
+ return filePath;
2853
+ }
2854
+ }
2855
+ return null;
2856
+ }
2857
+ function parseClaudeConfig(value, sourceLabel) {
2858
+ if (!value || typeof value !== "object") {
2859
+ return;
2860
+ }
2861
+ const claude = {};
2862
+ if (value.tools !== undefined) {
2863
+ claude.tools = readStringArray(value.tools, `${sourceLabel} [claude].tools`);
2864
+ }
2865
+ if (value.disallowed_tools !== undefined) {
2866
+ claude.disallowedTools = readStringArray(value.disallowed_tools, `${sourceLabel} [claude].disallowed_tools`);
2867
+ }
2868
+ if (value.model !== undefined) {
2869
+ claude.model = readEnum(value.model, ["sonnet", "opus", "haiku", "inherit"], `${sourceLabel} [claude].model`);
2870
+ }
2871
+ if (value.permission_mode !== undefined) {
2872
+ claude.permissionMode = readEnum(value.permission_mode, ["default", "acceptEdits", "dontAsk", "bypassPermissions", "plan"], `${sourceLabel} [claude].permission_mode`);
2873
+ }
2874
+ if (value.skills !== undefined) {
2875
+ claude.skills = readStringArray(value.skills, `${sourceLabel} [claude].skills`);
2876
+ }
2877
+ if (value.hooks !== undefined) {
2878
+ if (typeof value.hooks !== "object" || value.hooks === null) {
2879
+ throw new Error(`Invalid ${sourceLabel} [claude].hooks: expected a table`);
2880
+ }
2881
+ claude.hooks = value.hooks;
2882
+ }
2883
+ return hasClaudeConfig(claude) ? claude : undefined;
2884
+ }
2885
+ function parseCodexConfig(value, sourceLabel) {
2886
+ if (!value || typeof value !== "object") {
2887
+ return;
2888
+ }
2889
+ const codex = {};
2890
+ if (value.model !== undefined) {
2891
+ codex.model = readString(value.model, `${sourceLabel} [codex].model`);
2892
+ }
2893
+ if (value.model_reasoning_effort !== undefined) {
2894
+ codex.modelReasoningEffort = readEnum(value.model_reasoning_effort, ["low", "medium", "high", "xhigh"], `${sourceLabel} [codex].model_reasoning_effort`);
2895
+ }
2896
+ if (value.sandbox_mode !== undefined) {
2897
+ codex.sandboxMode = readEnum(value.sandbox_mode, ["read-only", "workspace-write", "danger-full-access"], `${sourceLabel} [codex].sandbox_mode`);
2898
+ }
2899
+ if (value.nickname_candidates !== undefined) {
2900
+ codex.nicknameCandidates = readStringArray(value.nickname_candidates, `${sourceLabel} [codex].nickname_candidates`);
2901
+ }
2902
+ return Object.keys(codex).length > 0 ? codex : undefined;
2903
+ }
2904
+ function withClaudeCompatibilityAliases(subagent) {
2905
+ if (subagent.claude) {
2906
+ if (subagent.claude.tools) {
2907
+ subagent.tools = subagent.claude.tools;
2908
+ }
2909
+ if (subagent.claude.disallowedTools) {
2910
+ subagent.disallowedTools = subagent.claude.disallowedTools;
2911
+ }
2912
+ if (subagent.claude.model) {
2913
+ subagent.model = subagent.claude.model;
2914
+ }
2915
+ if (subagent.claude.permissionMode) {
2916
+ subagent.permissionMode = subagent.claude.permissionMode;
2917
+ }
2918
+ if (subagent.claude.skills) {
2919
+ subagent.skills = subagent.claude.skills;
2920
+ }
2921
+ if (subagent.claude.hooks) {
2922
+ subagent.hooks = subagent.claude.hooks;
2923
+ }
2924
+ }
2925
+ return subagent;
2926
+ }
2927
+ function hasClaudeConfig(config) {
2928
+ return Object.keys(config).length > 0;
2929
+ }
2930
+ function readString(value, fieldName) {
2931
+ if (typeof value !== "string" || value.trim().length === 0) {
2932
+ throw new Error(`Invalid ${fieldName}: expected a non-empty string`);
2933
+ }
2934
+ return value.trim();
2935
+ }
2936
+ function readStringArray(value, fieldName) {
2937
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
2938
+ throw new Error(`Invalid ${fieldName}: expected an array of strings`);
2939
+ }
2940
+ return value.map((item) => item.trim()).filter(Boolean);
2941
+ }
2942
+ function readEnum(value, allowed, fieldName) {
2943
+ if (typeof value !== "string" || !allowed.includes(value)) {
2944
+ throw new Error(`Invalid ${fieldName}: expected one of ${allowed.join(", ")}`);
2945
+ }
2946
+ return value;
2732
2947
  }
2733
2948
  function parseCommaSeparatedList(value) {
2734
2949
  return value.split(",").map((item) => item.trim()).filter(Boolean);
2735
2950
  }
2736
- var init_subagents = () => {};
2951
+ var POSSIBLE_DIR_NAMES, LEGACY_AGENT_FILES, MANIFEST_FILE_NAMES, PROMPT_FILE_NAMES;
2952
+ var init_subagents = __esm(() => {
2953
+ init_dist();
2954
+ POSSIBLE_DIR_NAMES = ["subagents", "agents", "agent", "subagent"];
2955
+ LEGACY_AGENT_FILES = ["SUBAGENT.md", "subagent.md", "AGENT.md", "agent.md", "Agent.md"];
2956
+ MANIFEST_FILE_NAMES = ["agent.toml", "AGENT.toml"];
2957
+ PROMPT_FILE_NAMES = ["prompt.md", "PROMPT.md"];
2958
+ });
2737
2959
 
2738
2960
  // ../core/src/capability/loader.ts
2739
2961
  import { existsSync as existsSync10, readdirSync as readdirSync6 } from "node:fs";
@@ -2832,75 +3054,19 @@ function convertDocExports(docExports, capabilityId) {
2832
3054
  });
2833
3055
  }
2834
3056
  function convertSubagentExports(subagentExports, capabilityId) {
2835
- return subagentExports.map((subagentExport) => {
3057
+ return subagentExports.map((subagentExport, index) => {
2836
3058
  const exportObj = subagentExport;
2837
- const lines = exportObj.subagentMd.split(`
2838
- `);
2839
- let name = "unnamed";
2840
- let description = "";
2841
- let systemPrompt = exportObj.subagentMd;
2842
- let tools;
2843
- let disallowedTools;
2844
- let model;
2845
- let permissionMode;
2846
- let skills;
2847
- if (lines[0]?.trim() === "---") {
2848
- const endIndex = lines.findIndex((line, i) => i > 0 && line.trim() === "---");
2849
- if (endIndex > 0) {
2850
- const frontmatter = lines.slice(1, endIndex);
2851
- systemPrompt = lines.slice(endIndex + 1).join(`
2852
- `).trim();
2853
- for (const line of frontmatter) {
2854
- const match = line.match(/^(\w+):\s*(.+)$/);
2855
- if (match?.[1] && match[2]) {
2856
- const key = match[1];
2857
- const value = match[2].replace(/^["']|["']$/g, "");
2858
- switch (key) {
2859
- case "name":
2860
- name = value;
2861
- break;
2862
- case "description":
2863
- description = value;
2864
- break;
2865
- case "tools":
2866
- tools = value.split(",").map((t) => t.trim());
2867
- break;
2868
- case "disallowedTools":
2869
- disallowedTools = value.split(",").map((t) => t.trim());
2870
- break;
2871
- case "model":
2872
- model = value;
2873
- break;
2874
- case "permissionMode":
2875
- permissionMode = value;
2876
- break;
2877
- case "skills":
2878
- skills = value.split(",").map((s) => s.trim());
2879
- break;
2880
- }
2881
- }
2882
- }
3059
+ const sourceLabel = `programmatic subagent export[${index}]`;
3060
+ if (typeof exportObj.agentToml === "string" || typeof exportObj.promptMd === "string") {
3061
+ if (typeof exportObj.agentToml !== "string" || typeof exportObj.promptMd !== "string") {
3062
+ throw new Error(`Invalid ${sourceLabel}: agentToml and promptMd must both be provided`);
2883
3063
  }
3064
+ return parseSubagentManifest(exportObj.agentToml, exportObj.promptMd, capabilityId, sourceLabel);
2884
3065
  }
2885
- const result = {
2886
- name,
2887
- description,
2888
- systemPrompt,
2889
- capabilityId
2890
- };
2891
- if (tools)
2892
- result.tools = tools;
2893
- if (disallowedTools)
2894
- result.disallowedTools = disallowedTools;
2895
- if (model) {
2896
- result.model = model;
2897
- }
2898
- if (permissionMode) {
2899
- result.permissionMode = permissionMode;
2900
- }
2901
- if (skills)
2902
- result.skills = skills;
2903
- return result;
3066
+ if (typeof exportObj.subagentMd === "string") {
3067
+ return parseLegacySubagentMarkdown(exportObj.subagentMd, capabilityId, sourceLabel, `subagent-${index + 1}`);
3068
+ }
3069
+ throw new Error(`Invalid ${sourceLabel}: expected agentToml + promptMd or subagentMd`);
2904
3070
  });
2905
3071
  }
2906
3072
  function convertCommandExports(commandExports, capabilityId) {
@@ -3524,7 +3690,7 @@ import { existsSync as existsSync13 } from "node:fs";
3524
3690
  import { spawn } from "node:child_process";
3525
3691
  import { cp, mkdir, readdir, readFile as readFile10, rename, rm, stat, writeFile as writeFile3 } from "node:fs/promises";
3526
3692
  import { join as join9 } from "node:path";
3527
- import { createHash } from "node:crypto";
3693
+ import { createHash as createHash2 } from "node:crypto";
3528
3694
  async function spawnCapture(command, args, options) {
3529
3695
  const timeout = options?.timeout ?? 60000;
3530
3696
  return await new Promise((resolve2, reject) => {
@@ -3733,7 +3899,7 @@ function escapeTomlString(value) {
3733
3899
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
3734
3900
  }
3735
3901
  async function computeContentHash(dirPath, excludePatterns = CONTENT_HASH_EXCLUDES) {
3736
- const hash = createHash("sha256");
3902
+ const hash = createHash2("sha256");
3737
3903
  const files = [];
3738
3904
  async function collectFiles(currentPath, relativeTo) {
3739
3905
  const entries = await readdir(currentPath, { withFileTypes: true });
@@ -4758,7 +4924,15 @@ var init_sources = __esm(() => {
4758
4924
  /^copying(?:\..+)?$/i
4759
4925
  ];
4760
4926
  SKILL_FILES = ["SKILL.md", "skill.md", "Skill.md"];
4761
- AGENT_FILES = ["AGENT.md", "agent.md", "Agent.md", "SUBAGENT.md", "subagent.md"];
4927
+ AGENT_FILES = [
4928
+ "AGENT.md",
4929
+ "agent.md",
4930
+ "Agent.md",
4931
+ "SUBAGENT.md",
4932
+ "subagent.md",
4933
+ "agent.toml",
4934
+ "AGENT.toml"
4935
+ ];
4762
4936
  COMMAND_FILES = ["COMMAND.md", "command.md", "Command.md"];
4763
4937
  CONTENT_HASH_EXCLUDES = [
4764
4938
  ".git",
@@ -4786,6 +4960,211 @@ var init_capability = __esm(() => {
4786
4960
  init_subagents();
4787
4961
  });
4788
4962
 
4963
+ // ../core/src/hooks/provider-config.ts
4964
+ function getProviderDisplayName(providerId) {
4965
+ switch (providerId) {
4966
+ case "claude-code":
4967
+ return "Claude Code";
4968
+ case "codex":
4969
+ return "Codex";
4970
+ case "cursor":
4971
+ return "Cursor";
4972
+ case "opencode":
4973
+ return "OpenCode";
4974
+ }
4975
+ }
4976
+ function hasOwnKey(value, key) {
4977
+ return Object.hasOwn(value, key);
4978
+ }
4979
+ function hasHooksInConfig(config) {
4980
+ if (!config) {
4981
+ return false;
4982
+ }
4983
+ for (const event of HOOK_EVENTS) {
4984
+ const matchers = config[event];
4985
+ if (Array.isArray(matchers) && matchers.length > 0) {
4986
+ return true;
4987
+ }
4988
+ }
4989
+ return false;
4990
+ }
4991
+ function matcherMatchesAny(matcher, values) {
4992
+ if (matcher === undefined || matcher === "" || matcher === "*") {
4993
+ return true;
4994
+ }
4995
+ try {
4996
+ const regex = new RegExp(matcher);
4997
+ return values.some((value) => regex.test(value));
4998
+ } catch {
4999
+ return false;
5000
+ }
5001
+ }
5002
+ function cloneMatcher(matcher) {
5003
+ return {
5004
+ ...matcher.matcher !== undefined ? { matcher: matcher.matcher } : {},
5005
+ hooks: matcher.hooks.map((hook) => ({ ...hook }))
5006
+ };
5007
+ }
5008
+ function filterSharedHooksForCodex(config, capabilityName) {
5009
+ const warnings = [];
5010
+ if (process.platform === "win32") {
5011
+ if (hasHooksInConfig(config)) {
5012
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" has shared hooks, but Codex hooks are currently disabled on Windows. Skipping Codex hook output.`);
5013
+ }
5014
+ return { config: {}, warnings };
5015
+ }
5016
+ const filtered = {};
5017
+ for (const event of HOOK_EVENTS) {
5018
+ const matchers = config[event];
5019
+ if (!matchers || matchers.length === 0) {
5020
+ continue;
5021
+ }
5022
+ if (!CODEX_SUPPORTED_EVENTS.has(event)) {
5023
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" shared event "${event}" is not supported by Codex and was skipped.`);
5024
+ continue;
5025
+ }
5026
+ const filteredMatchers = [];
5027
+ for (const matcher of matchers) {
5028
+ const nextMatcher = cloneMatcher(matcher);
5029
+ const originalHookCount = nextMatcher.hooks.length;
5030
+ nextMatcher.hooks = nextMatcher.hooks.filter((hook, hookIndex) => {
5031
+ if (hook.type === "command") {
5032
+ return true;
5033
+ }
5034
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" shared ${event} hook ${hookIndex + 1} uses prompt hooks, which Codex does not support. It was skipped.`);
5035
+ return false;
5036
+ });
5037
+ if (nextMatcher.hooks.length === 0) {
5038
+ continue;
5039
+ }
5040
+ if (event === "UserPromptSubmit" || event === "Stop") {
5041
+ if (nextMatcher.matcher !== undefined && nextMatcher.matcher !== "" && nextMatcher.matcher !== "*") {
5042
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" shared ${event} matcher "${nextMatcher.matcher}" is ignored by Codex. The matcher was dropped.`);
5043
+ delete nextMatcher.matcher;
5044
+ }
5045
+ filteredMatchers.push(nextMatcher);
5046
+ continue;
5047
+ }
5048
+ if (event === "PreToolUse" || event === "PostToolUse") {
5049
+ if (!matcherMatchesAny(nextMatcher.matcher, ["Bash"])) {
5050
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" shared ${event} matcher "${nextMatcher.matcher ?? ""}" does not match Codex's current tool runtime ("Bash") and was skipped.`);
5051
+ continue;
5052
+ }
5053
+ filteredMatchers.push(nextMatcher);
5054
+ continue;
5055
+ }
5056
+ if (event === "SessionStart") {
5057
+ if (!matcherMatchesAny(nextMatcher.matcher, ["startup", "resume"])) {
5058
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" shared SessionStart matcher "${nextMatcher.matcher ?? ""}" does not match Codex's current sources ("startup" or "resume") and was skipped.`);
5059
+ continue;
5060
+ }
5061
+ }
5062
+ if (nextMatcher.hooks.length !== originalHookCount || nextMatcher.matcher !== matcher.matcher) {
5063
+ filteredMatchers.push(nextMatcher);
5064
+ } else {
5065
+ filteredMatchers.push(matcher);
5066
+ }
5067
+ }
5068
+ if (filteredMatchers.length > 0) {
5069
+ filtered[event] = filteredMatchers;
5070
+ }
5071
+ }
5072
+ return { config: filtered, warnings };
5073
+ }
5074
+ function getProviderOverrideWarnings(providerId, capabilityName, providerConfigs) {
5075
+ if (!providerConfigs) {
5076
+ return [];
5077
+ }
5078
+ if (providerId === "claude-code" && providerConfigs.codex) {
5079
+ return [
5080
+ `[hooks] Warning: Capability "${capabilityName}" defines [codex] hooks; they are not used by Claude Code.`
5081
+ ];
5082
+ }
5083
+ if (providerId === "codex" && providerConfigs.claude) {
5084
+ return [
5085
+ `[hooks] Warning: Capability "${capabilityName}" defines [claude] hooks; they are not used by Codex.`
5086
+ ];
5087
+ }
5088
+ return [];
5089
+ }
5090
+ function applyProviderOverrides(baseConfig, providerId, capabilityName, providerConfigs) {
5091
+ const warnings = getProviderOverrideWarnings(providerId, capabilityName, providerConfigs);
5092
+ const providerSection = PROVIDER_SECTION_BY_ID[providerId];
5093
+ if (!providerSection) {
5094
+ return { config: baseConfig, warnings };
5095
+ }
5096
+ const overrideConfig = providerConfigs?.[providerSection];
5097
+ if (!overrideConfig) {
5098
+ return { config: baseConfig, warnings };
5099
+ }
5100
+ const result = { ...baseConfig };
5101
+ for (const event of HOOK_EVENTS) {
5102
+ if (!hasOwnKey(overrideConfig, event)) {
5103
+ continue;
5104
+ }
5105
+ const eventValue = overrideConfig[event];
5106
+ if (!Array.isArray(eventValue)) {
5107
+ warnings.push(`[hooks] Warning: Capability "${capabilityName}" [${providerSection}].${event} must be an array to be emitted for ${getProviderDisplayName(providerId)}. The override was skipped.`);
5108
+ continue;
5109
+ }
5110
+ result[event] = eventValue;
5111
+ }
5112
+ return { config: result, warnings };
5113
+ }
5114
+ function mergeCapabilityHookConfig(target, capabilityConfig) {
5115
+ for (const event of HOOK_EVENTS) {
5116
+ const eventValue = capabilityConfig[event];
5117
+ if (!Array.isArray(eventValue) || eventValue.length === 0) {
5118
+ continue;
5119
+ }
5120
+ const existing = target[event];
5121
+ target[event] = Array.isArray(existing) ? [...existing, ...eventValue] : [...eventValue];
5122
+ }
5123
+ }
5124
+ function composeHooksForProvider(capabilityHooks, providerId) {
5125
+ if (providerId !== "claude-code" && providerId !== "codex") {
5126
+ return { config: {}, warnings: [] };
5127
+ }
5128
+ const warnings = [];
5129
+ const mergedConfig = {};
5130
+ for (const capHooks of capabilityHooks) {
5131
+ if (providerId === "codex" && process.platform === "win32") {
5132
+ if (hasHooksInConfig(capHooks.config) || hasHooksInConfig(capHooks.providerConfigs?.codex)) {
5133
+ warnings.push(`[hooks] Warning: Capability "${capHooks.capabilityName}" has Codex hooks configured, but Codex hooks are currently disabled on Windows. Skipping Codex hook output.`);
5134
+ }
5135
+ continue;
5136
+ }
5137
+ let capabilityConfig = capHooks.config;
5138
+ if (providerId === "codex") {
5139
+ const filtered = filterSharedHooksForCodex(capabilityConfig, capHooks.capabilityName);
5140
+ capabilityConfig = filtered.config;
5141
+ warnings.push(...filtered.warnings);
5142
+ }
5143
+ const withOverrides = applyProviderOverrides(capabilityConfig, providerId, capHooks.capabilityName, capHooks.providerConfigs);
5144
+ warnings.push(...withOverrides.warnings);
5145
+ mergeCapabilityHookConfig(mergedConfig, withOverrides.config);
5146
+ }
5147
+ return {
5148
+ config: mergedConfig,
5149
+ warnings
5150
+ };
5151
+ }
5152
+ var CODEX_SUPPORTED_EVENTS, PROVIDER_SECTION_BY_ID;
5153
+ var init_provider_config = __esm(() => {
5154
+ init_constants();
5155
+ CODEX_SUPPORTED_EVENTS = new Set([
5156
+ "SessionStart",
5157
+ "PreToolUse",
5158
+ "PostToolUse",
5159
+ "UserPromptSubmit",
5160
+ "Stop"
5161
+ ]);
5162
+ PROVIDER_SECTION_BY_ID = {
5163
+ "claude-code": "claude",
5164
+ codex: "codex"
5165
+ };
5166
+ });
5167
+
4789
5168
  // ../core/src/hooks/index.ts
4790
5169
  var init_hooks = __esm(() => {
4791
5170
  init_constants();
@@ -4794,6 +5173,7 @@ var init_hooks = __esm(() => {
4794
5173
  init_variables();
4795
5174
  init_loader();
4796
5175
  init_merger();
5176
+ init_provider_config();
4797
5177
  init_json_loader();
4798
5178
  });
4799
5179
 
@@ -5168,29 +5548,107 @@ var init_mcp_json = __esm(() => {
5168
5548
  });
5169
5549
 
5170
5550
  // ../core/src/state/manifest.ts
5171
- import { existsSync as existsSync17, mkdirSync as mkdirSync2, rmSync } from "node:fs";
5551
+ import { createHash as createHash3 } from "node:crypto";
5552
+ import { existsSync as existsSync17, mkdirSync as mkdirSync2, readdirSync as readdirSync7, rmSync } from "node:fs";
5553
+ import { dirname, join as join10 } from "node:path";
5172
5554
  import { readFile as readFile14, writeFile as writeFile7 } from "node:fs/promises";
5173
- async function loadManifest() {
5174
- if (!existsSync17(MANIFEST_PATH)) {
5555
+ function createEmptyManifest() {
5556
+ return {
5557
+ version: CURRENT_VERSION,
5558
+ syncedAt: new Date().toISOString(),
5559
+ capabilities: {},
5560
+ providers: {}
5561
+ };
5562
+ }
5563
+ function normalizeManifest(parsed) {
5564
+ if (parsed.version === CURRENT_VERSION && "providers" in parsed) {
5175
5565
  return {
5176
5566
  version: CURRENT_VERSION,
5177
- syncedAt: new Date().toISOString(),
5178
- capabilities: {}
5567
+ syncedAt: parsed.syncedAt,
5568
+ capabilities: parsed.capabilities,
5569
+ providers: parsed.providers ?? {}
5179
5570
  };
5180
5571
  }
5572
+ return {
5573
+ version: CURRENT_VERSION,
5574
+ syncedAt: parsed.syncedAt,
5575
+ capabilities: parsed.capabilities,
5576
+ providers: {}
5577
+ };
5578
+ }
5579
+ function hashContent2(content) {
5580
+ return createHash3("sha256").update(content).digest("hex");
5581
+ }
5582
+ function normalizePath(path) {
5583
+ return path.replace(/\\/g, "/");
5584
+ }
5585
+ function isDirectoryEmpty(path) {
5586
+ return readdirSync7(path).length === 0;
5587
+ }
5588
+ async function cleanupManagedOutput(output) {
5589
+ const outputPath = join10(process.cwd(), output.path);
5590
+ if (!existsSync17(outputPath)) {
5591
+ return { deleted: false };
5592
+ }
5593
+ if (output.cleanupStrategy === "remove-json-key") {
5594
+ if (!output.jsonKey) {
5595
+ return { deleted: false, reason: `missing jsonKey metadata for ${output.path}` };
5596
+ }
5597
+ let parsed;
5598
+ try {
5599
+ parsed = JSON.parse(await readFile14(outputPath, "utf-8"));
5600
+ } catch {
5601
+ return { deleted: false, reason: `could not parse JSON at ${output.path}` };
5602
+ }
5603
+ const currentValue = parsed[output.jsonKey];
5604
+ if (currentValue === undefined) {
5605
+ return { deleted: false };
5606
+ }
5607
+ if (hashContent2(JSON.stringify(currentValue)) !== output.hash) {
5608
+ return { deleted: false, reason: `managed section changed at ${output.path}` };
5609
+ }
5610
+ delete parsed[output.jsonKey];
5611
+ if (Object.keys(parsed).length === 0) {
5612
+ rmSync(outputPath);
5613
+ } else {
5614
+ await writeFile7(outputPath, `${JSON.stringify(parsed, null, 2)}
5615
+ `, "utf-8");
5616
+ }
5617
+ return { deleted: true };
5618
+ }
5619
+ const currentContent = await readFile14(outputPath, "utf-8");
5620
+ if (hashContent2(currentContent) !== output.hash) {
5621
+ return { deleted: false, reason: `managed file changed at ${output.path}` };
5622
+ }
5623
+ rmSync(outputPath);
5624
+ if (output.cleanupStrategy === "delete-file-and-prune-empty-parents" && output.pruneRoot) {
5625
+ const pruneRoot = join10(process.cwd(), output.pruneRoot);
5626
+ let currentDir = dirname(outputPath);
5627
+ while (normalizePath(currentDir).startsWith(normalizePath(pruneRoot)) && normalizePath(currentDir) !== normalizePath(pruneRoot) && existsSync17(currentDir) && isDirectoryEmpty(currentDir)) {
5628
+ rmSync(currentDir, { recursive: true });
5629
+ currentDir = dirname(currentDir);
5630
+ }
5631
+ }
5632
+ return { deleted: true };
5633
+ }
5634
+ async function loadManifest() {
5635
+ if (!existsSync17(MANIFEST_PATH)) {
5636
+ return createEmptyManifest();
5637
+ }
5181
5638
  const content = await readFile14(MANIFEST_PATH, "utf-8");
5182
- return JSON.parse(content);
5639
+ return normalizeManifest(JSON.parse(content));
5183
5640
  }
5184
5641
  async function saveManifest(manifest) {
5185
5642
  mkdirSync2(".omni/state", { recursive: true });
5186
5643
  await writeFile7(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}
5187
5644
  `, "utf-8");
5188
5645
  }
5189
- function buildManifestFromCapabilities(capabilities2) {
5646
+ function buildManifestFromCapabilities(capabilities2, providerOutputs = new Map) {
5190
5647
  const manifest = {
5191
5648
  version: CURRENT_VERSION,
5192
5649
  syncedAt: new Date().toISOString(),
5193
- capabilities: {}
5650
+ capabilities: {},
5651
+ providers: {}
5194
5652
  };
5195
5653
  for (const cap of capabilities2) {
5196
5654
  const resources = {
@@ -5202,38 +5660,59 @@ function buildManifestFromCapabilities(capabilities2) {
5202
5660
  };
5203
5661
  manifest.capabilities[cap.id] = resources;
5204
5662
  }
5663
+ for (const [providerId, outputs] of providerOutputs) {
5664
+ manifest.providers[providerId] = {
5665
+ outputs: Object.fromEntries(outputs.map((output) => [output.path, output]))
5666
+ };
5667
+ }
5205
5668
  return manifest;
5206
5669
  }
5207
- async function cleanupStaleResources(previousManifest, currentCapabilityIds) {
5208
- const result = {
5670
+ async function cleanupStaleResources(_previousManifest, _currentCapabilityIds) {
5671
+ return {
5209
5672
  deletedSkills: [],
5210
5673
  deletedRules: [],
5211
5674
  deletedCommands: [],
5212
5675
  deletedSubagents: [],
5213
5676
  deletedMcps: []
5214
5677
  };
5215
- for (const [capId, resources] of Object.entries(previousManifest.capabilities)) {
5216
- if (currentCapabilityIds.has(capId)) {
5217
- continue;
5678
+ }
5679
+ async function cleanupStaleManagedOutputs(previousManifest, nextProviders) {
5680
+ const result = {
5681
+ deletedPaths: [],
5682
+ skippedPaths: []
5683
+ };
5684
+ const claimedPaths = new Set;
5685
+ for (const outputs of nextProviders.values()) {
5686
+ for (const output of outputs) {
5687
+ claimedPaths.add(output.path);
5218
5688
  }
5219
- for (const skillName of resources.skills) {
5220
- const skillDir = `.claude/skills/${skillName}`;
5221
- if (existsSync17(skillDir)) {
5222
- rmSync(skillDir, { recursive: true });
5223
- result.deletedSkills.push(skillName);
5689
+ }
5690
+ for (const [providerId, providerState] of Object.entries(previousManifest.providers)) {
5691
+ const nextPaths = new Set((nextProviders.get(providerId) ?? []).map((output) => output.path));
5692
+ for (const output of Object.values(providerState.outputs)) {
5693
+ if (nextPaths.has(output.path)) {
5694
+ continue;
5224
5695
  }
5225
- }
5226
- for (const ruleName of resources.rules) {
5227
- const rulePath = `.cursor/rules/omnidev-${ruleName}.mdc`;
5228
- if (existsSync17(rulePath)) {
5229
- rmSync(rulePath);
5230
- result.deletedRules.push(ruleName);
5696
+ if (claimedPaths.has(output.path)) {
5697
+ continue;
5698
+ }
5699
+ const cleanup = await cleanupManagedOutput(output);
5700
+ if (cleanup.deleted) {
5701
+ result.deletedPaths.push(output.path);
5702
+ } else if (cleanup.reason) {
5703
+ result.skippedPaths.push({
5704
+ path: output.path,
5705
+ reason: cleanup.reason
5706
+ });
5231
5707
  }
5232
5708
  }
5233
5709
  }
5234
5710
  return result;
5235
5711
  }
5236
- var MANIFEST_PATH = ".omni/state/manifest.json", CURRENT_VERSION = 1;
5712
+ function getProviderManagedOutputs(manifest, providerId) {
5713
+ return Object.values(manifest.providers[providerId]?.outputs ?? {});
5714
+ }
5715
+ var MANIFEST_PATH = ".omni/state/manifest.json", CURRENT_VERSION = 2;
5237
5716
  var init_manifest = () => {};
5238
5717
 
5239
5718
  // ../core/src/state/providers.ts
@@ -5382,7 +5861,7 @@ var init_state = __esm(() => {
5382
5861
  // ../core/src/sync.ts
5383
5862
  import { spawn as spawn2 } from "node:child_process";
5384
5863
  import { existsSync as existsSync20, mkdirSync as mkdirSync5, readFileSync as readFileSync3 } from "node:fs";
5385
- import { join as join10 } from "node:path";
5864
+ import { join as join11 } from "node:path";
5386
5865
  function getDeclaredPackageManager(packageManager) {
5387
5866
  if (typeof packageManager !== "string" || packageManager.trim().length === 0) {
5388
5867
  return;
@@ -5391,8 +5870,8 @@ function getDeclaredPackageManager(packageManager) {
5391
5870
  return atIndex === -1 ? packageManager : packageManager.slice(0, atIndex);
5392
5871
  }
5393
5872
  function resolveCapabilityInstallCommand(capabilityPath, options) {
5394
- const packageJsonPath = join10(capabilityPath, "package.json");
5395
- const packageLockPath = join10(capabilityPath, "package-lock.json");
5873
+ const packageJsonPath = join11(capabilityPath, "package.json");
5874
+ const packageLockPath = join11(capabilityPath, "package-lock.json");
5396
5875
  let packageManager;
5397
5876
  try {
5398
5877
  const pkgJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
@@ -5410,13 +5889,13 @@ function resolveCapabilityInstallCommand(capabilityPath, options) {
5410
5889
  };
5411
5890
  }
5412
5891
  async function installCapabilityDependencies(silent) {
5413
- const { readdirSync: readdirSync7 } = await import("node:fs");
5892
+ const { readdirSync: readdirSync8 } = await import("node:fs");
5414
5893
  const { parse: parse2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
5415
5894
  const capabilitiesDir = ".omni/capabilities";
5416
5895
  if (!existsSync20(capabilitiesDir)) {
5417
5896
  return;
5418
5897
  }
5419
- const entries = readdirSync7(capabilitiesDir, { withFileTypes: true });
5898
+ const entries = readdirSync8(capabilitiesDir, { withFileTypes: true });
5420
5899
  async function commandExists(cmd) {
5421
5900
  return await new Promise((resolve2) => {
5422
5901
  const proc = spawn2(cmd, ["--version"], { stdio: "ignore" });
@@ -5432,9 +5911,9 @@ async function installCapabilityDependencies(silent) {
5432
5911
  if (!entry.isDirectory()) {
5433
5912
  continue;
5434
5913
  }
5435
- const capabilityPath = join10(capabilitiesDir, entry.name);
5436
- const packageJsonPath = join10(capabilityPath, "package.json");
5437
- const capabilityTomlPath = join10(capabilityPath, "capability.toml");
5914
+ const capabilityPath = join11(capabilitiesDir, entry.name);
5915
+ const packageJsonPath = join11(capabilityPath, "package.json");
5916
+ const capabilityTomlPath = join11(capabilityPath, "capability.toml");
5438
5917
  if (!existsSync20(packageJsonPath)) {
5439
5918
  continue;
5440
5919
  }
@@ -5472,7 +5951,7 @@ ${stderr}`));
5472
5951
  reject(error);
5473
5952
  });
5474
5953
  });
5475
- const hasIndexTs = existsSync20(join10(capabilityPath, "index.ts"));
5954
+ const hasIndexTs = existsSync20(join11(capabilityPath, "index.ts"));
5476
5955
  let hasBuildScript = false;
5477
5956
  try {
5478
5957
  const pkgJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
@@ -5501,7 +5980,7 @@ ${stderr}`));
5501
5980
  });
5502
5981
  });
5503
5982
  } else if (hasIndexTs && !silent) {
5504
- const hasBuiltIndex = existsSync20(join10(capabilityPath, "dist", "index.js"));
5983
+ const hasBuiltIndex = existsSync20(join11(capabilityPath, "dist", "index.js"));
5505
5984
  if (!hasBuiltIndex) {
5506
5985
  console.warn(`Warning: Capability at ${capabilityPath} has index.ts but no build script.
5507
5986
  Add a "build" script to package.json (e.g., "build": "tsc") to compile TypeScript.`);
@@ -5572,8 +6051,8 @@ async function syncAgentConfiguration(options) {
5572
6051
  }
5573
6052
  mkdirSync5(".omni", { recursive: true });
5574
6053
  await syncMcpJson(capabilities2, previousManifest);
5575
- const newManifest = buildManifestFromCapabilities(capabilities2);
5576
- await saveManifest(newManifest);
6054
+ const enabledProviderIds = new Set(adapters.map((adapter) => String(adapter.id)));
6055
+ const successfulProviderOutputs = new Map;
5577
6056
  if (adapters.length > 0) {
5578
6057
  const config2 = await loadConfig();
5579
6058
  const ctx = {
@@ -5582,12 +6061,33 @@ async function syncAgentConfiguration(options) {
5582
6061
  };
5583
6062
  for (const adapter of adapters) {
5584
6063
  try {
5585
- await adapter.sync(bundle, ctx);
6064
+ const adapterResult = await adapter.sync(bundle, ctx);
6065
+ successfulProviderOutputs.set(String(adapter.id), adapterResult.managedOutputs ?? []);
5586
6066
  } catch (error) {
5587
6067
  console.error(`Error running ${adapter.displayName} adapter:`, error);
5588
6068
  }
5589
6069
  }
5590
6070
  }
6071
+ const nextProviderOutputs = new Map;
6072
+ if (adapters.length === 0) {
6073
+ for (const providerId of Object.keys(previousManifest.providers)) {
6074
+ nextProviderOutputs.set(providerId, getProviderManagedOutputs(previousManifest, providerId));
6075
+ }
6076
+ } else {
6077
+ for (const providerId of enabledProviderIds) {
6078
+ if (successfulProviderOutputs.has(providerId)) {
6079
+ nextProviderOutputs.set(providerId, successfulProviderOutputs.get(providerId) ?? []);
6080
+ continue;
6081
+ }
6082
+ nextProviderOutputs.set(providerId, getProviderManagedOutputs(previousManifest, providerId));
6083
+ }
6084
+ }
6085
+ const cleanupResult = await cleanupStaleManagedOutputs(previousManifest, nextProviderOutputs);
6086
+ for (const skipped of cleanupResult.skippedPaths) {
6087
+ console.warn(`Warning: skipped cleanup for ${skipped.path} (${skipped.reason})`);
6088
+ }
6089
+ const newManifest = buildManifestFromCapabilities(capabilities2, nextProviderOutputs);
6090
+ await saveManifest(newManifest);
5591
6091
  const result = {
5592
6092
  capabilities: capabilities2.map((c) => c.id),
5593
6093
  skillCount: bundle.skills.length,
@@ -5648,7 +6148,7 @@ var init_types3 = __esm(() => {
5648
6148
  // ../core/src/security/scanner.ts
5649
6149
  import { existsSync as existsSync21 } from "node:fs";
5650
6150
  import { lstat, readdir as readdir2, readFile as readFile17, readlink, realpath } from "node:fs/promises";
5651
- import { join as join11, relative, resolve as resolve2 } from "node:path";
6151
+ import { join as join12, relative, resolve as resolve2 } from "node:path";
5652
6152
  async function scanFileForUnicode(filePath, relativePath) {
5653
6153
  const findings = [];
5654
6154
  try {
@@ -5835,7 +6335,7 @@ async function scanFileForNetworkRequests(filePath, relativePath) {
5835
6335
  async function checkSymlink(symlinkPath, relativePath, capabilityRoot) {
5836
6336
  try {
5837
6337
  const linkTarget = await readlink(symlinkPath);
5838
- const resolvedTarget = resolve2(join11(symlinkPath, "..", linkTarget));
6338
+ const resolvedTarget = resolve2(join12(symlinkPath, "..", linkTarget));
5839
6339
  const normalizedRoot = await realpath(capabilityRoot);
5840
6340
  if (linkTarget.startsWith("/")) {
5841
6341
  return {
@@ -5882,7 +6382,7 @@ async function scanCapability(capabilityId, capabilityPath, settings = DEFAULT_S
5882
6382
  async function scanDirectory(dirPath) {
5883
6383
  const entries = await readdir2(dirPath, { withFileTypes: true });
5884
6384
  for (const entry of entries) {
5885
- const fullPath = join11(dirPath, entry.name);
6385
+ const fullPath = join12(dirPath, entry.name);
5886
6386
  const relativePath = relative(capabilityPath, fullPath);
5887
6387
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "__pycache__") {
5888
6388
  continue;
@@ -6245,7 +6745,9 @@ var init_security = __esm(() => {
6245
6745
 
6246
6746
  // ../core/src/templates/agents.ts
6247
6747
  function generateAgentsTemplate() {
6248
- return `# Project Instructions
6748
+ return `> IMPORTANT: Do not edit this file directly. Edit \`OMNI.md\` instead. This file is a generated copy of \`OMNI.md\` and is ephemeral. Any persistent change must be made in \`OMNI.md\`.
6749
+
6750
+ # Project Instructions
6249
6751
 
6250
6752
  <!-- Add your project-specific instructions here -->
6251
6753
 
@@ -6372,7 +6874,9 @@ function formatDisplayName(kebabCase) {
6372
6874
 
6373
6875
  // ../core/src/templates/claude.ts
6374
6876
  function generateClaudeTemplate() {
6375
- return `# Project Instructions
6877
+ return `> IMPORTANT: Do not edit this file directly. Edit \`OMNI.md\` instead. This file is a generated copy of \`OMNI.md\` and is ephemeral. Any persistent change must be made in \`OMNI.md\`.
6878
+
6879
+ # Project Instructions
6376
6880
 
6377
6881
  <!-- Add your project-specific instructions here -->
6378
6882
  `;
@@ -6442,6 +6946,7 @@ __export(exports_src, {
6442
6946
  saveManifest: () => saveManifest,
6443
6947
  saveLockFile: () => saveLockFile,
6444
6948
  resolveEnabledCapabilities: () => resolveEnabledCapabilities,
6949
+ resolveCapabilityRootInValue: () => resolveCapabilityRootInValue,
6445
6950
  resolveCapabilityRootInConfig: () => resolveCapabilityRootInConfig,
6446
6951
  resolveCapabilityRoot: () => resolveCapabilityRoot,
6447
6952
  resolveCapabilityInstallCommand: () => resolveCapabilityInstallCommand,
@@ -6492,10 +6997,12 @@ __export(exports_src, {
6492
6997
  isFileSourceConfig: () => isFileSourceConfig,
6493
6998
  isFileSource: () => isFileSource,
6494
6999
  installCapabilityDependencies: () => installCapabilityDependencies,
7000
+ hasHooksInConfig: () => hasHooksInConfig,
6495
7001
  hasHooks: () => hasHooks,
6496
7002
  hasAnyHooks: () => hasAnyHooks,
6497
7003
  getVersion: () => getVersion,
6498
7004
  getSourceCapabilityPath: () => getSourceCapabilityPath,
7005
+ getProviderManagedOutputs: () => getProviderManagedOutputs,
6499
7006
  getLockFilePath: () => getLockFilePath,
6500
7007
  getHooksDirectory: () => getHooksDirectory,
6501
7008
  getHooksConfigPath: () => getHooksConfigPath,
@@ -6529,10 +7036,12 @@ __export(exports_src, {
6529
7036
  countHooks: () => countHooks,
6530
7037
  containsOmnidevVariables: () => containsOmnidevVariables,
6531
7038
  containsClaudeVariables: () => containsClaudeVariables,
7039
+ composeHooksForProvider: () => composeHooksForProvider,
6532
7040
  clearCapabilityAllows: () => clearCapabilityAllows,
6533
7041
  clearAllSecurityAllows: () => clearAllSecurityAllows,
6534
7042
  clearActiveProfileState: () => clearActiveProfileState,
6535
7043
  cleanupStaleResources: () => cleanupStaleResources,
7044
+ cleanupStaleManagedOutputs: () => cleanupStaleManagedOutputs,
6536
7045
  checkVersionMismatch: () => checkVersionMismatch,
6537
7046
  checkForUpdates: () => checkForUpdates,
6538
7047
  buildSyncBundle: () => buildSyncBundle,
@@ -6576,15 +7085,33 @@ var init_src = __esm(() => {
6576
7085
  import { run } from "@stricli/core";
6577
7086
 
6578
7087
  // src/lib/dynamic-app.ts
6579
- import { existsSync as existsSync31 } from "node:fs";
7088
+ import { existsSync as existsSync32 } from "node:fs";
6580
7089
  import { createRequire as createRequire2 } from "node:module";
6581
- import { join as join28 } from "node:path";
7090
+ import { join as join31 } from "node:path";
6582
7091
  import { buildApplication, buildRouteMap as buildRouteMap7 } from "@stricli/core";
6583
7092
 
6584
7093
  // src/commands/add.ts
6585
7094
  import { existsSync as existsSync24 } from "node:fs";
6586
7095
  import { basename as basename5, resolve as resolve3 } from "node:path";
6587
7096
 
7097
+ // ../adapters/src/writers/generic/managed-outputs.ts
7098
+ import { createHash } from "node:crypto";
7099
+ function hashContent(content) {
7100
+ return createHash("sha256").update(content).digest("hex");
7101
+ }
7102
+ function trimTrailingSlash(path) {
7103
+ return path.endsWith("/") ? path.slice(0, -1) : path;
7104
+ }
7105
+ function createManagedOutput(path, writerId, content, options) {
7106
+ return {
7107
+ path,
7108
+ writerId,
7109
+ hash: hashContent(content),
7110
+ cleanupStrategy: options?.cleanupStrategy ?? "delete-file",
7111
+ ...options?.pruneRoot ? { pruneRoot: trimTrailingSlash(options.pruneRoot) } : {},
7112
+ ...options?.jsonKey ? { jsonKey: options.jsonKey } : {}
7113
+ };
7114
+ }
6588
7115
  // ../adapters/src/writers/generic/executor.ts
6589
7116
  async function executeWriters(writerConfigs, bundle, projectRoot, providerId) {
6590
7117
  const seen = new Set;
@@ -6600,6 +7127,7 @@ async function executeWriters(writerConfigs, bundle, projectRoot, providerId) {
6600
7127
  uniqueConfigs.push(config);
6601
7128
  }
6602
7129
  const allFilesWritten = [];
7130
+ const allManagedOutputs = [];
6603
7131
  for (const config of uniqueConfigs) {
6604
7132
  const result = await config.writer.write(bundle, {
6605
7133
  outputPath: config.outputPath,
@@ -6607,27 +7135,29 @@ async function executeWriters(writerConfigs, bundle, projectRoot, providerId) {
6607
7135
  ...providerId ? { providerId } : {}
6608
7136
  });
6609
7137
  allFilesWritten.push(...result.filesWritten);
7138
+ allManagedOutputs.push(...result.managedOutputs ?? []);
6610
7139
  }
6611
7140
  return {
6612
7141
  filesWritten: allFilesWritten,
6613
- deduplicatedCount
7142
+ deduplicatedCount,
7143
+ managedOutputs: allManagedOutputs
6614
7144
  };
6615
7145
  }
6616
7146
  // ../adapters/src/writers/generic/hooks.ts
6617
7147
  init_src();
6618
7148
  import { existsSync as existsSync22 } from "node:fs";
6619
7149
  import { mkdir as mkdir2, readFile as readFile18, writeFile as writeFile10 } from "node:fs/promises";
6620
- import { dirname, join as join12 } from "node:path";
7150
+ import { dirname as dirname2, join as join13 } from "node:path";
6621
7151
  var HooksWriter = {
6622
7152
  id: "hooks",
6623
7153
  async write(bundle, ctx) {
6624
7154
  if (!bundle.hooks) {
6625
7155
  return { filesWritten: [] };
6626
7156
  }
6627
- const settingsPath = join12(ctx.projectRoot, ctx.outputPath);
6628
- const parentDir = dirname(settingsPath);
6629
- await mkdir2(parentDir, { recursive: true });
6630
7157
  const claudeHooks = transformHooksConfig(bundle.hooks, "toClaude");
7158
+ const settingsPath = join13(ctx.projectRoot, ctx.outputPath);
7159
+ const parentDir = dirname2(settingsPath);
7160
+ await mkdir2(parentDir, { recursive: true });
6631
7161
  let existingSettings = {};
6632
7162
  if (existsSync22(settingsPath)) {
6633
7163
  try {
@@ -6644,14 +7174,20 @@ var HooksWriter = {
6644
7174
  await writeFile10(settingsPath, `${JSON.stringify(newSettings, null, 2)}
6645
7175
  `, "utf-8");
6646
7176
  return {
6647
- filesWritten: [ctx.outputPath]
7177
+ filesWritten: [ctx.outputPath],
7178
+ managedOutputs: [
7179
+ createManagedOutput(ctx.outputPath, this.id, JSON.stringify(claudeHooks), {
7180
+ cleanupStrategy: "remove-json-key",
7181
+ jsonKey: "hooks"
7182
+ })
7183
+ ]
6648
7184
  };
6649
7185
  }
6650
7186
  };
6651
7187
  // ../adapters/src/writers/generic/instructions-md.ts
6652
7188
  import { existsSync as existsSync23 } from "node:fs";
6653
7189
  import { mkdir as mkdir3, readFile as readFile19, writeFile as writeFile11 } from "node:fs/promises";
6654
- import { dirname as dirname2, join as join13 } from "node:path";
7190
+ import { dirname as dirname3, join as join14 } from "node:path";
6655
7191
 
6656
7192
  // ../adapters/src/writers/generic/omni-md.ts
6657
7193
  init_src();
@@ -6673,20 +7209,32 @@ function renderOmniMdForProvider(content, providerId) {
6673
7209
  }
6674
7210
 
6675
7211
  // ../adapters/src/writers/generic/instructions-md.ts
7212
+ function getGeneratedCopyNotice(outputPath) {
7213
+ if (outputPath !== "AGENTS.md" && outputPath !== "CLAUDE.md") {
7214
+ return "";
7215
+ }
7216
+ return "> IMPORTANT: Do not edit this file directly. Edit `OMNI.md` instead. This file is a generated copy of `OMNI.md` and is ephemeral. Any persistent change must be made in `OMNI.md`.";
7217
+ }
6676
7218
  var InstructionsMdWriter = {
6677
7219
  id: "instructions-md",
6678
7220
  async write(bundle, ctx) {
6679
- const outputFullPath = join13(ctx.projectRoot, ctx.outputPath);
6680
- const parentDir = dirname2(outputFullPath);
7221
+ const outputFullPath = join14(ctx.projectRoot, ctx.outputPath);
7222
+ const parentDir = dirname3(outputFullPath);
6681
7223
  if (parentDir !== ctx.projectRoot) {
6682
7224
  await mkdir3(parentDir, { recursive: true });
6683
7225
  }
6684
- const omniMdPath = join13(ctx.projectRoot, "OMNI.md");
7226
+ const omniMdPath = join14(ctx.projectRoot, "OMNI.md");
6685
7227
  let omniMdContent = "";
6686
7228
  if (existsSync23(omniMdPath)) {
6687
7229
  omniMdContent = renderOmniMdForProvider(await readFile19(omniMdPath, "utf-8"), ctx.providerId);
6688
7230
  }
6689
7231
  let content = omniMdContent;
7232
+ const generatedCopyNotice = getGeneratedCopyNotice(ctx.outputPath);
7233
+ if (generatedCopyNotice) {
7234
+ content = content ? `${generatedCopyNotice}
7235
+
7236
+ ${content}` : generatedCopyNotice;
7237
+ }
6690
7238
  if (bundle.instructionsContent) {
6691
7239
  content += `
6692
7240
 
@@ -6695,23 +7243,30 @@ ${bundle.instructionsContent}
6695
7243
  }
6696
7244
  await writeFile11(outputFullPath, content, "utf-8");
6697
7245
  return {
6698
- filesWritten: [ctx.outputPath]
7246
+ filesWritten: [ctx.outputPath],
7247
+ managedOutputs: [createManagedOutput(ctx.outputPath, this.id, content)]
6699
7248
  };
6700
7249
  }
6701
7250
  };
6702
7251
  // ../adapters/src/writers/generic/skills.ts
6703
- import { mkdir as mkdir4, writeFile as writeFile12 } from "node:fs/promises";
6704
- import { join as join14 } from "node:path";
7252
+ import { cp as cp2, mkdir as mkdir4, readdir as readdir3, rm as rm2, writeFile as writeFile12 } from "node:fs/promises";
7253
+ import { join as join15, relative as relative2 } from "node:path";
6705
7254
  var SkillsWriter = {
6706
7255
  id: "skills",
6707
7256
  async write(bundle, ctx) {
6708
- const skillsDir = join14(ctx.projectRoot, ctx.outputPath);
7257
+ const skillsDir = join15(ctx.projectRoot, ctx.outputPath);
6709
7258
  await mkdir4(skillsDir, { recursive: true });
6710
7259
  const filesWritten = [];
7260
+ const managedOutputs = [];
6711
7261
  for (const skill of bundle.skills) {
6712
- const skillDir = join14(skillsDir, skill.name);
6713
- await mkdir4(skillDir, { recursive: true });
6714
- const skillPath = join14(skillDir, "SKILL.md");
7262
+ const skillDir = join15(skillsDir, skill.name);
7263
+ await rm2(skillDir, { recursive: true, force: true });
7264
+ if (skill.sourcePath) {
7265
+ await cp2(skill.sourcePath, skillDir, { recursive: true });
7266
+ } else {
7267
+ await mkdir4(skillDir, { recursive: true });
7268
+ }
7269
+ const skillPath = join15(skillDir, "SKILL.md");
6715
7270
  const content = `---
6716
7271
  name: ${skill.name}
6717
7272
  description: "${skill.description}"
@@ -6719,16 +7274,43 @@ description: "${skill.description}"
6719
7274
 
6720
7275
  ${skill.instructions}`;
6721
7276
  await writeFile12(skillPath, content, "utf-8");
6722
- filesWritten.push(join14(ctx.outputPath, skill.name, "SKILL.md"));
7277
+ const relativePaths = skill.sourcePath ? await listRelativeFiles(skillDir) : ["SKILL.md"];
7278
+ for (const relativeFile of relativePaths) {
7279
+ filesWritten.push(join15(ctx.outputPath, skill.name, relativeFile));
7280
+ }
7281
+ const relativePath = join15(ctx.outputPath, skill.name, "SKILL.md");
7282
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content, {
7283
+ cleanupStrategy: "delete-file-and-prune-empty-parents",
7284
+ pruneRoot: ctx.outputPath
7285
+ }));
6723
7286
  }
6724
7287
  return {
6725
- filesWritten
7288
+ filesWritten,
7289
+ managedOutputs
6726
7290
  };
6727
7291
  }
6728
7292
  };
7293
+ async function listRelativeFiles(basePath) {
7294
+ const files = [];
7295
+ const entries = await readdir3(basePath, { withFileTypes: true });
7296
+ for (const entry of entries) {
7297
+ const entryPath = join15(basePath, entry.name);
7298
+ if (entry.isDirectory()) {
7299
+ const nestedFiles = await listRelativeFiles(entryPath);
7300
+ for (const nestedFile of nestedFiles) {
7301
+ files.push(relative2(basePath, join15(entryPath, nestedFile)));
7302
+ }
7303
+ continue;
7304
+ }
7305
+ if (entry.isFile()) {
7306
+ files.push(entry.name);
7307
+ }
7308
+ }
7309
+ return files.sort((a, b) => a.localeCompare(b));
7310
+ }
6729
7311
  // ../adapters/src/writers/generic/commands-as-skills.ts
6730
7312
  import { mkdir as mkdir5, writeFile as writeFile13 } from "node:fs/promises";
6731
- import { join as join15 } from "node:path";
7313
+ import { join as join16 } from "node:path";
6732
7314
  function generateSkillFrontmatter(command) {
6733
7315
  const lines = ["---"];
6734
7316
  lines.push(`name: ${command.name}`);
@@ -6743,22 +7325,29 @@ function generateSkillFrontmatter(command) {
6743
7325
  var CommandsAsSkillsWriter = {
6744
7326
  id: "commands-as-skills",
6745
7327
  async write(bundle, ctx) {
6746
- const skillsDir = join15(ctx.projectRoot, ctx.outputPath);
7328
+ const skillsDir = join16(ctx.projectRoot, ctx.outputPath);
6747
7329
  await mkdir5(skillsDir, { recursive: true });
6748
7330
  const filesWritten = [];
7331
+ const managedOutputs = [];
6749
7332
  for (const command of bundle.commands) {
6750
- const commandSkillDir = join15(skillsDir, command.name);
7333
+ const commandSkillDir = join16(skillsDir, command.name);
6751
7334
  await mkdir5(commandSkillDir, { recursive: true });
6752
7335
  const frontmatter = generateSkillFrontmatter(command);
6753
7336
  const content = `${frontmatter}
6754
7337
 
6755
7338
  ${command.prompt}`;
6756
- const skillPath = join15(commandSkillDir, "SKILL.md");
7339
+ const skillPath = join16(commandSkillDir, "SKILL.md");
6757
7340
  await writeFile13(skillPath, content, "utf-8");
6758
- filesWritten.push(join15(ctx.outputPath, command.name, "SKILL.md"));
7341
+ const relativePath = join16(ctx.outputPath, command.name, "SKILL.md");
7342
+ filesWritten.push(relativePath);
7343
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content, {
7344
+ cleanupStrategy: "delete-file-and-prune-empty-parents",
7345
+ pruneRoot: ctx.outputPath
7346
+ }));
6759
7347
  }
6760
7348
  return {
6761
- filesWritten
7349
+ filesWritten,
7350
+ managedOutputs
6762
7351
  };
6763
7352
  }
6764
7353
  };
@@ -6802,34 +7391,47 @@ function createProviderScopedBundle(bundle, providerId) {
6802
7391
  subagents,
6803
7392
  instructionsContent: generateInstructionsContent2(rules, docs)
6804
7393
  };
6805
- const mergedHooks = mergeHooksConfigs(capabilities2.flatMap((capability3) => capability3.hooks ? [capability3.hooks] : []));
6806
- if (hasAnyHooks(mergedHooks)) {
6807
- scopedBundle.hooks = mergedHooks;
7394
+ const composedHooks = composeHooksForProvider(capabilities2.flatMap((capability3) => capability3.hooks ? [capability3.hooks] : []), providerId);
7395
+ for (const warning of composedHooks.warnings) {
7396
+ console.warn(warning);
7397
+ }
7398
+ if (hasHooksInConfig(composedHooks.config)) {
7399
+ scopedBundle.hooks = composedHooks.config;
6808
7400
  }
6809
7401
  return scopedBundle;
6810
7402
  }
6811
7403
 
6812
7404
  // ../adapters/src/writers/claude/agents.ts
6813
7405
  import { mkdir as mkdir6, writeFile as writeFile14 } from "node:fs/promises";
6814
- import { join as join16 } from "node:path";
7406
+ import { join as join17 } from "node:path";
7407
+ function getClaudeConfig(agent) {
7408
+ return {
7409
+ tools: agent.claude?.tools ?? agent.tools,
7410
+ disallowedTools: agent.claude?.disallowedTools ?? agent.disallowedTools,
7411
+ model: agent.claude?.model ?? agent.model,
7412
+ permissionMode: agent.claude?.permissionMode ?? agent.permissionMode,
7413
+ skills: agent.claude?.skills ?? agent.skills
7414
+ };
7415
+ }
6815
7416
  function generateFrontmatter(agent) {
7417
+ const claude2 = getClaudeConfig(agent);
6816
7418
  const lines = ["---"];
6817
7419
  lines.push(`name: ${agent.name}`);
6818
7420
  lines.push(`description: "${agent.description.replace(/"/g, "\\\"")}"`);
6819
- if (agent.tools && agent.tools.length > 0) {
6820
- lines.push(`tools: ${agent.tools.join(", ")}`);
7421
+ if (claude2.tools && claude2.tools.length > 0) {
7422
+ lines.push(`tools: ${claude2.tools.join(", ")}`);
6821
7423
  }
6822
- if (agent.disallowedTools && agent.disallowedTools.length > 0) {
6823
- lines.push(`disallowedTools: ${agent.disallowedTools.join(", ")}`);
7424
+ if (claude2.disallowedTools && claude2.disallowedTools.length > 0) {
7425
+ lines.push(`disallowedTools: ${claude2.disallowedTools.join(", ")}`);
6824
7426
  }
6825
- if (agent.model && agent.model !== "inherit") {
6826
- lines.push(`model: ${agent.model}`);
7427
+ if (claude2.model && claude2.model !== "inherit") {
7428
+ lines.push(`model: ${claude2.model}`);
6827
7429
  }
6828
- if (agent.permissionMode && agent.permissionMode !== "default") {
6829
- lines.push(`permissionMode: ${agent.permissionMode}`);
7430
+ if (claude2.permissionMode && claude2.permissionMode !== "default") {
7431
+ lines.push(`permissionMode: ${claude2.permissionMode}`);
6830
7432
  }
6831
- if (agent.skills && agent.skills.length > 0) {
6832
- lines.push(`skills: ${agent.skills.join(", ")}`);
7433
+ if (claude2.skills && claude2.skills.length > 0) {
7434
+ lines.push(`skills: ${claude2.skills.join(", ")}`);
6833
7435
  }
6834
7436
  lines.push("---");
6835
7437
  return lines.join(`
@@ -6838,20 +7440,24 @@ function generateFrontmatter(agent) {
6838
7440
  var ClaudeAgentsWriter = {
6839
7441
  id: "claude-agents",
6840
7442
  async write(bundle, ctx) {
6841
- const agentsDir = join16(ctx.projectRoot, ctx.outputPath);
7443
+ const agentsDir = join17(ctx.projectRoot, ctx.outputPath);
6842
7444
  await mkdir6(agentsDir, { recursive: true });
6843
7445
  const filesWritten = [];
7446
+ const managedOutputs = [];
6844
7447
  for (const agent of bundle.subagents) {
6845
7448
  const frontmatter = generateFrontmatter(agent);
6846
7449
  const content = `${frontmatter}
6847
7450
 
6848
7451
  ${agent.systemPrompt}`;
6849
- const agentPath = join16(agentsDir, `${agent.name}.md`);
7452
+ const agentPath = join17(agentsDir, `${agent.name}.md`);
6850
7453
  await writeFile14(agentPath, content, "utf-8");
6851
- filesWritten.push(join16(ctx.outputPath, `${agent.name}.md`));
7454
+ const relativePath = join17(ctx.outputPath, `${agent.name}.md`);
7455
+ filesWritten.push(relativePath);
7456
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content));
6852
7457
  }
6853
7458
  return {
6854
- filesWritten
7459
+ filesWritten,
7460
+ managedOutputs
6855
7461
  };
6856
7462
  }
6857
7463
  };
@@ -6878,18 +7484,89 @@ var claudeCodeAdapter = {
6878
7484
  const result = await executeWriters(this.writers, providerBundle, ctx.projectRoot, providerId);
6879
7485
  return {
6880
7486
  filesWritten: result.filesWritten,
6881
- filesDeleted: []
7487
+ filesDeleted: [],
7488
+ managedOutputs: result.managedOutputs
6882
7489
  };
6883
7490
  }
6884
7491
  };
6885
7492
  // ../adapters/src/codex/index.ts
6886
7493
  import { mkdirSync as mkdirSync6 } from "node:fs";
6887
- import { join as join18 } from "node:path";
7494
+ import { join as join21 } from "node:path";
6888
7495
 
6889
- // ../adapters/src/writers/codex/toml.ts
7496
+ // ../adapters/src/writers/codex/agents.ts
6890
7497
  init_dist();
6891
7498
  import { mkdir as mkdir7, writeFile as writeFile15 } from "node:fs/promises";
6892
- import { dirname as dirname3, join as join17 } from "node:path";
7499
+ import { join as join18 } from "node:path";
7500
+ function buildCodexAgentFile(agent) {
7501
+ const codex = agent.codex ?? {};
7502
+ const file = {
7503
+ name: agent.name,
7504
+ description: agent.description,
7505
+ developer_instructions: agent.systemPrompt
7506
+ };
7507
+ if (codex.model) {
7508
+ file.model = codex.model;
7509
+ }
7510
+ if (codex.modelReasoningEffort) {
7511
+ file.model_reasoning_effort = codex.modelReasoningEffort;
7512
+ }
7513
+ if (codex.sandboxMode) {
7514
+ file.sandbox_mode = codex.sandboxMode;
7515
+ }
7516
+ if (codex.nicknameCandidates && codex.nicknameCandidates.length > 0) {
7517
+ file.nickname_candidates = codex.nicknameCandidates;
7518
+ }
7519
+ return file;
7520
+ }
7521
+ var CodexAgentsWriter = {
7522
+ id: "codex-agents",
7523
+ async write(bundle, ctx) {
7524
+ if (bundle.subagents.length === 0) {
7525
+ return { filesWritten: [] };
7526
+ }
7527
+ const agentsDir = join18(ctx.projectRoot, ctx.outputPath);
7528
+ await mkdir7(agentsDir, { recursive: true });
7529
+ const filesWritten = [];
7530
+ const managedOutputs = [];
7531
+ for (const agent of bundle.subagents) {
7532
+ const content = stringify(buildCodexAgentFile(agent));
7533
+ const relativePath = join18(ctx.outputPath, `${agent.name}.toml`);
7534
+ const filePath = join18(ctx.projectRoot, relativePath);
7535
+ await writeFile15(filePath, content, "utf-8");
7536
+ filesWritten.push(relativePath);
7537
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content));
7538
+ }
7539
+ return {
7540
+ filesWritten,
7541
+ managedOutputs
7542
+ };
7543
+ }
7544
+ };
7545
+ // ../adapters/src/writers/codex/hooks.ts
7546
+ import { mkdir as mkdir8, writeFile as writeFile16 } from "node:fs/promises";
7547
+ import { dirname as dirname4, join as join19 } from "node:path";
7548
+ var CodexHooksWriter = {
7549
+ id: "codex-hooks",
7550
+ async write(bundle, ctx) {
7551
+ if (!bundle.hooks) {
7552
+ return { filesWritten: [] };
7553
+ }
7554
+ const hooksPath = join19(ctx.projectRoot, ctx.outputPath);
7555
+ await mkdir8(dirname4(hooksPath), { recursive: true });
7556
+ const content = `${JSON.stringify({ hooks: bundle.hooks }, null, 2)}
7557
+ `;
7558
+ await writeFile16(hooksPath, content, "utf-8");
7559
+ return {
7560
+ filesWritten: [ctx.outputPath],
7561
+ managedOutputs: [createManagedOutput(ctx.outputPath, this.id, content)]
7562
+ };
7563
+ }
7564
+ };
7565
+ // ../adapters/src/writers/codex/toml.ts
7566
+ init_src();
7567
+ init_dist();
7568
+ import { mkdir as mkdir9, writeFile as writeFile17 } from "node:fs/promises";
7569
+ import { dirname as dirname5, join as join20 } from "node:path";
6893
7570
  var FILE_HEADER = `# Generated by OmniDev - DO NOT EDIT
6894
7571
  # Run \`omnidev sync\` to regenerate
6895
7572
 
@@ -6937,12 +7614,13 @@ var CodexTomlWriter = {
6937
7614
  id: "codex-toml",
6938
7615
  async write(bundle, ctx) {
6939
7616
  const mcps = collectMcps(bundle);
6940
- if (mcps.size === 0) {
7617
+ const hasHooks2 = hasHooksInConfig(bundle.hooks);
7618
+ if (mcps.size === 0 && !hasHooks2) {
6941
7619
  return { filesWritten: [] };
6942
7620
  }
6943
- const configPath = join17(ctx.projectRoot, ctx.outputPath);
6944
- const parentDir = dirname3(configPath);
6945
- await mkdir7(parentDir, { recursive: true });
7621
+ const configPath = join20(ctx.projectRoot, ctx.outputPath);
7622
+ const parentDir = dirname5(configPath);
7623
+ await mkdir9(parentDir, { recursive: true });
6946
7624
  const mcpServers = {};
6947
7625
  for (const [id, mcp] of mcps) {
6948
7626
  const converted = buildCodexMcpConfig(id, mcp);
@@ -6950,16 +7628,21 @@ var CodexTomlWriter = {
6950
7628
  mcpServers[id] = converted;
6951
7629
  }
6952
7630
  }
6953
- if (Object.keys(mcpServers).length === 0) {
7631
+ if (Object.keys(mcpServers).length === 0 && !hasHooks2) {
6954
7632
  return { filesWritten: [] };
6955
7633
  }
6956
- const codexConfig = {
6957
- mcp_servers: mcpServers
6958
- };
7634
+ const codexConfig = {};
7635
+ if (hasHooks2) {
7636
+ codexConfig.features = { codex_hooks: true };
7637
+ }
7638
+ if (Object.keys(mcpServers).length > 0) {
7639
+ codexConfig.mcp_servers = mcpServers;
7640
+ }
6959
7641
  const tomlContent = FILE_HEADER + stringify(codexConfig);
6960
- await writeFile15(configPath, tomlContent, "utf-8");
7642
+ await writeFile17(configPath, tomlContent, "utf-8");
6961
7643
  return {
6962
- filesWritten: [ctx.outputPath]
7644
+ filesWritten: [ctx.outputPath],
7645
+ managedOutputs: [createManagedOutput(ctx.outputPath, this.id, tomlContent)]
6963
7646
  };
6964
7647
  }
6965
7648
  };
@@ -6971,10 +7654,12 @@ var codexAdapter = {
6971
7654
  { writer: InstructionsMdWriter, outputPath: "AGENTS.md" },
6972
7655
  { writer: SkillsWriter, outputPath: ".codex/skills/" },
6973
7656
  { writer: CommandsAsSkillsWriter, outputPath: ".codex/skills/" },
7657
+ { writer: CodexAgentsWriter, outputPath: ".codex/agents/" },
7658
+ { writer: CodexHooksWriter, outputPath: ".codex/hooks.json" },
6974
7659
  { writer: CodexTomlWriter, outputPath: ".codex/config.toml" }
6975
7660
  ],
6976
7661
  async init(ctx) {
6977
- const codexDir = join18(ctx.projectRoot, ".codex");
7662
+ const codexDir = join21(ctx.projectRoot, ".codex");
6978
7663
  mkdirSync6(codexDir, { recursive: true });
6979
7664
  return {
6980
7665
  filesCreated: [".codex/"],
@@ -6987,17 +7672,24 @@ var codexAdapter = {
6987
7672
  const result = await executeWriters(this.writers, providerBundle, ctx.projectRoot, providerId);
6988
7673
  return {
6989
7674
  filesWritten: result.filesWritten,
6990
- filesDeleted: []
7675
+ filesDeleted: [],
7676
+ managedOutputs: result.managedOutputs
6991
7677
  };
6992
7678
  }
6993
7679
  };
6994
7680
  // ../adapters/src/cursor/index.ts
6995
7681
  import { mkdirSync as mkdirSync7 } from "node:fs";
6996
- import { join as join23 } from "node:path";
7682
+ import { join as join26 } from "node:path";
6997
7683
 
6998
7684
  // ../adapters/src/writers/cursor/agents.ts
6999
- import { mkdir as mkdir8, writeFile as writeFile16 } from "node:fs/promises";
7000
- import { join as join19 } from "node:path";
7685
+ import { mkdir as mkdir10, writeFile as writeFile18 } from "node:fs/promises";
7686
+ import { join as join22 } from "node:path";
7687
+ function getClaudeConfig2(agent) {
7688
+ return {
7689
+ model: agent.claude?.model ?? agent.model,
7690
+ permissionMode: agent.claude?.permissionMode ?? agent.permissionMode
7691
+ };
7692
+ }
7001
7693
  function mapModelToCursor(model) {
7002
7694
  if (!model || model === "inherit")
7003
7695
  return "inherit";
@@ -7009,14 +7701,15 @@ function mapModelToCursor(model) {
7009
7701
  return modelMap[model] ?? "inherit";
7010
7702
  }
7011
7703
  function generateFrontmatter2(agent) {
7704
+ const claude2 = getClaudeConfig2(agent);
7012
7705
  const lines = ["---"];
7013
7706
  lines.push(`name: ${agent.name}`);
7014
7707
  lines.push(`description: "${agent.description.replace(/"/g, "\\\"")}"`);
7015
- const model = mapModelToCursor(agent.model);
7708
+ const model = mapModelToCursor(claude2.model);
7016
7709
  if (model) {
7017
7710
  lines.push(`model: ${model}`);
7018
7711
  }
7019
- if (agent.permissionMode === "plan") {
7712
+ if (claude2.permissionMode === "plan") {
7020
7713
  lines.push("readonly: true");
7021
7714
  }
7022
7715
  if (agent.isBackground) {
@@ -7029,50 +7722,58 @@ function generateFrontmatter2(agent) {
7029
7722
  var CursorAgentsWriter = {
7030
7723
  id: "cursor-agents",
7031
7724
  async write(bundle, ctx) {
7032
- const agentsDir = join19(ctx.projectRoot, ctx.outputPath);
7033
- await mkdir8(agentsDir, { recursive: true });
7725
+ const agentsDir = join22(ctx.projectRoot, ctx.outputPath);
7726
+ await mkdir10(agentsDir, { recursive: true });
7034
7727
  const filesWritten = [];
7728
+ const managedOutputs = [];
7035
7729
  for (const agent of bundle.subagents) {
7036
7730
  const frontmatter = generateFrontmatter2(agent);
7037
7731
  const content = `${frontmatter}
7038
7732
 
7039
7733
  ${agent.systemPrompt}`;
7040
- const agentPath = join19(agentsDir, `${agent.name}.md`);
7041
- await writeFile16(agentPath, content, "utf-8");
7042
- filesWritten.push(join19(ctx.outputPath, `${agent.name}.md`));
7734
+ const agentPath = join22(agentsDir, `${agent.name}.md`);
7735
+ await writeFile18(agentPath, content, "utf-8");
7736
+ const relativePath = join22(ctx.outputPath, `${agent.name}.md`);
7737
+ filesWritten.push(relativePath);
7738
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content));
7043
7739
  }
7044
7740
  return {
7045
- filesWritten
7741
+ filesWritten,
7742
+ managedOutputs
7046
7743
  };
7047
7744
  }
7048
7745
  };
7049
7746
  // ../adapters/src/writers/cursor/commands.ts
7050
- import { mkdir as mkdir9, writeFile as writeFile17 } from "node:fs/promises";
7051
- import { join as join20 } from "node:path";
7747
+ import { mkdir as mkdir11, writeFile as writeFile19 } from "node:fs/promises";
7748
+ import { join as join23 } from "node:path";
7052
7749
  var CursorCommandsWriter = {
7053
7750
  id: "cursor-commands",
7054
7751
  async write(bundle, ctx) {
7055
- const commandsDir = join20(ctx.projectRoot, ctx.outputPath);
7056
- await mkdir9(commandsDir, { recursive: true });
7752
+ const commandsDir = join23(ctx.projectRoot, ctx.outputPath);
7753
+ await mkdir11(commandsDir, { recursive: true });
7057
7754
  const filesWritten = [];
7755
+ const managedOutputs = [];
7058
7756
  for (const command of bundle.commands) {
7059
7757
  const content = `# ${command.name}
7060
7758
 
7061
7759
  ${command.description}
7062
7760
 
7063
7761
  ${command.prompt}`;
7064
- const commandPath = join20(commandsDir, `${command.name}.md`);
7065
- await writeFile17(commandPath, content, "utf-8");
7066
- filesWritten.push(join20(ctx.outputPath, `${command.name}.md`));
7762
+ const commandPath = join23(commandsDir, `${command.name}.md`);
7763
+ await writeFile19(commandPath, content, "utf-8");
7764
+ const relativePath = join23(ctx.outputPath, `${command.name}.md`);
7765
+ filesWritten.push(relativePath);
7766
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content));
7067
7767
  }
7068
7768
  return {
7069
- filesWritten
7769
+ filesWritten,
7770
+ managedOutputs
7070
7771
  };
7071
7772
  }
7072
7773
  };
7073
7774
  // ../adapters/src/writers/cursor/mcp-json.ts
7074
- import { mkdir as mkdir10, writeFile as writeFile18 } from "node:fs/promises";
7075
- import { dirname as dirname4, join as join21 } from "node:path";
7775
+ import { mkdir as mkdir12, writeFile as writeFile20 } from "node:fs/promises";
7776
+ import { dirname as dirname6, join as join24 } from "node:path";
7076
7777
  function buildCursorMcpConfig(mcp) {
7077
7778
  const transport = mcp.transport ?? "stdio";
7078
7779
  if (transport === "http" || transport === "sse") {
@@ -7117,9 +7818,9 @@ var CursorMcpJsonWriter = {
7117
7818
  if (mcps.size === 0) {
7118
7819
  return { filesWritten: [] };
7119
7820
  }
7120
- const configPath = join21(ctx.projectRoot, ctx.outputPath);
7121
- const parentDir = dirname4(configPath);
7122
- await mkdir10(parentDir, { recursive: true });
7821
+ const configPath = join24(ctx.projectRoot, ctx.outputPath);
7822
+ const parentDir = dirname6(configPath);
7823
+ await mkdir12(parentDir, { recursive: true });
7123
7824
  const mcpServers = {};
7124
7825
  for (const [id, mcp] of mcps) {
7125
7826
  const converted = buildCursorMcpConfig(mcp);
@@ -7133,29 +7834,35 @@ var CursorMcpJsonWriter = {
7133
7834
  const cursorMcpJson = {
7134
7835
  mcpServers
7135
7836
  };
7136
- await writeFile18(configPath, `${JSON.stringify(cursorMcpJson, null, 2)}
7137
- `, "utf-8");
7837
+ const content = `${JSON.stringify(cursorMcpJson, null, 2)}
7838
+ `;
7839
+ await writeFile20(configPath, content, "utf-8");
7138
7840
  return {
7139
- filesWritten: [ctx.outputPath]
7841
+ filesWritten: [ctx.outputPath],
7842
+ managedOutputs: [createManagedOutput(ctx.outputPath, this.id, content)]
7140
7843
  };
7141
7844
  }
7142
7845
  };
7143
7846
  // ../adapters/src/writers/cursor/rules.ts
7144
- import { mkdir as mkdir11, writeFile as writeFile19 } from "node:fs/promises";
7145
- import { join as join22 } from "node:path";
7847
+ import { mkdir as mkdir13, writeFile as writeFile21 } from "node:fs/promises";
7848
+ import { join as join25 } from "node:path";
7146
7849
  var CursorRulesWriter = {
7147
7850
  id: "cursor-rules",
7148
7851
  async write(bundle, ctx) {
7149
- const rulesDir = join22(ctx.projectRoot, ctx.outputPath);
7150
- await mkdir11(rulesDir, { recursive: true });
7852
+ const rulesDir = join25(ctx.projectRoot, ctx.outputPath);
7853
+ await mkdir13(rulesDir, { recursive: true });
7151
7854
  const filesWritten = [];
7855
+ const managedOutputs = [];
7152
7856
  for (const rule of bundle.rules) {
7153
- const rulePath = join22(rulesDir, `omnidev-${rule.name}.mdc`);
7154
- await writeFile19(rulePath, rule.content, "utf-8");
7155
- filesWritten.push(join22(ctx.outputPath, `omnidev-${rule.name}.mdc`));
7857
+ const rulePath = join25(rulesDir, `omnidev-${rule.name}.mdc`);
7858
+ await writeFile21(rulePath, rule.content, "utf-8");
7859
+ const relativePath = join25(ctx.outputPath, `omnidev-${rule.name}.mdc`);
7860
+ filesWritten.push(relativePath);
7861
+ managedOutputs.push(createManagedOutput(relativePath, this.id, rule.content));
7156
7862
  }
7157
7863
  return {
7158
- filesWritten
7864
+ filesWritten,
7865
+ managedOutputs
7159
7866
  };
7160
7867
  }
7161
7868
  };
@@ -7172,7 +7879,7 @@ var cursorAdapter = {
7172
7879
  { writer: CursorMcpJsonWriter, outputPath: ".cursor/mcp.json" }
7173
7880
  ],
7174
7881
  async init(ctx) {
7175
- const rulesDir = join23(ctx.projectRoot, ".cursor", "rules");
7882
+ const rulesDir = join26(ctx.projectRoot, ".cursor", "rules");
7176
7883
  mkdirSync7(rulesDir, { recursive: true });
7177
7884
  return {
7178
7885
  filesCreated: [".cursor/rules/"],
@@ -7192,17 +7899,25 @@ var cursorAdapter = {
7192
7899
  filesWritten: [
7193
7900
  ...new Set([...instructionsResult.filesWritten, ...cursorResult.filesWritten])
7194
7901
  ],
7195
- filesDeleted: []
7902
+ filesDeleted: [],
7903
+ managedOutputs: [...instructionsResult.managedOutputs, ...cursorResult.managedOutputs]
7196
7904
  };
7197
7905
  }
7198
7906
  };
7199
7907
  // ../adapters/src/opencode/index.ts
7200
7908
  import { mkdirSync as mkdirSync8 } from "node:fs";
7201
- import { join as join26 } from "node:path";
7909
+ import { join as join29 } from "node:path";
7202
7910
 
7203
7911
  // ../adapters/src/writers/opencode/agents.ts
7204
- import { mkdir as mkdir12, writeFile as writeFile20 } from "node:fs/promises";
7205
- import { join as join24 } from "node:path";
7912
+ import { mkdir as mkdir14, writeFile as writeFile22 } from "node:fs/promises";
7913
+ import { join as join27 } from "node:path";
7914
+ function getClaudeConfig3(agent) {
7915
+ return {
7916
+ tools: agent.claude?.tools ?? agent.tools,
7917
+ model: agent.claude?.model ?? agent.model,
7918
+ permissionMode: agent.claude?.permissionMode ?? agent.permissionMode
7919
+ };
7920
+ }
7206
7921
  function mapModelToOpenCode(model) {
7207
7922
  if (!model || model === "inherit")
7208
7923
  return;
@@ -7234,9 +7949,10 @@ function mapToolsToOpenCode(tools) {
7234
7949
  return toolsObject;
7235
7950
  }
7236
7951
  function generateFrontmatter3(agent) {
7952
+ const claude2 = getClaudeConfig3(agent);
7237
7953
  const lines = ["---"];
7238
7954
  lines.push(`description: "${agent.description.replace(/"/g, "\\\"")}"`);
7239
- const modelId = agent.modelId ?? mapModelToOpenCode(agent.model);
7955
+ const modelId = agent.modelId ?? mapModelToOpenCode(claude2.model);
7240
7956
  if (modelId) {
7241
7957
  lines.push(`model: ${modelId}`);
7242
7958
  }
@@ -7252,14 +7968,14 @@ function generateFrontmatter3(agent) {
7252
7968
  if (agent.hidden !== undefined) {
7253
7969
  lines.push(`hidden: ${agent.hidden}`);
7254
7970
  }
7255
- const toolsObj = agent.toolPermissions ?? mapToolsToOpenCode(agent.tools);
7971
+ const toolsObj = agent.toolPermissions ?? mapToolsToOpenCode(claude2.tools);
7256
7972
  if (toolsObj) {
7257
7973
  lines.push("tools:");
7258
7974
  for (const [tool, enabled] of Object.entries(toolsObj)) {
7259
7975
  lines.push(` ${tool}: ${enabled}`);
7260
7976
  }
7261
7977
  }
7262
- const permissions = agent.permissions ?? mapPermissionsToOpenCode(agent.permissionMode);
7978
+ const permissions = agent.permissions ?? mapPermissionsToOpenCode(claude2.permissionMode);
7263
7979
  if (permissions) {
7264
7980
  lines.push("permissions:");
7265
7981
  for (const [key, value] of Object.entries(permissions)) {
@@ -7280,26 +7996,30 @@ function generateFrontmatter3(agent) {
7280
7996
  var OpenCodeAgentsWriter = {
7281
7997
  id: "opencode-agents",
7282
7998
  async write(bundle, ctx) {
7283
- const agentsDir = join24(ctx.projectRoot, ctx.outputPath);
7284
- await mkdir12(agentsDir, { recursive: true });
7999
+ const agentsDir = join27(ctx.projectRoot, ctx.outputPath);
8000
+ await mkdir14(agentsDir, { recursive: true });
7285
8001
  const filesWritten = [];
8002
+ const managedOutputs = [];
7286
8003
  for (const agent of bundle.subagents) {
7287
8004
  const frontmatter = generateFrontmatter3(agent);
7288
8005
  const content = `${frontmatter}
7289
8006
 
7290
8007
  ${agent.systemPrompt}`;
7291
- const agentPath = join24(agentsDir, `${agent.name}.md`);
7292
- await writeFile20(agentPath, content, "utf-8");
7293
- filesWritten.push(join24(ctx.outputPath, `${agent.name}.md`));
8008
+ const agentPath = join27(agentsDir, `${agent.name}.md`);
8009
+ await writeFile22(agentPath, content, "utf-8");
8010
+ const relativePath = join27(ctx.outputPath, `${agent.name}.md`);
8011
+ filesWritten.push(relativePath);
8012
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content));
7294
8013
  }
7295
8014
  return {
7296
- filesWritten
8015
+ filesWritten,
8016
+ managedOutputs
7297
8017
  };
7298
8018
  }
7299
8019
  };
7300
8020
  // ../adapters/src/writers/opencode/commands.ts
7301
- import { mkdir as mkdir13, writeFile as writeFile21 } from "node:fs/promises";
7302
- import { join as join25 } from "node:path";
8021
+ import { mkdir as mkdir15, writeFile as writeFile23 } from "node:fs/promises";
8022
+ import { join as join28 } from "node:path";
7303
8023
  function generateFrontmatter4(command) {
7304
8024
  const lines = ["---"];
7305
8025
  lines.push(`description: "${command.description.replace(/"/g, "\\\"")}"`);
@@ -7316,20 +8036,24 @@ function generateFrontmatter4(command) {
7316
8036
  var OpenCodeCommandsWriter = {
7317
8037
  id: "opencode-commands",
7318
8038
  async write(bundle, ctx) {
7319
- const commandsDir = join25(ctx.projectRoot, ctx.outputPath);
7320
- await mkdir13(commandsDir, { recursive: true });
8039
+ const commandsDir = join28(ctx.projectRoot, ctx.outputPath);
8040
+ await mkdir15(commandsDir, { recursive: true });
7321
8041
  const filesWritten = [];
8042
+ const managedOutputs = [];
7322
8043
  for (const command of bundle.commands) {
7323
8044
  const frontmatter = generateFrontmatter4(command);
7324
8045
  const content = `${frontmatter}
7325
8046
 
7326
8047
  ${command.prompt}`;
7327
- const commandPath = join25(commandsDir, `${command.name}.md`);
7328
- await writeFile21(commandPath, content, "utf-8");
7329
- filesWritten.push(join25(ctx.outputPath, `${command.name}.md`));
8048
+ const commandPath = join28(commandsDir, `${command.name}.md`);
8049
+ await writeFile23(commandPath, content, "utf-8");
8050
+ const relativePath = join28(ctx.outputPath, `${command.name}.md`);
8051
+ filesWritten.push(relativePath);
8052
+ managedOutputs.push(createManagedOutput(relativePath, this.id, content));
7330
8053
  }
7331
8054
  return {
7332
- filesWritten
8055
+ filesWritten,
8056
+ managedOutputs
7333
8057
  };
7334
8058
  }
7335
8059
  };
@@ -7344,7 +8068,7 @@ var opencodeAdapter = {
7344
8068
  { writer: OpenCodeCommandsWriter, outputPath: ".opencode/commands/" }
7345
8069
  ],
7346
8070
  async init(ctx) {
7347
- const opencodeDir = join26(ctx.projectRoot, ".opencode");
8071
+ const opencodeDir = join29(ctx.projectRoot, ".opencode");
7348
8072
  mkdirSync8(opencodeDir, { recursive: true });
7349
8073
  return {
7350
8074
  filesCreated: [".opencode/"],
@@ -7357,7 +8081,8 @@ var opencodeAdapter = {
7357
8081
  const result = await executeWriters(this.writers, providerBundle, ctx.projectRoot, providerId);
7358
8082
  return {
7359
8083
  filesWritten: result.filesWritten,
7360
- filesDeleted: []
8084
+ filesDeleted: [],
8085
+ managedOutputs: result.managedOutputs
7361
8086
  };
7362
8087
  }
7363
8088
  };
@@ -7645,7 +8370,7 @@ If the capability name is omitted, it will be inferred from:
7645
8370
  - For GitHub sources: the repository name or last path segment
7646
8371
 
7647
8372
  Claude plugins (.claude-plugin/plugin.json) are automatically wrapped as OmniDev capabilities.
7648
- Hooks defined in hooks.json are also supported and will be synced to .claude/settings.json.
8373
+ Hooks defined in hooks.json are also supported and will be synced into OmniDev's hook pipeline.
7649
8374
 
7650
8375
  Examples:
7651
8376
  omnidev add cap my-cap --github expo/skills # Uses version = "latest"
@@ -7801,8 +8526,8 @@ var addRoutes = buildRouteMap({
7801
8526
 
7802
8527
  // src/commands/capability.ts
7803
8528
  import { existsSync as existsSync25, mkdirSync as mkdirSync9 } from "node:fs";
7804
- import { writeFile as writeFile22 } from "node:fs/promises";
7805
- import { join as join27 } from "node:path";
8529
+ import { writeFile as writeFile24 } from "node:fs/promises";
8530
+ import { join as join30 } from "node:path";
7806
8531
  import { input } from "@inquirer/prompts";
7807
8532
  init_src();
7808
8533
  import { buildCommand as buildCommand2, buildRouteMap as buildRouteMap2 } from "@stricli/core";
@@ -8022,21 +8747,21 @@ async function runCapabilityNew(flags, capabilityId) {
8022
8747
  const name = toTitleCase(id);
8023
8748
  mkdirSync9(capabilityDir, { recursive: true });
8024
8749
  const capabilityToml = generateCapabilityToml2({ id, name });
8025
- await writeFile22(join27(capabilityDir, "capability.toml"), capabilityToml, "utf-8");
8026
- const skillDir = join27(capabilityDir, "skills", "getting-started");
8750
+ await writeFile24(join30(capabilityDir, "capability.toml"), capabilityToml, "utf-8");
8751
+ const skillDir = join30(capabilityDir, "skills", "getting-started");
8027
8752
  mkdirSync9(skillDir, { recursive: true });
8028
- await writeFile22(join27(skillDir, "SKILL.md"), generateSkillTemplate("getting-started"), "utf-8");
8029
- const rulesDir = join27(capabilityDir, "rules");
8753
+ await writeFile24(join30(skillDir, "SKILL.md"), generateSkillTemplate("getting-started"), "utf-8");
8754
+ const rulesDir = join30(capabilityDir, "rules");
8030
8755
  mkdirSync9(rulesDir, { recursive: true });
8031
- await writeFile22(join27(rulesDir, "coding-standards.md"), generateRuleTemplate("coding-standards"), "utf-8");
8032
- const hooksDir = join27(capabilityDir, "hooks");
8756
+ await writeFile24(join30(rulesDir, "coding-standards.md"), generateRuleTemplate("coding-standards"), "utf-8");
8757
+ const hooksDir = join30(capabilityDir, "hooks");
8033
8758
  mkdirSync9(hooksDir, { recursive: true });
8034
- await writeFile22(join27(hooksDir, "hooks.toml"), generateHooksTemplate(), "utf-8");
8035
- await writeFile22(join27(hooksDir, "example-hook.sh"), generateHookScript(), "utf-8");
8036
- await writeFile22(join27(capabilityDir, ".gitignore"), generateGitignore(Boolean(flags.programmatic)), "utf-8");
8759
+ await writeFile24(join30(hooksDir, "hooks.toml"), generateHooksTemplate(), "utf-8");
8760
+ await writeFile24(join30(hooksDir, "example-hook.sh"), generateHookScript(), "utf-8");
8761
+ await writeFile24(join30(capabilityDir, ".gitignore"), generateGitignore(Boolean(flags.programmatic)), "utf-8");
8037
8762
  if (flags.programmatic) {
8038
- await writeFile22(join27(capabilityDir, "package.json"), generatePackageJson(id), "utf-8");
8039
- await writeFile22(join27(capabilityDir, "index.ts"), generateIndexTs(id, name), "utf-8");
8763
+ await writeFile24(join30(capabilityDir, "package.json"), generatePackageJson(id), "utf-8");
8764
+ await writeFile24(join30(capabilityDir, "index.ts"), generateIndexTs(id, name), "utf-8");
8040
8765
  }
8041
8766
  console.log(`✓ Created capability: ${name}`);
8042
8767
  console.log(` Location: ${capabilityDir}`);
@@ -8379,7 +9104,7 @@ async function checkCapabilitiesDir() {
8379
9104
  // src/commands/init.ts
8380
9105
  import { exec } from "node:child_process";
8381
9106
  import { existsSync as existsSync27, mkdirSync as mkdirSync10 } from "node:fs";
8382
- import { readFile as readFile21, writeFile as writeFile23 } from "node:fs/promises";
9107
+ import { readFile as readFile21, writeFile as writeFile25 } from "node:fs/promises";
8383
9108
  import { promisify as promisify2 } from "node:util";
8384
9109
  init_src();
8385
9110
  import { buildCommand as buildCommand4 } from "@stricli/core";
@@ -8465,7 +9190,7 @@ async function runInit(_flags, providerArg) {
8465
9190
  await setActiveProfile("default");
8466
9191
  }
8467
9192
  if (!existsSync27("OMNI.md")) {
8468
- await writeFile23("OMNI.md", generateOmniMdTemplate(), "utf-8");
9193
+ await writeFile25("OMNI.md", generateOmniMdTemplate(), "utf-8");
8469
9194
  }
8470
9195
  const config3 = await loadConfig();
8471
9196
  const ctx = {
@@ -8558,7 +9283,7 @@ async function addToGitignore(entriesToAdd, sectionHeader) {
8558
9283
  ${missingEntries.join(`
8559
9284
  `)}
8560
9285
  `;
8561
- await writeFile23(gitignorePath, content + section, "utf-8");
9286
+ await writeFile25(gitignorePath, content + section, "utf-8");
8562
9287
  }
8563
9288
  async function getTrackedProviderFiles(files) {
8564
9289
  const tracked = [];
@@ -8691,6 +9416,8 @@ async function runProfileSet(profileName) {
8691
9416
  }
8692
9417
 
8693
9418
  // src/commands/provider.ts
9419
+ import { existsSync as existsSync29 } from "node:fs";
9420
+ import { readFile as readFile22 } from "node:fs/promises";
8694
9421
  init_src();
8695
9422
  import { buildCommand as buildCommand6, buildRouteMap as buildRouteMap4 } from "@stricli/core";
8696
9423
  async function runProviderList() {
@@ -8724,6 +9451,7 @@ async function runProviderEnable(_flags, providerId) {
8724
9451
  }
8725
9452
  await enableProvider(providerId);
8726
9453
  console.log(`✓ Enabled provider: ${adapter.displayName}`);
9454
+ await maybeRemindAboutProviderGitignore(providerId);
8727
9455
  const enabledAdapters = await getEnabledAdapters();
8728
9456
  await syncAgentConfiguration({ silent: false, adapters: enabledAdapters });
8729
9457
  }
@@ -8804,10 +9532,33 @@ var providerRoutes = buildRouteMap4({
8804
9532
  brief: "Manage AI provider adapters"
8805
9533
  }
8806
9534
  });
9535
+ async function maybeRemindAboutProviderGitignore(providerId) {
9536
+ const missingEntries = await getMissingGitignoreEntries(getProviderGitignoreFiles([providerId]));
9537
+ if (missingEntries.length === 0) {
9538
+ return;
9539
+ }
9540
+ console.log("");
9541
+ console.log("Also update your .gitignore to ignore provider files if you do not want to commit them:");
9542
+ for (const entry of missingEntries) {
9543
+ console.log(` - ${entry}`);
9544
+ }
9545
+ }
9546
+ async function getMissingGitignoreEntries(entries) {
9547
+ if (entries.length === 0) {
9548
+ return [];
9549
+ }
9550
+ let content = "";
9551
+ if (existsSync29(".gitignore")) {
9552
+ content = await readFile22(".gitignore", "utf-8");
9553
+ }
9554
+ const lines = new Set(content.split(`
9555
+ `).map((line) => line.trim()));
9556
+ return [...new Set(entries)].filter((entry) => !lines.has(entry));
9557
+ }
8807
9558
 
8808
9559
  // src/commands/security.ts
8809
9560
  init_src();
8810
- import { existsSync as existsSync29 } from "node:fs";
9561
+ import { existsSync as existsSync30 } from "node:fs";
8811
9562
  import { buildCommand as buildCommand7, buildRouteMap as buildRouteMap5 } from "@stricli/core";
8812
9563
  var VALID_FINDING_TYPES = [
8813
9564
  "unicode_bidi",
@@ -8926,7 +9677,7 @@ function formatFindingsWithHints(summary) {
8926
9677
  }
8927
9678
  async function runSecurityIssues(flags = {}) {
8928
9679
  try {
8929
- if (!existsSync29("omni.toml")) {
9680
+ if (!existsSync30("omni.toml")) {
8930
9681
  console.log("No config file found");
8931
9682
  console.log(" Run: omnidev init");
8932
9683
  process.exit(1);
@@ -9181,7 +9932,7 @@ var securityRoutes = buildRouteMap5({
9181
9932
  });
9182
9933
 
9183
9934
  // src/commands/sync.ts
9184
- import { existsSync as existsSync30 } from "node:fs";
9935
+ import { existsSync as existsSync31 } from "node:fs";
9185
9936
  init_src();
9186
9937
  import { buildCommand as buildCommand8 } from "@stricli/core";
9187
9938
  var PROVIDERS_STATE_PATH = ".omni/state/providers.json";
@@ -9199,7 +9950,7 @@ async function runSync() {
9199
9950
  const config3 = await loadConfig();
9200
9951
  const activeProfile = await getActiveProfile() ?? "default";
9201
9952
  let adapters = await getEnabledAdapters();
9202
- if (!existsSync30(PROVIDERS_STATE_PATH) || adapters.length === 0) {
9953
+ if (!existsSync31(PROVIDERS_STATE_PATH) || adapters.length === 0) {
9203
9954
  console.log("No providers configured yet. Select your provider(s):");
9204
9955
  const providerIds = await promptForProviders();
9205
9956
  await writeEnabledProviders(providerIds);
@@ -9409,9 +10160,9 @@ async function buildDynamicApp() {
9409
10160
  security: securityRoutes
9410
10161
  };
9411
10162
  debug("Core routes registered", Object.keys(routes));
9412
- const configPath = join28(process.cwd(), "omni.toml");
9413
- debug("Checking for config", { configPath, exists: existsSync31(configPath), cwd: process.cwd() });
9414
- if (existsSync31(configPath)) {
10163
+ const configPath = join31(process.cwd(), "omni.toml");
10164
+ debug("Checking for config", { configPath, exists: existsSync32(configPath), cwd: process.cwd() });
10165
+ if (existsSync32(configPath)) {
9415
10166
  try {
9416
10167
  debug("Loading capability commands...");
9417
10168
  const capabilityCommands = await loadCapabilityCommands();
@@ -9486,25 +10237,25 @@ async function loadCapabilityCommands() {
9486
10237
  return commands;
9487
10238
  }
9488
10239
  async function loadCapabilityExport(capability3) {
9489
- const capabilityPath = join28(process.cwd(), capability3.path);
9490
- const builtIndexPath = join28(capabilityPath, "dist", "index.js");
9491
- const jsIndexPath = join28(capabilityPath, "index.js");
9492
- const tsIndexPath = join28(capabilityPath, "index.ts");
10240
+ const capabilityPath = join31(process.cwd(), capability3.path);
10241
+ const builtIndexPath = join31(capabilityPath, "dist", "index.js");
10242
+ const jsIndexPath = join31(capabilityPath, "index.js");
10243
+ const tsIndexPath = join31(capabilityPath, "index.ts");
9493
10244
  debug(`Checking entry points for '${capability3.id}'`, {
9494
10245
  capabilityPath,
9495
10246
  builtIndexPath,
9496
- builtExists: existsSync31(builtIndexPath),
10247
+ builtExists: existsSync32(builtIndexPath),
9497
10248
  jsIndexPath,
9498
- jsExists: existsSync31(jsIndexPath),
10249
+ jsExists: existsSync32(jsIndexPath),
9499
10250
  tsIndexPath,
9500
- tsExists: existsSync31(tsIndexPath)
10251
+ tsExists: existsSync32(tsIndexPath)
9501
10252
  });
9502
10253
  let indexPath = null;
9503
- if (existsSync31(builtIndexPath)) {
10254
+ if (existsSync32(builtIndexPath)) {
9504
10255
  indexPath = builtIndexPath;
9505
- } else if (existsSync31(jsIndexPath)) {
10256
+ } else if (existsSync32(jsIndexPath)) {
9506
10257
  indexPath = jsIndexPath;
9507
- } else if (existsSync31(tsIndexPath)) {
10258
+ } else if (existsSync32(tsIndexPath)) {
9508
10259
  indexPath = tsIndexPath;
9509
10260
  }
9510
10261
  if (!indexPath) {