@openchamber/web 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -330,11 +330,32 @@ function getClaudeSkillPath(workingDirectory, skillName) {
330
330
  return path.join(getClaudeSkillDir(workingDirectory, skillName), 'SKILL.md');
331
331
  }
332
332
 
333
+ function getUserAgentsSkillDir(skillName) {
334
+ return path.join(os.homedir(), '.agents', 'skills', skillName);
335
+ }
336
+
337
+ function getUserAgentsSkillPath(skillName) {
338
+ return path.join(getUserAgentsSkillDir(skillName), 'SKILL.md');
339
+ }
340
+
341
+ function getProjectAgentsSkillDir(workingDirectory, skillName) {
342
+ return path.join(workingDirectory, '.agents', 'skills', skillName);
343
+ }
344
+
345
+ function getProjectAgentsSkillPath(workingDirectory, skillName) {
346
+ return path.join(getProjectAgentsSkillDir(workingDirectory, skillName), 'SKILL.md');
347
+ }
348
+
333
349
  /**
334
350
  * Determine skill scope based on where the SKILL.md file exists
335
351
  * Priority: project level (.opencode) > user level > claude-compat (.claude/skills)
336
352
  */
337
353
  function getSkillScope(skillName, workingDirectory) {
354
+ const discovered = discoverSkills(workingDirectory).find((skill) => skill.name === skillName);
355
+ if (discovered?.path) {
356
+ return { scope: discovered.scope || null, path: discovered.path, source: discovered.source || null };
357
+ }
358
+
338
359
  if (workingDirectory) {
339
360
  // Check .opencode/skill first
340
361
  const projectPath = getProjectSkillPath(workingDirectory, skillName);
@@ -689,6 +710,129 @@ function readConfig(workingDirectory) {
689
710
  return readConfigLayers(workingDirectory).mergedConfig;
690
711
  }
691
712
 
713
+ function getAncestors(startDir, stopDir) {
714
+ if (!startDir) return [];
715
+ const result = [];
716
+ let current = path.resolve(startDir);
717
+ const resolvedStop = stopDir ? path.resolve(stopDir) : null;
718
+
719
+ while (true) {
720
+ result.push(current);
721
+ if (resolvedStop && current === resolvedStop) {
722
+ break;
723
+ }
724
+ const parent = path.dirname(current);
725
+ if (parent === current) {
726
+ break;
727
+ }
728
+ current = parent;
729
+ }
730
+
731
+ return result;
732
+ }
733
+
734
+ function findWorktreeRoot(startDir) {
735
+ if (!startDir) return null;
736
+ let current = path.resolve(startDir);
737
+
738
+ while (true) {
739
+ if (fs.existsSync(path.join(current, '.git'))) {
740
+ return current;
741
+ }
742
+ const parent = path.dirname(current);
743
+ if (parent === current) {
744
+ return null;
745
+ }
746
+ current = parent;
747
+ }
748
+ }
749
+
750
+ function walkSkillMdFiles(rootDir) {
751
+ if (!rootDir || !fs.existsSync(rootDir)) return [];
752
+
753
+ const results = [];
754
+ const walk = (dir) => {
755
+ let entries = [];
756
+ try {
757
+ entries = fs.readdirSync(dir, { withFileTypes: true });
758
+ } catch {
759
+ return;
760
+ }
761
+
762
+ for (const entry of entries) {
763
+ const fullPath = path.join(dir, entry.name);
764
+ if (entry.isDirectory()) {
765
+ walk(fullPath);
766
+ continue;
767
+ }
768
+ if (entry.isFile() && entry.name === 'SKILL.md') {
769
+ results.push(fullPath);
770
+ }
771
+ }
772
+ };
773
+
774
+ walk(rootDir);
775
+ return results;
776
+ }
777
+
778
+ function addSkillFromMdFile(skillsMap, skillMdPath, scope, source) {
779
+ let parsed;
780
+ try {
781
+ parsed = parseMdFile(skillMdPath);
782
+ } catch {
783
+ return;
784
+ }
785
+
786
+ const name = typeof parsed.frontmatter?.name === 'string'
787
+ ? parsed.frontmatter.name.trim()
788
+ : '';
789
+ const description = typeof parsed.frontmatter?.description === 'string'
790
+ ? parsed.frontmatter.description
791
+ : '';
792
+
793
+ if (!name) {
794
+ return;
795
+ }
796
+
797
+ skillsMap.set(name, {
798
+ name,
799
+ path: skillMdPath,
800
+ scope,
801
+ source,
802
+ description,
803
+ });
804
+ }
805
+
806
+ function resolveSkillSearchDirectories(workingDirectory) {
807
+ const directories = [];
808
+ const pushDir = (dir) => {
809
+ if (!dir) return;
810
+ const resolved = path.resolve(dir);
811
+ if (!directories.includes(resolved)) {
812
+ directories.push(resolved);
813
+ }
814
+ };
815
+
816
+ // Equivalent to Opencode Config.directories order.
817
+ pushDir(OPENCODE_CONFIG_DIR);
818
+
819
+ if (workingDirectory) {
820
+ const worktreeRoot = findWorktreeRoot(workingDirectory) || path.resolve(workingDirectory);
821
+ const projectDirs = getAncestors(workingDirectory, worktreeRoot)
822
+ .map((dir) => path.join(dir, '.opencode'));
823
+ projectDirs.forEach(pushDir);
824
+ }
825
+
826
+ pushDir(path.join(os.homedir(), '.opencode'));
827
+
828
+ const customConfigDir = process.env.OPENCODE_CONFIG_DIR
829
+ ? path.resolve(process.env.OPENCODE_CONFIG_DIR)
830
+ : null;
831
+ pushDir(customConfigDir);
832
+
833
+ return directories;
834
+ }
835
+
692
836
  function getConfigForPath(layers, targetPath) {
693
837
  if (!targetPath) {
694
838
  return layers.userConfig;
@@ -1491,87 +1635,95 @@ function deleteCommand(commandName, workingDirectory) {
1491
1635
  */
1492
1636
  function discoverSkills(workingDirectory) {
1493
1637
  const skills = new Map();
1494
-
1495
- // Helper to add skill if not already found (first found wins by priority)
1496
- const addSkill = (name, skillPath, scope, source) => {
1497
- if (!skills.has(name)) {
1498
- skills.set(name, { name, path: skillPath, scope, source });
1638
+
1639
+ // 1) External global (.claude, .agents)
1640
+ for (const externalRootName of ['.claude', '.agents']) {
1641
+ const homeRoot = path.join(os.homedir(), externalRootName, 'skills');
1642
+ const source = externalRootName === '.agents' ? 'agents' : 'claude';
1643
+ for (const skillMdPath of walkSkillMdFiles(homeRoot)) {
1644
+ addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.USER, source);
1499
1645
  }
1500
- };
1501
-
1502
- // 1. Project level .opencode/skills/ (highest priority)
1646
+ }
1647
+
1648
+ // 2) External project ancestors (.claude, .agents)
1503
1649
  if (workingDirectory) {
1504
- const projectSkillDir = path.join(workingDirectory, '.opencode', 'skills');
1505
- if (fs.existsSync(projectSkillDir)) {
1506
- const entries = fs.readdirSync(projectSkillDir, { withFileTypes: true });
1507
- for (const entry of entries) {
1508
- if (entry.isDirectory()) {
1509
- const skillMdPath = path.join(projectSkillDir, entry.name, 'SKILL.md');
1510
- if (fs.existsSync(skillMdPath)) {
1511
- addSkill(entry.name, skillMdPath, SKILL_SCOPE.PROJECT, 'opencode');
1512
- }
1650
+ const worktreeRoot = findWorktreeRoot(workingDirectory) || path.resolve(workingDirectory);
1651
+ const ancestors = getAncestors(workingDirectory, worktreeRoot);
1652
+ for (const ancestor of ancestors) {
1653
+ for (const externalRootName of ['.claude', '.agents']) {
1654
+ const source = externalRootName === '.agents' ? 'agents' : 'claude';
1655
+ const externalSkillsRoot = path.join(ancestor, externalRootName, 'skills');
1656
+ for (const skillMdPath of walkSkillMdFiles(externalSkillsRoot)) {
1657
+ addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.PROJECT, source);
1513
1658
  }
1514
1659
  }
1515
1660
  }
1661
+ }
1516
1662
 
1517
- const legacyProjectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
1518
- if (fs.existsSync(legacyProjectSkillDir)) {
1519
- const entries = fs.readdirSync(legacyProjectSkillDir, { withFileTypes: true });
1520
- for (const entry of entries) {
1521
- if (entry.isDirectory()) {
1522
- const skillMdPath = path.join(legacyProjectSkillDir, entry.name, 'SKILL.md');
1523
- if (fs.existsSync(skillMdPath)) {
1524
- addSkill(entry.name, skillMdPath, SKILL_SCOPE.PROJECT, 'opencode');
1525
- }
1526
- }
1527
- }
1528
- }
1529
-
1530
- // 2. Claude-compatible .claude/skills/
1531
- const claudeSkillDir = path.join(workingDirectory, '.claude', 'skills');
1532
- if (fs.existsSync(claudeSkillDir)) {
1533
- const entries = fs.readdirSync(claudeSkillDir, { withFileTypes: true });
1534
- for (const entry of entries) {
1535
- if (entry.isDirectory()) {
1536
- const skillMdPath = path.join(claudeSkillDir, entry.name, 'SKILL.md');
1537
- if (fs.existsSync(skillMdPath)) {
1538
- addSkill(entry.name, skillMdPath, SKILL_SCOPE.PROJECT, 'claude');
1539
- }
1540
- }
1663
+ // 3) Config directories: {skill,skills}/**/SKILL.md
1664
+ const configDirectories = resolveSkillSearchDirectories(workingDirectory);
1665
+ const homeOpencodeDir = path.resolve(path.join(os.homedir(), '.opencode'));
1666
+ const customConfigDir = process.env.OPENCODE_CONFIG_DIR
1667
+ ? path.resolve(process.env.OPENCODE_CONFIG_DIR)
1668
+ : null;
1669
+ for (const dir of configDirectories) {
1670
+ for (const subDir of ['skill', 'skills']) {
1671
+ const root = path.join(dir, subDir);
1672
+ for (const skillMdPath of walkSkillMdFiles(root)) {
1673
+ const isUserConfigDir = dir === OPENCODE_CONFIG_DIR
1674
+ || dir === homeOpencodeDir
1675
+ || (customConfigDir && dir === customConfigDir);
1676
+ const scope = isUserConfigDir ? SKILL_SCOPE.USER : SKILL_SCOPE.PROJECT;
1677
+ addSkillFromMdFile(skills, skillMdPath, scope, 'opencode');
1541
1678
  }
1542
1679
  }
1543
1680
  }
1544
-
1545
- // 3. User level ~/.config/opencode/skills/
1546
- if (fs.existsSync(SKILL_DIR)) {
1547
- const entries = fs.readdirSync(SKILL_DIR, { withFileTypes: true });
1548
- for (const entry of entries) {
1549
- if (entry.isDirectory()) {
1550
- const skillMdPath = path.join(SKILL_DIR, entry.name, 'SKILL.md');
1551
- if (fs.existsSync(skillMdPath)) {
1552
- addSkill(entry.name, skillMdPath, SKILL_SCOPE.USER, 'opencode');
1553
- }
1554
- }
1681
+
1682
+ // 4) Additional config.skills.paths
1683
+ let configuredPaths = [];
1684
+ try {
1685
+ const config = readConfig(workingDirectory);
1686
+ configuredPaths = Array.isArray(config?.skills?.paths) ? config.skills.paths : [];
1687
+ } catch {
1688
+ configuredPaths = [];
1689
+ }
1690
+ for (const skillPath of configuredPaths) {
1691
+ if (typeof skillPath !== 'string' || !skillPath.trim()) continue;
1692
+ const expanded = skillPath.startsWith('~/')
1693
+ ? path.join(os.homedir(), skillPath.slice(2))
1694
+ : skillPath;
1695
+ const resolved = path.isAbsolute(expanded)
1696
+ ? path.resolve(expanded)
1697
+ : path.resolve(workingDirectory || process.cwd(), expanded);
1698
+ for (const skillMdPath of walkSkillMdFiles(resolved)) {
1699
+ addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.PROJECT, 'opencode');
1555
1700
  }
1556
1701
  }
1557
1702
 
1558
- const legacyUserSkillDir = path.join(OPENCODE_CONFIG_DIR, 'skill');
1559
- if (fs.existsSync(legacyUserSkillDir)) {
1560
- const entries = fs.readdirSync(legacyUserSkillDir, { withFileTypes: true });
1703
+ // 5) Cached skills from config.skills.urls pulls (best-effort, no network)
1704
+ const cacheCandidates = [];
1705
+ if (process.env.XDG_CACHE_HOME) {
1706
+ cacheCandidates.push(path.join(process.env.XDG_CACHE_HOME, 'opencode', 'skills'));
1707
+ }
1708
+ cacheCandidates.push(path.join(os.homedir(), '.cache', 'opencode', 'skills'));
1709
+ cacheCandidates.push(path.join(os.homedir(), 'Library', 'Caches', 'opencode', 'skills'));
1710
+
1711
+ for (const cacheRoot of cacheCandidates) {
1712
+ if (!fs.existsSync(cacheRoot)) continue;
1713
+ const entries = fs.readdirSync(cacheRoot, { withFileTypes: true });
1561
1714
  for (const entry of entries) {
1562
- if (entry.isDirectory()) {
1563
- const skillMdPath = path.join(legacyUserSkillDir, entry.name, 'SKILL.md');
1564
- if (fs.existsSync(skillMdPath)) {
1565
- addSkill(entry.name, skillMdPath, SKILL_SCOPE.USER, 'opencode');
1566
- }
1715
+ if (!entry.isDirectory()) continue;
1716
+ const skillRoot = path.join(cacheRoot, entry.name);
1717
+ for (const skillMdPath of walkSkillMdFiles(skillRoot)) {
1718
+ addSkillFromMdFile(skills, skillMdPath, SKILL_SCOPE.USER, 'opencode');
1567
1719
  }
1568
1720
  }
1569
1721
  }
1570
-
1722
+
1571
1723
  return Array.from(skills.values());
1572
1724
  }
1573
1725
 
1574
- function getSkillSources(skillName, workingDirectory) {
1726
+ function getSkillSources(skillName, workingDirectory, discoveredSkill = null) {
1575
1727
  // Check all possible locations
1576
1728
  const projectPath = workingDirectory ? getProjectSkillPath(workingDirectory, skillName) : null;
1577
1729
  const projectExists = projectPath && fs.existsSync(projectPath);
@@ -1584,6 +1736,10 @@ function getSkillSources(skillName, workingDirectory) {
1584
1736
  const userPath = getUserSkillPath(skillName);
1585
1737
  const userExists = fs.existsSync(userPath);
1586
1738
  const userDir = userExists ? path.dirname(userPath) : null;
1739
+
1740
+ const matchedDiscovered = discoveredSkill && discoveredSkill.name === skillName
1741
+ ? discoveredSkill
1742
+ : discoverSkills(workingDirectory).find((skill) => skill.name === skillName);
1587
1743
 
1588
1744
  // Determine which md file to use (priority: project > claude > user)
1589
1745
  let mdPath = null;
@@ -1606,6 +1762,11 @@ function getSkillSources(skillName, workingDirectory) {
1606
1762
  mdScope = SKILL_SCOPE.USER;
1607
1763
  mdSource = 'opencode';
1608
1764
  mdDir = userDir;
1765
+ } else if (matchedDiscovered?.path) {
1766
+ mdPath = matchedDiscovered.path;
1767
+ mdScope = matchedDiscovered.scope || null;
1768
+ mdSource = matchedDiscovered.source || null;
1769
+ mdDir = path.dirname(matchedDiscovered.path);
1609
1770
  }
1610
1771
 
1611
1772
  const mdExists = !!mdPath;
@@ -1675,14 +1836,27 @@ function createSkill(skillName, config, workingDirectory, scope) {
1675
1836
  let targetPath;
1676
1837
  let targetScope;
1677
1838
 
1678
- if (scope === SKILL_SCOPE.PROJECT && workingDirectory) {
1839
+ const requestedScope = scope === SKILL_SCOPE.PROJECT ? SKILL_SCOPE.PROJECT : SKILL_SCOPE.USER;
1840
+ const requestedSource = config?.source === 'agents' ? 'agents' : 'opencode';
1841
+
1842
+ if (requestedScope === SKILL_SCOPE.PROJECT && workingDirectory) {
1679
1843
  ensureProjectSkillDir(workingDirectory);
1680
- targetDir = getProjectSkillDir(workingDirectory, skillName);
1681
- targetPath = getProjectSkillPath(workingDirectory, skillName);
1844
+ if (requestedSource === 'agents') {
1845
+ targetDir = getProjectAgentsSkillDir(workingDirectory, skillName);
1846
+ targetPath = getProjectAgentsSkillPath(workingDirectory, skillName);
1847
+ } else {
1848
+ targetDir = getProjectSkillDir(workingDirectory, skillName);
1849
+ targetPath = getProjectSkillPath(workingDirectory, skillName);
1850
+ }
1682
1851
  targetScope = SKILL_SCOPE.PROJECT;
1683
1852
  } else {
1684
- targetDir = getUserSkillDir(skillName);
1685
- targetPath = getUserSkillPath(skillName);
1853
+ if (requestedSource === 'agents') {
1854
+ targetDir = getUserAgentsSkillDir(skillName);
1855
+ targetPath = getUserAgentsSkillPath(skillName);
1856
+ } else {
1857
+ targetDir = getUserSkillDir(skillName);
1858
+ targetPath = getUserSkillPath(skillName);
1859
+ }
1686
1860
  targetScope = SKILL_SCOPE.USER;
1687
1861
  }
1688
1862
 
@@ -1690,7 +1864,9 @@ function createSkill(skillName, config, workingDirectory, scope) {
1690
1864
  fs.mkdirSync(targetDir, { recursive: true });
1691
1865
 
1692
1866
  // Extract fields - scope is only for path determination
1693
- const { instructions, scope: _scopeFromConfig, supportingFiles, ...frontmatter } = config;
1867
+ const { instructions, scope: _scopeFromConfig, source: _sourceFromConfig, supportingFiles, ...frontmatter } = config;
1868
+ void _scopeFromConfig;
1869
+ void _sourceFromConfig;
1694
1870
 
1695
1871
  // Ensure required fields
1696
1872
  if (!frontmatter.name) {
@@ -1789,6 +1965,13 @@ function deleteSkill(skillName, workingDirectory) {
1789
1965
  console.log(`Deleted claude-compat skill directory: ${claudeDir}`);
1790
1966
  deleted = true;
1791
1967
  }
1968
+
1969
+ const projectAgentsDir = getProjectAgentsSkillDir(workingDirectory, skillName);
1970
+ if (fs.existsSync(projectAgentsDir)) {
1971
+ fs.rmSync(projectAgentsDir, { recursive: true, force: true });
1972
+ console.log(`Deleted project-level agents skill directory: ${projectAgentsDir}`);
1973
+ deleted = true;
1974
+ }
1792
1975
  }
1793
1976
 
1794
1977
  // User level
@@ -1799,6 +1982,13 @@ function deleteSkill(skillName, workingDirectory) {
1799
1982
  deleted = true;
1800
1983
  }
1801
1984
 
1985
+ const userAgentsDir = getUserAgentsSkillDir(skillName);
1986
+ if (fs.existsSync(userAgentsDir)) {
1987
+ fs.rmSync(userAgentsDir, { recursive: true, force: true });
1988
+ console.log(`Deleted user-level agents skill directory: ${userAgentsDir}`);
1989
+ deleted = true;
1990
+ }
1991
+
1802
1992
  if (!deleted) {
1803
1993
  throw new Error(`Skill "${skillName}" not found`);
1804
1994
  }
@@ -43,8 +43,13 @@ async function ensureDir(dirPath) {
43
43
  await fs.promises.mkdir(dirPath, { recursive: true });
44
44
  }
45
45
 
46
- function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName }) {
46
+ function getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName }) {
47
+ const source = targetSource === 'agents' ? 'agents' : 'opencode';
48
+
47
49
  if (scope === 'user') {
50
+ if (source === 'agents') {
51
+ return path.join(os.homedir(), '.agents', 'skills', skillName);
52
+ }
48
53
  return path.join(userSkillDir, skillName);
49
54
  }
50
55
 
@@ -52,6 +57,10 @@ function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName })
52
57
  throw new Error('workingDirectory is required for project installs');
53
58
  }
54
59
 
60
+ if (source === 'agents') {
61
+ return path.join(workingDirectory, '.agents', 'skills', skillName);
62
+ }
63
+
55
64
  return path.join(workingDirectory, '.opencode', 'skills', skillName);
56
65
  }
57
66
 
@@ -59,6 +68,7 @@ function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName })
59
68
  * Install skills from ClawdHub registry
60
69
  * @param {Object} options
61
70
  * @param {string} options.scope - 'user' or 'project'
71
+ * @param {string} [options.targetSource] - 'opencode' or 'agents'
62
72
  * @param {string} [options.workingDirectory] - Required for project scope
63
73
  * @param {string} options.userSkillDir - User skills directory
64
74
  * @param {Array} options.selections - Array of { skillDir, clawdhub: { slug, version } }
@@ -68,6 +78,7 @@ function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName })
68
78
  */
69
79
  export async function installSkillsFromClawdHub({
70
80
  scope,
81
+ targetSource,
71
82
  workingDirectory,
72
83
  userSkillDir,
73
84
  selections,
@@ -78,6 +89,10 @@ export async function installSkillsFromClawdHub({
78
89
  return { ok: false, error: { kind: 'invalidSource', message: 'Invalid scope' } };
79
90
  }
80
91
 
92
+ if (targetSource !== undefined && targetSource !== 'opencode' && targetSource !== 'agents') {
93
+ return { ok: false, error: { kind: 'invalidSource', message: 'Invalid target source' } };
94
+ }
95
+
81
96
  if (!userSkillDir) {
82
97
  return { ok: false, error: { kind: 'unknown', message: 'userSkillDir is required' } };
83
98
  }
@@ -114,12 +129,12 @@ export async function installSkillsFromClawdHub({
114
129
  continue;
115
130
  }
116
131
 
117
- const targetDir = getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName: plan.slug });
132
+ const targetDir = getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName: plan.slug });
118
133
  if (fs.existsSync(targetDir)) {
119
134
  const decision = conflictDecisions?.[plan.slug];
120
135
  const hasAutoPolicy = conflictPolicy === 'skipAll' || conflictPolicy === 'overwriteAll';
121
136
  if (!decision && !hasAutoPolicy) {
122
- conflicts.push({ skillName: plan.slug, scope });
137
+ conflicts.push({ skillName: plan.slug, scope, source: targetSource === 'agents' ? 'agents' : 'opencode' });
123
138
  }
124
139
  }
125
140
  }
@@ -164,7 +179,7 @@ export async function installSkillsFromClawdHub({
164
179
  }
165
180
  }
166
181
 
167
- const targetDir = getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName: plan.slug });
182
+ const targetDir = getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName: plan.slug });
168
183
  const exists = fs.existsSync(targetDir);
169
184
 
170
185
  // Determine conflict resolution
@@ -205,7 +220,7 @@ export async function installSkillsFromClawdHub({
205
220
  await ensureDir(path.dirname(targetDir));
206
221
  await fs.promises.rename(tempDir, targetDir);
207
222
 
208
- installed.push({ skillName: plan.slug, scope });
223
+ installed.push({ skillName: plan.slug, scope, source: targetSource === 'agents' ? 'agents' : 'opencode' });
209
224
  } catch (extractError) {
210
225
  await safeRm(tempDir);
211
226
  throw extractError;
@@ -105,8 +105,13 @@ async function cloneRepo({ cloneUrl, identity, tempDir }) {
105
105
  };
106
106
  }
107
107
 
108
- function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName }) {
108
+ function getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName }) {
109
+ const source = targetSource === 'agents' ? 'agents' : 'opencode';
110
+
109
111
  if (scope === 'user') {
112
+ if (source === 'agents') {
113
+ return path.join(os.homedir(), '.agents', 'skills', skillName);
114
+ }
110
115
  return path.join(userSkillDir, skillName);
111
116
  }
112
117
 
@@ -114,6 +119,10 @@ function getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName })
114
119
  throw new Error('workingDirectory is required for project installs');
115
120
  }
116
121
 
122
+ if (source === 'agents') {
123
+ return path.join(workingDirectory, '.agents', 'skills', skillName);
124
+ }
125
+
117
126
  return path.join(workingDirectory, '.opencode', 'skills', skillName);
118
127
  }
119
128
 
@@ -123,6 +132,7 @@ export async function installSkillsFromRepository({
123
132
  defaultSubpath,
124
133
  identity,
125
134
  scope,
135
+ targetSource,
126
136
  workingDirectory,
127
137
  userSkillDir,
128
138
  selections,
@@ -147,6 +157,10 @@ export async function installSkillsFromRepository({
147
157
  return { ok: false, error: { kind: 'invalidSource', message: 'Invalid scope' } };
148
158
  }
149
159
 
160
+ if (targetSource !== undefined && targetSource !== 'opencode' && targetSource !== 'agents') {
161
+ return { ok: false, error: { kind: 'invalidSource', message: 'Invalid target source' } };
162
+ }
163
+
150
164
  if (scope === 'project' && !workingDirectory) {
151
165
  return { ok: false, error: { kind: 'invalidSource', message: 'Project installs require a directory parameter' } };
152
166
  }
@@ -178,12 +192,12 @@ export async function installSkillsFromRepository({
178
192
  continue;
179
193
  }
180
194
 
181
- const targetDir = getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName: plan.skillName });
195
+ const targetDir = getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName: plan.skillName });
182
196
  if (fs.existsSync(targetDir)) {
183
197
  const decision = conflictDecisions?.[plan.skillName];
184
198
  const hasAutoPolicy = conflictPolicy === 'skipAll' || conflictPolicy === 'overwriteAll';
185
199
  if (!decision && !hasAutoPolicy) {
186
- conflicts.push({ skillName: plan.skillName, scope });
200
+ conflicts.push({ skillName: plan.skillName, scope, source: targetSource === 'agents' ? 'agents' : 'opencode' });
187
201
  }
188
202
  }
189
203
  }
@@ -239,7 +253,7 @@ export async function installSkillsFromRepository({
239
253
  continue;
240
254
  }
241
255
 
242
- const targetDir = getTargetSkillDir({ scope, workingDirectory, userSkillDir, skillName: plan.skillName });
256
+ const targetDir = getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName: plan.skillName });
243
257
  const exists = fs.existsSync(targetDir);
244
258
 
245
259
  let decision = conflictDecisions?.[plan.skillName] || null;
@@ -263,7 +277,7 @@ export async function installSkillsFromRepository({
263
277
 
264
278
  try {
265
279
  await copyDirectoryNoSymlinks(srcDir, targetDir);
266
- installed.push({ skillName: plan.skillName, scope });
280
+ installed.push({ skillName: plan.skillName, scope, source: targetSource === 'agents' ? 'agents' : 'opencode' });
267
281
  } catch (error) {
268
282
  await safeRm(targetDir);
269
283
  skipped.push({