shoplazza-ai-dev-cli 0.1.2 → 0.1.5
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.mjs +258 -201
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -79,6 +79,11 @@ function decomposePortalRef(ref) {
|
|
|
79
79
|
}
|
|
80
80
|
return null;
|
|
81
81
|
}
|
|
82
|
+
function portalRefToSkillPath(ref) {
|
|
83
|
+
const m = ref.match(/^(?:@public|@teams\/[^/]+)\/(skills|rules)\/([^/]+)$/);
|
|
84
|
+
if (!m) return null;
|
|
85
|
+
return `${m[1]}/${m[2]}/SKILL.md`;
|
|
86
|
+
}
|
|
82
87
|
function sanitizeSubpath(subpath) {
|
|
83
88
|
const segments = subpath.replace(/\\/g, "/").split("/");
|
|
84
89
|
for (const segment of segments) if (segment === "..") throw new Error(`Unsafe subpath: "${subpath}" contains path traversal segments. Subpaths must not contain ".." components.`);
|
|
@@ -313,7 +318,8 @@ const agents = {
|
|
|
313
318
|
globalSkillsDir: join(codexHome, "skills"),
|
|
314
319
|
detectInstalled: async () => {
|
|
315
320
|
return existsSync(codexHome) || existsSync("/etc/codex");
|
|
316
|
-
}
|
|
321
|
+
},
|
|
322
|
+
hiddenInPicker: true
|
|
317
323
|
},
|
|
318
324
|
cursor: {
|
|
319
325
|
name: "cursor",
|
|
@@ -331,6 +337,9 @@ async function detectInstalledAgents() {
|
|
|
331
337
|
installed: await config.detectInstalled()
|
|
332
338
|
})))).filter((r) => r.installed).map((r) => r.type);
|
|
333
339
|
}
|
|
340
|
+
function getPickerVisibleAgents() {
|
|
341
|
+
return Object.entries(agents).filter(([_, config]) => !config.hiddenInPicker).map(([type]) => type);
|
|
342
|
+
}
|
|
334
343
|
function getUniversalAgents() {
|
|
335
344
|
return Object.entries(agents).filter(([_, config]) => config.skillsDir === ".agents/skills" && config.showInUniversalList !== false).map(([type]) => type);
|
|
336
345
|
}
|
|
@@ -490,7 +499,153 @@ function configCommand(args) {
|
|
|
490
499
|
console.error("Available: get | set | unset | list | path");
|
|
491
500
|
process.exit(2);
|
|
492
501
|
}
|
|
493
|
-
const
|
|
502
|
+
const FORGE_CURSOR_EVENTS = ["beforeReadFile", "beforeSubmitPrompt"];
|
|
503
|
+
const CURSOR_HOOK_SCRIPT_BASENAME = "track-skill-cursor.mjs";
|
|
504
|
+
const LEGACY_CURSOR_HOOK_BASENAME = "track-skill-read.mjs";
|
|
505
|
+
function getCursorHookScript(portalUrl) {
|
|
506
|
+
const portal = portalUrl.replace(/\/$/, "");
|
|
507
|
+
return `#!/usr/bin/env node
|
|
508
|
+
// forge:track-skill-cursor
|
|
509
|
+
// Auto-generated by ai-dev-cli. Do not edit; run \`ai-dev-cli add\` to refresh.
|
|
510
|
+
// Cursor (2.4+) has no dedicated skill_invoked event. We listen on:
|
|
511
|
+
// - beforeReadFile → agent reads SKILL.md when natural-language-loading
|
|
512
|
+
// a skill into context.
|
|
513
|
+
// - beforeSubmitPrompt → user types \`/<skill-name>\` (slash command);
|
|
514
|
+
// Cursor loads the skill internally without reading
|
|
515
|
+
// SKILL.md, so we parse the prompt instead.
|
|
516
|
+
// IMPORTANT: both are permission-gating hooks. We MUST output \`{}\` and
|
|
517
|
+
// exit 0 on every path so Cursor never blocks user input or file reads.
|
|
518
|
+
import fs from 'node:fs';
|
|
519
|
+
import os from 'node:os';
|
|
520
|
+
import path from 'node:path';
|
|
521
|
+
|
|
522
|
+
const PORTAL = ${JSON.stringify(portal)};
|
|
523
|
+
|
|
524
|
+
// Always allow; never block on any code path.
|
|
525
|
+
function allowAndExit() {
|
|
526
|
+
try { process.stdout.write('{}'); } catch {}
|
|
527
|
+
process.exit(0);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Hard ceiling: even if fetch hangs past its own 2s timeout, free Cursor
|
|
531
|
+
// after 5s total.
|
|
532
|
+
const exitTimer = setTimeout(allowAndExit, 5000);
|
|
533
|
+
exitTimer.unref?.();
|
|
534
|
+
|
|
535
|
+
function readManifest(dir) {
|
|
536
|
+
try {
|
|
537
|
+
const raw = fs.readFileSync(path.join(dir, '.forge-source.json'), 'utf8');
|
|
538
|
+
return JSON.parse(raw);
|
|
539
|
+
} catch {
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Walk up parent directories from a SKILL.md path looking for the
|
|
545
|
+
// .forge-source.json manifest forge writes at install time. Bounded depth
|
|
546
|
+
// so a stray read on an unrelated file can't walk to filesystem root.
|
|
547
|
+
function manifestFromFilePath(filePath) {
|
|
548
|
+
if (!filePath || typeof filePath !== 'string') return null;
|
|
549
|
+
let dir = path.dirname(filePath);
|
|
550
|
+
for (let i = 0; i < 8; i++) {
|
|
551
|
+
const m = readManifest(dir);
|
|
552
|
+
if (m) return m;
|
|
553
|
+
const parent = path.dirname(dir);
|
|
554
|
+
if (parent === dir) break;
|
|
555
|
+
dir = parent;
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Resolve a skill name (as typed in a slash command) to its forge manifest.
|
|
561
|
+
// Checks the IDE entry first (~/.cursor/skills/<name>/), then falls back
|
|
562
|
+
// to the canonical store (~/.ai-dev-cli/store/skills|plugins) which is
|
|
563
|
+
// authoritative regardless of where the IDE entry symlink lives.
|
|
564
|
+
function manifestFromSkillName(skillId) {
|
|
565
|
+
if (!skillId || typeof skillId !== 'string') return null;
|
|
566
|
+
const name = skillId.includes(':') ? skillId.split(':').pop() : skillId;
|
|
567
|
+
if (!name) return null;
|
|
568
|
+
const cursorHome = path.join(os.homedir(), '.cursor');
|
|
569
|
+
const m = readManifest(path.join(cursorHome, 'skills', name));
|
|
570
|
+
if (m) return m;
|
|
571
|
+
const storeRoot = path.join(os.homedir(), '.ai-dev-cli', 'store');
|
|
572
|
+
for (const sub of ['skills', 'plugins']) {
|
|
573
|
+
try {
|
|
574
|
+
const root = path.join(storeRoot, sub);
|
|
575
|
+
for (const entry of fs.readdirSync(root)) {
|
|
576
|
+
if (sub === 'skills') {
|
|
577
|
+
if (!entry.endsWith('__' + name)) continue;
|
|
578
|
+
const m2 = readManifest(path.join(root, entry));
|
|
579
|
+
if (m2 && m2.name === name) return m2;
|
|
580
|
+
} else {
|
|
581
|
+
const m2 = readManifest(path.join(root, entry, 'skills', name));
|
|
582
|
+
if (m2 && m2.name === name) return m2;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
} catch {
|
|
586
|
+
/* store dir missing — fall through */
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
let buf = '';
|
|
593
|
+
process.stdin.on('data', (c) => (buf += c));
|
|
594
|
+
process.stdin.on('end', () => {
|
|
595
|
+
try {
|
|
596
|
+
const ev = JSON.parse(buf);
|
|
597
|
+
let manifest = null;
|
|
598
|
+
|
|
599
|
+
if (ev.hook_event_name === 'beforeReadFile') {
|
|
600
|
+
const filePath = ev.file_path;
|
|
601
|
+
if (
|
|
602
|
+
typeof filePath !== 'string' ||
|
|
603
|
+
(!filePath.endsWith(path.sep + 'SKILL.md') && !filePath.endsWith('/SKILL.md'))
|
|
604
|
+
) {
|
|
605
|
+
return allowAndExit();
|
|
606
|
+
}
|
|
607
|
+
manifest = manifestFromFilePath(filePath);
|
|
608
|
+
} else if (ev.hook_event_name === 'beforeSubmitPrompt') {
|
|
609
|
+
const prompt = typeof ev.prompt === 'string' ? ev.prompt.trimStart() : '';
|
|
610
|
+
// Slash commands are at the start of the prompt; the token after \`/\`
|
|
611
|
+
// up to the first whitespace is the skill identifier the user typed.
|
|
612
|
+
const m = prompt.match(/^\\/([^\\s]+)/);
|
|
613
|
+
if (!m) return allowAndExit();
|
|
614
|
+
manifest = manifestFromSkillName(m[1]);
|
|
615
|
+
} else {
|
|
616
|
+
return allowAndExit();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!manifest?.ref) return allowAndExit();
|
|
620
|
+
|
|
621
|
+
fetch(PORTAL + '/events', {
|
|
622
|
+
method: 'POST',
|
|
623
|
+
headers: { 'Content-Type': 'application/json' },
|
|
624
|
+
body: JSON.stringify({
|
|
625
|
+
event_type: 'skill_invoked',
|
|
626
|
+
asset_ref: manifest.ref,
|
|
627
|
+
install_mode: manifest.install_mode || 'cursor',
|
|
628
|
+
metadata: {
|
|
629
|
+
parent_plugin_ref: manifest.parent_plugin_ref || null,
|
|
630
|
+
},
|
|
631
|
+
}),
|
|
632
|
+
signal: AbortSignal.timeout(2000),
|
|
633
|
+
})
|
|
634
|
+
.catch(() => {})
|
|
635
|
+
.finally(allowAndExit);
|
|
636
|
+
} catch {
|
|
637
|
+
allowAndExit();
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
`;
|
|
641
|
+
}
|
|
642
|
+
function isForgeOwnedCommand(command) {
|
|
643
|
+
if (typeof command !== "string") return false;
|
|
644
|
+
return command.includes(CURSOR_HOOK_SCRIPT_BASENAME) || command.includes(LEGACY_CURSOR_HOOK_BASENAME);
|
|
645
|
+
}
|
|
646
|
+
function isFlatEntry(e) {
|
|
647
|
+
return !!e && typeof e === "object" && typeof e.command === "string";
|
|
648
|
+
}
|
|
494
649
|
async function upsertCursorHooks(hooksJsonPath, scriptPath) {
|
|
495
650
|
let raw = "";
|
|
496
651
|
try {
|
|
@@ -498,24 +653,37 @@ async function upsertCursorHooks(hooksJsonPath, scriptPath) {
|
|
|
498
653
|
} catch {}
|
|
499
654
|
const cfg = raw.trim() ? JSON.parse(raw) : { version: 1 };
|
|
500
655
|
if (!cfg.hooks) cfg.hooks = {};
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
hooks
|
|
507
|
-
}
|
|
508
|
-
cfg.hooks.postToolUse.
|
|
656
|
+
if (Array.isArray(cfg.hooks.postToolUse)) {
|
|
657
|
+
for (let i = cfg.hooks.postToolUse.length - 1; i >= 0; i--) {
|
|
658
|
+
const m = cfg.hooks.postToolUse[i];
|
|
659
|
+
if (!m || !Array.isArray(m.hooks)) continue;
|
|
660
|
+
m.hooks = m.hooks.filter((h) => !isForgeOwnedCommand(h.command));
|
|
661
|
+
if (m.hooks.length === 0 && m.matcher === "Read") cfg.hooks.postToolUse.splice(i, 1);
|
|
662
|
+
}
|
|
663
|
+
if (cfg.hooks.postToolUse.length === 0) delete cfg.hooks.postToolUse;
|
|
509
664
|
}
|
|
510
|
-
if (!Array.isArray(readMatcher.hooks)) readMatcher.hooks = [];
|
|
511
665
|
const command = `node ${shellEscape$2(scriptPath)}`;
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
666
|
+
for (const eventName of FORGE_CURSOR_EVENTS) {
|
|
667
|
+
if (!Array.isArray(cfg.hooks[eventName])) cfg.hooks[eventName] = [];
|
|
668
|
+
const arr = cfg.hooks[eventName];
|
|
669
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
670
|
+
const item = arr[i];
|
|
671
|
+
if (!item || typeof item !== "object") continue;
|
|
672
|
+
if (isFlatEntry(item)) {
|
|
673
|
+
if (isForgeOwnedCommand(item.command)) arr.splice(i, 1);
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const wrapper = item;
|
|
677
|
+
if (Array.isArray(wrapper.hooks)) {
|
|
678
|
+
wrapper.hooks = wrapper.hooks.filter((h) => !isForgeOwnedCommand(h?.command));
|
|
679
|
+
if (wrapper.hooks.length === 0 && wrapper.matcher === void 0) arr.splice(i, 1);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
arr.push({
|
|
683
|
+
type: "command",
|
|
684
|
+
command
|
|
685
|
+
});
|
|
686
|
+
}
|
|
519
687
|
await mkdir(dirname(hooksJsonPath), { recursive: true });
|
|
520
688
|
await writeFile(hooksJsonPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
521
689
|
}
|
|
@@ -574,7 +742,7 @@ function removeSection(toml, header) {
|
|
|
574
742
|
function jsonString(s) {
|
|
575
743
|
return JSON.stringify(s);
|
|
576
744
|
}
|
|
577
|
-
const FORGE_HOOK_BASENAME = "track-skill-
|
|
745
|
+
const FORGE_HOOK_BASENAME = "track-skill-claude-code.mjs";
|
|
578
746
|
async function upsertCodexHooks(hooksJsonPath, configTomlPath, scriptPath) {
|
|
579
747
|
let raw = "";
|
|
580
748
|
try {
|
|
@@ -615,9 +783,9 @@ function shellEscape$1(p) {
|
|
|
615
783
|
if (!/[\s"'$`\\]/.test(p)) return p;
|
|
616
784
|
return `'${p.replace(/'/g, `'\\''`)}'`;
|
|
617
785
|
}
|
|
618
|
-
const HOOK_SCRIPT_BASENAME = "track-skill-
|
|
786
|
+
const HOOK_SCRIPT_BASENAME = "track-skill-claude-code.mjs";
|
|
619
787
|
const HOOK_DIRNAME = "forge-hooks";
|
|
620
|
-
const FORGE_HOOK_TAG = "forge:track-skill-
|
|
788
|
+
const FORGE_HOOK_TAG = "forge:track-skill-claude-code";
|
|
621
789
|
async function writeForgeSourceManifest(skillDir, manifest) {
|
|
622
790
|
const target = join(skillDir, ".forge-source.json");
|
|
623
791
|
await mkdir(skillDir, { recursive: true });
|
|
@@ -742,27 +910,34 @@ async function ensureForgeHooks(agent) {
|
|
|
742
910
|
try {
|
|
743
911
|
const portal = loadConfig().portal || DEFAULT_PORTAL;
|
|
744
912
|
const home = getAgentHome(agent);
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
else if (agent === "
|
|
913
|
+
if (agent === "claude-code") {
|
|
914
|
+
const scriptPath = join(home, HOOK_DIRNAME, HOOK_SCRIPT_BASENAME);
|
|
915
|
+
await writeScriptIfChanged(scriptPath, getForgeHookScript(portal));
|
|
916
|
+
await upsertClaudeCodeSettings(join(home, "settings.json"), scriptPath);
|
|
917
|
+
} else if (agent === "cursor") {
|
|
918
|
+
const cursorScriptPath = join(home, HOOK_DIRNAME, CURSOR_HOOK_SCRIPT_BASENAME);
|
|
919
|
+
await writeScriptIfChanged(cursorScriptPath, getCursorHookScript(portal));
|
|
920
|
+
await upsertCursorHooks(join(home, "hooks.json"), cursorScriptPath);
|
|
921
|
+
} else if (agent === "codex") {
|
|
922
|
+
const scriptPath = join(home, HOOK_DIRNAME, HOOK_SCRIPT_BASENAME);
|
|
923
|
+
await writeScriptIfChanged(scriptPath, getForgeHookScript(portal));
|
|
924
|
+
await upsertCodexHooks(join(home, "hooks.json"), join(home, "config.toml"), scriptPath);
|
|
925
|
+
}
|
|
750
926
|
} catch (err) {
|
|
751
927
|
warnings.push(`Failed to install forge hooks for ${agent}: ${err instanceof Error ? err.message : String(err)}`);
|
|
752
928
|
}
|
|
753
929
|
return { warnings };
|
|
754
930
|
}
|
|
755
|
-
async function
|
|
756
|
-
const next = getForgeHookScript(portalUrl);
|
|
931
|
+
async function writeScriptIfChanged(scriptPath, content) {
|
|
757
932
|
let prev = null;
|
|
758
933
|
if (existsSync(scriptPath)) try {
|
|
759
934
|
prev = await readFile(scriptPath, "utf-8");
|
|
760
935
|
} catch {
|
|
761
936
|
prev = null;
|
|
762
937
|
}
|
|
763
|
-
if (prev ===
|
|
938
|
+
if (prev === content) return;
|
|
764
939
|
await mkdir(dirname(scriptPath), { recursive: true });
|
|
765
|
-
await writeFile(scriptPath,
|
|
940
|
+
await writeFile(scriptPath, content, {
|
|
766
941
|
encoding: "utf-8",
|
|
767
942
|
mode: 493
|
|
768
943
|
});
|
|
@@ -1598,7 +1773,6 @@ async function installSkillForAgent(skill, agentType, opts) {
|
|
|
1598
1773
|
if (!isPathSafe(join(storeRoot, "skills"), canonicalDir)) return {
|
|
1599
1774
|
success: false,
|
|
1600
1775
|
path: "",
|
|
1601
|
-
mode: "symlink",
|
|
1602
1776
|
error: "Invalid skill name: path traversal detected",
|
|
1603
1777
|
forgeOtherReplacements: []
|
|
1604
1778
|
};
|
|
@@ -1625,7 +1799,6 @@ async function installSkillForAgent(skill, agentType, opts) {
|
|
|
1625
1799
|
if (!proceed) return {
|
|
1626
1800
|
success: false,
|
|
1627
1801
|
path: entry,
|
|
1628
|
-
mode: "symlink",
|
|
1629
1802
|
error: "aborted: user-owned content at entry path",
|
|
1630
1803
|
forgeOtherReplacements: []
|
|
1631
1804
|
};
|
|
@@ -1659,7 +1832,6 @@ async function installSkillForAgent(skill, agentType, opts) {
|
|
|
1659
1832
|
return {
|
|
1660
1833
|
success: true,
|
|
1661
1834
|
path: entry,
|
|
1662
|
-
mode: "symlink",
|
|
1663
1835
|
symlinkFailed: true,
|
|
1664
1836
|
canonicalPath: canonicalDir,
|
|
1665
1837
|
forgeOtherReplacements
|
|
@@ -1668,7 +1840,6 @@ async function installSkillForAgent(skill, agentType, opts) {
|
|
|
1668
1840
|
return {
|
|
1669
1841
|
success: true,
|
|
1670
1842
|
path: entry,
|
|
1671
|
-
mode: "symlink",
|
|
1672
1843
|
canonicalPath: canonicalDir,
|
|
1673
1844
|
forgeOtherReplacements
|
|
1674
1845
|
};
|
|
@@ -1676,7 +1847,6 @@ async function installSkillForAgent(skill, agentType, opts) {
|
|
|
1676
1847
|
return {
|
|
1677
1848
|
success: false,
|
|
1678
1849
|
path: entry,
|
|
1679
|
-
mode: "symlink",
|
|
1680
1850
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
1681
1851
|
forgeOtherReplacements: []
|
|
1682
1852
|
};
|
|
@@ -1689,7 +1859,6 @@ async function _installSkillForAgentLegacy(skill, agentType, options) {
|
|
|
1689
1859
|
if (isGlobal && agent.globalSkillsDir === void 0) return {
|
|
1690
1860
|
success: false,
|
|
1691
1861
|
path: "",
|
|
1692
|
-
mode: options.mode ?? "symlink",
|
|
1693
1862
|
error: `${agent.displayName} does not support global skill installation`,
|
|
1694
1863
|
forgeOtherReplacements: []
|
|
1695
1864
|
};
|
|
@@ -1698,39 +1867,25 @@ async function _installSkillForAgentLegacy(skill, agentType, options) {
|
|
|
1698
1867
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1699
1868
|
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1700
1869
|
const agentDir = join(agentBase, skillName);
|
|
1701
|
-
const installMode = options.mode ?? "symlink";
|
|
1702
1870
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1703
1871
|
success: false,
|
|
1704
1872
|
path: agentDir,
|
|
1705
|
-
mode: installMode,
|
|
1706
1873
|
error: "Invalid skill name: potential path traversal detected",
|
|
1707
1874
|
forgeOtherReplacements: []
|
|
1708
1875
|
};
|
|
1709
1876
|
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1710
1877
|
success: false,
|
|
1711
1878
|
path: agentDir,
|
|
1712
|
-
mode: installMode,
|
|
1713
1879
|
error: "Invalid skill name: potential path traversal detected",
|
|
1714
1880
|
forgeOtherReplacements: []
|
|
1715
1881
|
};
|
|
1716
1882
|
try {
|
|
1717
|
-
if (installMode === "copy") {
|
|
1718
|
-
await cleanAndCreateDirectory(agentDir);
|
|
1719
|
-
await copyDirectory(skill.path, agentDir);
|
|
1720
|
-
return {
|
|
1721
|
-
success: true,
|
|
1722
|
-
path: agentDir,
|
|
1723
|
-
mode: "copy",
|
|
1724
|
-
forgeOtherReplacements: []
|
|
1725
|
-
};
|
|
1726
|
-
}
|
|
1727
1883
|
await cleanAndCreateDirectory(canonicalDir);
|
|
1728
1884
|
await copyDirectory(skill.path, canonicalDir);
|
|
1729
1885
|
if (isGlobal && isUniversalAgent(agentType)) return {
|
|
1730
1886
|
success: true,
|
|
1731
1887
|
path: canonicalDir,
|
|
1732
1888
|
canonicalPath: canonicalDir,
|
|
1733
|
-
mode: "symlink",
|
|
1734
1889
|
forgeOtherReplacements: []
|
|
1735
1890
|
};
|
|
1736
1891
|
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
@@ -1740,7 +1895,6 @@ async function _installSkillForAgentLegacy(skill, agentType, options) {
|
|
|
1740
1895
|
success: true,
|
|
1741
1896
|
path: agentDir,
|
|
1742
1897
|
canonicalPath: canonicalDir,
|
|
1743
|
-
mode: "symlink",
|
|
1744
1898
|
symlinkFailed: true,
|
|
1745
1899
|
forgeOtherReplacements: []
|
|
1746
1900
|
};
|
|
@@ -1749,14 +1903,12 @@ async function _installSkillForAgentLegacy(skill, agentType, options) {
|
|
|
1749
1903
|
success: true,
|
|
1750
1904
|
path: agentDir,
|
|
1751
1905
|
canonicalPath: canonicalDir,
|
|
1752
|
-
mode: "symlink",
|
|
1753
1906
|
forgeOtherReplacements: []
|
|
1754
1907
|
};
|
|
1755
1908
|
} catch (error) {
|
|
1756
1909
|
return {
|
|
1757
1910
|
success: false,
|
|
1758
1911
|
path: agentDir,
|
|
1759
|
-
mode: installMode,
|
|
1760
1912
|
error: error instanceof Error ? error.message : "Unknown error",
|
|
1761
1913
|
forgeOtherReplacements: []
|
|
1762
1914
|
};
|
|
@@ -1816,11 +1968,9 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1816
1968
|
const agent = agents[agentType];
|
|
1817
1969
|
const isGlobal = options.global ?? false;
|
|
1818
1970
|
const cwd = options.cwd || process.cwd();
|
|
1819
|
-
const installMode = options.mode ?? "symlink";
|
|
1820
1971
|
if (isGlobal && agent.globalSkillsDir === void 0) return {
|
|
1821
1972
|
success: false,
|
|
1822
1973
|
path: "",
|
|
1823
|
-
mode: installMode,
|
|
1824
1974
|
error: `${agent.displayName} does not support global skill installation`
|
|
1825
1975
|
};
|
|
1826
1976
|
const skillName = sanitizeName(skill.installName);
|
|
@@ -1831,13 +1981,11 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1831
1981
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1832
1982
|
success: false,
|
|
1833
1983
|
path: agentDir,
|
|
1834
|
-
mode: installMode,
|
|
1835
1984
|
error: "Invalid skill name: potential path traversal detected"
|
|
1836
1985
|
};
|
|
1837
1986
|
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1838
1987
|
success: false,
|
|
1839
1988
|
path: agentDir,
|
|
1840
|
-
mode: installMode,
|
|
1841
1989
|
error: "Invalid skill name: potential path traversal detected"
|
|
1842
1990
|
};
|
|
1843
1991
|
async function writeSkillFiles(targetDir) {
|
|
@@ -1850,22 +1998,12 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1850
1998
|
}
|
|
1851
1999
|
}
|
|
1852
2000
|
try {
|
|
1853
|
-
if (installMode === "copy") {
|
|
1854
|
-
await cleanAndCreateDirectory(agentDir);
|
|
1855
|
-
await writeSkillFiles(agentDir);
|
|
1856
|
-
return {
|
|
1857
|
-
success: true,
|
|
1858
|
-
path: agentDir,
|
|
1859
|
-
mode: "copy"
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
2001
|
await cleanAndCreateDirectory(canonicalDir);
|
|
1863
2002
|
await writeSkillFiles(canonicalDir);
|
|
1864
2003
|
if (isGlobal && isUniversalAgent(agentType)) return {
|
|
1865
2004
|
success: true,
|
|
1866
2005
|
path: canonicalDir,
|
|
1867
|
-
canonicalPath: canonicalDir
|
|
1868
|
-
mode: "symlink"
|
|
2006
|
+
canonicalPath: canonicalDir
|
|
1869
2007
|
};
|
|
1870
2008
|
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
1871
2009
|
await cleanAndCreateDirectory(agentDir);
|
|
@@ -1874,21 +2012,18 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1874
2012
|
success: true,
|
|
1875
2013
|
path: agentDir,
|
|
1876
2014
|
canonicalPath: canonicalDir,
|
|
1877
|
-
mode: "symlink",
|
|
1878
2015
|
symlinkFailed: true
|
|
1879
2016
|
};
|
|
1880
2017
|
}
|
|
1881
2018
|
return {
|
|
1882
2019
|
success: true,
|
|
1883
2020
|
path: agentDir,
|
|
1884
|
-
canonicalPath: canonicalDir
|
|
1885
|
-
mode: "symlink"
|
|
2021
|
+
canonicalPath: canonicalDir
|
|
1886
2022
|
};
|
|
1887
2023
|
} catch (error) {
|
|
1888
2024
|
return {
|
|
1889
2025
|
success: false,
|
|
1890
2026
|
path: agentDir,
|
|
1891
|
-
mode: installMode,
|
|
1892
2027
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
1893
2028
|
};
|
|
1894
2029
|
}
|
|
@@ -1897,11 +2032,9 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1897
2032
|
const agent = agents[agentType];
|
|
1898
2033
|
const isGlobal = options.global ?? false;
|
|
1899
2034
|
const cwd = options.cwd || process.cwd();
|
|
1900
|
-
const installMode = options.mode ?? "symlink";
|
|
1901
2035
|
if (isGlobal && agent.globalSkillsDir === void 0) return {
|
|
1902
2036
|
success: false,
|
|
1903
2037
|
path: "",
|
|
1904
|
-
mode: installMode,
|
|
1905
2038
|
error: `${agent.displayName} does not support global skill installation`
|
|
1906
2039
|
};
|
|
1907
2040
|
const skillName = sanitizeName(skill.installName);
|
|
@@ -1912,13 +2045,11 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1912
2045
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1913
2046
|
success: false,
|
|
1914
2047
|
path: agentDir,
|
|
1915
|
-
mode: installMode,
|
|
1916
2048
|
error: "Invalid skill name: potential path traversal detected"
|
|
1917
2049
|
};
|
|
1918
2050
|
if (!isPathSafe(agentBase, agentDir)) return {
|
|
1919
2051
|
success: false,
|
|
1920
2052
|
path: agentDir,
|
|
1921
|
-
mode: installMode,
|
|
1922
2053
|
error: "Invalid skill name: potential path traversal detected"
|
|
1923
2054
|
};
|
|
1924
2055
|
async function writeSkillFiles(targetDir) {
|
|
@@ -1931,22 +2062,12 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1931
2062
|
}
|
|
1932
2063
|
}
|
|
1933
2064
|
try {
|
|
1934
|
-
if (installMode === "copy") {
|
|
1935
|
-
await cleanAndCreateDirectory(agentDir);
|
|
1936
|
-
await writeSkillFiles(agentDir);
|
|
1937
|
-
return {
|
|
1938
|
-
success: true,
|
|
1939
|
-
path: agentDir,
|
|
1940
|
-
mode: "copy"
|
|
1941
|
-
};
|
|
1942
|
-
}
|
|
1943
2065
|
await cleanAndCreateDirectory(canonicalDir);
|
|
1944
2066
|
await writeSkillFiles(canonicalDir);
|
|
1945
2067
|
if (isGlobal && isUniversalAgent(agentType)) return {
|
|
1946
2068
|
success: true,
|
|
1947
2069
|
path: canonicalDir,
|
|
1948
|
-
canonicalPath: canonicalDir
|
|
1949
|
-
mode: "symlink"
|
|
2070
|
+
canonicalPath: canonicalDir
|
|
1950
2071
|
};
|
|
1951
2072
|
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
1952
2073
|
await cleanAndCreateDirectory(agentDir);
|
|
@@ -1955,21 +2076,18 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1955
2076
|
success: true,
|
|
1956
2077
|
path: agentDir,
|
|
1957
2078
|
canonicalPath: canonicalDir,
|
|
1958
|
-
mode: "symlink",
|
|
1959
2079
|
symlinkFailed: true
|
|
1960
2080
|
};
|
|
1961
2081
|
}
|
|
1962
2082
|
return {
|
|
1963
2083
|
success: true,
|
|
1964
2084
|
path: agentDir,
|
|
1965
|
-
canonicalPath: canonicalDir
|
|
1966
|
-
mode: "symlink"
|
|
2085
|
+
canonicalPath: canonicalDir
|
|
1967
2086
|
};
|
|
1968
2087
|
} catch (error) {
|
|
1969
2088
|
return {
|
|
1970
2089
|
success: false,
|
|
1971
2090
|
path: agentDir,
|
|
1972
|
-
mode: installMode,
|
|
1973
2091
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
1974
2092
|
};
|
|
1975
2093
|
}
|
|
@@ -3270,23 +3388,18 @@ function splitAgentsByType(agentTypes) {
|
|
|
3270
3388
|
symlinked
|
|
3271
3389
|
};
|
|
3272
3390
|
}
|
|
3273
|
-
function buildAgentSummaryLines(targetAgents,
|
|
3391
|
+
function buildAgentSummaryLines(targetAgents, entry) {
|
|
3274
3392
|
const lines = [];
|
|
3275
3393
|
const { universal, symlinked } = splitAgentsByType(targetAgents);
|
|
3276
|
-
if (
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
} else lines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList(symlinked)}`);
|
|
3286
|
-
} else {
|
|
3287
|
-
const allNames = targetAgents.map((a) => agents[a].displayName);
|
|
3288
|
-
lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList(allNames)}`);
|
|
3289
|
-
}
|
|
3394
|
+
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList(universal)}`);
|
|
3395
|
+
if (symlinked.length > 0) if (entry) {
|
|
3396
|
+
const sanitized = sanitizeName(entry.skillName);
|
|
3397
|
+
for (const a of targetAgents) {
|
|
3398
|
+
if (isUniversalAgent(a)) continue;
|
|
3399
|
+
const entryPath = join(getAgentBaseDir(a, entry.scope === "global", entry.cwd), sanitized);
|
|
3400
|
+
lines.push(` ${import_picocolors.default.dim("symlink →")} ${import_picocolors.default.cyan(shortenPath$2(entryPath, entry.cwd))} ${import_picocolors.default.dim(`(${agents[a].displayName})`)}`);
|
|
3401
|
+
}
|
|
3402
|
+
} else lines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList(symlinked)}`);
|
|
3290
3403
|
return lines;
|
|
3291
3404
|
}
|
|
3292
3405
|
function ensureUniversalAgents(targetAgents) {
|
|
@@ -3347,7 +3460,7 @@ async function selectAgentsInteractive(options) {
|
|
|
3347
3460
|
const supportsGlobalFilter = (a) => !options.global || agents[a].globalSkillsDir;
|
|
3348
3461
|
return await searchMultiselect({
|
|
3349
3462
|
message: "Which agents do you want to install to?",
|
|
3350
|
-
items: buildAgentChoices(
|
|
3463
|
+
items: buildAgentChoices(getPickerVisibleAgents().filter(supportsGlobalFilter), options),
|
|
3351
3464
|
initialSelected: [],
|
|
3352
3465
|
required: true
|
|
3353
3466
|
});
|
|
@@ -3436,7 +3549,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3436
3549
|
M.info("Installing to all agents");
|
|
3437
3550
|
} else {
|
|
3438
3551
|
M.info("Select agents to install skills to");
|
|
3439
|
-
const selected = await promptForAgents("Which agents do you want to install to?", buildAgentChoices(
|
|
3552
|
+
const selected = await promptForAgents("Which agents do you want to install to?", buildAgentChoices(getPickerVisibleAgents(), { global: options.global }));
|
|
3440
3553
|
if (pD(selected)) {
|
|
3441
3554
|
xe("Installation cancelled");
|
|
3442
3555
|
process.exit(0);
|
|
@@ -3479,27 +3592,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3479
3592
|
}
|
|
3480
3593
|
installGlobally = scope;
|
|
3481
3594
|
}
|
|
3482
|
-
let installMode = options.copy ? "copy" : "symlink";
|
|
3483
|
-
const uniqueDirs = new Set(targetAgents.map((a) => agents[a].skillsDir));
|
|
3484
|
-
if (!options.copy && !options.yes && uniqueDirs.size > 1) {
|
|
3485
|
-
const modeChoice = await ve({
|
|
3486
|
-
message: "Installation method",
|
|
3487
|
-
options: [{
|
|
3488
|
-
value: "symlink",
|
|
3489
|
-
label: "Symlink (Recommended)",
|
|
3490
|
-
hint: "Single source of truth, easy updates"
|
|
3491
|
-
}, {
|
|
3492
|
-
value: "copy",
|
|
3493
|
-
label: "Copy to all agents",
|
|
3494
|
-
hint: "Independent copies for each agent"
|
|
3495
|
-
}]
|
|
3496
|
-
});
|
|
3497
|
-
if (pD(modeChoice)) {
|
|
3498
|
-
xe("Installation cancelled");
|
|
3499
|
-
process.exit(0);
|
|
3500
|
-
}
|
|
3501
|
-
installMode = modeChoice;
|
|
3502
|
-
} else if (uniqueDirs.size <= 1) installMode = "copy";
|
|
3503
3595
|
const cwd = process.cwd();
|
|
3504
3596
|
const summaryLines = [];
|
|
3505
3597
|
targetAgents.map((a) => agents[a].displayName);
|
|
@@ -3517,7 +3609,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3517
3609
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
3518
3610
|
const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
|
|
3519
3611
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
3520
|
-
summaryLines.push(...buildAgentSummaryLines(targetAgents,
|
|
3612
|
+
summaryLines.push(...buildAgentSummaryLines(targetAgents, {
|
|
3521
3613
|
skillName: skill.installName,
|
|
3522
3614
|
scope: installGlobally ? "global" : "project",
|
|
3523
3615
|
cwd
|
|
@@ -3541,10 +3633,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3541
3633
|
spinner.start("Installing skills...");
|
|
3542
3634
|
const results = [];
|
|
3543
3635
|
for (const skill of selectedSkills) for (const agent of targetAgents) {
|
|
3544
|
-
const result = await installWellKnownSkillForAgent(skill, agent, {
|
|
3545
|
-
global: installGlobally,
|
|
3546
|
-
mode: installMode
|
|
3547
|
-
});
|
|
3636
|
+
const result = await installWellKnownSkillForAgent(skill, agent, { global: installGlobally });
|
|
3548
3637
|
results.push({
|
|
3549
3638
|
skill: skill.installName,
|
|
3550
3639
|
agent: agents[agent].displayName,
|
|
@@ -3596,24 +3685,16 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3596
3685
|
bySkill.set(r.skill, skillResults);
|
|
3597
3686
|
}
|
|
3598
3687
|
const skillCount = bySkill.size;
|
|
3599
|
-
const symlinkFailures = successful.filter((r) => r.
|
|
3688
|
+
const symlinkFailures = successful.filter((r) => r.symlinkFailed);
|
|
3600
3689
|
const copiedAgents = symlinkFailures.map((r) => r.agent);
|
|
3601
3690
|
const resultLines = [];
|
|
3602
3691
|
for (const [skillName, skillResults] of bySkill) {
|
|
3603
3692
|
const firstResult = skillResults[0];
|
|
3604
|
-
if (firstResult.
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
}
|
|
3610
|
-
} else {
|
|
3611
|
-
if (firstResult.canonicalPath) {
|
|
3612
|
-
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
3613
|
-
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
3614
|
-
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
3615
|
-
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
3616
|
-
}
|
|
3693
|
+
if (firstResult.canonicalPath) {
|
|
3694
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
3695
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
3696
|
+
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
3697
|
+
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
3617
3698
|
}
|
|
3618
3699
|
const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
3619
3700
|
Me(resultLines.join("\n"), title);
|
|
@@ -3848,7 +3929,7 @@ async function runAdd(args, options = {}) {
|
|
|
3848
3929
|
spinner.stop(`${Object.keys(agents).length} agents`);
|
|
3849
3930
|
if (options.yes) targetAgents = installedAgents.length > 0 ? ensureUniversalAgents(installedAgents) : validAgents;
|
|
3850
3931
|
else if (installedAgents.length === 0) {
|
|
3851
|
-
const selected = await promptForAgents("Which IDEs do you want to install this plugin to?", buildAgentChoices(
|
|
3932
|
+
const selected = await promptForAgents("Which IDEs do you want to install this plugin to?", buildAgentChoices(getPickerVisibleAgents(), {
|
|
3852
3933
|
global: options.global,
|
|
3853
3934
|
forPlugin: true
|
|
3854
3935
|
}));
|
|
@@ -4257,7 +4338,7 @@ async function runAdd(args, options = {}) {
|
|
|
4257
4338
|
M.info("Installing to all agents");
|
|
4258
4339
|
} else {
|
|
4259
4340
|
M.info("Select agents to install skills to");
|
|
4260
|
-
const selected = await promptForAgents("Which agents do you want to install to?", buildAgentChoices(
|
|
4341
|
+
const selected = await promptForAgents("Which agents do you want to install to?", buildAgentChoices(getPickerVisibleAgents(), { global: options.global }));
|
|
4261
4342
|
if (pD(selected)) {
|
|
4262
4343
|
xe("Installation cancelled");
|
|
4263
4344
|
await cleanup(tempDir);
|
|
@@ -4303,28 +4384,6 @@ async function runAdd(args, options = {}) {
|
|
|
4303
4384
|
}
|
|
4304
4385
|
installGlobally = scope;
|
|
4305
4386
|
}
|
|
4306
|
-
let installMode = options.copy ? "copy" : "symlink";
|
|
4307
|
-
const uniqueDirs = new Set(targetAgents.map((a) => agents[a].skillsDir));
|
|
4308
|
-
if (!options.copy && !options.yes && uniqueDirs.size > 1) {
|
|
4309
|
-
const modeChoice = await ve({
|
|
4310
|
-
message: "Installation method",
|
|
4311
|
-
options: [{
|
|
4312
|
-
value: "symlink",
|
|
4313
|
-
label: "Symlink (Recommended)",
|
|
4314
|
-
hint: "Single source of truth, easy updates"
|
|
4315
|
-
}, {
|
|
4316
|
-
value: "copy",
|
|
4317
|
-
label: "Copy to all agents",
|
|
4318
|
-
hint: "Independent copies for each agent"
|
|
4319
|
-
}]
|
|
4320
|
-
});
|
|
4321
|
-
if (pD(modeChoice)) {
|
|
4322
|
-
xe("Installation cancelled");
|
|
4323
|
-
await cleanup(tempDir);
|
|
4324
|
-
process.exit(0);
|
|
4325
|
-
}
|
|
4326
|
-
installMode = modeChoice;
|
|
4327
|
-
}
|
|
4328
4387
|
const cwd = process.cwd();
|
|
4329
4388
|
const _summaryPortalRef = parsed.type === "portal" || parsed.type === "public" || parsed.type === "team" ? parsed.portalRef ?? null : null;
|
|
4330
4389
|
const _summaryIsTeam = _summaryPortalRef?.startsWith("@teams/") ?? false;
|
|
@@ -4356,7 +4415,7 @@ async function runAdd(args, options = {}) {
|
|
|
4356
4415
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
4357
4416
|
const shortCanonical = shortenPath$2(join(_summaryStoreRoot, "skills", `${_summaryScopeNs}__${sanitizeName(skill.name)}`), cwd);
|
|
4358
4417
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
4359
|
-
summaryLines.push(...buildAgentSummaryLines(targetAgents,
|
|
4418
|
+
summaryLines.push(...buildAgentSummaryLines(targetAgents, {
|
|
4360
4419
|
skillName: skill.name,
|
|
4361
4420
|
scope: _summaryScope,
|
|
4362
4421
|
cwd
|
|
@@ -4414,17 +4473,13 @@ async function runAdd(args, options = {}) {
|
|
|
4414
4473
|
result = await installBlobSkillForAgent({
|
|
4415
4474
|
installName: blobSkill.name,
|
|
4416
4475
|
files: blobSkill.files
|
|
4417
|
-
}, agent, {
|
|
4418
|
-
global: installGlobally,
|
|
4419
|
-
mode: installMode
|
|
4420
|
-
});
|
|
4476
|
+
}, agent, { global: installGlobally });
|
|
4421
4477
|
} else {
|
|
4422
4478
|
const skillRef = _installPortalRef ? `${_installPortalRef}/${skill.name ?? ""}` : skill.name ?? "unknown";
|
|
4423
4479
|
result = await installSkillForAgent(skill, agent, {
|
|
4424
4480
|
scope: installGlobally ? "global" : "project",
|
|
4425
4481
|
ref: skillRef,
|
|
4426
4482
|
scopeNamespace: _installScopeNs,
|
|
4427
|
-
mode: installMode,
|
|
4428
4483
|
forceOverwrite: options.force || options.yes,
|
|
4429
4484
|
onConflictPrompt: (conflicts) => promptForeignOverwrite(conflicts, {
|
|
4430
4485
|
yes: options.yes,
|
|
@@ -4537,7 +4592,7 @@ async function runAdd(args, options = {}) {
|
|
|
4537
4592
|
const hash = await computeSkillFolderHash(join(repoRoot, dirname(rawSkillPath)));
|
|
4538
4593
|
if (hash) skillFolderHash = hash;
|
|
4539
4594
|
}
|
|
4540
|
-
const lockSkillPath = portalDecomp && rawSkillPath && rawSkillPath.startsWith(portalDecomp.pathPrefix) ? rawSkillPath.slice(portalDecomp.pathPrefix.length) : rawSkillPath;
|
|
4595
|
+
const lockSkillPath = parsed.portalRef ? portalRefToSkillPath(parsed.portalRef) ?? rawSkillPath : portalDecomp && rawSkillPath && rawSkillPath.startsWith(portalDecomp.pathPrefix) ? rawSkillPath.slice(portalDecomp.pathPrefix.length) : rawSkillPath;
|
|
4541
4596
|
await addSkillToLock(skill.name, {
|
|
4542
4597
|
source: lockSource ?? normalizedSource ?? "",
|
|
4543
4598
|
sourceType: parsed.type,
|
|
@@ -4557,7 +4612,7 @@ async function runAdd(args, options = {}) {
|
|
|
4557
4612
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
4558
4613
|
const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
|
|
4559
4614
|
const rawSkillPath = skillFiles[skill.name];
|
|
4560
|
-
const lockSkillPath = portalDecomp && rawSkillPath && rawSkillPath.startsWith(portalDecomp.pathPrefix) ? rawSkillPath.slice(portalDecomp.pathPrefix.length) : rawSkillPath;
|
|
4615
|
+
const lockSkillPath = parsed.portalRef ? portalRefToSkillPath(parsed.portalRef) ?? rawSkillPath : portalDecomp && rawSkillPath && rawSkillPath.startsWith(portalDecomp.pathPrefix) ? rawSkillPath.slice(portalDecomp.pathPrefix.length) : rawSkillPath;
|
|
4561
4616
|
await addSkillToLocalLock(skill.name, {
|
|
4562
4617
|
source: lockSource || parsed.url,
|
|
4563
4618
|
ref: parsed.ref,
|
|
@@ -4583,26 +4638,18 @@ async function runAdd(args, options = {}) {
|
|
|
4583
4638
|
} else ungroupedResults.push(r);
|
|
4584
4639
|
}
|
|
4585
4640
|
const skillCount = bySkill.size;
|
|
4586
|
-
const symlinkFailures = successful.filter((r) => r.
|
|
4641
|
+
const symlinkFailures = successful.filter((r) => r.symlinkFailed);
|
|
4587
4642
|
const copiedAgents = symlinkFailures.map((r) => r.agent);
|
|
4588
4643
|
const resultLines = [];
|
|
4589
4644
|
const printSkillResults = (entries) => {
|
|
4590
4645
|
for (const entry of entries) {
|
|
4591
4646
|
const skillResults = bySkill.get(entry.skill) || [];
|
|
4592
4647
|
const firstResult = skillResults[0];
|
|
4593
|
-
if (firstResult.
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
}
|
|
4599
|
-
} else {
|
|
4600
|
-
if (firstResult.canonicalPath) {
|
|
4601
|
-
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
4602
|
-
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
4603
|
-
} else resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill}`);
|
|
4604
|
-
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
4605
|
-
}
|
|
4648
|
+
if (firstResult.canonicalPath) {
|
|
4649
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
4650
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
4651
|
+
} else resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill}`);
|
|
4652
|
+
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
4606
4653
|
}
|
|
4607
4654
|
};
|
|
4608
4655
|
const sortedResultGroups = Object.keys(groupedResults).sort();
|
|
@@ -4744,8 +4791,7 @@ function parseAddOptions(args) {
|
|
|
4744
4791
|
else if (arg === "--team") {
|
|
4745
4792
|
i++;
|
|
4746
4793
|
options.team = args[i];
|
|
4747
|
-
} else if (arg === "--
|
|
4748
|
-
else if (arg === "--dangerously-accept-openclaw-risks") options.dangerouslyAcceptOpenclawRisks = true;
|
|
4794
|
+
} else if (arg === "--dangerously-accept-openclaw-risks") options.dangerouslyAcceptOpenclawRisks = true;
|
|
4749
4795
|
else if (arg && !arg.startsWith("-")) source.push(arg);
|
|
4750
4796
|
}
|
|
4751
4797
|
return {
|
|
@@ -5102,7 +5148,7 @@ async function runSync(args, options = {}) {
|
|
|
5102
5148
|
} else {
|
|
5103
5149
|
const selected = await searchMultiselect({
|
|
5104
5150
|
message: "Which agents do you want to install to?",
|
|
5105
|
-
items:
|
|
5151
|
+
items: getPickerVisibleAgents().map((a) => {
|
|
5106
5152
|
const cfg = agents[a];
|
|
5107
5153
|
const baseHint = cfg.skillsDir;
|
|
5108
5154
|
const hint = isUniversalAgent(a) ? `${baseHint}, shared` : baseHint;
|
|
@@ -5125,7 +5171,7 @@ async function runSync(args, options = {}) {
|
|
|
5125
5171
|
targetAgents = [...installedAgents];
|
|
5126
5172
|
for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
|
|
5127
5173
|
} else {
|
|
5128
|
-
const allAgents =
|
|
5174
|
+
const allAgents = getPickerVisibleAgents().filter((a) => installedAgents.includes(a));
|
|
5129
5175
|
const selected = await searchMultiselect({
|
|
5130
5176
|
message: "Which agents do you want to install to?",
|
|
5131
5177
|
items: allAgents.map((a) => {
|
|
@@ -5168,8 +5214,7 @@ async function runSync(args, options = {}) {
|
|
|
5168
5214
|
for (const skill of toInstall) for (const agent of targetAgents) {
|
|
5169
5215
|
const result = await installSkillForAgent(skill, agent, {
|
|
5170
5216
|
global: false,
|
|
5171
|
-
cwd
|
|
5172
|
-
mode: "symlink"
|
|
5217
|
+
cwd
|
|
5173
5218
|
});
|
|
5174
5219
|
results.push({
|
|
5175
5220
|
skill: skill.name,
|
|
@@ -6303,7 +6348,6 @@ ${BOLD}Add Options:${RESET}
|
|
|
6303
6348
|
-y, --yes Skip confirmation prompts
|
|
6304
6349
|
--team <scope> Install every skill owned by the team
|
|
6305
6350
|
(no positional source; mutually exclusive with <source>)
|
|
6306
|
-
--copy Copy files instead of symlinking to agent directories
|
|
6307
6351
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
6308
6352
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
6309
6353
|
|
|
@@ -6464,12 +6508,14 @@ async function runUpdate(args = []) {
|
|
|
6464
6508
|
name: pl.name,
|
|
6465
6509
|
ref: pl.ref,
|
|
6466
6510
|
canonical: pl.canonical,
|
|
6511
|
+
agents: pl.agents,
|
|
6467
6512
|
hasProjectEntry: hasProjectEntryFor(pl.canonical, cwd)
|
|
6468
6513
|
})), ...inventory.skills.filter((s) => Boolean(s.ref)).map((s) => ({
|
|
6469
6514
|
kind: "skill",
|
|
6470
6515
|
name: s.name,
|
|
6471
6516
|
ref: s.ref,
|
|
6472
6517
|
canonical: s.canonical,
|
|
6518
|
+
agents: s.agents,
|
|
6473
6519
|
hasProjectEntry: hasProjectEntryFor(s.canonical, cwd)
|
|
6474
6520
|
}))];
|
|
6475
6521
|
if (options.names && options.names.length > 0) {
|
|
@@ -6511,12 +6557,23 @@ async function runUpdate(args = []) {
|
|
|
6511
6557
|
for (const t of targets) {
|
|
6512
6558
|
const safeName = sanitizeMetadata(t.name);
|
|
6513
6559
|
if (!options.json) console.log(`${TEXT}Updating ${safeName}...${RESET} ${DIM}(${t.ref})${RESET}`);
|
|
6560
|
+
if (t.agents.length === 0) {
|
|
6561
|
+
fail++;
|
|
6562
|
+
if (!options.json) console.log(` ${DIM}✗ skipped ${safeName} — no IDE entries point at this canonical${RESET}`);
|
|
6563
|
+
results.push({
|
|
6564
|
+
name: t.name,
|
|
6565
|
+
kind: t.kind,
|
|
6566
|
+
ok: false
|
|
6567
|
+
});
|
|
6568
|
+
continue;
|
|
6569
|
+
}
|
|
6514
6570
|
const passthrough = [
|
|
6515
6571
|
"add",
|
|
6516
6572
|
t.ref,
|
|
6517
6573
|
"-y"
|
|
6518
6574
|
];
|
|
6519
6575
|
if (!t.hasProjectEntry) passthrough.push("-g");
|
|
6576
|
+
passthrough.push("-a", ...t.agents);
|
|
6520
6577
|
const ok = spawnSync(process.execPath, [cliEntry, ...passthrough], {
|
|
6521
6578
|
stdio: options.json ? [
|
|
6522
6579
|
"ignore",
|