omniagent 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -38,7 +38,7 @@ async function findRepoRoot(startDir) {
38
38
  }
39
39
  return await findUp(startDir, "package.json");
40
40
  }
41
- function hashIdentifier$3(value) {
41
+ function hashIdentifier$4(value) {
42
42
  return createHash("sha256").update(value).digest("hex");
43
43
  }
44
44
  async function pathExists$1(candidate) {
@@ -72,7 +72,7 @@ async function listRepoSlashCommandManifestPaths(repoRoot) {
72
72
  return matches;
73
73
  }
74
74
  function resolveRepoScopedStatePaths(repoRoot, homeDir) {
75
- const repoHash = hashIdentifier$3(repoRoot);
75
+ const repoHash = hashIdentifier$4(repoRoot);
76
76
  return [
77
77
  path.join(homeDir, ".omniagent", "state", "managed-outputs", "projects", repoHash),
78
78
  path.join(homeDir, ".omniagent", "state", "instructions", "projects", repoHash),
@@ -333,11 +333,20 @@ function matchAny(name, patterns) {
333
333
  }
334
334
  return matched;
335
335
  }
336
+ function normalizeItemSelection(item) {
337
+ if (!item) {
338
+ return { canonicalName: "", enabledByDefault: true };
339
+ }
340
+ if (typeof item === "string") {
341
+ return { canonicalName: item, enabledByDefault: true };
342
+ }
343
+ return item;
344
+ }
336
345
  function createProfileItemFilter(resolved) {
337
346
  if (!resolved || resolved.names.length === 0) {
338
347
  return {
339
348
  enabled: false,
340
- includes: () => true,
349
+ includes: (_category, item) => normalizeItemSelection(item).enabledByDefault,
341
350
  collectUnknownWarnings: () => []
342
351
  };
343
352
  }
@@ -349,17 +358,20 @@ function createProfileItemFilter(resolved) {
349
358
  }
350
359
  return {
351
360
  enabled: true,
352
- includes(category, canonicalName) {
361
+ includes(category, item) {
362
+ const { canonicalName, enabledByDefault } = normalizeItemSelection(item);
353
363
  const enablePatterns = enablePatternsByCategory.get(category) ?? [];
354
364
  const disablePatterns = disablePatternsByCategory.get(category) ?? [];
355
365
  const enableApplies = enablePatterns.length > 0;
356
- if (enableApplies && !matchAny(canonicalName, enablePatterns)) {
366
+ const enableMatches = matchAny(canonicalName, enablePatterns);
367
+ const disableMatches = matchAny(canonicalName, disablePatterns);
368
+ if (disableMatches) {
357
369
  return false;
358
370
  }
359
- if (matchAny(canonicalName, disablePatterns)) {
360
- return false;
371
+ if (enableApplies) {
372
+ return enableMatches;
361
373
  }
362
- return true;
374
+ return enabledByDefault;
363
375
  },
364
376
  collectUnknownWarnings() {
365
377
  const warnings = [];
@@ -976,6 +988,28 @@ async function listSkillDirectories(root) {
976
988
  }
977
989
  return directories;
978
990
  }
991
+ const SYNC_ROUTING_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["targets", "targetagents", "enabled"]);
992
+ function resolveFrontmatterEnabledByDefault(options) {
993
+ const rawEnabled = options.frontmatter.enabled;
994
+ if (rawEnabled === void 0) {
995
+ return true;
996
+ }
997
+ if (Array.isArray(rawEnabled)) {
998
+ throw new Error(
999
+ `${options.itemKind} "${options.itemName}" has invalid enabled value in ${options.sourcePath}. Expected true or false.`
1000
+ );
1001
+ }
1002
+ const normalized = rawEnabled.trim().toLowerCase();
1003
+ if (normalized === "true") {
1004
+ return true;
1005
+ }
1006
+ if (normalized === "false") {
1007
+ return false;
1008
+ }
1009
+ throw new Error(
1010
+ `${options.itemKind} "${options.itemName}" has invalid enabled value "${rawEnabled}" in ${options.sourcePath}. Expected true or false.`
1011
+ );
1012
+ }
979
1013
  function resolveLocalPrecedence(options) {
980
1014
  const local = [...options.localPath, ...options.localSuffix];
981
1015
  const localPathKeys = new Set(options.localPath.map(options.key));
@@ -1389,7 +1423,6 @@ function stripFrontmatterFields(contents, keysToRemove) {
1389
1423
  const outputLines = [lines[0], ...filtered, ...lines.slice(endIndex)];
1390
1424
  return outputLines.join(eol);
1391
1425
  }
1392
- const TARGET_FRONTMATTER_KEYS$3 = /* @__PURE__ */ new Set(["targets", "targetagents"]);
1393
1426
  function isSlashCommandLike(value) {
1394
1427
  if (!value || typeof value !== "object") {
1395
1428
  return false;
@@ -1459,7 +1492,7 @@ const copilotTarget = {
1459
1492
  outputs: [
1460
1493
  {
1461
1494
  outputPath: agentPath,
1462
- content: stripFrontmatterFields(item.rawContents, TARGET_FRONTMATTER_KEYS$3)
1495
+ content: stripFrontmatterFields(item.rawContents, SYNC_ROUTING_FRONTMATTER_KEYS)
1463
1496
  },
1464
1497
  {
1465
1498
  outputPath: promptPath,
@@ -1534,7 +1567,7 @@ const BUILTIN_TARGETS = [
1534
1567
  copilotTarget
1535
1568
  ];
1536
1569
  Object.freeze(BUILTIN_TARGETS.map((target) => target.id));
1537
- function resolveSkillName(frontmatter, fallback) {
1570
+ function resolveSkillName$1(frontmatter, fallback) {
1538
1571
  const rawName = frontmatter.name;
1539
1572
  if (typeof rawName === "string") {
1540
1573
  const trimmed = rawName.trim();
@@ -1547,7 +1580,7 @@ function resolveSkillName(frontmatter, fallback) {
1547
1580
  function normalizeSkillKey$1(relativePath) {
1548
1581
  return path.normalize(relativePath).replace(/\\/g, "/").toLowerCase();
1549
1582
  }
1550
- function resolveSkillRelativePath(skillsRoot, directoryPath) {
1583
+ function resolveSkillRelativePath$1(skillsRoot, directoryPath) {
1551
1584
  const relativePath = path.relative(skillsRoot, directoryPath);
1552
1585
  if (!relativePath) {
1553
1586
  return { relativePath, hadLocalSuffix: false };
@@ -1561,6 +1594,16 @@ function resolveSkillRelativePath(skillsRoot, directoryPath) {
1561
1594
  const normalized = parent === "." ? strippedBase : path.join(parent, strippedBase);
1562
1595
  return { relativePath: normalized, hadLocalSuffix: true };
1563
1596
  }
1597
+ function resolveSkillRoutingError(options) {
1598
+ if (options.invalidTargets.length > 0) {
1599
+ const invalidList = options.invalidTargets.join(", ");
1600
+ return `Skill "${options.name}" has unsupported targets (${invalidList}) in ${options.sourcePath}.`;
1601
+ }
1602
+ if (hasRawTargetValues(options.rawTargets) && (!options.targets || options.targets.length === 0)) {
1603
+ return `Skill "${options.name}" has empty targets in ${options.sourcePath}.`;
1604
+ }
1605
+ return null;
1606
+ }
1564
1607
  async function buildSkillDefinition(options) {
1565
1608
  let metadata;
1566
1609
  if (options.sourceType === "local") {
@@ -1576,24 +1619,32 @@ async function buildSkillDefinition(options) {
1576
1619
  const rawContents = await readFile(sourcePath, "utf8");
1577
1620
  const { frontmatter, body } = extractFrontmatter$1(rawContents);
1578
1621
  const relativePath = options.relativePath ?? path.relative(options.skillsRoot, options.directoryPath);
1579
- const name = resolveSkillName(frontmatter, relativePath || path.basename(options.directoryPath));
1622
+ const name = resolveSkillName$1(frontmatter, relativePath || path.basename(options.directoryPath));
1623
+ const enabledByDefault = resolveFrontmatterEnabledByDefault({
1624
+ frontmatter,
1625
+ itemKind: "Skill",
1626
+ itemName: name,
1627
+ sourcePath
1628
+ });
1580
1629
  const rawTargets = [frontmatter.targets, frontmatter.targetAgents];
1581
1630
  const { targets, invalidTargets } = resolveFrontmatterTargets(
1582
1631
  rawTargets,
1583
1632
  options.resolveTargetName
1584
1633
  );
1585
- if (invalidTargets.length > 0) {
1586
- const invalidList = invalidTargets.join(", ");
1587
- throw new InvalidFrontmatterTargetsError(
1588
- `Skill "${name}" has unsupported targets (${invalidList}) in ${sourcePath}.`
1589
- );
1590
- }
1591
- if (hasRawTargetValues(rawTargets) && (!targets || targets.length === 0)) {
1592
- throw new InvalidFrontmatterTargetsError(`Skill "${name}" has empty targets in ${sourcePath}.`);
1634
+ const routingError = resolveSkillRoutingError({
1635
+ name,
1636
+ sourcePath,
1637
+ rawTargets,
1638
+ targets,
1639
+ invalidTargets
1640
+ });
1641
+ if (enabledByDefault && routingError) {
1642
+ throw new InvalidFrontmatterTargetsError(routingError);
1593
1643
  }
1594
1644
  const { outputFileName } = stripLocalSuffix(options.skillFileName, ".md");
1595
1645
  return {
1596
1646
  name,
1647
+ enabledByDefault,
1597
1648
  relativePath,
1598
1649
  directoryPath: options.directoryPath,
1599
1650
  sourcePath,
@@ -1606,9 +1657,15 @@ async function buildSkillDefinition(options) {
1606
1657
  frontmatter,
1607
1658
  body,
1608
1659
  targetAgents: targets,
1609
- invalidTargets
1660
+ invalidTargets,
1661
+ routingError
1610
1662
  };
1611
1663
  }
1664
+ function assertSkillDefinitionUsable(skill) {
1665
+ if (skill.routingError) {
1666
+ throw new InvalidFrontmatterTargetsError(skill.routingError);
1667
+ }
1668
+ }
1612
1669
  async function loadSkillCatalog(repoRoot, options = {}) {
1613
1670
  const includeLocal = options.includeLocal ?? true;
1614
1671
  const fallbackResolver = createTargetNameResolver(BUILTIN_TARGETS).resolveTargetName;
@@ -1629,7 +1686,7 @@ async function loadSkillCatalog(repoRoot, options = {}) {
1629
1686
  const localPathSkills = [];
1630
1687
  const localSuffixSkills = [];
1631
1688
  for (const entry2 of sharedEntries) {
1632
- const { relativePath, hadLocalSuffix } = resolveSkillRelativePath(
1689
+ const { relativePath, hadLocalSuffix } = resolveSkillRelativePath$1(
1633
1690
  skillsRoot,
1634
1691
  entry2.directoryPath
1635
1692
  );
@@ -1686,7 +1743,7 @@ async function loadSkillCatalog(repoRoot, options = {}) {
1686
1743
  if (!skillFileName) {
1687
1744
  continue;
1688
1745
  }
1689
- const { relativePath } = resolveSkillRelativePath(localSkillsRoot, entry2.directoryPath);
1746
+ const { relativePath } = resolveSkillRelativePath$1(localSkillsRoot, entry2.directoryPath);
1690
1747
  localPathSkills.push(
1691
1748
  await buildSkillDefinition({
1692
1749
  directoryPath: entry2.directoryPath,
@@ -1740,7 +1797,13 @@ async function buildCommandDefinition(options) {
1740
1797
  const contents = await readFile(options.filePath, "utf8");
1741
1798
  const { frontmatter, body } = extractFrontmatter$1(contents);
1742
1799
  const prompt = body.trimEnd();
1743
- if (!prompt.trim()) {
1800
+ const enabledByDefault = resolveFrontmatterEnabledByDefault({
1801
+ frontmatter,
1802
+ itemKind: "Slash command",
1803
+ itemName: options.commandName,
1804
+ sourcePath: options.filePath
1805
+ });
1806
+ if (enabledByDefault && !prompt.trim()) {
1744
1807
  throw new Error(`Slash command "${options.commandName}" has an empty prompt.`);
1745
1808
  }
1746
1809
  const rawTargets = [frontmatter.targets, frontmatter.targetAgents];
@@ -1748,16 +1811,15 @@ async function buildCommandDefinition(options) {
1748
1811
  rawTargets,
1749
1812
  options.resolveTargetName
1750
1813
  );
1814
+ let routingError = null;
1751
1815
  if (invalidTargets.length > 0) {
1752
1816
  const invalidList = invalidTargets.join(", ");
1753
- throw new InvalidFrontmatterTargetsError(
1754
- `Slash command "${options.commandName}" has unsupported targets (${invalidList}) in ${options.filePath}.`
1755
- );
1817
+ routingError = `Slash command "${options.commandName}" has unsupported targets (${invalidList}) in ${options.filePath}.`;
1818
+ } else if (hasRawTargetValues(rawTargets) && (!targets || targets.length === 0)) {
1819
+ routingError = `Slash command "${options.commandName}" has empty targets in ${options.filePath}.`;
1756
1820
  }
1757
- if (hasRawTargetValues(rawTargets) && (!targets || targets.length === 0)) {
1758
- throw new InvalidFrontmatterTargetsError(
1759
- `Slash command "${options.commandName}" has empty targets in ${options.filePath}.`
1760
- );
1821
+ if (enabledByDefault && routingError) {
1822
+ throw new InvalidFrontmatterTargetsError(routingError);
1761
1823
  }
1762
1824
  let metadata;
1763
1825
  if (options.sourceType === "local") {
@@ -1771,6 +1833,7 @@ async function buildCommandDefinition(options) {
1771
1833
  }
1772
1834
  return {
1773
1835
  name: options.commandName,
1836
+ enabledByDefault,
1774
1837
  prompt,
1775
1838
  sourcePath: options.filePath,
1776
1839
  sourceType: metadata.sourceType,
@@ -1779,9 +1842,18 @@ async function buildCommandDefinition(options) {
1779
1842
  rawContents: contents,
1780
1843
  targetAgents: targets,
1781
1844
  invalidTargets,
1782
- frontmatter
1845
+ frontmatter,
1846
+ routingError
1783
1847
  };
1784
1848
  }
1849
+ function assertSlashCommandDefinitionUsable(command) {
1850
+ if (!command.prompt.trim()) {
1851
+ throw new Error(`Slash command "${command.name}" has an empty prompt.`);
1852
+ }
1853
+ if (command.routingError) {
1854
+ throw new InvalidFrontmatterTargetsError(command.routingError);
1855
+ }
1856
+ }
1785
1857
  function registerUniqueName$1(seen, commandName, filePath) {
1786
1858
  const lowerName = normalizeName(commandName);
1787
1859
  if (seen.has(lowerName)) {
@@ -2003,9 +2075,6 @@ async function buildSubagentDefinition(options) {
2003
2075
  const message = error instanceof Error ? error.message : String(error);
2004
2076
  throw new Error(`Invalid frontmatter in ${options.filePath}: ${message}`);
2005
2077
  }
2006
- if (!body.trim()) {
2007
- throw new Error(`Subagent file has empty body: ${options.filePath}.`);
2008
- }
2009
2078
  let resolvedName;
2010
2079
  try {
2011
2080
  resolvedName = resolveSubagentName(frontmatter, options.fileName);
@@ -2013,21 +2082,29 @@ async function buildSubagentDefinition(options) {
2013
2082
  const message = error instanceof Error ? error.message : String(error);
2014
2083
  throw new Error(`Invalid frontmatter in ${options.filePath}: ${message}`);
2015
2084
  }
2085
+ const enabledByDefault = resolveFrontmatterEnabledByDefault({
2086
+ frontmatter,
2087
+ itemKind: "Subagent",
2088
+ itemName: resolvedName,
2089
+ sourcePath: options.filePath
2090
+ });
2091
+ if (enabledByDefault && !body.trim()) {
2092
+ throw new Error(`Subagent file has empty body: ${options.filePath}.`);
2093
+ }
2016
2094
  const rawTargets = [frontmatter.targets, frontmatter.targetAgents];
2017
2095
  const { targets, invalidTargets } = resolveFrontmatterTargets(
2018
2096
  rawTargets,
2019
2097
  options.resolveTargetName
2020
2098
  );
2099
+ let routingError = null;
2021
2100
  if (invalidTargets.length > 0) {
2022
2101
  const invalidList = invalidTargets.join(", ");
2023
- throw new InvalidFrontmatterTargetsError(
2024
- `Subagent "${resolvedName}" has unsupported targets (${invalidList}) in ${options.filePath}.`
2025
- );
2102
+ routingError = `Subagent "${resolvedName}" has unsupported targets (${invalidList}) in ${options.filePath}.`;
2103
+ } else if (hasRawTargetValues(rawTargets) && (!targets || targets.length === 0)) {
2104
+ routingError = `Subagent "${resolvedName}" has empty targets in ${options.filePath}.`;
2026
2105
  }
2027
- if (hasRawTargetValues(rawTargets) && (!targets || targets.length === 0)) {
2028
- throw new InvalidFrontmatterTargetsError(
2029
- `Subagent "${resolvedName}" has empty targets in ${options.filePath}.`
2030
- );
2106
+ if (enabledByDefault && routingError) {
2107
+ throw new InvalidFrontmatterTargetsError(routingError);
2031
2108
  }
2032
2109
  let metadata;
2033
2110
  if (options.sourceType === "local") {
@@ -2041,6 +2118,7 @@ async function buildSubagentDefinition(options) {
2041
2118
  }
2042
2119
  return {
2043
2120
  resolvedName,
2121
+ enabledByDefault,
2044
2122
  sourcePath: options.filePath,
2045
2123
  fileName: options.fileName,
2046
2124
  sourceType: metadata.sourceType,
@@ -2050,9 +2128,18 @@ async function buildSubagentDefinition(options) {
2050
2128
  frontmatter,
2051
2129
  body,
2052
2130
  targetAgents: targets,
2053
- invalidTargets
2131
+ invalidTargets,
2132
+ routingError
2054
2133
  };
2055
2134
  }
2135
+ function assertSubagentDefinitionUsable(subagent) {
2136
+ if (!subagent.body.trim()) {
2137
+ throw new Error(`Subagent file has empty body: ${subagent.sourcePath}.`);
2138
+ }
2139
+ if (subagent.routingError) {
2140
+ throw new InvalidFrontmatterTargetsError(subagent.routingError);
2141
+ }
2142
+ }
2056
2143
  function registerUniqueName(seen, resolvedName, filePath) {
2057
2144
  const nameKey = normalizeName(resolvedName);
2058
2145
  const existingPath = seen.get(nameKey);
@@ -2811,6 +2898,26 @@ function resolveTargets(options) {
2811
2898
  disabledTargets
2812
2899
  };
2813
2900
  }
2901
+ const PROFILE_SCHEMA_URL = "https://raw.githubusercontent.com/JoeRoddy/omniagent/master/schemas/profile.v1.json";
2902
+ const ANSI_GRAY = "\x1B[90m";
2903
+ const ANSI_RESET_FOREGROUND = "\x1B[39m";
2904
+ const PROFILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
2905
+ const STARTER_PROFILE = {
2906
+ $schema: PROFILE_SCHEMA_URL,
2907
+ description: "",
2908
+ targets: {},
2909
+ enable: {
2910
+ skills: [],
2911
+ subagents: [],
2912
+ commands: []
2913
+ },
2914
+ disable: {
2915
+ skills: [],
2916
+ subagents: [],
2917
+ commands: []
2918
+ },
2919
+ variables: {}
2920
+ };
2814
2921
  async function resolveRepoAndAgentsDir(argv) {
2815
2922
  const startDir = process.cwd();
2816
2923
  const repoRoot = await findRepoRoot(startDir);
@@ -2844,6 +2951,122 @@ function formatAnnotations(entry2) {
2844
2951
  }
2845
2952
  return annotations;
2846
2953
  }
2954
+ function validateProfileNamePart(name) {
2955
+ if (!PROFILE_NAME_PATTERN.test(name)) {
2956
+ return "Profile names may only contain letters, numbers, dots, underscores, and hyphens, and must start with a letter or number.";
2957
+ }
2958
+ return null;
2959
+ }
2960
+ function parseInitProfileTarget(name) {
2961
+ if (name.endsWith(".local")) {
2962
+ const profileName = name.slice(0, -".local".length);
2963
+ const issue2 = validateProfileNamePart(profileName);
2964
+ if (issue2) {
2965
+ return { error: issue2 };
2966
+ }
2967
+ if (profileName.endsWith(".local")) {
2968
+ return { error: 'Profile names cannot end with ".local.local".' };
2969
+ }
2970
+ return {
2971
+ profileName,
2972
+ isLocal: true
2973
+ };
2974
+ }
2975
+ const issue = validateProfileNamePart(name);
2976
+ if (issue) {
2977
+ return { error: issue };
2978
+ }
2979
+ return {
2980
+ profileName: name,
2981
+ isLocal: false
2982
+ };
2983
+ }
2984
+ function colorsEnabled() {
2985
+ if (process.env.NO_COLOR !== void 0) {
2986
+ return false;
2987
+ }
2988
+ const forced = process.env.FORCE_COLOR;
2989
+ if (forced !== void 0) {
2990
+ return forced !== "0" && forced.toLowerCase() !== "false";
2991
+ }
2992
+ return Boolean(process.stdout.isTTY);
2993
+ }
2994
+ function findLineCommentStart(line) {
2995
+ let inString = false;
2996
+ let escaped = false;
2997
+ for (let index = 0; index < line.length; index += 1) {
2998
+ const char = line[index];
2999
+ if (escaped) {
3000
+ escaped = false;
3001
+ continue;
3002
+ }
3003
+ if (char === "\\" && inString) {
3004
+ escaped = true;
3005
+ continue;
3006
+ }
3007
+ if (char === '"') {
3008
+ inString = !inString;
3009
+ continue;
3010
+ }
3011
+ if (!inString && char === "/" && line[index + 1] === "/") {
3012
+ return index;
3013
+ }
3014
+ }
3015
+ return null;
3016
+ }
3017
+ function colorizeGuideComments(text) {
3018
+ if (!colorsEnabled()) {
3019
+ return text;
3020
+ }
3021
+ return text.split("\n").map((line) => {
3022
+ const commentStart = findLineCommentStart(line);
3023
+ if (commentStart === null) {
3024
+ return line;
3025
+ }
3026
+ return `${line.slice(0, commentStart)}${ANSI_GRAY}${line.slice(commentStart)}${ANSI_RESET_FOREGROUND}`;
3027
+ }).join("\n");
3028
+ }
3029
+ function initGuide(target, displayPath) {
3030
+ const label = target.isLocal ? `Created local profile "${target.profileName}" at ${displayPath}.` : `Created profile "${target.profileName}" at ${displayPath}.`;
3031
+ const localHint = target.isLocal ? `
3032
+ Use profile name "${target.profileName}" when syncing; ".local" is only the file suffix.
3033
+ ` : "";
3034
+ return colorizeGuideComments(`${label}${localHint}
3035
+
3036
+ Profile files must be valid JSON. This commented version is just a guide:
3037
+
3038
+ {
3039
+ "$schema": "${PROFILE_SCHEMA_URL}",
3040
+
3041
+ "description": "shown in \`omniagent profiles\`",
3042
+
3043
+ "targets": {
3044
+ "claude": { "enabled": true }, // includes Claude; overrides an earlier profile setting it false
3045
+ "gemini": { "enabled": false } // skips Gemini for this profile
3046
+ },
3047
+
3048
+ "enable": { // names/globs to include; also opts in items marked enabled:false
3049
+ "skills": ["code-review"],
3050
+ "subagents": ["reviewer"],
3051
+ "commands": []
3052
+ },
3053
+
3054
+ "disable": { // names/globs to exclude after enable rules
3055
+ "skills": [],
3056
+ "subagents": [],
3057
+ "commands": ["*-legacy"]
3058
+ },
3059
+
3060
+ // https://github.com/JoeRoddy/omniagent/blob/master/docs/templating.md
3061
+ "variables": {
3062
+ "REVIEW_STYLE": "thorough" // replaces {{REVIEW_STYLE}} in synced files
3063
+ }
3064
+ }
3065
+
3066
+ Try it:
3067
+ omniagent profiles show ${target.profileName}
3068
+ omniagent sync --profile ${target.profileName}`);
3069
+ }
2847
3070
  async function loadProfileValidationCatalog(repoRoot, agentsDir) {
2848
3071
  const { config } = await loadTargetConfig({ repoRoot, agentsDir });
2849
3072
  const validation = validateTargetConfig({ config, builtIns: BUILTIN_TARGETS });
@@ -2863,23 +3086,64 @@ async function loadProfileValidationCatalog(repoRoot, agentsDir) {
2863
3086
  ]);
2864
3087
  return {
2865
3088
  resolveTargetName,
2866
- skillNames: skillCatalog.skills.map((skill) => skill.name),
2867
- commandNames: commandCatalog.commands.map((command) => command.name),
2868
- subagentNames: subagentCatalog.subagents.map((subagent) => subagent.resolvedName)
3089
+ skills: skillCatalog.skills,
3090
+ commands: commandCatalog.commands,
3091
+ subagents: subagentCatalog.subagents
2869
3092
  };
2870
3093
  }
2871
3094
  function collectProfileReferenceIssues(profileName, resolvedProfile, catalog) {
2872
3095
  const filter = createProfileItemFilter(resolvedProfile);
2873
- for (const skillName of catalog.skillNames) {
2874
- filter.includes("skills", skillName);
3096
+ const issues = [];
3097
+ for (const skill of catalog.skills) {
3098
+ const included = filter.includes("skills", {
3099
+ canonicalName: skill.name,
3100
+ enabledByDefault: skill.enabledByDefault
3101
+ });
3102
+ if (!included) {
3103
+ continue;
3104
+ }
3105
+ try {
3106
+ assertSkillDefinitionUsable(skill);
3107
+ } catch (error) {
3108
+ const message = error instanceof Error ? error.message : String(error);
3109
+ issues.push(`profile "${profileName}" includes unusable skill "${skill.name}": ${message}`);
3110
+ }
2875
3111
  }
2876
- for (const commandName of catalog.commandNames) {
2877
- filter.includes("commands", commandName);
3112
+ for (const command of catalog.commands) {
3113
+ const included = filter.includes("commands", {
3114
+ canonicalName: command.name,
3115
+ enabledByDefault: command.enabledByDefault
3116
+ });
3117
+ if (!included) {
3118
+ continue;
3119
+ }
3120
+ try {
3121
+ assertSlashCommandDefinitionUsable(command);
3122
+ } catch (error) {
3123
+ const message = error instanceof Error ? error.message : String(error);
3124
+ issues.push(
3125
+ `profile "${profileName}" includes unusable command "${command.name}": ${message}`
3126
+ );
3127
+ }
2878
3128
  }
2879
- for (const subagentName of catalog.subagentNames) {
2880
- filter.includes("subagents", subagentName);
3129
+ for (const subagent of catalog.subagents) {
3130
+ const included = filter.includes("subagents", {
3131
+ canonicalName: subagent.resolvedName,
3132
+ enabledByDefault: subagent.enabledByDefault
3133
+ });
3134
+ if (!included) {
3135
+ continue;
3136
+ }
3137
+ try {
3138
+ assertSubagentDefinitionUsable(subagent);
3139
+ } catch (error) {
3140
+ const message = error instanceof Error ? error.message : String(error);
3141
+ issues.push(
3142
+ `profile "${profileName}" includes unusable subagent "${subagent.resolvedName}": ${message}`
3143
+ );
3144
+ }
2881
3145
  }
2882
- const issues = filter.collectUnknownWarnings();
3146
+ issues.push(...filter.collectUnknownWarnings());
2883
3147
  for (const targetName of Object.keys(resolvedProfile.targets)) {
2884
3148
  if (catalog.resolveTargetName(targetName)) {
2885
3149
  continue;
@@ -2888,6 +3152,70 @@ function collectProfileReferenceIssues(profileName, resolvedProfile, catalog) {
2888
3152
  }
2889
3153
  return issues;
2890
3154
  }
3155
+ const initSubcommand = {
3156
+ command: "init <name>",
3157
+ describe: "Create a new sync profile",
3158
+ builder: (yargs2) => yargs2.positional("name", {
3159
+ type: "string",
3160
+ demandOption: true,
3161
+ describe: "Profile name"
3162
+ }).option("agentsDir", {
3163
+ type: "string",
3164
+ describe: "Override the agents directory",
3165
+ defaultDescription: DEFAULT_AGENTS_DIR
3166
+ }),
3167
+ handler: async (argv) => {
3168
+ const typed = argv;
3169
+ const initTarget = parseInitProfileTarget(typed.name);
3170
+ if ("error" in initTarget) {
3171
+ console.error(`Error: ${initTarget.error}`);
3172
+ process.exit(1);
3173
+ return;
3174
+ }
3175
+ const resolved = await resolveRepoAndAgentsDir(typed);
3176
+ if (!resolved) return;
3177
+ if (initTarget.isLocal) {
3178
+ const existingDedicatedPath = profileLocalDedicatedPath(
3179
+ resolved.repoRoot,
3180
+ initTarget.profileName,
3181
+ resolved.agentsDir
3182
+ );
3183
+ const inspected = await inspectProfileFiles(
3184
+ resolved.repoRoot,
3185
+ initTarget.profileName,
3186
+ resolved.agentsDir
3187
+ );
3188
+ if (inspected.localDedicated.exists) {
3189
+ const displayPath2 = path.relative(resolved.repoRoot, existingDedicatedPath).split(path.sep).join("/");
3190
+ console.error(
3191
+ `Error: Local profile "${initTarget.profileName}" already exists at ${displayPath2}.`
3192
+ );
3193
+ process.exit(1);
3194
+ return;
3195
+ }
3196
+ }
3197
+ const targetPath = initTarget.isLocal ? profileLocalSiblingPath(resolved.repoRoot, initTarget.profileName, resolved.agentsDir) : profileSharedPath(resolved.repoRoot, initTarget.profileName, resolved.agentsDir);
3198
+ const displayPath = path.relative(resolved.repoRoot, targetPath).split(path.sep).join("/");
3199
+ const starterContents = `${JSON.stringify(STARTER_PROFILE, null, 2)}
3200
+ `;
3201
+ try {
3202
+ await mkdir(path.dirname(targetPath), { recursive: true });
3203
+ await writeFile(targetPath, starterContents, { encoding: "utf8", flag: "wx" });
3204
+ } catch (error) {
3205
+ const code = error.code;
3206
+ if (code === "EEXIST") {
3207
+ const label = initTarget.isLocal ? "Local profile" : "Profile";
3208
+ console.error(
3209
+ `Error: ${label} "${initTarget.profileName}" already exists at ${displayPath}.`
3210
+ );
3211
+ process.exit(1);
3212
+ return;
3213
+ }
3214
+ throw error;
3215
+ }
3216
+ console.log(initGuide(initTarget, displayPath));
3217
+ }
3218
+ };
2891
3219
  const listSubcommand = {
2892
3220
  command: "$0",
2893
3221
  describe: "List available sync profiles",
@@ -3063,7 +3391,7 @@ const validateSubcommand = {
3063
3391
  const profilesCommand = {
3064
3392
  command: "profiles",
3065
3393
  describe: "Inspect and validate sync profiles",
3066
- builder: (yargs2) => yargs2.command(listSubcommand).command(showSubcommand).command(validateSubcommand).demandCommand(0).strictCommands(),
3394
+ builder: (yargs2) => yargs2.command(listSubcommand).command(initSubcommand).command(showSubcommand).command(validateSubcommand).demandCommand(0).strictCommands(),
3067
3395
  handler: () => {
3068
3396
  }
3069
3397
  };
@@ -3232,11 +3560,11 @@ function applyAgentTemplating(options) {
3232
3560
  sourcePath: options.sourcePath
3233
3561
  });
3234
3562
  }
3235
- function hashIdentifier$2(value) {
3563
+ function hashIdentifier$3(value) {
3236
3564
  return createHash("sha256").update(value).digest("hex");
3237
3565
  }
3238
3566
  function resolveIgnorePreferencePath(repoRoot, homeDir = os.homedir()) {
3239
- const repoHash = hashIdentifier$2(repoRoot);
3567
+ const repoHash = hashIdentifier$3(repoRoot);
3240
3568
  return path.join(homeDir, ".omniagent", "state", "ignore-rules", "projects", `${repoHash}.json`);
3241
3569
  }
3242
3570
  async function readIgnorePreference(repoRoot, homeDir = os.homedir()) {
@@ -3248,7 +3576,7 @@ async function readIgnorePreference(repoRoot, homeDir = os.homedir()) {
3248
3576
  return null;
3249
3577
  }
3250
3578
  return {
3251
- projectId: parsed.projectId ?? hashIdentifier$2(repoRoot),
3579
+ projectId: parsed.projectId ?? hashIdentifier$3(repoRoot),
3252
3580
  ignorePromptDeclined: parsed.ignorePromptDeclined,
3253
3581
  updatedAt: parsed.updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
3254
3582
  };
@@ -3263,7 +3591,7 @@ async function readIgnorePreference(repoRoot, homeDir = os.homedir()) {
3263
3591
  async function recordIgnorePromptDeclined(repoRoot, homeDir = os.homedir()) {
3264
3592
  const filePath = resolveIgnorePreferencePath(repoRoot, homeDir);
3265
3593
  const preference = {
3266
- projectId: hashIdentifier$2(repoRoot),
3594
+ projectId: hashIdentifier$3(repoRoot),
3267
3595
  ignorePromptDeclined: true,
3268
3596
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3269
3597
  };
@@ -3957,7 +4285,7 @@ async function runConvertHook(hooks, stage, context) {
3957
4285
  }
3958
4286
  await runHook(hooks[stage], context);
3959
4287
  }
3960
- function hashIdentifier$1(value) {
4288
+ function hashIdentifier$2(value) {
3961
4289
  return createHash("sha256").update(value).digest("hex");
3962
4290
  }
3963
4291
  function hashContent$1(value) {
@@ -4018,7 +4346,7 @@ async function hashOutputPath(outputPath) {
4018
4346
  }
4019
4347
  }
4020
4348
  function resolveManagedOutputsPath(repoRoot, homeDir = os.homedir()) {
4021
- const repoHash = hashIdentifier$1(repoRoot);
4349
+ const repoHash = hashIdentifier$2(repoRoot);
4022
4350
  const baseDir = path.join(
4023
4351
  homeDir,
4024
4352
  ".omniagent",
@@ -4547,9 +4875,8 @@ function listTemplateScriptExecutions(runtime) {
4547
4875
  );
4548
4876
  }
4549
4877
  const utf8Decoder$1 = new TextDecoder("utf-8", { fatal: true });
4550
- const TARGET_FRONTMATTER_KEYS$2 = /* @__PURE__ */ new Set(["targets", "targetagents"]);
4551
4878
  const SKILL_FRONTMATTER_KEYS_TO_REMOVE = /* @__PURE__ */ new Set([
4552
- ...TARGET_FRONTMATTER_KEYS$2,
4879
+ ...SYNC_ROUTING_FRONTMATTER_KEYS,
4553
4880
  "tools",
4554
4881
  "model",
4555
4882
  "color"
@@ -4741,7 +5068,7 @@ async function copySkillDirectory(options) {
4741
5068
  validAgents: options.validAgents,
4742
5069
  sourcePath: winner.sourcePath
4743
5070
  });
4744
- const output = winner.isSkillFile ? stripFrontmatterFields(templated, TARGET_FRONTMATTER_KEYS$2) : templated;
5071
+ const output = winner.isSkillFile ? stripFrontmatterFields(templated, SYNC_ROUTING_FRONTMATTER_KEYS) : templated;
4745
5072
  await mkdir(path.dirname(winner.destinationPath), { recursive: true });
4746
5073
  await writeFile(winner.destinationPath, output, "utf8");
4747
5074
  }
@@ -4785,7 +5112,7 @@ const defaultSubagentWriter = {
4785
5112
  validAgents: options.context.validAgents,
4786
5113
  sourcePath: item.sourcePath
4787
5114
  });
4788
- const cleaned = item.outputKind === "skill" ? stripFrontmatterFields(templated, SKILL_FRONTMATTER_KEYS_TO_REMOVE) : stripFrontmatterFields(templated, TARGET_FRONTMATTER_KEYS$2);
5115
+ const cleaned = item.outputKind === "skill" ? stripFrontmatterFields(templated, SKILL_FRONTMATTER_KEYS_TO_REMOVE) : stripFrontmatterFields(templated, SYNC_ROUTING_FRONTMATTER_KEYS);
4789
5116
  if (item.outputKind === "skill") {
4790
5117
  const destinationPath = path.join(options.outputPath, "SKILL.md");
4791
5118
  return writeOutputFile(destinationPath, cleaned);
@@ -4806,16 +5133,16 @@ const defaultInstructionWriter = {
4806
5133
  async function writeFileOutput(outputPath, content) {
4807
5134
  return writeOutputFile(outputPath, content);
4808
5135
  }
4809
- function hashIdentifier(value) {
5136
+ function hashIdentifier$1(value) {
4810
5137
  return createHash("sha256").update(value).digest("hex");
4811
5138
  }
4812
- function resolveManifestPath(repoRoot, homeDir = os.homedir()) {
4813
- const repoHash = hashIdentifier(repoRoot);
5139
+ function resolveManifestPath$1(repoRoot, homeDir = os.homedir()) {
5140
+ const repoHash = hashIdentifier$1(repoRoot);
4814
5141
  const baseDir = path.join(homeDir, ".omniagent", "state", "instructions", "projects", repoHash);
4815
5142
  return path.join(baseDir, "instruction-outputs.json");
4816
5143
  }
4817
- async function readManifest(repoRoot, homeDir) {
4818
- const manifestPath = resolveManifestPath(repoRoot, homeDir);
5144
+ async function readManifest$1(repoRoot, homeDir) {
5145
+ const manifestPath = resolveManifestPath$1(repoRoot, homeDir);
4819
5146
  try {
4820
5147
  const contents = await readFile(manifestPath, "utf8");
4821
5148
  const parsed = JSON.parse(contents);
@@ -4828,7 +5155,7 @@ async function readManifest(repoRoot, homeDir) {
4828
5155
  }
4829
5156
  }
4830
5157
  async function writeManifest(repoRoot, manifest, homeDir) {
4831
- const manifestPath = resolveManifestPath(repoRoot, homeDir);
5158
+ const manifestPath = resolveManifestPath$1(repoRoot, homeDir);
4832
5159
  const sorted = [...manifest.entries].sort((left, right) => {
4833
5160
  const targetCompare = left.targetName.localeCompare(right.targetName);
4834
5161
  if (targetCompare !== 0) {
@@ -5397,7 +5724,7 @@ async function syncInstructions(request) {
5397
5724
  groupResult.hadFailure = true;
5398
5725
  }
5399
5726
  }
5400
- const previousManifest = await readManifest(request.repoRoot) ?? { entries: [] };
5727
+ const previousManifest = await readManifest$1(request.repoRoot) ?? { entries: [] };
5401
5728
  const previousEntries = /* @__PURE__ */ new Map();
5402
5729
  for (const entry2 of previousManifest.entries) {
5403
5730
  const group = resolveInstructionTargetGroup(entry2.targetName);
@@ -5649,6 +5976,9 @@ function formatDisplayPath$3(repoRoot, absolutePath) {
5649
5976
  const isWithinRepo = relative && !relative.startsWith("..") && !path.isAbsolute(relative);
5650
5977
  return isWithinRepo ? relative : absolutePath;
5651
5978
  }
5979
+ function buildManagedOutputPathKey(entry2) {
5980
+ return `${entry2.targetId}:${normalizeManagedOutputPath(entry2.outputPath)}`;
5981
+ }
5652
5982
  function buildInvalidTargetWarnings$2(skills) {
5653
5983
  const warnings = [];
5654
5984
  for (const skill of skills) {
@@ -5662,6 +5992,20 @@ function buildInvalidTargetWarnings$2(skills) {
5662
5992
  }
5663
5993
  return warnings;
5664
5994
  }
5995
+ function assertUsableSkillsForTargets(options) {
5996
+ for (const skill of options.skills) {
5997
+ const effectiveTargets = resolveEffectiveTargets({
5998
+ defaultTargets: skill.targetAgents,
5999
+ overrideOnly: options.overrideOnly ?? void 0,
6000
+ overrideSkip: options.overrideSkip ?? void 0,
6001
+ allTargets: options.allTargets
6002
+ });
6003
+ if (!effectiveTargets.some((targetId) => options.activeTargetIds.has(targetId))) {
6004
+ continue;
6005
+ }
6006
+ assertSkillDefinitionUsable(skill);
6007
+ }
6008
+ }
5665
6009
  async function syncSkills(request) {
5666
6010
  const templateScriptRuntime = request.templateScriptRuntime ?? createTemplateScriptRuntime({ cwd: request.repoRoot });
5667
6011
  const sourcePath = resolveSharedCategoryRoot(request.repoRoot, "skills", request.agentsDir);
@@ -5680,17 +6024,25 @@ async function syncSkills(request) {
5680
6024
  agentsDir: request.agentsDir,
5681
6025
  resolveTargetName: request.resolveTargetName
5682
6026
  });
5683
- if (request.includeItem) {
5684
- const includeItem = request.includeItem;
5685
- const predicate = (skill) => includeItem(skill.name);
5686
- catalog.skills = catalog.skills.filter(predicate);
5687
- catalog.sharedSkills = catalog.sharedSkills.filter(predicate);
5688
- catalog.localSkills = catalog.localSkills.filter(predicate);
5689
- catalog.localEffectiveSkills = catalog.localEffectiveSkills.filter(predicate);
5690
- }
5691
- const warnings = buildInvalidTargetWarnings$2(catalog.skills);
6027
+ const includeItem = request.includeItem;
6028
+ const predicate = (skill) => includeItem ? includeItem({
6029
+ canonicalName: skill.name,
6030
+ enabledByDefault: skill.enabledByDefault
6031
+ }) : skill.enabledByDefault;
6032
+ catalog.skills = catalog.skills.filter(predicate);
6033
+ catalog.sharedSkills = catalog.sharedSkills.filter(predicate);
6034
+ catalog.localSkills = catalog.localSkills.filter(predicate);
6035
+ catalog.localEffectiveSkills = catalog.localEffectiveSkills.filter(predicate);
5692
6036
  const allTargetIds = request.targets.map((target) => target.id);
5693
6037
  const targetNames = new Set(skillTargets.map((target) => target.id));
6038
+ assertUsableSkillsForTargets({
6039
+ skills: catalog.skills,
6040
+ activeTargetIds: targetNames,
6041
+ overrideOnly: request.overrideOnly,
6042
+ overrideSkip: request.overrideSkip,
6043
+ allTargets: allTargetIds
6044
+ });
6045
+ const warnings = buildInvalidTargetWarnings$2(catalog.skills);
5694
6046
  const effectiveTargetsBySkill = /* @__PURE__ */ new Map();
5695
6047
  const activeSourcesByTarget = /* @__PURE__ */ new Map();
5696
6048
  for (const skill of catalog.skills) {
@@ -5950,7 +6302,13 @@ async function syncSkills(request) {
5950
6302
  if (managedManifest.entries.length > 0 || nextManaged.size > 0) {
5951
6303
  const updatedEntries = [];
5952
6304
  const managedTargetIds = new Set(skillTargets.map((target) => target.id));
6305
+ const claimedSkillOutputPaths = new Set(
6306
+ Array.from(nextManaged.values()).filter((entry2) => entry2.sourceType === "skill").map((entry2) => buildManagedOutputPathKey(entry2))
6307
+ );
5953
6308
  for (const entry2 of managedManifest.entries) {
6309
+ if (entry2.sourceType === "subagent" && claimedSkillOutputPaths.has(buildManagedOutputPathKey(entry2))) {
6310
+ continue;
6311
+ }
5954
6312
  if (entry2.sourceType !== "skill" || !managedTargetIds.has(entry2.targetId)) {
5955
6313
  updatedEntries.push(entry2);
5956
6314
  continue;
@@ -6075,11 +6433,10 @@ function formatTomlValue(value) {
6075
6433
  function formatYamlString(value) {
6076
6434
  return JSON.stringify(value);
6077
6435
  }
6078
- const TARGET_FRONTMATTER_KEYS$1 = /* @__PURE__ */ new Set(["targets", "targetagents"]);
6079
6436
  function stripTargetMetadata(frontmatter) {
6080
6437
  const filtered = {};
6081
6438
  for (const [key, value] of Object.entries(frontmatter)) {
6082
- if (TARGET_FRONTMATTER_KEYS$1.has(key.toLowerCase())) {
6439
+ if (SYNC_ROUTING_FRONTMATTER_KEYS.has(key.toLowerCase())) {
6083
6440
  continue;
6084
6441
  }
6085
6442
  filtered[key] = value;
@@ -6141,9 +6498,9 @@ function renderYamlFrontmatter(frontmatter, defaultName) {
6141
6498
  lines.push("---", "");
6142
6499
  return lines.join("\n");
6143
6500
  }
6144
- const TOML_RESERVED_KEYS = /* @__PURE__ */ new Set(["prompt", "targets", "targetagents"]);
6501
+ const TOML_RESERVED_KEYS = /* @__PURE__ */ new Set(["prompt", ...SYNC_ROUTING_FRONTMATTER_KEYS]);
6145
6502
  function renderMarkdownCommand(command) {
6146
- return stripFrontmatterFields(command.rawContents, TARGET_FRONTMATTER_KEYS$1);
6503
+ return stripFrontmatterFields(command.rawContents, SYNC_ROUTING_FRONTMATTER_KEYS);
6147
6504
  }
6148
6505
  function renderTomlCommand(command) {
6149
6506
  const lines = [];
@@ -6281,6 +6638,36 @@ function formatSyncSummary(summary, jsonOutput) {
6281
6638
  }
6282
6639
  return lines.join("\n");
6283
6640
  }
6641
+ function includeCommandByDefault(command, includeItem) {
6642
+ if (!includeItem) {
6643
+ return command.enabledByDefault;
6644
+ }
6645
+ return includeItem({
6646
+ canonicalName: command.name,
6647
+ enabledByDefault: command.enabledByDefault
6648
+ });
6649
+ }
6650
+ function filterCommandCatalog(catalog, includeItem) {
6651
+ const predicate = (command) => includeCommandByDefault(command, includeItem);
6652
+ catalog.commands = catalog.commands.filter(predicate);
6653
+ catalog.sharedCommands = catalog.sharedCommands.filter(predicate);
6654
+ catalog.localCommands = catalog.localCommands.filter(predicate);
6655
+ catalog.localEffectiveCommands = catalog.localEffectiveCommands.filter(predicate);
6656
+ }
6657
+ function assertUsableCommandsForTargets(options) {
6658
+ for (const command of options.commands) {
6659
+ const effectiveTargets = resolveEffectiveTargets({
6660
+ defaultTargets: command.targetAgents,
6661
+ overrideOnly: options.overrideOnly ?? void 0,
6662
+ overrideSkip: options.overrideSkip ?? void 0,
6663
+ allTargets: options.allTargets
6664
+ });
6665
+ if (!effectiveTargets.some((targetId) => options.activeTargetIds.has(targetId))) {
6666
+ continue;
6667
+ }
6668
+ assertSlashCommandDefinitionUsable(command);
6669
+ }
6670
+ }
6284
6671
  function renderCommandOutput(command, outputKind, outputPath) {
6285
6672
  if (outputKind === "skill") {
6286
6673
  return renderSkillFromCommand(command);
@@ -6310,14 +6697,7 @@ async function syncSlashCommands(request) {
6310
6697
  agentsDir: request.agentsDir,
6311
6698
  resolveTargetName: request.resolveTargetName
6312
6699
  });
6313
- if (request.includeItem) {
6314
- const includeItem = request.includeItem;
6315
- const predicate = (command) => includeItem(command.name);
6316
- catalog.commands = catalog.commands.filter(predicate);
6317
- catalog.sharedCommands = catalog.sharedCommands.filter(predicate);
6318
- catalog.localCommands = catalog.localCommands.filter(predicate);
6319
- catalog.localEffectiveCommands = catalog.localEffectiveCommands.filter(predicate);
6320
- }
6700
+ filterCommandCatalog(catalog, request.includeItem);
6321
6701
  const targets = request.targets.filter(
6322
6702
  (target) => normalizeCommandOutputDefinition(target.outputs.commands) !== null
6323
6703
  );
@@ -6338,6 +6718,13 @@ async function syncSlashCommands(request) {
6338
6718
  const removeMissing = request.removeMissing ?? false;
6339
6719
  const allTargetIds = request.targets.map((target) => target.id);
6340
6720
  const activeTargetIds = new Set(targets.map((target) => target.id));
6721
+ assertUsableCommandsForTargets({
6722
+ commands: catalog.commands,
6723
+ activeTargetIds,
6724
+ overrideOnly: request.overrideOnly,
6725
+ overrideSkip: request.overrideSkip,
6726
+ allTargets: allTargetIds
6727
+ });
6341
6728
  const effectiveTargetsByCommand = /* @__PURE__ */ new Map();
6342
6729
  const activeSourcesByTarget = /* @__PURE__ */ new Map();
6343
6730
  for (const command of catalog.commands) {
@@ -6803,39 +7190,125 @@ async function syncSlashCommands(request) {
6803
7190
  sourceCounts
6804
7191
  };
6805
7192
  }
6806
- const TARGET_FRONTMATTER_KEYS = /* @__PURE__ */ new Set(["targets", "targetagents"]);
6807
- /* @__PURE__ */ new Set([
6808
- ...TARGET_FRONTMATTER_KEYS,
6809
- "tools",
6810
- "model",
6811
- "color"
6812
- ]);
6813
- function emptySummaryCounts() {
6814
- return { created: 0, updated: 0, removed: 0, converted: 0, skipped: 0 };
7193
+ function hashIdentifier(value) {
7194
+ return createHash("sha256").update(value).digest("hex");
6815
7195
  }
6816
- function normalizeSkillKey(name) {
6817
- return path.normalize(name).replace(/\\/g, "/").toLowerCase();
7196
+ function resolveManifestPath(repoRoot, targetName, homeDir) {
7197
+ const repoHash = hashIdentifier(repoRoot);
7198
+ const baseDir = path.join(homeDir, ".omniagent", "state", "subagents", "projects", repoHash);
7199
+ return path.join(baseDir, `${targetName}.toml`);
6818
7200
  }
6819
- function normalizeSkillRelativePath(relativePath) {
6820
- if (!relativePath) {
6821
- return relativePath;
7201
+ function parseTomlValue(rawValue) {
7202
+ const trimmed = rawValue.trim();
7203
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
7204
+ try {
7205
+ return JSON.parse(trimmed);
7206
+ } catch {
7207
+ return trimmed.slice(1, -1);
7208
+ }
6822
7209
  }
6823
- const baseName = path.basename(relativePath);
6824
- const { baseName: strippedBase } = stripLocalPathSuffix(baseName);
6825
- const parent = path.dirname(relativePath);
6826
- return parent === "." ? strippedBase : path.join(parent, strippedBase);
6827
- }
6828
- function formatDisplayPath$1(repoRoot, absolutePath) {
6829
- const relative = path.relative(repoRoot, absolutePath);
6830
- const isWithinRepo = relative && !relative.startsWith("..") && !path.isAbsolute(relative);
6831
- return isWithinRepo ? relative : absolutePath;
7210
+ return trimmed;
6832
7211
  }
6833
- function buildSourceCounts(subagents, targets, allTargets, request) {
6834
- const targetSet = new Set(targets.map((target) => target.toLowerCase()));
6835
- const counts = {
6836
- shared: 0,
6837
- local: 0,
6838
- excludedLocal: request.excludeLocal ?? false
7212
+ function parseManifest(contents) {
7213
+ const lines = contents.split(/\r?\n/);
7214
+ let targetName = null;
7215
+ const managedSubagents = [];
7216
+ let current = null;
7217
+ for (const line of lines) {
7218
+ const trimmed = line.trim();
7219
+ if (!trimmed || trimmed.startsWith("#")) {
7220
+ continue;
7221
+ }
7222
+ if (trimmed === "[[managedSubagents]]") {
7223
+ if (current?.name && current.hash && current.lastSyncedAt) {
7224
+ managedSubagents.push({
7225
+ name: current.name,
7226
+ hash: current.hash,
7227
+ lastSyncedAt: current.lastSyncedAt
7228
+ });
7229
+ }
7230
+ current = {};
7231
+ continue;
7232
+ }
7233
+ const match = trimmed.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
7234
+ if (!match) {
7235
+ continue;
7236
+ }
7237
+ const [, key, rawValue] = match;
7238
+ const value = parseTomlValue(rawValue);
7239
+ if (current) {
7240
+ if (key === "name") {
7241
+ current.name = value;
7242
+ continue;
7243
+ }
7244
+ if (key === "hash") {
7245
+ current.hash = value;
7246
+ continue;
7247
+ }
7248
+ if (key === "lastSyncedAt") {
7249
+ current.lastSyncedAt = value;
7250
+ continue;
7251
+ }
7252
+ }
7253
+ if (key === "targetName") {
7254
+ targetName = value;
7255
+ }
7256
+ }
7257
+ if (current?.name && current.hash && current.lastSyncedAt) {
7258
+ managedSubagents.push({
7259
+ name: current.name,
7260
+ hash: current.hash,
7261
+ lastSyncedAt: current.lastSyncedAt
7262
+ });
7263
+ }
7264
+ if (!targetName) {
7265
+ return null;
7266
+ }
7267
+ return {
7268
+ targetName,
7269
+ managedSubagents
7270
+ };
7271
+ }
7272
+ async function readManifest(manifestPath) {
7273
+ try {
7274
+ const contents = await readFile(manifestPath, "utf8");
7275
+ return parseManifest(contents);
7276
+ } catch {
7277
+ return null;
7278
+ }
7279
+ }
7280
+ /* @__PURE__ */ new Set([
7281
+ ...SYNC_ROUTING_FRONTMATTER_KEYS,
7282
+ "tools",
7283
+ "model",
7284
+ "color"
7285
+ ]);
7286
+ function emptySummaryCounts() {
7287
+ return { created: 0, updated: 0, removed: 0, converted: 0, skipped: 0 };
7288
+ }
7289
+ function normalizeSkillKey(name) {
7290
+ return path.normalize(name).replace(/\\/g, "/").toLowerCase();
7291
+ }
7292
+ function includeCanonicalSkill(options) {
7293
+ if (!options.includeSkill) {
7294
+ return options.enabledByDefault;
7295
+ }
7296
+ return options.includeSkill({
7297
+ canonicalName: options.canonicalName,
7298
+ enabledByDefault: options.enabledByDefault
7299
+ });
7300
+ }
7301
+ function formatDisplayPath$1(repoRoot, absolutePath) {
7302
+ const relative = path.relative(repoRoot, absolutePath);
7303
+ const isWithinRepo = relative && !relative.startsWith("..") && !path.isAbsolute(relative);
7304
+ return isWithinRepo ? relative : absolutePath;
7305
+ }
7306
+ function buildSourceCounts(subagents, targets, allTargets, request) {
7307
+ const targetSet = new Set(targets.map((target) => target.toLowerCase()));
7308
+ const counts = {
7309
+ shared: 0,
7310
+ local: 0,
7311
+ excludedLocal: request.excludeLocal ?? false
6839
7312
  };
6840
7313
  for (const subagent of subagents) {
6841
7314
  const effectiveTargets = resolveEffectiveTargets({
@@ -6858,71 +7331,313 @@ function buildSourceCounts(subagents, targets, allTargets, request) {
6858
7331
  }
6859
7332
  return counts;
6860
7333
  }
6861
- async function loadCanonicalSkillIndex(repoRoot, options = {}) {
7334
+ function resolveSkillRelativePath(skillsRoot, directoryPath) {
7335
+ const relativePath = path.relative(skillsRoot, directoryPath);
7336
+ if (!relativePath) {
7337
+ return { relativePath, hadLocalSuffix: false };
7338
+ }
7339
+ const baseName = path.basename(relativePath);
7340
+ const { baseName: strippedBase, hadLocalSuffix } = stripLocalPathSuffix(baseName);
7341
+ if (!hadLocalSuffix) {
7342
+ return { relativePath, hadLocalSuffix: false };
7343
+ }
7344
+ const parent = path.dirname(relativePath);
7345
+ const normalized = parent === "." ? strippedBase : path.join(parent, strippedBase);
7346
+ return { relativePath: normalized, hadLocalSuffix: true };
7347
+ }
7348
+ function resolveSkillName(frontmatter, fallback) {
7349
+ const rawName = frontmatter.name;
7350
+ if (typeof rawName === "string") {
7351
+ const trimmed = rawName.trim();
7352
+ if (trimmed) {
7353
+ return trimmed;
7354
+ }
7355
+ }
7356
+ return fallback;
7357
+ }
7358
+ function shouldIncludeCanonicalSkillRelativePath(relativePath, skillKeyFilter) {
7359
+ if (!skillKeyFilter) {
7360
+ return true;
7361
+ }
7362
+ return skillKeyFilter.has(normalizeSkillKey(relativePath));
7363
+ }
7364
+ async function loadCanonicalSkillIndex(repoRoot, options) {
6862
7365
  const includeLocal = options.includeLocal ?? true;
7366
+ const fallbackResolver = createTargetNameResolver(BUILTIN_TARGETS).resolveTargetName;
7367
+ const resolveTargetName = options.resolveTargetName ?? fallbackResolver;
6863
7368
  const skillsRoot = resolveSharedCategoryRoot(repoRoot, "skills", options.agentsDir);
6864
7369
  const localSkillsRoot = resolveLocalCategoryRoot(repoRoot, "skills", options.agentsDir);
6865
- let directories = [];
6866
- try {
6867
- directories = await listSkillDirectories(skillsRoot);
6868
- } catch (error) {
6869
- const code = error.code;
6870
- if (code !== "ENOENT" && code !== "ENOTDIR") {
6871
- throw error;
6872
- }
6873
- }
7370
+ const targetFilter = options.targetIds ? new Set(options.targetIds) : null;
7371
+ const skillKeyFilter = options.skillKeys ? new Set(options.skillKeys) : null;
6874
7372
  const index = /* @__PURE__ */ new Map();
6875
- const addEntry = (relativePath, skillPath) => {
6876
- if (!relativePath) {
6877
- return;
7373
+ const sharedStats = await readDirectoryStats(skillsRoot);
7374
+ if (sharedStats && !sharedStats.isDirectory()) {
7375
+ throw new Error(`Skills root is not a directory: ${skillsRoot}.`);
7376
+ }
7377
+ const localStats = includeLocal ? await readDirectoryStats(localSkillsRoot) : null;
7378
+ if (localStats && !localStats.isDirectory()) {
7379
+ throw new Error(`Local skills root is not a directory: ${localSkillsRoot}.`);
7380
+ }
7381
+ const sharedEntries = sharedStats ? await listSkillDirectories(skillsRoot) : [];
7382
+ const localEntries = localStats ? await listSkillDirectories(localSkillsRoot) : [];
7383
+ const sharedSkills = [];
7384
+ const localPathSkills = [];
7385
+ const localSuffixSkills = [];
7386
+ for (const entry2 of sharedEntries) {
7387
+ const { relativePath, hadLocalSuffix } = resolveSkillRelativePath(
7388
+ skillsRoot,
7389
+ entry2.directoryPath
7390
+ );
7391
+ if (!shouldIncludeCanonicalSkillRelativePath(relativePath, skillKeyFilter)) {
7392
+ continue;
6878
7393
  }
6879
- const normalized = normalizeSkillKey(normalizeSkillRelativePath(relativePath));
6880
- index.set(normalized, skillPath);
6881
- };
6882
- for (const entry2 of directories) {
6883
- const relative = path.relative(skillsRoot, entry2.directoryPath);
6884
- if (!relative) {
7394
+ if (hadLocalSuffix) {
7395
+ if (!includeLocal) {
7396
+ continue;
7397
+ }
7398
+ const skillFileName = entry2.localSkillFile ?? entry2.sharedSkillFile;
7399
+ if (!skillFileName) {
7400
+ continue;
7401
+ }
7402
+ localSuffixSkills.push({
7403
+ relativePath,
7404
+ sourcePath: path.join(entry2.directoryPath, skillFileName)
7405
+ });
6885
7406
  continue;
6886
7407
  }
6887
- const isLocalDir = stripLocalPathSuffix(path.basename(entry2.directoryPath)).hadLocalSuffix;
6888
- if (!isLocalDir && entry2.sharedSkillFile) {
6889
- addEntry(relative, path.join(entry2.directoryPath, entry2.sharedSkillFile));
7408
+ if (entry2.sharedSkillFile) {
7409
+ sharedSkills.push({
7410
+ relativePath,
7411
+ sourcePath: path.join(entry2.directoryPath, entry2.sharedSkillFile)
7412
+ });
6890
7413
  }
6891
- if (includeLocal) {
6892
- if (isLocalDir) {
6893
- const skillFileName = entry2.localSkillFile ?? entry2.sharedSkillFile;
6894
- if (skillFileName) {
6895
- addEntry(relative, path.join(entry2.directoryPath, skillFileName));
6896
- }
6897
- } else if (entry2.localSkillFile) {
6898
- addEntry(relative, path.join(entry2.directoryPath, entry2.localSkillFile));
6899
- }
7414
+ if (includeLocal && entry2.localSkillFile) {
7415
+ localSuffixSkills.push({
7416
+ relativePath,
7417
+ sourcePath: path.join(entry2.directoryPath, entry2.localSkillFile)
7418
+ });
6900
7419
  }
6901
7420
  }
6902
7421
  if (includeLocal) {
6903
- let localDirectories = [];
6904
- try {
6905
- localDirectories = await listSkillDirectories(localSkillsRoot);
6906
- } catch (error) {
6907
- const code = error.code;
6908
- if (code === "ENOENT" || code === "ENOTDIR") {
6909
- return index;
7422
+ for (const entry2 of localEntries) {
7423
+ const skillFileName = entry2.sharedSkillFile ?? entry2.localSkillFile;
7424
+ if (!skillFileName) {
7425
+ continue;
6910
7426
  }
6911
- throw error;
7427
+ const { relativePath } = resolveSkillRelativePath(localSkillsRoot, entry2.directoryPath);
7428
+ if (!shouldIncludeCanonicalSkillRelativePath(relativePath, skillKeyFilter)) {
7429
+ continue;
7430
+ }
7431
+ localPathSkills.push({
7432
+ relativePath,
7433
+ sourcePath: path.join(entry2.directoryPath, skillFileName)
7434
+ });
6912
7435
  }
6913
- for (const entry2 of localDirectories) {
6914
- const relative = path.relative(localSkillsRoot, entry2.directoryPath);
6915
- if (!relative) {
7436
+ }
7437
+ const { localEffective: localEffectiveSkills, sharedEffective: sharedEffectiveSkills } = resolveLocalPrecedence({
7438
+ shared: sharedSkills,
7439
+ localPath: localPathSkills,
7440
+ localSuffix: localSuffixSkills,
7441
+ key: (skill) => normalizeSkillKey(skill.relativePath)
7442
+ });
7443
+ const effectiveSkills = includeLocal ? [...sharedEffectiveSkills, ...localEffectiveSkills] : sharedSkills;
7444
+ for (const skill of effectiveSkills) {
7445
+ const rawContents = await readFile(skill.sourcePath, "utf8");
7446
+ const { frontmatter } = extractFrontmatter$1(rawContents);
7447
+ const fallbackName = skill.relativePath || path.basename(path.dirname(skill.sourcePath));
7448
+ const skillName = resolveSkillName(frontmatter, fallbackName);
7449
+ const rawTargets = [frontmatter.targets, frontmatter.targetAgents];
7450
+ const { targets, invalidTargets } = resolveFrontmatterTargets(rawTargets, resolveTargetName);
7451
+ const hasEmptyTargets = hasRawTargetValues(rawTargets) && (!targets || targets.length === 0);
7452
+ const effectiveTargets = hasEmptyTargets ? [] : resolveEffectiveTargets({
7453
+ defaultTargets: targets,
7454
+ overrideOnly: options.overrideOnly ?? void 0,
7455
+ overrideSkip: options.overrideSkip ?? void 0,
7456
+ allTargets: options.allTargets
7457
+ });
7458
+ const matchingTargets = targetFilter ? effectiveTargets.filter((targetId) => targetFilter.has(targetId)) : effectiveTargets;
7459
+ if (matchingTargets.length === 0) {
7460
+ continue;
7461
+ }
7462
+ const enabledByDefault = resolveFrontmatterEnabledByDefault({
7463
+ frontmatter,
7464
+ itemKind: "Skill",
7465
+ itemName: skillName,
7466
+ sourcePath: skill.sourcePath
7467
+ });
7468
+ if (!includeCanonicalSkill({
7469
+ canonicalName: skillName,
7470
+ enabledByDefault,
7471
+ includeSkill: options.includeSkill
7472
+ })) {
7473
+ continue;
7474
+ }
7475
+ if (invalidTargets.length > 0) {
7476
+ const invalidList = invalidTargets.join(", ");
7477
+ throw new InvalidFrontmatterTargetsError(
7478
+ `Skill "${skillName}" has unsupported targets (${invalidList}) in ${skill.sourcePath}.`
7479
+ );
7480
+ }
7481
+ if (hasEmptyTargets) {
7482
+ throw new InvalidFrontmatterTargetsError(
7483
+ `Skill "${skillName}" has empty targets in ${skill.sourcePath}.`
7484
+ );
7485
+ }
7486
+ const skillKey = normalizeSkillKey(skill.relativePath);
7487
+ for (const targetId of matchingTargets) {
7488
+ const targetSkills = index.get(targetId) ?? /* @__PURE__ */ new Map();
7489
+ targetSkills.set(skillKey, skill.sourcePath);
7490
+ index.set(targetId, targetSkills);
7491
+ }
7492
+ }
7493
+ return index;
7494
+ }
7495
+ function collectRelevantCanonicalSkillKeys(options) {
7496
+ const relevant = /* @__PURE__ */ new Set();
7497
+ for (const subagent of options.subagents) {
7498
+ const effectiveTargets = resolveEffectiveTargets({
7499
+ defaultTargets: subagent.targetAgents,
7500
+ overrideOnly: options.overrideOnly ?? void 0,
7501
+ overrideSkip: options.overrideSkip ?? void 0,
7502
+ allTargets: options.allTargets
7503
+ });
7504
+ if (!effectiveTargets.some((targetId) => options.activeTargetIds.has(targetId))) {
7505
+ continue;
7506
+ }
7507
+ relevant.add(normalizeSkillKey(subagent.resolvedName));
7508
+ }
7509
+ return relevant;
7510
+ }
7511
+ async function collectManagedCanonicalSkillKeys(options) {
7512
+ const managed = /* @__PURE__ */ new Set();
7513
+ const homeDir = os.homedir();
7514
+ const targetIds = new Set(options.targetIds);
7515
+ for (const targetId of options.targetIds) {
7516
+ const manifest = await readManifest(resolveManifestPath(options.repoRoot, targetId, homeDir));
7517
+ if (!manifest || manifest.targetName !== targetId) {
7518
+ continue;
7519
+ }
7520
+ for (const entry2 of manifest.managedSubagents) {
7521
+ managed.add(normalizeSkillKey(entry2.name));
7522
+ }
7523
+ }
7524
+ const managedOutputs = await readManagedOutputs(options.repoRoot, homeDir);
7525
+ for (const entry2 of managedOutputs?.entries ?? []) {
7526
+ if (entry2.sourceType !== "subagent" || !targetIds.has(entry2.targetId)) {
7527
+ continue;
7528
+ }
7529
+ managed.add(normalizeSkillKey(entry2.sourceId));
7530
+ }
7531
+ return managed;
7532
+ }
7533
+ function getCanonicalSkillPath(index, targetId, skillKey) {
7534
+ return index.get(targetId)?.get(skillKey);
7535
+ }
7536
+ function recordShadowedSubagentSource(shadowedSources, targetId, sourceId) {
7537
+ const existing = shadowedSources.get(targetId) ?? /* @__PURE__ */ new Set();
7538
+ existing.add(normalizeName(sourceId));
7539
+ shadowedSources.set(targetId, existing);
7540
+ }
7541
+ function includeSubagentByDefault(subagent, includeItem) {
7542
+ if (!includeItem) {
7543
+ return subagent.enabledByDefault;
7544
+ }
7545
+ return includeItem({
7546
+ canonicalName: subagent.resolvedName,
7547
+ enabledByDefault: subagent.enabledByDefault
7548
+ });
7549
+ }
7550
+ function filterSubagentCatalog(catalog, includeItem) {
7551
+ const predicate = (subagent) => includeSubagentByDefault(subagent, includeItem);
7552
+ catalog.subagents = catalog.subagents.filter(predicate);
7553
+ catalog.sharedSubagents = catalog.sharedSubagents.filter(predicate);
7554
+ catalog.localSubagents = catalog.localSubagents.filter(predicate);
7555
+ catalog.localEffectiveSubagents = catalog.localEffectiveSubagents.filter(predicate);
7556
+ }
7557
+ function assertUsableSubagentsForTargets(options) {
7558
+ for (const subagent of options.subagents) {
7559
+ const effectiveTargets = resolveEffectiveTargets({
7560
+ defaultTargets: subagent.targetAgents,
7561
+ overrideOnly: options.overrideOnly ?? void 0,
7562
+ overrideSkip: options.overrideSkip ?? void 0,
7563
+ allTargets: options.allTargets
7564
+ });
7565
+ if (!effectiveTargets.some((targetId) => options.activeTargetIds.has(targetId))) {
7566
+ continue;
7567
+ }
7568
+ assertSubagentDefinitionUsable(subagent);
7569
+ }
7570
+ }
7571
+ function targetRequiresCanonicalSkillLookup(target) {
7572
+ const outputDef = normalizeOutputDefinition(target.outputs.subagents);
7573
+ if (!outputDef) {
7574
+ return false;
7575
+ }
7576
+ if (outputDef.fallback?.mode !== "convert" || outputDef.fallback.targetType !== "skills") {
7577
+ return false;
7578
+ }
7579
+ return normalizeOutputDefinition(target.outputs.skills) !== null;
7580
+ }
7581
+ async function resolveCanonicalSkillsForSubagentSync(options) {
7582
+ const targetIds = options.activeTargets.filter(targetRequiresCanonicalSkillLookup).map((target) => target.id);
7583
+ if (targetIds.length === 0) {
7584
+ return /* @__PURE__ */ new Map();
7585
+ }
7586
+ const relevantCanonicalSkillKeys = collectRelevantCanonicalSkillKeys({
7587
+ subagents: options.subagents,
7588
+ activeTargetIds: new Set(targetIds),
7589
+ overrideOnly: options.overrideOnly,
7590
+ overrideSkip: options.overrideSkip,
7591
+ allTargets: options.allTargetIds
7592
+ });
7593
+ for (const skillKey of await collectManagedCanonicalSkillKeys({
7594
+ repoRoot: options.repoRoot,
7595
+ targetIds
7596
+ })) {
7597
+ relevantCanonicalSkillKeys.add(skillKey);
7598
+ }
7599
+ if (relevantCanonicalSkillKeys.size === 0) {
7600
+ return /* @__PURE__ */ new Map();
7601
+ }
7602
+ return loadCanonicalSkillIndex(options.repoRoot, {
7603
+ includeLocal: options.includeLocalSkills ?? true,
7604
+ agentsDir: options.agentsDir,
7605
+ resolveTargetName: options.resolveTargetName,
7606
+ overrideOnly: options.overrideOnly ?? null,
7607
+ overrideSkip: options.overrideSkip ?? null,
7608
+ allTargets: options.allTargetIds,
7609
+ targetIds,
7610
+ skillKeys: relevantCanonicalSkillKeys,
7611
+ includeSkill: options.includeSkill
7612
+ });
7613
+ }
7614
+ async function resolveShadowedSubagentNamesForTargets(options) {
7615
+ const activeTargetIds = new Set(options.activeTargets.map((target) => target.id));
7616
+ const canonicalSkills = await resolveCanonicalSkillsForSubagentSync(options);
7617
+ const shadowedSources = /* @__PURE__ */ new Map();
7618
+ for (const subagent of options.subagents) {
7619
+ const effectiveTargets = resolveEffectiveTargets({
7620
+ defaultTargets: subagent.targetAgents,
7621
+ overrideOnly: options.overrideOnly ?? void 0,
7622
+ overrideSkip: options.overrideSkip ?? void 0,
7623
+ allTargets: options.allTargetIds
7624
+ });
7625
+ for (const targetId of effectiveTargets) {
7626
+ if (!activeTargetIds.has(targetId)) {
6916
7627
  continue;
6917
7628
  }
6918
- const skillFileName = entry2.sharedSkillFile ?? entry2.localSkillFile;
6919
- if (!skillFileName) {
7629
+ const canonicalSkillPath = getCanonicalSkillPath(
7630
+ canonicalSkills,
7631
+ targetId,
7632
+ normalizeSkillKey(subagent.resolvedName)
7633
+ );
7634
+ if (!canonicalSkillPath) {
6920
7635
  continue;
6921
7636
  }
6922
- addEntry(relative, path.join(entry2.directoryPath, skillFileName));
7637
+ recordShadowedSubagentSource(shadowedSources, targetId, subagent.resolvedName);
6923
7638
  }
6924
7639
  }
6925
- return index;
7640
+ return shadowedSources;
6926
7641
  }
6927
7642
  function buildInvalidTargetWarnings(subagents) {
6928
7643
  const warnings = [];
@@ -6974,14 +7689,7 @@ async function syncSubagents(request) {
6974
7689
  agentsDir: request.agentsDir,
6975
7690
  resolveTargetName: request.resolveTargetName
6976
7691
  });
6977
- if (request.includeItem) {
6978
- const includeItem = request.includeItem;
6979
- const predicate = (subagent) => includeItem(subagent.resolvedName);
6980
- catalog.subagents = catalog.subagents.filter(predicate);
6981
- catalog.sharedSubagents = catalog.sharedSubagents.filter(predicate);
6982
- catalog.localSubagents = catalog.localSubagents.filter(predicate);
6983
- catalog.localEffectiveSubagents = catalog.localEffectiveSubagents.filter(predicate);
6984
- }
7692
+ filterSubagentCatalog(catalog, request.includeItem);
6985
7693
  const targets = request.targets.filter(
6986
7694
  (target) => normalizeOutputDefinition(target.outputs.subagents) !== null
6987
7695
  );
@@ -7002,6 +7710,13 @@ async function syncSubagents(request) {
7002
7710
  }
7003
7711
  const allTargetIds = request.targets.map((target) => target.id);
7004
7712
  const activeTargetIds = new Set(targets.map((target) => target.id));
7713
+ assertUsableSubagentsForTargets({
7714
+ subagents: catalog.subagents,
7715
+ activeTargetIds,
7716
+ overrideOnly: request.overrideOnly,
7717
+ overrideSkip: request.overrideSkip,
7718
+ allTargets: allTargetIds
7719
+ });
7005
7720
  const effectiveTargetsBySubagent = /* @__PURE__ */ new Map();
7006
7721
  const activeSourcesByTarget = /* @__PURE__ */ new Map();
7007
7722
  for (const subagent of catalog.subagents) {
@@ -7036,6 +7751,7 @@ async function syncSubagents(request) {
7036
7751
  const managedManifest = await readManagedOutputs(request.repoRoot, homeDir) ?? { entries: [] };
7037
7752
  const nextManaged = /* @__PURE__ */ new Map();
7038
7753
  const activeOutputPaths = /* @__PURE__ */ new Set();
7754
+ const shadowedSubagentSources = /* @__PURE__ */ new Map();
7039
7755
  const countsByTarget = /* @__PURE__ */ new Map();
7040
7756
  const getCounts = (targetId) => {
7041
7757
  const existing = countsByTarget.get(targetId) ?? emptySummaryCounts();
@@ -7046,9 +7762,17 @@ async function syncSubagents(request) {
7046
7762
  const writerRegistry = /* @__PURE__ */ new Map([
7047
7763
  [defaultSubagentWriter.id, defaultSubagentWriter]
7048
7764
  ]);
7049
- const canonicalSkills = await loadCanonicalSkillIndex(request.repoRoot, {
7050
- includeLocal: request.includeLocalSkills ?? true,
7051
- agentsDir: request.agentsDir
7765
+ const canonicalSkills = await resolveCanonicalSkillsForSubagentSync({
7766
+ repoRoot: request.repoRoot,
7767
+ activeTargets: targets,
7768
+ allTargetIds,
7769
+ subagents: catalog.subagents,
7770
+ agentsDir: request.agentsDir,
7771
+ includeLocalSkills: request.includeLocalSkills,
7772
+ resolveTargetName: request.resolveTargetName,
7773
+ overrideOnly: request.overrideOnly,
7774
+ overrideSkip: request.overrideSkip,
7775
+ includeSkill: request.includeSkill
7052
7776
  });
7053
7777
  const validAgents = request.validAgents ?? buildSupportedAgentNames(request.targets);
7054
7778
  const outputDefs = /* @__PURE__ */ new Map();
@@ -7114,8 +7838,13 @@ async function syncSubagents(request) {
7114
7838
  }
7115
7839
  if (outputKind === "skill") {
7116
7840
  const canonicalSkillKey = normalizeSkillKey(subagent.resolvedName);
7117
- const canonicalSkillPath = canonicalSkills.get(canonicalSkillKey);
7841
+ const canonicalSkillPath = getCanonicalSkillPath(
7842
+ canonicalSkills,
7843
+ target.id,
7844
+ canonicalSkillKey
7845
+ );
7118
7846
  if (canonicalSkillPath) {
7847
+ recordShadowedSubagentSource(shadowedSubagentSources, target.id, subagent.resolvedName);
7119
7848
  warnings.push(
7120
7849
  `Skipped ${target.displayName} skill "${subagent.resolvedName}" because canonical skill exists at ${canonicalSkillPath}.`
7121
7850
  );
@@ -7320,6 +8049,35 @@ async function syncSubagents(request) {
7320
8049
  if (nextManaged.has(key)) {
7321
8050
  continue;
7322
8051
  }
8052
+ const canonicalSkillPath = getCanonicalSkillPath(
8053
+ canonicalSkills,
8054
+ entry2.targetId,
8055
+ normalizeSkillKey(entry2.sourceId)
8056
+ );
8057
+ const skillDef = skillDefs.get(entry2.targetId);
8058
+ if (canonicalSkillPath && skillDef) {
8059
+ const expectedSkillOutputPath = resolveOutputPath({
8060
+ template: skillDef.path,
8061
+ context: {
8062
+ repoRoot: request.repoRoot,
8063
+ agentsDir: agentsDirPath,
8064
+ homeDir,
8065
+ targetId: entry2.targetId,
8066
+ itemName: entry2.sourceId
8067
+ },
8068
+ item: { name: entry2.sourceId },
8069
+ baseDir: request.repoRoot
8070
+ });
8071
+ if (normalizeManagedOutputPath(expectedSkillOutputPath) === normalizeManagedOutputPath(entry2.outputPath)) {
8072
+ updatedEntries.push(entry2);
8073
+ continue;
8074
+ }
8075
+ }
8076
+ const shadowedSources = shadowedSubagentSources.get(entry2.targetId);
8077
+ if (shadowedSources?.has(normalizeName(entry2.sourceId))) {
8078
+ updatedEntries.push(entry2);
8079
+ continue;
8080
+ }
7323
8081
  const activeSources = activeSourcesByTarget.get(entry2.targetId);
7324
8082
  const sourceStillActive = activeSources?.has(entry2.sourceId) ?? false;
7325
8083
  if (!removeMissing || sourceStillActive) {
@@ -7813,12 +8571,21 @@ function replayTraversedProfileWarnings(profile, traversedNames) {
7813
8571
  }
7814
8572
  });
7815
8573
  for (const name of traversedNames.skills) {
8574
+ if (!name) {
8575
+ continue;
8576
+ }
7816
8577
  warningFilter.includes("skills", name);
7817
8578
  }
7818
8579
  for (const name of traversedNames.subagents) {
8580
+ if (!name) {
8581
+ continue;
8582
+ }
7819
8583
  warningFilter.includes("subagents", name);
7820
8584
  }
7821
8585
  for (const name of traversedNames.commands) {
8586
+ if (!name) {
8587
+ continue;
8588
+ }
7822
8589
  warningFilter.includes("commands", name);
7823
8590
  }
7824
8591
  return warningFilter.collectUnknownWarnings();
@@ -7838,6 +8605,18 @@ function addTemplateScriptSource(sources, templatePath, content) {
7838
8605
  function intersectsTargets(targets, selectedTargetIds) {
7839
8606
  return targets.some((target) => selectedTargetIds.has(target));
7840
8607
  }
8608
+ function areSelectedTargetsFullyShadowed(options) {
8609
+ const relevantTargets = options.effectiveTargets.filter(
8610
+ (targetId) => options.selectedTargetIds.has(targetId)
8611
+ );
8612
+ if (relevantTargets.length === 0) {
8613
+ return false;
8614
+ }
8615
+ const normalizedName = options.subagentName.trim().toLowerCase();
8616
+ return relevantTargets.every(
8617
+ (targetId) => options.shadowedSubagentsByTarget.get(targetId)?.has(normalizedName)
8618
+ );
8619
+ }
7841
8620
  async function listAllFiles(root) {
7842
8621
  const entries = await readdir(root, { withFileTypes: true });
7843
8622
  const files = [];
@@ -7872,7 +8651,10 @@ async function gatherTemplateScriptSources(options) {
7872
8651
  resolveTargetName: options.resolveTargetName
7873
8652
  });
7874
8653
  for (const command of commandCatalog.commands) {
7875
- if (options.includeCommand && !options.includeCommand(command.name)) {
8654
+ if (options.includeCommand && !options.includeCommand({
8655
+ canonicalName: command.name,
8656
+ enabledByDefault: command.enabledByDefault
8657
+ })) {
7876
8658
  continue;
7877
8659
  }
7878
8660
  const effectiveTargets = resolveEffectiveTargets({
@@ -7884,6 +8666,7 @@ async function gatherTemplateScriptSources(options) {
7884
8666
  if (!intersectsTargets(effectiveTargets, selectedCommandTargetIds)) {
7885
8667
  continue;
7886
8668
  }
8669
+ assertSlashCommandDefinitionUsable(command);
7887
8670
  addTemplateScriptSource(sources, command.sourcePath, command.rawContents);
7888
8671
  }
7889
8672
  }
@@ -7893,8 +8676,23 @@ async function gatherTemplateScriptSources(options) {
7893
8676
  agentsDir: options.agentsDir,
7894
8677
  resolveTargetName: options.resolveTargetName
7895
8678
  });
8679
+ const shadowedSubagentsByTarget = await resolveShadowedSubagentNamesForTargets({
8680
+ repoRoot: options.repoRoot,
8681
+ activeTargets: options.selectedSubagentTargets,
8682
+ allTargetIds: options.allTargetIds,
8683
+ subagents: subagentCatalog.subagents,
8684
+ agentsDir: options.agentsDir,
8685
+ includeLocalSkills: !options.excludeLocalSkills,
8686
+ resolveTargetName: options.resolveTargetName,
8687
+ overrideOnly: options.overrideOnly,
8688
+ overrideSkip: options.overrideSkip,
8689
+ includeSkill: options.includeSkill
8690
+ });
7896
8691
  for (const subagent of subagentCatalog.subagents) {
7897
- if (options.includeSubagent && !options.includeSubagent(subagent.resolvedName)) {
8692
+ if (options.includeSubagent && !options.includeSubagent({
8693
+ canonicalName: subagent.resolvedName,
8694
+ enabledByDefault: subagent.enabledByDefault
8695
+ })) {
7898
8696
  continue;
7899
8697
  }
7900
8698
  const effectiveTargets = resolveEffectiveTargets({
@@ -7906,6 +8704,15 @@ async function gatherTemplateScriptSources(options) {
7906
8704
  if (!intersectsTargets(effectiveTargets, selectedSubagentTargetIds)) {
7907
8705
  continue;
7908
8706
  }
8707
+ if (areSelectedTargetsFullyShadowed({
8708
+ selectedTargetIds: selectedSubagentTargetIds,
8709
+ effectiveTargets,
8710
+ shadowedSubagentsByTarget,
8711
+ subagentName: subagent.resolvedName
8712
+ })) {
8713
+ continue;
8714
+ }
8715
+ assertSubagentDefinitionUsable(subagent);
7909
8716
  addTemplateScriptSource(sources, subagent.sourcePath, subagent.rawContents);
7910
8717
  }
7911
8718
  }
@@ -7936,7 +8743,10 @@ async function gatherTemplateScriptSources(options) {
7936
8743
  resolveTargetName: options.resolveTargetName
7937
8744
  });
7938
8745
  for (const skill of skillCatalog.skills) {
7939
- if (options.includeSkill && !options.includeSkill(skill.name)) {
8746
+ if (options.includeSkill && !options.includeSkill({
8747
+ canonicalName: skill.name,
8748
+ enabledByDefault: skill.enabledByDefault
8749
+ })) {
7940
8750
  continue;
7941
8751
  }
7942
8752
  const effectiveTargets = resolveEffectiveTargets({
@@ -7948,6 +8758,7 @@ async function gatherTemplateScriptSources(options) {
7948
8758
  if (!intersectsTargets(effectiveTargets, selectedSkillTargetIds)) {
7949
8759
  continue;
7950
8760
  }
8761
+ assertSkillDefinitionUsable(skill);
7951
8762
  const files = await listAllFiles(skill.directoryPath);
7952
8763
  for (const filePath of files) {
7953
8764
  const buffer = await readFile(filePath);
@@ -8088,6 +8899,16 @@ async function validateTemplatingSources(options) {
8088
8899
  sourcePath
8089
8900
  });
8090
8901
  };
8902
+ const selectedSkillTargetIds = new Set(options.selectedSkillTargets.map((target) => target.id));
8903
+ const selectedCommandTargetIds = new Set(
8904
+ options.selectedCommandTargets.map((target) => target.id)
8905
+ );
8906
+ const selectedSubagentTargetIds = new Set(
8907
+ options.selectedSubagentTargets.map((target) => target.id)
8908
+ );
8909
+ const selectedInstructionTargetIds = new Set(
8910
+ options.selectedInstructionTargets.map((target) => target.id)
8911
+ );
8091
8912
  if (options.commandsAvailable) {
8092
8913
  const commandCatalog = await loadCommandCatalog(options.repoRoot, {
8093
8914
  includeLocal: options.includeLocalCommands,
@@ -8095,9 +8916,20 @@ async function validateTemplatingSources(options) {
8095
8916
  resolveTargetName: options.resolveTargetName
8096
8917
  });
8097
8918
  for (const command of commandCatalog.commands) {
8098
- if (options.includeCommand && !options.includeCommand(command.name)) {
8919
+ if (options.includeCommand && !options.includeCommand({
8920
+ canonicalName: command.name,
8921
+ enabledByDefault: command.enabledByDefault
8922
+ })) {
8099
8923
  continue;
8100
8924
  }
8925
+ const effectiveTargets = resolveEffectiveTargets({
8926
+ defaultTargets: command.targetAgents,
8927
+ allTargets: options.selectedCommandTargets.map((target) => target.id)
8928
+ });
8929
+ if (!intersectsTargets(effectiveTargets, selectedCommandTargetIds)) {
8930
+ continue;
8931
+ }
8932
+ assertSlashCommandDefinitionUsable(command);
8101
8933
  validateContent(command.sourcePath, command.rawContents);
8102
8934
  }
8103
8935
  }
@@ -8108,9 +8940,20 @@ async function validateTemplatingSources(options) {
8108
8940
  resolveTargetName: options.resolveTargetName
8109
8941
  });
8110
8942
  for (const skill of skillCatalog.skills) {
8111
- if (options.includeSkill && !options.includeSkill(skill.name)) {
8943
+ if (options.includeSkill && !options.includeSkill({
8944
+ canonicalName: skill.name,
8945
+ enabledByDefault: skill.enabledByDefault
8946
+ })) {
8112
8947
  continue;
8113
8948
  }
8949
+ const effectiveTargets = resolveEffectiveTargets({
8950
+ defaultTargets: skill.targetAgents,
8951
+ allTargets: options.selectedSkillTargets.map((target) => target.id)
8952
+ });
8953
+ if (!intersectsTargets(effectiveTargets, selectedSkillTargetIds)) {
8954
+ continue;
8955
+ }
8956
+ assertSkillDefinitionUsable(skill);
8114
8957
  const files = await listFiles(skill.directoryPath);
8115
8958
  const filesToValidate = options.includeLocalSkills ? files : files.filter((filePath) => !hasLocalMarker(filePath));
8116
8959
  for (const filePath of filesToValidate) {
@@ -8129,29 +8972,61 @@ async function validateTemplatingSources(options) {
8129
8972
  agentsDir: options.agentsDir,
8130
8973
  resolveTargetName: options.resolveTargetName
8131
8974
  });
8975
+ const shadowedSubagentsByTarget = await resolveShadowedSubagentNamesForTargets({
8976
+ repoRoot: options.repoRoot,
8977
+ activeTargets: options.selectedSubagentTargets,
8978
+ allTargetIds: options.selectedSubagentTargets.map((target) => target.id),
8979
+ subagents: subagentCatalog.subagents,
8980
+ agentsDir: options.agentsDir,
8981
+ includeLocalSkills: options.includeLocalSkills,
8982
+ resolveTargetName: options.resolveTargetName,
8983
+ includeSkill: options.includeSkill
8984
+ });
8132
8985
  for (const subagent of subagentCatalog.subagents) {
8133
- if (options.includeSubagent && !options.includeSubagent(subagent.resolvedName)) {
8986
+ if (options.includeSubagent && !options.includeSubagent({
8987
+ canonicalName: subagent.resolvedName,
8988
+ enabledByDefault: subagent.enabledByDefault
8989
+ })) {
8134
8990
  continue;
8135
8991
  }
8992
+ const effectiveTargets = resolveEffectiveTargets({
8993
+ defaultTargets: subagent.targetAgents,
8994
+ allTargets: options.selectedSubagentTargets.map((target) => target.id)
8995
+ });
8996
+ if (!intersectsTargets(effectiveTargets, selectedSubagentTargetIds)) {
8997
+ continue;
8998
+ }
8999
+ if (areSelectedTargetsFullyShadowed({
9000
+ selectedTargetIds: selectedSubagentTargetIds,
9001
+ effectiveTargets,
9002
+ shadowedSubagentsByTarget,
9003
+ subagentName: subagent.resolvedName
9004
+ })) {
9005
+ continue;
9006
+ }
9007
+ assertSubagentDefinitionUsable(subagent);
8136
9008
  validateContent(subagent.sourcePath, subagent.rawContents);
8137
9009
  }
8138
9010
  }
8139
9011
  if (options.instructionsAvailable) {
8140
- const entries = await scanInstructionTemplateSources({
9012
+ const catalog = await loadInstructionTemplateCatalog({
8141
9013
  repoRoot: options.repoRoot,
8142
9014
  includeLocal: options.includeLocalInstructions,
8143
- agentsDir: options.agentsDir
9015
+ agentsDir: options.agentsDir,
9016
+ resolveTargetName: options.resolveTargetName
8144
9017
  });
8145
- for (const entry2 of entries) {
8146
- const buffer = await readFile(entry2.sourcePath);
8147
- const contents = decodeUtf8(buffer);
8148
- if (contents === null) {
9018
+ for (const template of catalog.templates) {
9019
+ const effectiveTargets = resolveEffectiveTargets({
9020
+ defaultTargets: template.targets,
9021
+ allTargets: options.selectedInstructionTargets.map((target) => target.id)
9022
+ });
9023
+ if (!intersectsTargets(effectiveTargets, selectedInstructionTargetIds)) {
8149
9024
  continue;
8150
9025
  }
8151
9026
  validateAgentTemplating({
8152
- content: contents,
9027
+ content: template.rawContents,
8153
9028
  validAgents: options.validAgents,
8154
- sourcePath: entry2.sourcePath
9029
+ sourcePath: template.sourcePath
8155
9030
  });
8156
9031
  }
8157
9032
  }
@@ -8746,12 +9621,11 @@ Config: auto-discovered as omniagent.config.(ts|mts|cts|js|mjs|cjs) in the agent
8746
9621
  commands: /* @__PURE__ */ new Set()
8747
9622
  };
8748
9623
  const includeItemFor = (category) => {
8749
- if (!profileItemFilter.enabled) {
8750
- return void 0;
8751
- }
8752
- return (name) => {
8753
- traversedProfileNames[category].add(name);
8754
- return profileItemFilter.includes(category, name);
9624
+ return (item) => {
9625
+ if (profileItemFilter.enabled) {
9626
+ traversedProfileNames[category].add(item.canonicalName);
9627
+ }
9628
+ return profileItemFilter.includes(category, item);
8755
9629
  };
8756
9630
  };
8757
9631
  if (filteredTargets.length === 0 && !listLocal) {
@@ -8957,6 +9831,10 @@ Config: auto-discovered as omniagent.config.(ts|mts|cts|js|mjs|cjs) in the agent
8957
9831
  repoRoot,
8958
9832
  agentsDir,
8959
9833
  validAgents,
9834
+ selectedSkillTargets,
9835
+ selectedCommandTargets,
9836
+ selectedSubagentTargets,
9837
+ selectedInstructionTargets,
8960
9838
  commandsAvailable: hasCommandsToSync,
8961
9839
  skillsAvailable: hasSkillsToSync,
8962
9840
  subagentsAvailable: hasSubagentsToSync,
@@ -9159,7 +10037,8 @@ Config: auto-discovered as omniagent.config.(ts|mts|cts|js|mjs|cjs) in the agent
9159
10037
  resolveTargetName,
9160
10038
  hooks: globalHooks,
9161
10039
  templateScriptRuntime: scriptRuntime,
9162
- includeItem: includeItemFor("subagents")
10040
+ includeItem: includeItemFor("subagents"),
10041
+ includeSkill: includeItemFor("skills")
9163
10042
  });
9164
10043
  if (availabilitySubagentSkips.length > 0) {
9165
10044
  const subagentWarnings = mergeWarnings(