@openchamber/web 1.7.0 → 1.7.2
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/assets/{ToolOutputDialog-B0blBQQ2.js → ToolOutputDialog-DK5URr2G.js} +1 -1
- package/dist/assets/{index-hNdAvOl1.js → index-DXslkSyX.js} +2 -2
- package/dist/assets/index-OzAB93EH.css +1 -0
- package/dist/assets/main-CBV37ECk.js +294 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/index.js +195 -22
- package/server/lib/opencode-config.js +258 -68
- package/server/lib/quota/providers/codex.js +3 -1
- package/server/lib/skills-catalog/clawdhub/install.js +20 -5
- package/server/lib/skills-catalog/install.js +19 -5
- package/dist/assets/index-DWAk1auj.css +0 -1
- package/dist/assets/main-K9J17_P4.js +0 -288
|
@@ -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
|
-
//
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
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
|
-
//
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 2) External project ancestors (.claude, .agents)
|
|
1503
1649
|
if (workingDirectory) {
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
for (const
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
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
|
-
//
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
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
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1681
|
-
|
|
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
|
-
|
|
1685
|
-
|
|
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
|
}
|
|
@@ -52,7 +52,9 @@ export const fetchQuota = async () => {
|
|
|
52
52
|
providerName,
|
|
53
53
|
ok: false,
|
|
54
54
|
configured: true,
|
|
55
|
-
error:
|
|
55
|
+
error: response.status === 401
|
|
56
|
+
? 'Session expired \u2014 please re-authenticate with OpenAI'
|
|
57
|
+
: `API error: ${response.status}`
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -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({
|