shoplazza-ai-dev-cli 0.1.1 → 0.1.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/_chunks/libs/@clack/core.mjs +1 -1
- package/dist/_chunks/libs/@clack/prompts.mjs +1 -1
- package/dist/_chunks/libs/@kwsites/file-exists.mjs +1 -1
- package/dist/_chunks/libs/core-util-is.mjs +1 -1
- package/dist/_chunks/libs/inherits.mjs +1 -1
- package/dist/_chunks/libs/jszip.mjs +1 -1
- package/dist/_chunks/libs/simple-git.mjs +1 -1
- package/dist/_chunks/rolldown-runtime.mjs +1 -10
- package/dist/cli.mjs +1207 -1314
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { r as __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
3
3
|
import { l as pD, u as require_picocolors } from "./_chunks/libs/@clack/core.mjs";
|
|
4
4
|
import { a as Y, c as ve, i as Se, l as xe, n as M, o as be, r as Me, s as fe, t as Ie, u as ye } from "./_chunks/libs/@clack/prompts.mjs";
|
|
5
5
|
import "./_chunks/libs/@kwsites/file-exists.mjs";
|
|
@@ -11,12 +11,13 @@ import "./_chunks/libs/core-util-is.mjs";
|
|
|
11
11
|
import "./_chunks/libs/inherits.mjs";
|
|
12
12
|
import "./_chunks/libs/immediate.mjs";
|
|
13
13
|
import { exec, execSync, spawnSync } from "child_process";
|
|
14
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
14
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "fs";
|
|
15
15
|
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
16
|
-
import { homedir, platform, tmpdir } from "os";
|
|
17
16
|
import { URL as URL$1, fileURLToPath } from "url";
|
|
18
17
|
import { stripVTControlCharacters } from "node:util";
|
|
18
|
+
import { homedir, hostname, platform, tmpdir } from "os";
|
|
19
19
|
import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
20
|
+
import { createServer } from "http";
|
|
20
21
|
import { gunzipSync } from "node:zlib";
|
|
21
22
|
import { mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
|
|
22
23
|
import { dirname as dirname$1, normalize as normalize$1, resolve as resolve$1, sep as sep$1 } from "node:path";
|
|
@@ -24,7 +25,6 @@ import { parse } from "yaml";
|
|
|
24
25
|
import * as readline from "readline";
|
|
25
26
|
import { Writable } from "stream";
|
|
26
27
|
import { createHash } from "crypto";
|
|
27
|
-
import { createServer } from "http";
|
|
28
28
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
29
29
|
function getOwnerRepo(parsed) {
|
|
30
30
|
if (parsed.type === "local") return null;
|
|
@@ -318,7 +318,7 @@ const agents = {
|
|
|
318
318
|
cursor: {
|
|
319
319
|
name: "cursor",
|
|
320
320
|
displayName: "Cursor",
|
|
321
|
-
skillsDir: ".
|
|
321
|
+
skillsDir: ".cursor/skills",
|
|
322
322
|
globalSkillsDir: join(home, ".cursor/skills"),
|
|
323
323
|
detectInstalled: async () => {
|
|
324
324
|
return existsSync(join(home, ".cursor"));
|
|
@@ -340,8 +340,8 @@ function isUniversalAgent(type) {
|
|
|
340
340
|
function getStoreRoot(_scope, _cwd) {
|
|
341
341
|
return join(home, FORGE_STORE_DIRNAME, "store");
|
|
342
342
|
}
|
|
343
|
-
function getAgentEntryRoot(
|
|
344
|
-
if (scope === "global") return
|
|
343
|
+
function getAgentEntryRoot(_agent, scope, cwd) {
|
|
344
|
+
if (scope === "global") return home;
|
|
345
345
|
return cwd ?? process.cwd();
|
|
346
346
|
}
|
|
347
347
|
function getInstallTargets(agent, component, scope, cwd) {
|
|
@@ -628,35 +628,93 @@ function getForgeHookScript(portalUrl) {
|
|
|
628
628
|
return `#!/usr/bin/env node
|
|
629
629
|
// ${FORGE_HOOK_TAG}
|
|
630
630
|
// Auto-generated by ai-dev-cli. Do not edit; run \`ai-dev-cli add\` to refresh.
|
|
631
|
-
// Reports {type:'skill_invoked', asset_ref, install_mode} when
|
|
632
|
-
//
|
|
631
|
+
// Reports {type:'skill_invoked', asset_ref, install_mode} when Claude Code
|
|
632
|
+
// loads a forge-installed skill — via slash command (UserPromptExpansion)
|
|
633
|
+
// or via the Skill tool (PreToolUse). PostToolUse(Skill) is unreliable on
|
|
634
|
+
// Claude Code 2.1.x; see anthropics/claude-code#43630.
|
|
633
635
|
// Anonymous: /events accepts unauthenticated posts.
|
|
634
636
|
import fs from 'node:fs';
|
|
637
|
+
import os from 'node:os';
|
|
635
638
|
import path from 'node:path';
|
|
636
639
|
|
|
637
640
|
const PORTAL = ${JSON.stringify(portal)};
|
|
638
641
|
|
|
639
|
-
|
|
642
|
+
// Safety net: never let a stuck fetch block the IDE for more than 5 seconds.
|
|
643
|
+
// The fetch itself uses AbortSignal.timeout(2000); this catches edge cases
|
|
644
|
+
// like a stalled DNS lookup before the abort fires.
|
|
645
|
+
const exitTimer = setTimeout(() => process.exit(0), 5000);
|
|
646
|
+
exitTimer.unref?.();
|
|
647
|
+
|
|
648
|
+
function readManifest(dir) {
|
|
649
|
+
try {
|
|
650
|
+
const raw = fs.readFileSync(path.join(dir, '.forge-source.json'), 'utf8');
|
|
651
|
+
return JSON.parse(raw);
|
|
652
|
+
} catch {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function manifestFromSkillName(skillId, cwd) {
|
|
658
|
+
if (!skillId || typeof skillId !== 'string') return null;
|
|
659
|
+
// \`plugin:skill-name\` → drop the plugin prefix; forge installs flatten into
|
|
660
|
+
// .claude/skills/<name>/ regardless of plugin namespace (see agents.ts).
|
|
661
|
+
const name = skillId.includes(':') ? skillId.split(':').pop() : skillId;
|
|
662
|
+
if (!name) return null;
|
|
663
|
+
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || path.join(os.homedir(), '.claude');
|
|
664
|
+
// 1) IDE entry under cwd (project-scope install).
|
|
665
|
+
// 2) IDE entry under CLAUDE_CONFIG_DIR (global-scope install).
|
|
666
|
+
const ideCandidates = [];
|
|
667
|
+
if (cwd) ideCandidates.push(path.join(cwd, '.claude', 'skills', name));
|
|
668
|
+
ideCandidates.push(path.join(claudeHome, 'skills', name));
|
|
669
|
+
for (const dir of ideCandidates) {
|
|
670
|
+
const m = readManifest(dir);
|
|
671
|
+
if (m) return m;
|
|
672
|
+
}
|
|
673
|
+
// 3) Forge canonical store. Truth source — regardless of where the IDE
|
|
674
|
+
// entry symlink lives or what the agent's cwd is, the manifest always
|
|
675
|
+
// exists under ~/.ai-dev-cli/store. Skill dirs are named \`<scope>__<name>\`;
|
|
676
|
+
// plugin-internal skills live one level deeper.
|
|
677
|
+
const storeRoot = path.join(os.homedir(), '.ai-dev-cli', 'store');
|
|
678
|
+
for (const sub of ['skills', 'plugins']) {
|
|
679
|
+
try {
|
|
680
|
+
const root = path.join(storeRoot, sub);
|
|
681
|
+
for (const entry of fs.readdirSync(root)) {
|
|
682
|
+
if (sub === 'skills') {
|
|
683
|
+
if (!entry.endsWith('__' + name)) continue;
|
|
684
|
+
const m = readManifest(path.join(root, entry));
|
|
685
|
+
if (m && m.name === name) return m;
|
|
686
|
+
} else {
|
|
687
|
+
const m = readManifest(path.join(root, entry, 'skills', name));
|
|
688
|
+
if (m && m.name === name) return m;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
/* store dir missing — fall through */
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
640
697
|
|
|
641
698
|
let buf = '';
|
|
642
699
|
process.stdin.on('data', (c) => (buf += c));
|
|
643
700
|
process.stdin.on('end', () => {
|
|
701
|
+
let skillId = null;
|
|
644
702
|
try {
|
|
645
703
|
const ev = JSON.parse(buf);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
704
|
+
if (ev.hook_event_name === 'UserPromptExpansion') {
|
|
705
|
+
// Slash command path. \`command_name\` is the skill identifier as the
|
|
706
|
+
// user typed it (without the leading slash), e.g. "ai-coding-llm-wiki".
|
|
707
|
+
if (ev.expansion_type !== 'slash_command') process.exit(0);
|
|
708
|
+
skillId = ev.command_name;
|
|
709
|
+
} else if (ev.hook_event_name === 'PreToolUse' && ev.tool_name === 'Skill') {
|
|
710
|
+
skillId = ev.tool_input?.skill ?? ev.tool_input?.name;
|
|
711
|
+
} else {
|
|
712
|
+
process.exit(0);
|
|
713
|
+
}
|
|
714
|
+
const manifest = manifestFromSkillName(skillId, ev.cwd || process.cwd());
|
|
715
|
+
if (!manifest?.ref) {
|
|
716
|
+
process.exit(0);
|
|
658
717
|
}
|
|
659
|
-
if (!manifest?.ref) return;
|
|
660
718
|
|
|
661
719
|
fetch(PORTAL + '/events', {
|
|
662
720
|
method: 'POST',
|
|
@@ -670,9 +728,11 @@ process.stdin.on('end', () => {
|
|
|
670
728
|
},
|
|
671
729
|
}),
|
|
672
730
|
signal: AbortSignal.timeout(2000),
|
|
673
|
-
})
|
|
731
|
+
})
|
|
732
|
+
.catch(() => {})
|
|
733
|
+
.finally(() => process.exit(0));
|
|
674
734
|
} catch {
|
|
675
|
-
|
|
735
|
+
process.exit(0);
|
|
676
736
|
}
|
|
677
737
|
});
|
|
678
738
|
`;
|
|
@@ -710,6 +770,27 @@ async function writeHookScript(scriptPath, portalUrl) {
|
|
|
710
770
|
await chmod(scriptPath, 493);
|
|
711
771
|
} catch {}
|
|
712
772
|
}
|
|
773
|
+
const FORGE_LEGACY_LOCATIONS = [
|
|
774
|
+
{
|
|
775
|
+
event: "PostToolUse",
|
|
776
|
+
matcher: "Read"
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
event: "PostToolUse",
|
|
780
|
+
matcher: "Read|Skill"
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
event: "PostToolUse",
|
|
784
|
+
matcher: "Skill"
|
|
785
|
+
}
|
|
786
|
+
];
|
|
787
|
+
const FORGE_CANONICAL_LOCATIONS = [{
|
|
788
|
+
event: "UserPromptExpansion",
|
|
789
|
+
matcher: void 0
|
|
790
|
+
}, {
|
|
791
|
+
event: "PreToolUse",
|
|
792
|
+
matcher: "Skill"
|
|
793
|
+
}];
|
|
713
794
|
async function upsertClaudeCodeSettings(settingsPath, scriptPath) {
|
|
714
795
|
const command = `node ${shellEscape(scriptPath)}`;
|
|
715
796
|
let raw = "";
|
|
@@ -725,26 +806,38 @@ async function upsertClaudeCodeSettings(settingsPath, scriptPath) {
|
|
|
725
806
|
throw new Error(`Refusing to overwrite invalid JSON at ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
726
807
|
}
|
|
727
808
|
if (!settings.hooks || typeof settings.hooks !== "object") settings.hooks = {};
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
809
|
+
const hooks = settings.hooks;
|
|
810
|
+
const isOurEntry = (h) => !!(h && typeof h.command === "string" && h.command.includes(HOOK_SCRIPT_BASENAME));
|
|
811
|
+
const owned = new Set([...FORGE_LEGACY_LOCATIONS, ...FORGE_CANONICAL_LOCATIONS].map((l) => `${l.event}::${l.matcher ?? ""}`));
|
|
812
|
+
for (const eventName of Object.keys(hooks)) {
|
|
813
|
+
const matcherList = hooks[eventName];
|
|
814
|
+
if (!Array.isArray(matcherList)) continue;
|
|
815
|
+
for (let i = matcherList.length - 1; i >= 0; i--) {
|
|
816
|
+
const m = matcherList[i];
|
|
817
|
+
if (!m || !Array.isArray(m.hooks)) continue;
|
|
818
|
+
m.hooks = m.hooks.filter((h) => !isOurEntry(h));
|
|
819
|
+
const key = `${eventName}::${m.matcher ?? ""}`;
|
|
820
|
+
if (m.hooks.length === 0 && owned.has(key)) matcherList.splice(i, 1);
|
|
821
|
+
}
|
|
822
|
+
if (matcherList.length === 0) delete hooks[eventName];
|
|
823
|
+
}
|
|
824
|
+
for (const loc of FORGE_CANONICAL_LOCATIONS) {
|
|
825
|
+
if (!Array.isArray(hooks[loc.event])) hooks[loc.event] = [];
|
|
826
|
+
const matcherList = hooks[loc.event];
|
|
827
|
+
let target = matcherList.find((m) => m && (m.matcher ?? void 0) === loc.matcher);
|
|
828
|
+
if (!target) {
|
|
829
|
+
target = loc.matcher === void 0 ? { hooks: [] } : {
|
|
830
|
+
matcher: loc.matcher,
|
|
831
|
+
hooks: []
|
|
832
|
+
};
|
|
833
|
+
matcherList.push(target);
|
|
834
|
+
}
|
|
835
|
+
if (!Array.isArray(target.hooks)) target.hooks = [];
|
|
836
|
+
target.hooks.push({
|
|
837
|
+
type: "command",
|
|
838
|
+
command
|
|
839
|
+
});
|
|
737
840
|
}
|
|
738
|
-
if (!Array.isArray(readMatcher.hooks)) readMatcher.hooks = [];
|
|
739
|
-
const ourEntry = readMatcher.hooks.find((h) => typeof h?.command === "string" && h.command.includes(HOOK_SCRIPT_BASENAME));
|
|
740
|
-
if (ourEntry) {
|
|
741
|
-
if (ourEntry.command === command && ourEntry.type === "command") return;
|
|
742
|
-
ourEntry.type = "command";
|
|
743
|
-
ourEntry.command = command;
|
|
744
|
-
} else readMatcher.hooks.push({
|
|
745
|
-
type: "command",
|
|
746
|
-
command
|
|
747
|
-
});
|
|
748
841
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
749
842
|
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
750
843
|
}
|
|
@@ -752,18 +845,273 @@ function shellEscape(p) {
|
|
|
752
845
|
if (!/[\s"'$`\\]/.test(p)) return p;
|
|
753
846
|
return `'${p.replace(/'/g, `'\\''`)}'`;
|
|
754
847
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
848
|
+
function getRandomPort() {
|
|
849
|
+
return new Promise((resolve, reject) => {
|
|
850
|
+
const server = createServer();
|
|
851
|
+
server.listen(0, "127.0.0.1", () => {
|
|
852
|
+
const address = server.address();
|
|
853
|
+
if (address && typeof address !== "string" && "port" in address) {
|
|
854
|
+
const port = address.port;
|
|
855
|
+
server.close(() => resolve(port));
|
|
856
|
+
} else server.close(() => reject(/* @__PURE__ */ new Error("Failed to get port")));
|
|
857
|
+
});
|
|
858
|
+
server.on("error", reject);
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
const PORTAL_THEME_CSS = `
|
|
862
|
+
:root {
|
|
863
|
+
--bg: #FDFCF8;
|
|
864
|
+
--ink: #201F1D;
|
|
865
|
+
--text: #37352F;
|
|
866
|
+
--muted: #827E7A;
|
|
867
|
+
--line: #EDECE8;
|
|
868
|
+
--success: #0F6E37;
|
|
869
|
+
--success-soft: #EDF7F1;
|
|
870
|
+
--danger: #E5484D;
|
|
871
|
+
--danger-soft: #FDF1EF;
|
|
872
|
+
--serif: "Newsreader", "Source Serif Pro", Georgia, serif;
|
|
873
|
+
--sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "Segoe UI", sans-serif;
|
|
874
|
+
--shadow-sm: 0 1px 2px rgba(32, 31, 29, 0.06);
|
|
875
|
+
}
|
|
876
|
+
@media (prefers-color-scheme: dark) {
|
|
877
|
+
:root {
|
|
878
|
+
--bg: #27251F;
|
|
879
|
+
--ink: #EDEAE5;
|
|
880
|
+
--text: #D4CFC8;
|
|
881
|
+
--muted: #918D89;
|
|
882
|
+
--line: #35332F;
|
|
883
|
+
--success: #3FC67A;
|
|
884
|
+
--success-soft: #062A14;
|
|
885
|
+
--danger: #E5484D;
|
|
886
|
+
--danger-soft: #2B1212;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
* { box-sizing: border-box; }
|
|
890
|
+
body {
|
|
891
|
+
margin: 0;
|
|
892
|
+
min-height: 100vh;
|
|
893
|
+
background: var(--bg);
|
|
894
|
+
color: var(--text);
|
|
895
|
+
font-family: var(--sans);
|
|
896
|
+
font-size: 15px;
|
|
897
|
+
line-height: 1.65;
|
|
898
|
+
display: flex;
|
|
899
|
+
align-items: center;
|
|
900
|
+
justify-content: center;
|
|
901
|
+
}
|
|
902
|
+
.card {
|
|
903
|
+
max-width: 480px;
|
|
904
|
+
margin: 0 24px;
|
|
905
|
+
padding: 40px 32px;
|
|
906
|
+
text-align: center;
|
|
907
|
+
}
|
|
908
|
+
.icon {
|
|
909
|
+
width: 56px;
|
|
910
|
+
height: 56px;
|
|
911
|
+
margin: 0 auto 24px;
|
|
912
|
+
border-radius: 50%;
|
|
913
|
+
display: flex;
|
|
914
|
+
align-items: center;
|
|
915
|
+
justify-content: center;
|
|
916
|
+
}
|
|
917
|
+
.icon.success { background: var(--success-soft); }
|
|
918
|
+
.icon.error { background: var(--danger-soft); }
|
|
919
|
+
.icon svg { display: block; }
|
|
920
|
+
h1 {
|
|
921
|
+
font-family: var(--serif);
|
|
922
|
+
font-size: 28px;
|
|
923
|
+
font-weight: 500;
|
|
924
|
+
letter-spacing: -0.01em;
|
|
925
|
+
color: var(--ink);
|
|
926
|
+
margin: 0 0 12px;
|
|
927
|
+
}
|
|
928
|
+
p { color: var(--muted); margin: 0 0 8px; }
|
|
929
|
+
p.detail { color: var(--text); }
|
|
930
|
+
`;
|
|
931
|
+
function renderCallbackPage(opts) {
|
|
932
|
+
return `<!DOCTYPE html>
|
|
933
|
+
<html>
|
|
934
|
+
<head>
|
|
935
|
+
<meta charset="utf-8">
|
|
936
|
+
<title>${opts.title}</title>
|
|
937
|
+
<style>${PORTAL_THEME_CSS}</style>
|
|
938
|
+
</head>
|
|
939
|
+
<body>
|
|
940
|
+
<div class="card">
|
|
941
|
+
<div class="icon ${opts.variant}">${opts.iconSvg}</div>
|
|
942
|
+
<h1>${opts.heading}</h1>
|
|
943
|
+
${opts.body}
|
|
944
|
+
</div>
|
|
945
|
+
</body>
|
|
946
|
+
</html>`;
|
|
947
|
+
}
|
|
948
|
+
function getSuccessHtml() {
|
|
949
|
+
return renderCallbackPage({
|
|
950
|
+
title: "Login successful",
|
|
951
|
+
heading: "Login successful",
|
|
952
|
+
body: "<p>You can close this window and return to the CLI.</p>",
|
|
953
|
+
variant: "success",
|
|
954
|
+
iconSvg: `
|
|
955
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
956
|
+
stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"
|
|
957
|
+
style="color: var(--success);">
|
|
958
|
+
<polyline points="4 12 10 18 20 6"></polyline>
|
|
959
|
+
</svg>`
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
function escapeHtml(input) {
|
|
963
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
964
|
+
}
|
|
965
|
+
function getErrorHtml(error) {
|
|
966
|
+
return renderCallbackPage({
|
|
967
|
+
title: "Login failed",
|
|
968
|
+
heading: "Login failed",
|
|
969
|
+
body: `<p class="detail">${escapeHtml(error)}</p><p>Please try again.</p>`,
|
|
970
|
+
variant: "error",
|
|
971
|
+
iconSvg: `
|
|
972
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
973
|
+
stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"
|
|
974
|
+
style="color: var(--danger);">
|
|
975
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
976
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
977
|
+
</svg>`
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
let callbackResolve = null;
|
|
981
|
+
async function runLogin(options = {}) {
|
|
982
|
+
const cfg = loadConfig();
|
|
983
|
+
if (options.logout) {
|
|
984
|
+
if (cfg.token) {
|
|
985
|
+
saveConfig({
|
|
986
|
+
token: void 0,
|
|
987
|
+
token_name: void 0
|
|
988
|
+
});
|
|
989
|
+
M.success("Logged out successfully.");
|
|
990
|
+
M.message(import_picocolors.default.dim("Your token has been removed from the config."));
|
|
991
|
+
} else M.message("You are not logged in.");
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (cfg.token && !options.force) {
|
|
995
|
+
M.message("You are already logged in.");
|
|
996
|
+
M.info(import_picocolors.default.dim(`Token: ${cfg.token_name || "default"}`));
|
|
997
|
+
const shouldReauth = await ye({
|
|
998
|
+
message: "Re-authenticate?",
|
|
999
|
+
initialValue: false
|
|
1000
|
+
});
|
|
1001
|
+
if (isCancelled$1(shouldReauth)) {
|
|
1002
|
+
Se(import_picocolors.default.dim("Cancelled"));
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (!shouldReauth) return;
|
|
1006
|
+
}
|
|
1007
|
+
console.log();
|
|
1008
|
+
Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" login ")));
|
|
1009
|
+
const portalUrl = cfg.portal;
|
|
1010
|
+
const port = await getRandomPort();
|
|
1011
|
+
const redirectUrl = `http://127.0.0.1:${port}/callback`;
|
|
1012
|
+
const machine = hostname().slice(0, 64);
|
|
1013
|
+
const authUrl = `${portalUrl}/cli-auth?redirect=${encodeURIComponent(redirectUrl)}&hostname=${encodeURIComponent(machine)}`;
|
|
1014
|
+
M.message(`Opening browser for authentication...`);
|
|
1015
|
+
M.info(import_picocolors.default.dim(authUrl));
|
|
1016
|
+
await new Promise((resolve, reject) => {
|
|
1017
|
+
exec(`open "${authUrl}"`, (err) => {
|
|
1018
|
+
if (err) reject(err);
|
|
1019
|
+
else resolve();
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
const result = await new Promise((resolve) => {
|
|
1023
|
+
callbackResolve = resolve;
|
|
1024
|
+
const server = createServer((req, res) => {
|
|
1025
|
+
const url = new URL$1(req.url || "", `http://127.0.0.1:${port}`);
|
|
1026
|
+
if (url.pathname === "/callback") {
|
|
1027
|
+
const token = url.searchParams.get("token");
|
|
1028
|
+
const tokenName = url.searchParams.get("token_name");
|
|
1029
|
+
const error = url.searchParams.get("error");
|
|
1030
|
+
if (error) {
|
|
1031
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
1032
|
+
res.end(getErrorHtml(error));
|
|
1033
|
+
resolve({
|
|
1034
|
+
success: false,
|
|
1035
|
+
error
|
|
1036
|
+
});
|
|
1037
|
+
} else if (token) {
|
|
1038
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1039
|
+
res.end(getSuccessHtml());
|
|
1040
|
+
resolve({
|
|
1041
|
+
success: true,
|
|
1042
|
+
token,
|
|
1043
|
+
tokenName: tokenName || "default"
|
|
1044
|
+
});
|
|
1045
|
+
} else {
|
|
1046
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
1047
|
+
res.end(getErrorHtml("No token received"));
|
|
1048
|
+
resolve({
|
|
1049
|
+
success: false,
|
|
1050
|
+
error: "No token received"
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
res.writeHead(404);
|
|
1055
|
+
res.end("Not found");
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
server.listen(port, "127.0.0.1", () => {});
|
|
1059
|
+
setTimeout(() => {
|
|
1060
|
+
server.close();
|
|
1061
|
+
if (callbackResolve) callbackResolve({
|
|
1062
|
+
success: false,
|
|
1063
|
+
error: "Login timed out"
|
|
1064
|
+
});
|
|
1065
|
+
}, 12e4);
|
|
1066
|
+
});
|
|
1067
|
+
if (!result.success) {
|
|
1068
|
+
Se(import_picocolors.default.red("Login failed: " + result.error));
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
saveConfig({
|
|
1072
|
+
token: result.token,
|
|
1073
|
+
token_name: result.tokenName
|
|
1074
|
+
});
|
|
1075
|
+
M.success("Login successful!");
|
|
1076
|
+
M.message(import_picocolors.default.dim(`Token saved as: ${result.tokenName}`));
|
|
1077
|
+
Se(import_picocolors.default.green("You can now publish skills."));
|
|
1078
|
+
}
|
|
1079
|
+
function isCancelled$1(value) {
|
|
1080
|
+
return typeof value === "symbol";
|
|
1081
|
+
}
|
|
1082
|
+
function parseLoginOptions(args) {
|
|
1083
|
+
const options = {};
|
|
1084
|
+
for (let i = 0; i < args.length; i++) {
|
|
1085
|
+
const arg = args[i];
|
|
1086
|
+
if (arg === "--logout" || arg === "-l") options.logout = true;
|
|
1087
|
+
}
|
|
1088
|
+
return options;
|
|
1089
|
+
}
|
|
1090
|
+
async function rawFetch(url, headers, portalUrl) {
|
|
760
1091
|
try {
|
|
761
|
-
|
|
1092
|
+
return await fetch(url, { headers });
|
|
762
1093
|
} catch (err) {
|
|
763
1094
|
const msg = err instanceof Error ? err.message : String(err);
|
|
764
1095
|
throw new Error(`Failed to reach portal at ${portalUrl}: ${msg}`);
|
|
765
1096
|
}
|
|
766
|
-
|
|
1097
|
+
}
|
|
1098
|
+
async function fetchWithAutoAuth(url, accept, bearerToken, portalUrl) {
|
|
1099
|
+
const buildHeaders = (token) => {
|
|
1100
|
+
const h = { Accept: accept };
|
|
1101
|
+
if (token) h.Authorization = `Bearer ${token}`;
|
|
1102
|
+
return h;
|
|
1103
|
+
};
|
|
1104
|
+
let resp = await rawFetch(url, buildHeaders(bearerToken), portalUrl);
|
|
1105
|
+
if (resp.status !== 401) return resp;
|
|
1106
|
+
await resp.arrayBuffer().catch(() => void 0);
|
|
1107
|
+
await runLogin({ force: true });
|
|
1108
|
+
const refreshed = loadConfig().token;
|
|
1109
|
+
if (!refreshed) return resp;
|
|
1110
|
+
return rawFetch(url, buildHeaders(refreshed), portalUrl);
|
|
1111
|
+
}
|
|
1112
|
+
async function fetchSkillArchive(ref, portalUrl, bearerToken) {
|
|
1113
|
+
const resp = await fetchWithAutoAuth(`${portalUrl.replace(/\/$/, "")}/api/cli/skills/archive?ref=${encodeURIComponent(ref)}`, "application/gzip", bearerToken, portalUrl);
|
|
1114
|
+
if (resp.status === 401) throw new Error("Authentication failed after login. Please try again.");
|
|
767
1115
|
if (resp.status === 403) throw new Error(`Permission denied for ${ref}`);
|
|
768
1116
|
if (resp.status === 404) throw new Error(`Skill not found: ${ref}`);
|
|
769
1117
|
if (!resp.ok) {
|
|
@@ -773,19 +1121,8 @@ async function fetchSkillArchive(ref, portalUrl, bearerToken) {
|
|
|
773
1121
|
return Buffer.from(await resp.arrayBuffer());
|
|
774
1122
|
}
|
|
775
1123
|
async function fetchTeamAssets(teamScope, portalUrl, bearerToken) {
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
Accept: "application/json",
|
|
779
|
-
Authorization: `Bearer ${bearerToken}`
|
|
780
|
-
};
|
|
781
|
-
let resp;
|
|
782
|
-
try {
|
|
783
|
-
resp = await fetch(url, { headers });
|
|
784
|
-
} catch (err) {
|
|
785
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
786
|
-
throw new Error(`Failed to reach portal at ${portalUrl}: ${msg}`);
|
|
787
|
-
}
|
|
788
|
-
if (resp.status === 401) throw new Error("Login required: run `shoplazza-ai-dev-cli login` first");
|
|
1124
|
+
const resp = await fetchWithAutoAuth(`${portalUrl.replace(/\/$/, "")}/api/cli/teams/${encodeURIComponent(teamScope)}/assets`, "application/json", bearerToken, portalUrl);
|
|
1125
|
+
if (resp.status === 401) throw new Error("Authentication failed after login. Please try again.");
|
|
789
1126
|
if (resp.status === 403) throw new Error(`Not a member of team "${teamScope}"`);
|
|
790
1127
|
if (resp.status === 404) throw new Error(`Team not found: ${teamScope}`);
|
|
791
1128
|
if (!resp.ok) {
|
|
@@ -795,17 +1132,8 @@ async function fetchTeamAssets(teamScope, portalUrl, bearerToken) {
|
|
|
795
1132
|
return (await resp.json()).items ?? [];
|
|
796
1133
|
}
|
|
797
1134
|
async function fetchPluginArchive(ref, portalUrl, bearerToken) {
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
if (bearerToken) headers.Authorization = `Bearer ${bearerToken}`;
|
|
801
|
-
let resp;
|
|
802
|
-
try {
|
|
803
|
-
resp = await fetch(url, { headers });
|
|
804
|
-
} catch (err) {
|
|
805
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
806
|
-
throw new Error(`Failed to reach portal at ${portalUrl}: ${msg}`);
|
|
807
|
-
}
|
|
808
|
-
if (resp.status === 401) throw new Error("Login required: run `ai-dev-cli login` first");
|
|
1135
|
+
const resp = await fetchWithAutoAuth(`${portalUrl.replace(/\/$/, "")}/api/cli/plugins/archive?ref=${encodeURIComponent(ref)}`, "application/gzip", bearerToken, portalUrl);
|
|
1136
|
+
if (resp.status === 401) throw new Error("Authentication failed after login. Please try again.");
|
|
809
1137
|
if (resp.status === 403) throw new Error(`Permission denied for ${ref}`);
|
|
810
1138
|
if (resp.status === 404) throw new Error(`Plugin not found: ${ref}`);
|
|
811
1139
|
if (!resp.ok) {
|
|
@@ -947,7 +1275,7 @@ async function promptForeignOverwrite(conflicts, opts = {}) {
|
|
|
947
1275
|
}
|
|
948
1276
|
return choice === "yes";
|
|
949
1277
|
}
|
|
950
|
-
const AGENTS_DIR$
|
|
1278
|
+
const AGENTS_DIR$1 = ".agents";
|
|
951
1279
|
const SKILLS_SUBDIR = "skills";
|
|
952
1280
|
function parseFrontmatter(raw) {
|
|
953
1281
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
@@ -1187,17 +1515,8 @@ function isPathSafe(basePath, targetPath) {
|
|
|
1187
1515
|
const normalizedTarget = normalize(resolve(targetPath));
|
|
1188
1516
|
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
|
|
1189
1517
|
}
|
|
1190
|
-
async function isDirEntryOrSymlinkToDir(entry, entryPath) {
|
|
1191
|
-
if (entry.isDirectory()) return true;
|
|
1192
|
-
if (!entry.isSymbolicLink()) return false;
|
|
1193
|
-
try {
|
|
1194
|
-
return (await stat(entryPath)).isDirectory();
|
|
1195
|
-
} catch {
|
|
1196
|
-
return false;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
1518
|
function getCanonicalSkillsDir(global, cwd) {
|
|
1200
|
-
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$
|
|
1519
|
+
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$1, SKILLS_SUBDIR);
|
|
1201
1520
|
}
|
|
1202
1521
|
function getAgentBaseDir(agentType, global, cwd) {
|
|
1203
1522
|
if (isUniversalAgent(agentType)) return getCanonicalSkillsDir(global, cwd);
|
|
@@ -1283,8 +1602,7 @@ async function installSkillForAgent(skill, agentType, opts) {
|
|
|
1283
1602
|
error: "Invalid skill name: path traversal detected",
|
|
1284
1603
|
forgeOtherReplacements: []
|
|
1285
1604
|
};
|
|
1286
|
-
const
|
|
1287
|
-
const entry = agentType === "claude-code" ? join(entryAgentRoot, ".claude", "skills", skillName) : agentType === "cursor" ? join(entryAgentRoot, ".cursor", "skills", skillName) : join(entryAgentRoot, ".agents", "skills", skillName);
|
|
1605
|
+
const entry = join(getAgentBaseDir(agentType, scope === "global", cwd), skillName);
|
|
1288
1606
|
try {
|
|
1289
1607
|
const status = await assertEntryReplaceable(entry, {
|
|
1290
1608
|
intendedRef: newOpts.ref,
|
|
@@ -1487,15 +1805,6 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
1487
1805
|
return false;
|
|
1488
1806
|
}
|
|
1489
1807
|
}
|
|
1490
|
-
function getInstallPath(skillName, agentType, options = {}) {
|
|
1491
|
-
agents[agentType];
|
|
1492
|
-
options.cwd || process.cwd();
|
|
1493
|
-
const sanitized = sanitizeName(skillName);
|
|
1494
|
-
const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
|
|
1495
|
-
const installPath = join(targetBase, sanitized);
|
|
1496
|
-
if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1497
|
-
return installPath;
|
|
1498
|
-
}
|
|
1499
1808
|
function getCanonicalPath(skillName, options = {}) {
|
|
1500
1809
|
const sanitized = sanitizeName(skillName);
|
|
1501
1810
|
const canonicalBase = getCanonicalSkillsDir(options.global ?? false, options.cwd);
|
|
@@ -1665,166 +1974,6 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
1665
1974
|
};
|
|
1666
1975
|
}
|
|
1667
1976
|
}
|
|
1668
|
-
async function listInstalledSkills(options = {}) {
|
|
1669
|
-
const cwd = options.cwd || process.cwd();
|
|
1670
|
-
const skillsMap = /* @__PURE__ */ new Map();
|
|
1671
|
-
const scopes = [];
|
|
1672
|
-
const detectedAgents = await detectInstalledAgents();
|
|
1673
|
-
const agentFilter = options.agentFilter;
|
|
1674
|
-
const agentsToCheck = agentFilter ? detectedAgents.filter((a) => agentFilter.includes(a)) : detectedAgents;
|
|
1675
|
-
const scopeTypes = [];
|
|
1676
|
-
if (options.global === void 0) scopeTypes.push({ global: false }, { global: true });
|
|
1677
|
-
else scopeTypes.push({ global: options.global });
|
|
1678
|
-
for (const { global: isGlobal } of scopeTypes) {
|
|
1679
|
-
scopes.push({
|
|
1680
|
-
global: isGlobal,
|
|
1681
|
-
path: getCanonicalSkillsDir(isGlobal, cwd)
|
|
1682
|
-
});
|
|
1683
|
-
for (const agentType of agentsToCheck) {
|
|
1684
|
-
const agent = agents[agentType];
|
|
1685
|
-
if (isGlobal && agent.globalSkillsDir === void 0) continue;
|
|
1686
|
-
const agentDir = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
|
|
1687
|
-
if (!scopes.some((s) => s.path === agentDir && s.global === isGlobal)) scopes.push({
|
|
1688
|
-
global: isGlobal,
|
|
1689
|
-
path: agentDir,
|
|
1690
|
-
agentType
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
const allAgentTypes = Object.keys(agents);
|
|
1694
|
-
for (const agentType of allAgentTypes) {
|
|
1695
|
-
if (agentsToCheck.includes(agentType)) continue;
|
|
1696
|
-
const agent = agents[agentType];
|
|
1697
|
-
if (isGlobal && agent.globalSkillsDir === void 0) continue;
|
|
1698
|
-
const agentDir = isGlobal ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
|
|
1699
|
-
if (scopes.some((s) => s.path === agentDir && s.global === isGlobal)) continue;
|
|
1700
|
-
if (existsSync(agentDir)) scopes.push({
|
|
1701
|
-
global: isGlobal,
|
|
1702
|
-
path: agentDir,
|
|
1703
|
-
agentType
|
|
1704
|
-
});
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
for (const scope of scopes) try {
|
|
1708
|
-
const entries = await readdir(scope.path, { withFileTypes: true });
|
|
1709
|
-
for (const entry of entries) {
|
|
1710
|
-
const skillDir = join(scope.path, entry.name);
|
|
1711
|
-
if (!await isDirEntryOrSymlinkToDir(entry, skillDir)) continue;
|
|
1712
|
-
const skillMdPath = join(skillDir, "SKILL.md");
|
|
1713
|
-
try {
|
|
1714
|
-
await stat(skillMdPath);
|
|
1715
|
-
} catch {
|
|
1716
|
-
continue;
|
|
1717
|
-
}
|
|
1718
|
-
const skill = await parseSkillMd(skillMdPath);
|
|
1719
|
-
if (!skill) continue;
|
|
1720
|
-
const scopeKey = scope.global ? "global" : "project";
|
|
1721
|
-
const skillKey = `${scopeKey}:${skill.name}`;
|
|
1722
|
-
if (scope.agentType) {
|
|
1723
|
-
if (skillsMap.has(skillKey)) {
|
|
1724
|
-
const existing = skillsMap.get(skillKey);
|
|
1725
|
-
if (!existing.agents.includes(scope.agentType)) existing.agents.push(scope.agentType);
|
|
1726
|
-
} else skillsMap.set(skillKey, {
|
|
1727
|
-
name: skill.name,
|
|
1728
|
-
description: skill.description,
|
|
1729
|
-
path: skillDir,
|
|
1730
|
-
canonicalPath: skillDir,
|
|
1731
|
-
scope: scopeKey,
|
|
1732
|
-
agents: [scope.agentType]
|
|
1733
|
-
});
|
|
1734
|
-
continue;
|
|
1735
|
-
}
|
|
1736
|
-
const sanitizedSkillName = sanitizeName(skill.name);
|
|
1737
|
-
const installedAgents = [];
|
|
1738
|
-
for (const agentType of agentsToCheck) {
|
|
1739
|
-
const agent = agents[agentType];
|
|
1740
|
-
if (scope.global && agent.globalSkillsDir === void 0) continue;
|
|
1741
|
-
const agentBase = scope.global ? agent.globalSkillsDir : join(cwd, agent.skillsDir);
|
|
1742
|
-
let found = false;
|
|
1743
|
-
const possibleNames = Array.from(new Set([
|
|
1744
|
-
entry.name,
|
|
1745
|
-
sanitizedSkillName,
|
|
1746
|
-
skill.name.toLowerCase().replace(/\s+/g, "-").replace(/[\/\\:\0]/g, "")
|
|
1747
|
-
]));
|
|
1748
|
-
for (const possibleName of possibleNames) {
|
|
1749
|
-
const agentSkillDir = join(agentBase, possibleName);
|
|
1750
|
-
if (!isPathSafe(agentBase, agentSkillDir)) continue;
|
|
1751
|
-
try {
|
|
1752
|
-
await access(agentSkillDir);
|
|
1753
|
-
found = true;
|
|
1754
|
-
break;
|
|
1755
|
-
} catch {}
|
|
1756
|
-
}
|
|
1757
|
-
if (!found) try {
|
|
1758
|
-
const agentEntries = await readdir(agentBase, { withFileTypes: true });
|
|
1759
|
-
for (const agentEntry of agentEntries) {
|
|
1760
|
-
const candidateDir = join(agentBase, agentEntry.name);
|
|
1761
|
-
if (!await isDirEntryOrSymlinkToDir(agentEntry, candidateDir)) continue;
|
|
1762
|
-
if (!isPathSafe(agentBase, candidateDir)) continue;
|
|
1763
|
-
try {
|
|
1764
|
-
const candidateSkillMd = join(candidateDir, "SKILL.md");
|
|
1765
|
-
await stat(candidateSkillMd);
|
|
1766
|
-
const candidateSkill = await parseSkillMd(candidateSkillMd);
|
|
1767
|
-
if (candidateSkill && candidateSkill.name === skill.name) {
|
|
1768
|
-
found = true;
|
|
1769
|
-
break;
|
|
1770
|
-
}
|
|
1771
|
-
} catch {}
|
|
1772
|
-
}
|
|
1773
|
-
} catch {}
|
|
1774
|
-
if (found) installedAgents.push(agentType);
|
|
1775
|
-
}
|
|
1776
|
-
if (skillsMap.has(skillKey)) {
|
|
1777
|
-
const existing = skillsMap.get(skillKey);
|
|
1778
|
-
for (const agent of installedAgents) if (!existing.agents.includes(agent)) existing.agents.push(agent);
|
|
1779
|
-
} else skillsMap.set(skillKey, {
|
|
1780
|
-
name: skill.name,
|
|
1781
|
-
description: skill.description,
|
|
1782
|
-
path: skillDir,
|
|
1783
|
-
canonicalPath: skillDir,
|
|
1784
|
-
scope: scopeKey,
|
|
1785
|
-
agents: installedAgents
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
} catch {}
|
|
1789
|
-
return Array.from(skillsMap.values());
|
|
1790
|
-
}
|
|
1791
|
-
const USER_START = "<!-- forge:user-content:start -->";
|
|
1792
|
-
const USER_END = "<!-- forge:user-content:end -->";
|
|
1793
|
-
function takeoverWithUserContent(existing) {
|
|
1794
|
-
if (existing.includes(USER_START)) return existing;
|
|
1795
|
-
return `${USER_START}\n${existing.trim()}\n${USER_END}\n`;
|
|
1796
|
-
}
|
|
1797
|
-
function injectPluginRules(agentsMd, plugin, rules) {
|
|
1798
|
-
const start = `<!-- forge-plugin:${plugin.key}:start ref=${plugin.ref} install_mode=codex -->`;
|
|
1799
|
-
const end = `<!-- forge-plugin:${plugin.key}:end -->`;
|
|
1800
|
-
const block = `${start}\n${rules.map((r) => {
|
|
1801
|
-
const rs = `<!-- forge-rule:${r.relativePath}:start -->`;
|
|
1802
|
-
const re = `<!-- forge-rule:${r.relativePath}:end -->`;
|
|
1803
|
-
return ` ${rs}\n${r.content.trimEnd()}\n ${re}`;
|
|
1804
|
-
}).join("\n")}\n${end}\n`;
|
|
1805
|
-
const existing = findPluginBlock(agentsMd, plugin.key);
|
|
1806
|
-
if (existing) return agentsMd.slice(0, existing.start) + block + agentsMd.slice(existing.end);
|
|
1807
|
-
return agentsMd + (agentsMd.endsWith("\n") ? "" : "\n") + "\n" + block;
|
|
1808
|
-
}
|
|
1809
|
-
function removePluginRules(agentsMd, pluginKey) {
|
|
1810
|
-
const existing = findPluginBlock(agentsMd, pluginKey);
|
|
1811
|
-
if (!existing) return agentsMd;
|
|
1812
|
-
return agentsMd.slice(0, existing.start) + agentsMd.slice(existing.end);
|
|
1813
|
-
}
|
|
1814
|
-
function findPluginBlock(agentsMd, pluginKey) {
|
|
1815
|
-
const startMarker = `<!-- forge-plugin:${pluginKey}:start`;
|
|
1816
|
-
const endMarker = `<!-- forge-plugin:${pluginKey}:end -->`;
|
|
1817
|
-
const startIdx = agentsMd.indexOf(startMarker);
|
|
1818
|
-
if (startIdx < 0) return null;
|
|
1819
|
-
const endIdx = agentsMd.indexOf(endMarker, startIdx);
|
|
1820
|
-
if (endIdx < 0) return null;
|
|
1821
|
-
let blockEnd = endIdx + endMarker.length;
|
|
1822
|
-
if (agentsMd[blockEnd] === "\n") blockEnd++;
|
|
1823
|
-
return {
|
|
1824
|
-
start: startIdx,
|
|
1825
|
-
end: blockEnd
|
|
1826
|
-
};
|
|
1827
|
-
}
|
|
1828
1977
|
async function installPlugin(ref, opts) {
|
|
1829
1978
|
const cfg = loadConfig();
|
|
1830
1979
|
const tarball = await fetchPluginArchive(ref, cfg.portal, cfg.token);
|
|
@@ -1866,7 +2015,7 @@ async function installFromExtracted(ref, extractedDir, opts) {
|
|
|
1866
2015
|
const forgeOtherReplacements = [];
|
|
1867
2016
|
const statuses = /* @__PURE__ */ new Map();
|
|
1868
2017
|
for (const pe of preflight) {
|
|
1869
|
-
const intendedRef = ref + "/" + componentSubRef(pe.component);
|
|
2018
|
+
const intendedRef = ref + "/" + componentSubRef$1(pe.component);
|
|
1870
2019
|
const status = await assertEntryReplaceable(pe.entryPath, {
|
|
1871
2020
|
intendedRef,
|
|
1872
2021
|
intendedCanonical: pe.intendedCanonical,
|
|
@@ -2053,7 +2202,7 @@ function parsePluginRef$1(ref) {
|
|
|
2053
2202
|
};
|
|
2054
2203
|
throw new Error(`invalid plugin ref: ${ref}`);
|
|
2055
2204
|
}
|
|
2056
|
-
function componentSubRef(c) {
|
|
2205
|
+
function componentSubRef$1(c) {
|
|
2057
2206
|
switch (c.kind) {
|
|
2058
2207
|
case "plugin-skill": return `skills/${c.skillName}`;
|
|
2059
2208
|
case "plugin-agent-manifest": return `agents/${c.filename}`;
|
|
@@ -2140,14 +2289,6 @@ async function commitPluginInstall(params) {
|
|
|
2140
2289
|
else await copyFileShallow(pe.intendedCanonical, pe.entryPath);
|
|
2141
2290
|
}
|
|
2142
2291
|
}
|
|
2143
|
-
if (opts.agents.includes("codex") && components.ruleManifests.length > 0) await injectCodexAgentsMdForPlugin({
|
|
2144
|
-
pluginCanonical,
|
|
2145
|
-
pluginRef: ref,
|
|
2146
|
-
pluginKey: `${scopeNamespace}:${pluginName}`,
|
|
2147
|
-
ruleManifests: components.ruleManifests,
|
|
2148
|
-
scope: opts.scope,
|
|
2149
|
-
cwd: opts.cwd
|
|
2150
|
-
});
|
|
2151
2292
|
if (opts.agents.includes("codex") && components.agentManifests.length > 0) await registerCodexAgents({
|
|
2152
2293
|
pluginCanonical,
|
|
2153
2294
|
agentFilenames: components.agentManifests,
|
|
@@ -2172,38 +2313,6 @@ async function copyFileShallow(src, dest) {
|
|
|
2172
2313
|
await mkdir(dirname(dest), { recursive: true });
|
|
2173
2314
|
await writeFile(dest, buf);
|
|
2174
2315
|
}
|
|
2175
|
-
async function injectCodexAgentsMdForPlugin(params) {
|
|
2176
|
-
const codexAgentsMd = join(params.scope === "global" ? getAgentHome("codex") : join(params.cwd ?? process.cwd(), ".codex"), "AGENTS.md");
|
|
2177
|
-
const canonicalAgentsMd = join(getStoreRoot(params.scope, params.cwd), "codex-agents.md");
|
|
2178
|
-
let userContent = "";
|
|
2179
|
-
try {
|
|
2180
|
-
if ((await stat(codexAgentsMd)).isFile()) userContent = await readFile(codexAgentsMd, "utf-8");
|
|
2181
|
-
} catch {}
|
|
2182
|
-
let canonicalContent = "";
|
|
2183
|
-
try {
|
|
2184
|
-
canonicalContent = await readFile(canonicalAgentsMd, "utf-8");
|
|
2185
|
-
} catch {}
|
|
2186
|
-
if (!canonicalContent) canonicalContent = takeoverWithUserContent(userContent);
|
|
2187
|
-
const rules = [];
|
|
2188
|
-
for (const fname of params.ruleManifests) {
|
|
2189
|
-
const content = await readFile(join(params.pluginCanonical, "rules", fname), "utf-8");
|
|
2190
|
-
rules.push({
|
|
2191
|
-
relativePath: `rules/${fname}`,
|
|
2192
|
-
content
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
const updated = injectPluginRules(canonicalContent, {
|
|
2196
|
-
ref: params.pluginRef,
|
|
2197
|
-
key: params.pluginKey
|
|
2198
|
-
}, rules);
|
|
2199
|
-
await mkdir(dirname(canonicalAgentsMd), { recursive: true });
|
|
2200
|
-
await writeFile(canonicalAgentsMd, updated, "utf-8");
|
|
2201
|
-
try {
|
|
2202
|
-
await rm(codexAgentsMd, { force: true });
|
|
2203
|
-
} catch {}
|
|
2204
|
-
await mkdir(dirname(codexAgentsMd), { recursive: true });
|
|
2205
|
-
await symlink(canonicalAgentsMd, codexAgentsMd);
|
|
2206
|
-
}
|
|
2207
2316
|
async function registerCodexAgents(params) {
|
|
2208
2317
|
const configToml = join(params.scope === "global" ? getAgentHome("codex") : join(params.cwd ?? process.cwd(), ".codex"), "config.toml");
|
|
2209
2318
|
let raw = "";
|
|
@@ -2497,8 +2606,6 @@ function track(data) {
|
|
|
2497
2606
|
if (!portalUrl) portalUrl = loadConfig().portal;
|
|
2498
2607
|
try {
|
|
2499
2608
|
const bodyPayload = {};
|
|
2500
|
-
if ("event" in data) bodyPayload.event = data.event;
|
|
2501
|
-
else if (data.event_type) bodyPayload.event_type = data.event_type;
|
|
2502
2609
|
for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) bodyPayload[key] = value;
|
|
2503
2610
|
const p = fetch(`${portalUrl}${TELEMETRY_URL_SUFFIX}`, {
|
|
2504
2611
|
method: "POST",
|
|
@@ -2705,16 +2812,16 @@ var WellKnownProvider = class {
|
|
|
2705
2812
|
}
|
|
2706
2813
|
};
|
|
2707
2814
|
const wellKnownProvider = new WellKnownProvider();
|
|
2708
|
-
const AGENTS_DIR
|
|
2709
|
-
const LOCK_FILE
|
|
2815
|
+
const AGENTS_DIR = ".agents";
|
|
2816
|
+
const LOCK_FILE = ".skill-lock.json";
|
|
2710
2817
|
const CURRENT_VERSION$1 = 3;
|
|
2711
|
-
function getSkillLockPath
|
|
2818
|
+
function getSkillLockPath() {
|
|
2712
2819
|
const xdgStateHome = process.env.XDG_STATE_HOME;
|
|
2713
|
-
if (xdgStateHome) return join(xdgStateHome, "skills", LOCK_FILE
|
|
2714
|
-
return join(homedir(), AGENTS_DIR
|
|
2820
|
+
if (xdgStateHome) return join(xdgStateHome, "skills", LOCK_FILE);
|
|
2821
|
+
return join(homedir(), AGENTS_DIR, LOCK_FILE);
|
|
2715
2822
|
}
|
|
2716
|
-
async function readSkillLock
|
|
2717
|
-
const lockPath = getSkillLockPath
|
|
2823
|
+
async function readSkillLock() {
|
|
2824
|
+
const lockPath = getSkillLockPath();
|
|
2718
2825
|
try {
|
|
2719
2826
|
const content = await readFile(lockPath, "utf-8");
|
|
2720
2827
|
const parsed = JSON.parse(content);
|
|
@@ -2726,7 +2833,7 @@ async function readSkillLock$1() {
|
|
|
2726
2833
|
}
|
|
2727
2834
|
}
|
|
2728
2835
|
async function writeSkillLock(lock) {
|
|
2729
|
-
const lockPath = getSkillLockPath
|
|
2836
|
+
const lockPath = getSkillLockPath();
|
|
2730
2837
|
await mkdir(dirname(lockPath), { recursive: true });
|
|
2731
2838
|
await writeFile(lockPath, JSON.stringify(lock, null, 2), "utf-8");
|
|
2732
2839
|
}
|
|
@@ -2746,14 +2853,8 @@ function getGitHubToken() {
|
|
|
2746
2853
|
} catch {}
|
|
2747
2854
|
return null;
|
|
2748
2855
|
}
|
|
2749
|
-
async function fetchSkillFolderHash(ownerRepo, skillPath, token, ref) {
|
|
2750
|
-
const { fetchRepoTree, getSkillFolderHashFromTree } = await Promise.resolve().then(() => blob_exports);
|
|
2751
|
-
const tree = await fetchRepoTree(ownerRepo, ref, token);
|
|
2752
|
-
if (!tree) return null;
|
|
2753
|
-
return getSkillFolderHashFromTree(tree, skillPath);
|
|
2754
|
-
}
|
|
2755
2856
|
async function addSkillToLock(skillName, entry) {
|
|
2756
|
-
const lock = await readSkillLock
|
|
2857
|
+
const lock = await readSkillLock();
|
|
2757
2858
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2758
2859
|
const existingEntry = lock.skills[skillName];
|
|
2759
2860
|
lock.skills[skillName] = {
|
|
@@ -2764,20 +2865,14 @@ async function addSkillToLock(skillName, entry) {
|
|
|
2764
2865
|
await writeSkillLock(lock);
|
|
2765
2866
|
}
|
|
2766
2867
|
async function removeSkillFromLock(skillName) {
|
|
2767
|
-
const lock = await readSkillLock
|
|
2868
|
+
const lock = await readSkillLock();
|
|
2768
2869
|
if (!(skillName in lock.skills)) return false;
|
|
2769
2870
|
delete lock.skills[skillName];
|
|
2770
2871
|
await writeSkillLock(lock);
|
|
2771
2872
|
return true;
|
|
2772
2873
|
}
|
|
2773
|
-
async function getSkillFromLock(skillName) {
|
|
2774
|
-
return (await readSkillLock$1()).skills[skillName] ?? null;
|
|
2775
|
-
}
|
|
2776
|
-
async function getAllLockedSkills() {
|
|
2777
|
-
return (await readSkillLock$1()).skills;
|
|
2778
|
-
}
|
|
2779
2874
|
async function addPluginToLock(pluginName, entry) {
|
|
2780
|
-
const lock = await readSkillLock
|
|
2875
|
+
const lock = await readSkillLock();
|
|
2781
2876
|
if (!lock.plugins) lock.plugins = {};
|
|
2782
2877
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2783
2878
|
const existing = lock.plugins[pluginName];
|
|
@@ -2789,7 +2884,7 @@ async function addPluginToLock(pluginName, entry) {
|
|
|
2789
2884
|
await writeSkillLock(lock);
|
|
2790
2885
|
}
|
|
2791
2886
|
async function removePluginFromLock(pluginName) {
|
|
2792
|
-
const lock = await readSkillLock
|
|
2887
|
+
const lock = await readSkillLock();
|
|
2793
2888
|
if (!lock.plugins || !(pluginName in lock.plugins)) return false;
|
|
2794
2889
|
delete lock.plugins[pluginName];
|
|
2795
2890
|
await writeSkillLock(lock);
|
|
@@ -2804,10 +2899,10 @@ function createEmptyLockFile() {
|
|
|
2804
2899
|
};
|
|
2805
2900
|
}
|
|
2806
2901
|
async function isPromptDismissed(promptKey) {
|
|
2807
|
-
return (await readSkillLock
|
|
2902
|
+
return (await readSkillLock()).dismissed?.[promptKey] === true;
|
|
2808
2903
|
}
|
|
2809
2904
|
async function dismissPrompt(promptKey) {
|
|
2810
|
-
const lock = await readSkillLock
|
|
2905
|
+
const lock = await readSkillLock();
|
|
2811
2906
|
if (!lock.dismissed) lock.dismissed = {};
|
|
2812
2907
|
lock.dismissed[promptKey] = true;
|
|
2813
2908
|
await writeSkillLock(lock);
|
|
@@ -2877,6 +2972,13 @@ async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
|
2877
2972
|
lock.skills[skillName] = entry;
|
|
2878
2973
|
await writeLocalLock(lock, cwd);
|
|
2879
2974
|
}
|
|
2975
|
+
async function removeSkillFromLocalLock(skillName, cwd) {
|
|
2976
|
+
const lock = await readLocalLock(cwd);
|
|
2977
|
+
if (!(skillName in lock.skills)) return false;
|
|
2978
|
+
delete lock.skills[skillName];
|
|
2979
|
+
await writeLocalLock(lock, cwd);
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2880
2982
|
async function addPluginToLocalLock(pluginName, entry, cwd) {
|
|
2881
2983
|
const lock = await readLocalLock(cwd);
|
|
2882
2984
|
if (!lock.plugins) lock.plugins = {};
|
|
@@ -2896,13 +2998,6 @@ function createEmptyLocalLock() {
|
|
|
2896
2998
|
skills: {}
|
|
2897
2999
|
};
|
|
2898
3000
|
}
|
|
2899
|
-
var blob_exports = /* @__PURE__ */ __exportAll({
|
|
2900
|
-
fetchRepoTree: () => fetchRepoTree,
|
|
2901
|
-
findSkillMdPaths: () => findSkillMdPaths,
|
|
2902
|
-
getSkillFolderHashFromTree: () => getSkillFolderHashFromTree,
|
|
2903
|
-
toSkillSlug: () => toSkillSlug,
|
|
2904
|
-
tryBlobInstall: () => tryBlobInstall
|
|
2905
|
-
});
|
|
2906
3001
|
const DOWNLOAD_BASE_URL = process.env.SKILLS_DOWNLOAD_URL || "https://skills.sh";
|
|
2907
3002
|
const FETCH_TIMEOUT = 1e4;
|
|
2908
3003
|
function toSkillSlug(name) {
|
|
@@ -3159,7 +3254,7 @@ function shortenPath$2(fullPath, cwd) {
|
|
|
3159
3254
|
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
3160
3255
|
return fullPath;
|
|
3161
3256
|
}
|
|
3162
|
-
function formatList
|
|
3257
|
+
function formatList(items, maxShow = 5) {
|
|
3163
3258
|
if (items.length <= maxShow) return items.join(", ");
|
|
3164
3259
|
const shown = items.slice(0, maxShow);
|
|
3165
3260
|
const remaining = items.length - maxShow;
|
|
@@ -3175,15 +3270,22 @@ function splitAgentsByType(agentTypes) {
|
|
|
3175
3270
|
symlinked
|
|
3176
3271
|
};
|
|
3177
3272
|
}
|
|
3178
|
-
function buildAgentSummaryLines(targetAgents, installMode) {
|
|
3273
|
+
function buildAgentSummaryLines(targetAgents, installMode, entry) {
|
|
3179
3274
|
const lines = [];
|
|
3180
3275
|
const { universal, symlinked } = splitAgentsByType(targetAgents);
|
|
3181
3276
|
if (installMode === "symlink") {
|
|
3182
|
-
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList
|
|
3183
|
-
if (symlinked.length > 0)
|
|
3277
|
+
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList(universal)}`);
|
|
3278
|
+
if (symlinked.length > 0) if (entry) {
|
|
3279
|
+
const sanitized = sanitizeName(entry.skillName);
|
|
3280
|
+
for (const a of targetAgents) {
|
|
3281
|
+
if (isUniversalAgent(a)) continue;
|
|
3282
|
+
const entryPath = join(getAgentBaseDir(a, entry.scope === "global", entry.cwd), sanitized);
|
|
3283
|
+
lines.push(` ${import_picocolors.default.dim("symlink →")} ${import_picocolors.default.cyan(shortenPath$2(entryPath, entry.cwd))} ${import_picocolors.default.dim(`(${agents[a].displayName})`)}`);
|
|
3284
|
+
}
|
|
3285
|
+
} else lines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList(symlinked)}`);
|
|
3184
3286
|
} else {
|
|
3185
3287
|
const allNames = targetAgents.map((a) => agents[a].displayName);
|
|
3186
|
-
lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList
|
|
3288
|
+
lines.push(` ${import_picocolors.default.dim("copy →")} ${formatList(allNames)}`);
|
|
3187
3289
|
}
|
|
3188
3290
|
return lines;
|
|
3189
3291
|
}
|
|
@@ -3198,9 +3300,9 @@ function buildResultLines(results, targetAgents) {
|
|
|
3198
3300
|
const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
|
|
3199
3301
|
const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !universal.includes(r.agent)).map((r) => r.agent);
|
|
3200
3302
|
const failedSymlinks = results.filter((r) => r.symlinkFailed).map((r) => r.agent);
|
|
3201
|
-
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList
|
|
3202
|
-
if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList
|
|
3203
|
-
if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList
|
|
3303
|
+
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList(universal)}`);
|
|
3304
|
+
if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList(successfulSymlinks)}`);
|
|
3305
|
+
if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList(failedSymlinks)}`);
|
|
3204
3306
|
return lines;
|
|
3205
3307
|
}
|
|
3206
3308
|
function multiselect(opts) {
|
|
@@ -3218,15 +3320,26 @@ async function promptForAgents(message, choices) {
|
|
|
3218
3320
|
required: true
|
|
3219
3321
|
});
|
|
3220
3322
|
}
|
|
3323
|
+
const PLUGIN_ENTRY_ROOTS = {
|
|
3324
|
+
"claude-code": [".claude"],
|
|
3325
|
+
cursor: [".cursor"],
|
|
3326
|
+
codex: [".codex", ".agents/skills"]
|
|
3327
|
+
};
|
|
3221
3328
|
function buildAgentChoices(agentList, options = {}) {
|
|
3222
3329
|
return agentList.map((a) => {
|
|
3223
3330
|
const cfg = agents[a];
|
|
3224
|
-
|
|
3225
|
-
|
|
3331
|
+
let hint;
|
|
3332
|
+
if (options.forPlugin) {
|
|
3333
|
+
const roots = PLUGIN_ENTRY_ROOTS[a];
|
|
3334
|
+
hint = options.global ? roots.map((r) => `~/${r}`).join(", ") : roots.join(", ");
|
|
3335
|
+
} else {
|
|
3336
|
+
const baseHint = options.global && cfg.globalSkillsDir ? cfg.globalSkillsDir : cfg.skillsDir;
|
|
3337
|
+
hint = !options.global && isUniversalAgent(a) ? `${baseHint}, shared` : baseHint;
|
|
3338
|
+
}
|
|
3226
3339
|
return {
|
|
3227
3340
|
value: a,
|
|
3228
3341
|
label: cfg.displayName,
|
|
3229
|
-
hint
|
|
3342
|
+
hint
|
|
3230
3343
|
};
|
|
3231
3344
|
});
|
|
3232
3345
|
}
|
|
@@ -3404,11 +3517,15 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3404
3517
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
3405
3518
|
const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
|
|
3406
3519
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
3407
|
-
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode
|
|
3520
|
+
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode, {
|
|
3521
|
+
skillName: skill.installName,
|
|
3522
|
+
scope: installGlobally ? "global" : "project",
|
|
3523
|
+
cwd
|
|
3524
|
+
}));
|
|
3408
3525
|
if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
|
|
3409
3526
|
const skillOverwrites = overwriteStatus.get(skill.installName);
|
|
3410
3527
|
const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
|
|
3411
|
-
if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList
|
|
3528
|
+
if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList(overwriteAgents)}`);
|
|
3412
3529
|
}
|
|
3413
3530
|
console.log();
|
|
3414
3531
|
Me(summaryLines.join("\n"), "Installation Summary");
|
|
@@ -3501,7 +3618,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3501
3618
|
const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
3502
3619
|
Me(resultLines.join("\n"), title);
|
|
3503
3620
|
if (symlinkFailures.length > 0) {
|
|
3504
|
-
M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList
|
|
3621
|
+
M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList(copiedAgents)}`));
|
|
3505
3622
|
M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
|
|
3506
3623
|
}
|
|
3507
3624
|
}
|
|
@@ -3515,14 +3632,16 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3515
3632
|
await promptForFindSkills(options, targetAgents);
|
|
3516
3633
|
}
|
|
3517
3634
|
async function runTeamBatchAdd(teamScope, options) {
|
|
3518
|
-
|
|
3635
|
+
let cfg = loadConfig();
|
|
3519
3636
|
if (!cfg.token) {
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3637
|
+
await runLogin({ force: true });
|
|
3638
|
+
cfg = loadConfig();
|
|
3639
|
+
if (!cfg.token) {
|
|
3640
|
+
console.log();
|
|
3641
|
+
console.log(import_picocolors.default.bgRed(import_picocolors.default.white(import_picocolors.default.bold(" ERROR "))) + " " + import_picocolors.default.red("Login required for team installs"));
|
|
3642
|
+
console.log();
|
|
3643
|
+
process.exit(1);
|
|
3644
|
+
}
|
|
3526
3645
|
}
|
|
3527
3646
|
console.log();
|
|
3528
3647
|
Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills ")));
|
|
@@ -3537,10 +3656,9 @@ async function runTeamBatchAdd(teamScope, options) {
|
|
|
3537
3656
|
Se(import_picocolors.default.red(msg));
|
|
3538
3657
|
process.exit(1);
|
|
3539
3658
|
}
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
Se(import_picocolors.default.dim(`Team "${teamScope}" has no skills.`));
|
|
3659
|
+
spinner.stop(`Found ${items.length} asset${items.length === 1 ? "" : "s"}`);
|
|
3660
|
+
if (items.length === 0) {
|
|
3661
|
+
Se(import_picocolors.default.dim(`Team "${teamScope}" has no assets.`));
|
|
3544
3662
|
return;
|
|
3545
3663
|
}
|
|
3546
3664
|
const validAgents = Object.keys(agents);
|
|
@@ -3616,8 +3734,8 @@ async function runTeamBatchAdd(teamScope, options) {
|
|
|
3616
3734
|
global: installGlobally
|
|
3617
3735
|
};
|
|
3618
3736
|
let failures = 0;
|
|
3619
|
-
for (const item of
|
|
3620
|
-
M.step(`Installing ${import_picocolors.default.cyan(item.name)}`);
|
|
3737
|
+
for (const item of items) {
|
|
3738
|
+
M.step(`Installing ${import_picocolors.default.cyan(item.name)} (${item.kind})`);
|
|
3621
3739
|
try {
|
|
3622
3740
|
await runAdd([item.ref], childOptions);
|
|
3623
3741
|
} catch (err) {
|
|
@@ -3627,9 +3745,9 @@ async function runTeamBatchAdd(teamScope, options) {
|
|
|
3627
3745
|
}
|
|
3628
3746
|
}
|
|
3629
3747
|
console.log();
|
|
3630
|
-
if (failures === 0) Se(import_picocolors.default.green(`Installed ${
|
|
3748
|
+
if (failures === 0) Se(import_picocolors.default.green(`Installed ${items.length} asset${items.length === 1 ? "" : "s"} from team "${teamScope}"`));
|
|
3631
3749
|
else {
|
|
3632
|
-
Se(import_picocolors.default.yellow(`Installed ${
|
|
3750
|
+
Se(import_picocolors.default.yellow(`Installed ${items.length - failures}/${items.length} assets (${failures} failed) from team "${teamScope}"`));
|
|
3633
3751
|
process.exit(1);
|
|
3634
3752
|
}
|
|
3635
3753
|
await promptForFindSkills(options, targetAgents);
|
|
@@ -3730,7 +3848,10 @@ async function runAdd(args, options = {}) {
|
|
|
3730
3848
|
spinner.stop(`${Object.keys(agents).length} agents`);
|
|
3731
3849
|
if (options.yes) targetAgents = installedAgents.length > 0 ? ensureUniversalAgents(installedAgents) : validAgents;
|
|
3732
3850
|
else if (installedAgents.length === 0) {
|
|
3733
|
-
const selected = await promptForAgents("Which IDEs do you want to install this plugin to?", buildAgentChoices(Object.keys(agents), {
|
|
3851
|
+
const selected = await promptForAgents("Which IDEs do you want to install this plugin to?", buildAgentChoices(Object.keys(agents), {
|
|
3852
|
+
global: options.global,
|
|
3853
|
+
forPlugin: true
|
|
3854
|
+
}));
|
|
3734
3855
|
if (pD(selected)) {
|
|
3735
3856
|
xe("Installation cancelled");
|
|
3736
3857
|
await cleanup(tempDir);
|
|
@@ -3741,7 +3862,10 @@ async function runAdd(args, options = {}) {
|
|
|
3741
3862
|
targetAgents = ensureUniversalAgents(installedAgents);
|
|
3742
3863
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[installedAgents[0]].displayName)}`);
|
|
3743
3864
|
} else {
|
|
3744
|
-
const selected = await selectAgentsInteractive({
|
|
3865
|
+
const selected = await selectAgentsInteractive({
|
|
3866
|
+
global: options.global,
|
|
3867
|
+
forPlugin: true
|
|
3868
|
+
});
|
|
3745
3869
|
if (pD(selected)) {
|
|
3746
3870
|
xe("Installation cancelled");
|
|
3747
3871
|
await cleanup(tempDir);
|
|
@@ -3776,8 +3900,8 @@ async function runAdd(args, options = {}) {
|
|
|
3776
3900
|
const summaryLines = [];
|
|
3777
3901
|
summaryLines.push(import_picocolors.default.cyan(shortenPath$2(previewCanonical, process.cwd())));
|
|
3778
3902
|
const agentNames = targetAgents.map((a) => agents[a].displayName);
|
|
3779
|
-
if (agentNames.length > 0) summaryLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList
|
|
3780
|
-
if (existsSync(previewCanonical)) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList
|
|
3903
|
+
if (agentNames.length > 0) summaryLines.push(` ${import_picocolors.default.dim("symlink →")} ${formatList(agentNames)}`);
|
|
3904
|
+
if (existsSync(previewCanonical)) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList(agentNames)}`);
|
|
3781
3905
|
console.log();
|
|
3782
3906
|
Me(summaryLines.join("\n"), "Installation Summary");
|
|
3783
3907
|
}
|
|
@@ -3848,7 +3972,7 @@ async function runAdd(args, options = {}) {
|
|
|
3848
3972
|
pluginPath: lockPluginPath,
|
|
3849
3973
|
computedHash: lockHash
|
|
3850
3974
|
});
|
|
3851
|
-
M.info(`Recorded in lock file: ${import_picocolors.default.dim(getSkillLockPath
|
|
3975
|
+
M.info(`Recorded in lock file: ${import_picocolors.default.dim(getSkillLockPath())}`);
|
|
3852
3976
|
} else {
|
|
3853
3977
|
await addPluginToLocalLock(lockPluginName, {
|
|
3854
3978
|
source: lockSource,
|
|
@@ -4225,16 +4349,21 @@ async function runAdd(args, options = {}) {
|
|
|
4225
4349
|
if (!groupedSummary[group]) groupedSummary[group] = [];
|
|
4226
4350
|
groupedSummary[group].push(skill);
|
|
4227
4351
|
} else ungroupedSummary.push(skill);
|
|
4228
|
-
const
|
|
4352
|
+
const _summaryScope = installGlobally ? "global" : "project";
|
|
4353
|
+
const _summaryStoreRoot = getStoreRoot(_summaryScope, cwd);
|
|
4229
4354
|
const printSkillSummary = (skills) => {
|
|
4230
4355
|
for (const skill of skills) {
|
|
4231
4356
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
4232
4357
|
const shortCanonical = shortenPath$2(join(_summaryStoreRoot, "skills", `${_summaryScopeNs}__${sanitizeName(skill.name)}`), cwd);
|
|
4233
4358
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
4234
|
-
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode
|
|
4359
|
+
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode, {
|
|
4360
|
+
skillName: skill.name,
|
|
4361
|
+
scope: _summaryScope,
|
|
4362
|
+
cwd
|
|
4363
|
+
}));
|
|
4235
4364
|
const skillOverwrites = overwriteStatus.get(skill.name);
|
|
4236
4365
|
const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
|
|
4237
|
-
if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList
|
|
4366
|
+
if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList(overwriteAgents)}`);
|
|
4238
4367
|
}
|
|
4239
4368
|
};
|
|
4240
4369
|
const sortedGroups = Object.keys(groupedSummary).sort();
|
|
@@ -4493,7 +4622,7 @@ async function runAdd(args, options = {}) {
|
|
|
4493
4622
|
const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
4494
4623
|
Me(resultLines.join("\n"), title);
|
|
4495
4624
|
if (symlinkFailures.length > 0) {
|
|
4496
|
-
M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList
|
|
4625
|
+
M.warn(import_picocolors.default.yellow(`Symlinks failed for: ${formatList(copiedAgents)}`));
|
|
4497
4626
|
M.message(import_picocolors.default.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
|
|
4498
4627
|
}
|
|
4499
4628
|
}
|
|
@@ -4627,7 +4756,7 @@ function parseAddOptions(args) {
|
|
|
4627
4756
|
const RESET$2 = "\x1B[0m";
|
|
4628
4757
|
const BOLD$2 = "\x1B[1m";
|
|
4629
4758
|
const DIM$2 = "\x1B[38;5;102m";
|
|
4630
|
-
const TEXT$
|
|
4759
|
+
const TEXT$2 = "\x1B[38;5;145m";
|
|
4631
4760
|
const CYAN$1 = "\x1B[36m";
|
|
4632
4761
|
const SEARCH_API_BASE = process.env.SKILLS_API_URL || "https://skills.sh";
|
|
4633
4762
|
function formatInstalls(count) {
|
|
@@ -4672,7 +4801,7 @@ async function runSearchPrompt(initialQuery = "") {
|
|
|
4672
4801
|
process.stdout.write(CLEAR_DOWN);
|
|
4673
4802
|
const lines = [];
|
|
4674
4803
|
const cursor = `${BOLD$2}_${RESET$2}`;
|
|
4675
|
-
lines.push(`${TEXT$
|
|
4804
|
+
lines.push(`${TEXT$2}Search skills:${RESET$2} ${query}${cursor}`);
|
|
4676
4805
|
lines.push("");
|
|
4677
4806
|
if (!query || query.length < 2) lines.push(`${DIM$2}Start typing to search (min 2 chars)${RESET$2}`);
|
|
4678
4807
|
else if (results.length === 0 && loading) lines.push(`${DIM$2}Searching...${RESET$2}`);
|
|
@@ -4683,7 +4812,7 @@ async function runSearchPrompt(initialQuery = "") {
|
|
|
4683
4812
|
const skill = visible[i];
|
|
4684
4813
|
const isSelected = i === selectedIndex;
|
|
4685
4814
|
const arrow = isSelected ? `${BOLD$2}>${RESET$2}` : " ";
|
|
4686
|
-
const name = isSelected ? `${BOLD$2}${skill.name}${RESET$2}` : `${TEXT$
|
|
4815
|
+
const name = isSelected ? `${BOLD$2}${skill.name}${RESET$2}` : `${TEXT$2}${skill.name}${RESET$2}`;
|
|
4687
4816
|
const source = skill.source ? ` ${DIM$2}${skill.source}${RESET$2}` : "";
|
|
4688
4817
|
const installs = formatInstalls(skill.installs);
|
|
4689
4818
|
const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$2}` : "";
|
|
@@ -4793,11 +4922,6 @@ ${DIM$2} 1) npx skills find [query]${RESET$2}
|
|
|
4793
4922
|
${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
4794
4923
|
if (query) {
|
|
4795
4924
|
const results = await searchSkillsAPI(query);
|
|
4796
|
-
track({
|
|
4797
|
-
event: "find",
|
|
4798
|
-
query,
|
|
4799
|
-
resultCount: String(results.length)
|
|
4800
|
-
});
|
|
4801
4925
|
if (results.length === 0) {
|
|
4802
4926
|
console.log(`${DIM$2}No skills found for "${query}"${RESET$2}`);
|
|
4803
4927
|
return;
|
|
@@ -4807,7 +4931,7 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
4807
4931
|
for (const skill of results.slice(0, 6)) {
|
|
4808
4932
|
const pkg = skill.source || skill.slug;
|
|
4809
4933
|
const installs = formatInstalls(skill.installs);
|
|
4810
|
-
console.log(`${TEXT$
|
|
4934
|
+
console.log(`${TEXT$2}${pkg}@${skill.name}${RESET$2}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
|
|
4811
4935
|
console.log(`${DIM$2}└ https://skills.sh/${skill.slug}${RESET$2}`);
|
|
4812
4936
|
console.log();
|
|
4813
4937
|
}
|
|
@@ -4818,12 +4942,6 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
4818
4942
|
console.log();
|
|
4819
4943
|
}
|
|
4820
4944
|
const selected = await runSearchPrompt();
|
|
4821
|
-
track({
|
|
4822
|
-
event: "find",
|
|
4823
|
-
query: "",
|
|
4824
|
-
resultCount: selected ? "1" : "0",
|
|
4825
|
-
interactive: "1"
|
|
4826
|
-
});
|
|
4827
4945
|
if (!selected) {
|
|
4828
4946
|
console.log(`${DIM$2}Search cancelled${RESET$2}`);
|
|
4829
4947
|
console.log();
|
|
@@ -4832,7 +4950,7 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
4832
4950
|
const pkg = selected.source || selected.slug;
|
|
4833
4951
|
const skillName = selected.name;
|
|
4834
4952
|
console.log();
|
|
4835
|
-
console.log(`${TEXT$
|
|
4953
|
+
console.log(`${TEXT$2}Installing ${BOLD$2}${skillName}${RESET$2} from ${DIM$2}${pkg}${RESET$2}...`);
|
|
4836
4954
|
console.log();
|
|
4837
4955
|
const { source, options } = parseAddOptions([
|
|
4838
4956
|
pkg,
|
|
@@ -4842,11 +4960,11 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
4842
4960
|
await runAdd(source, options);
|
|
4843
4961
|
console.log();
|
|
4844
4962
|
const info = getOwnerRepoFromString(pkg);
|
|
4845
|
-
if (info && await isRepoPublic(info.owner, info.repo)) console.log(`${DIM$2}View the skill at${RESET$2} ${TEXT$
|
|
4846
|
-
else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$
|
|
4963
|
+
if (info && await isRepoPublic(info.owner, info.repo)) console.log(`${DIM$2}View the skill at${RESET$2} ${TEXT$2}https://skills.sh/${selected.slug}${RESET$2}`);
|
|
4964
|
+
else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$2}https://skills.sh${RESET$2}`);
|
|
4847
4965
|
console.log();
|
|
4848
4966
|
}
|
|
4849
|
-
const isCancelled
|
|
4967
|
+
const isCancelled = (value) => typeof value === "symbol";
|
|
4850
4968
|
function shortenPath$1(fullPath, cwd) {
|
|
4851
4969
|
const home = homedir();
|
|
4852
4970
|
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
@@ -4997,7 +5115,7 @@ async function runSync(args, options = {}) {
|
|
|
4997
5115
|
initialSelected: [],
|
|
4998
5116
|
required: true
|
|
4999
5117
|
});
|
|
5000
|
-
if (isCancelled
|
|
5118
|
+
if (isCancelled(selected)) {
|
|
5001
5119
|
xe("Sync cancelled");
|
|
5002
5120
|
process.exit(0);
|
|
5003
5121
|
}
|
|
@@ -5023,7 +5141,7 @@ async function runSync(args, options = {}) {
|
|
|
5023
5141
|
initialSelected: installedAgents.filter((a) => allAgents.includes(a)),
|
|
5024
5142
|
required: true
|
|
5025
5143
|
});
|
|
5026
|
-
if (isCancelled
|
|
5144
|
+
if (isCancelled(selected)) {
|
|
5027
5145
|
xe("Sync cancelled");
|
|
5028
5146
|
process.exit(0);
|
|
5029
5147
|
}
|
|
@@ -5102,12 +5220,6 @@ async function runSync(args, options = {}) {
|
|
|
5102
5220
|
M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
|
|
5103
5221
|
for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
|
|
5104
5222
|
}
|
|
5105
|
-
track({
|
|
5106
|
-
event: "experimental_sync",
|
|
5107
|
-
skillCount: String(toInstall.length),
|
|
5108
|
-
successCount: String(successfulSkillNames.size),
|
|
5109
|
-
agents: targetAgents.join(",")
|
|
5110
|
-
});
|
|
5111
5223
|
console.log();
|
|
5112
5224
|
Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
|
|
5113
5225
|
}
|
|
@@ -5183,26 +5295,14 @@ async function runInstallFromLock(args) {
|
|
|
5183
5295
|
const RESET$1 = "\x1B[0m";
|
|
5184
5296
|
const BOLD$1 = "\x1B[1m";
|
|
5185
5297
|
const DIM$1 = "\x1B[38;5;102m";
|
|
5298
|
+
const TEXT$1 = "\x1B[38;5;145m";
|
|
5186
5299
|
const CYAN = "\x1B[36m";
|
|
5187
5300
|
const YELLOW = "\x1B[33m";
|
|
5188
|
-
function shortenPath(fullPath, cwd) {
|
|
5189
|
-
const home = homedir();
|
|
5190
|
-
if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
|
|
5191
|
-
if (fullPath.startsWith(cwd)) return "." + fullPath.slice(cwd.length);
|
|
5192
|
-
return fullPath;
|
|
5193
|
-
}
|
|
5194
|
-
function formatList(items, maxShow = 5) {
|
|
5195
|
-
if (items.length <= maxShow) return items.join(", ");
|
|
5196
|
-
const shown = items.slice(0, maxShow);
|
|
5197
|
-
const remaining = items.length - maxShow;
|
|
5198
|
-
return `${shown.join(", ")} +${remaining} more`;
|
|
5199
|
-
}
|
|
5200
5301
|
function parseListOptions(args) {
|
|
5201
5302
|
const options = {};
|
|
5202
5303
|
for (let i = 0; i < args.length; i++) {
|
|
5203
5304
|
const arg = args[i];
|
|
5204
|
-
if (arg === "
|
|
5205
|
-
else if (arg === "--json") options.json = true;
|
|
5305
|
+
if (arg === "--json") options.json = true;
|
|
5206
5306
|
else if (arg === "-a" || arg === "--agent") {
|
|
5207
5307
|
options.agent = options.agent || [];
|
|
5208
5308
|
while (i + 1 < args.length && !args[i + 1].startsWith("-")) options.agent.push(args[++i]);
|
|
@@ -5210,152 +5310,393 @@ function parseListOptions(args) {
|
|
|
5210
5310
|
}
|
|
5211
5311
|
return options;
|
|
5212
5312
|
}
|
|
5313
|
+
function shortenPath(fullPath, cwd) {
|
|
5314
|
+
const home = homedir();
|
|
5315
|
+
if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
|
|
5316
|
+
if (fullPath.startsWith(cwd)) return "." + fullPath.slice(cwd.length);
|
|
5317
|
+
return fullPath;
|
|
5318
|
+
}
|
|
5319
|
+
function ledgerToComponent$1(c, pluginCanonical) {
|
|
5320
|
+
switch (c.type) {
|
|
5321
|
+
case "skill": return {
|
|
5322
|
+
kind: "plugin-skill",
|
|
5323
|
+
pluginCanonical,
|
|
5324
|
+
skillName: c.name
|
|
5325
|
+
};
|
|
5326
|
+
case "agent-manifest": return {
|
|
5327
|
+
kind: "plugin-agent-manifest",
|
|
5328
|
+
pluginCanonical,
|
|
5329
|
+
filename: c.name
|
|
5330
|
+
};
|
|
5331
|
+
case "agent-deps-dir": return {
|
|
5332
|
+
kind: "plugin-agent-deps-dir",
|
|
5333
|
+
pluginCanonical,
|
|
5334
|
+
dirname: c.name
|
|
5335
|
+
};
|
|
5336
|
+
case "rule-manifest": return {
|
|
5337
|
+
kind: "plugin-rule-manifest",
|
|
5338
|
+
pluginCanonical,
|
|
5339
|
+
filename: c.name
|
|
5340
|
+
};
|
|
5341
|
+
case "rule-deps-dir": return {
|
|
5342
|
+
kind: "plugin-rule-deps-dir",
|
|
5343
|
+
pluginCanonical,
|
|
5344
|
+
dirname: c.name
|
|
5345
|
+
};
|
|
5346
|
+
default: return null;
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
async function entryHits(entryPath, canonicalRoot) {
|
|
5350
|
+
if (!entryPath) return false;
|
|
5351
|
+
try {
|
|
5352
|
+
const realEntry = await realpath(entryPath);
|
|
5353
|
+
let realCanon = canonicalRoot;
|
|
5354
|
+
try {
|
|
5355
|
+
realCanon = await realpath(canonicalRoot);
|
|
5356
|
+
} catch {}
|
|
5357
|
+
return realEntry === realCanon || realEntry.startsWith(realCanon + sep) || realEntry === canonicalRoot || realEntry.startsWith(canonicalRoot + sep);
|
|
5358
|
+
} catch {
|
|
5359
|
+
return false;
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
function standaloneSkillEntryPaths(skillName, agent, cwd) {
|
|
5363
|
+
const paths = [];
|
|
5364
|
+
if (agent === "claude-code") {
|
|
5365
|
+
paths.push(join(cwd, ".claude", "skills", skillName));
|
|
5366
|
+
paths.push(join(homedir(), ".claude", "skills", skillName));
|
|
5367
|
+
} else if (agent === "cursor") {
|
|
5368
|
+
paths.push(join(cwd, ".cursor", "skills", skillName));
|
|
5369
|
+
paths.push(join(homedir(), ".cursor", "skills", skillName));
|
|
5370
|
+
} else if (agent === "codex") {
|
|
5371
|
+
paths.push(join(cwd, ".agents", "skills", skillName));
|
|
5372
|
+
paths.push(join(homedir(), ".agents", "skills", skillName));
|
|
5373
|
+
}
|
|
5374
|
+
return paths;
|
|
5375
|
+
}
|
|
5376
|
+
async function collectPluginLinks(pluginCanonical, installedComponents, agentFilter, cwd) {
|
|
5377
|
+
const out = [];
|
|
5378
|
+
for (const ag of agentFilter) for (const c of installedComponents) {
|
|
5379
|
+
const comp = ledgerToComponent$1(c, pluginCanonical);
|
|
5380
|
+
if (!comp) continue;
|
|
5381
|
+
for (const scope of ["project", "global"]) {
|
|
5382
|
+
const t = getInstallTargets(ag, comp, scope, cwd);
|
|
5383
|
+
if (!t.entry) continue;
|
|
5384
|
+
if (await entryHits(t.entry, pluginCanonical)) out.push({
|
|
5385
|
+
agent: ag,
|
|
5386
|
+
entryPath: t.entry,
|
|
5387
|
+
componentType: c.type,
|
|
5388
|
+
componentName: c.name
|
|
5389
|
+
});
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
return out;
|
|
5393
|
+
}
|
|
5394
|
+
async function collectSkillLinks(canonical, skillName, agentFilter, cwd) {
|
|
5395
|
+
const out = [];
|
|
5396
|
+
for (const ag of agentFilter) for (const p of standaloneSkillEntryPaths(skillName, ag, cwd)) if (await entryHits(p, canonical)) out.push({
|
|
5397
|
+
agent: ag,
|
|
5398
|
+
entryPath: p
|
|
5399
|
+
});
|
|
5400
|
+
return out;
|
|
5401
|
+
}
|
|
5402
|
+
function uniqueAgents(links) {
|
|
5403
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5404
|
+
for (const l of links) seen.add(l.agent);
|
|
5405
|
+
return Array.from(seen);
|
|
5406
|
+
}
|
|
5407
|
+
async function scanStore(cwd, agentFilter) {
|
|
5408
|
+
const storeRoot = getStoreRoot();
|
|
5409
|
+
const pluginsDir = join(storeRoot, "plugins");
|
|
5410
|
+
const skillsDir = join(storeRoot, "skills");
|
|
5411
|
+
const agentList = agentFilter ?? Object.keys(agents);
|
|
5412
|
+
const plugins = [];
|
|
5413
|
+
const skills = [];
|
|
5414
|
+
let pluginDirs = [];
|
|
5415
|
+
try {
|
|
5416
|
+
pluginDirs = (await readdir(pluginsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
5417
|
+
} catch {}
|
|
5418
|
+
for (const dirName of pluginDirs) {
|
|
5419
|
+
const canonical = join(pluginsDir, dirName);
|
|
5420
|
+
const ledgerPath = join(canonical, ".forge-plugin.json");
|
|
5421
|
+
let ledger;
|
|
5422
|
+
try {
|
|
5423
|
+
ledger = JSON.parse(await readFile(ledgerPath, "utf-8"));
|
|
5424
|
+
} catch {
|
|
5425
|
+
continue;
|
|
5426
|
+
}
|
|
5427
|
+
if (!ledger.ref || !ledger.name) continue;
|
|
5428
|
+
const installedComponents = ledger.installed_components ?? [];
|
|
5429
|
+
const components = {
|
|
5430
|
+
skills: [],
|
|
5431
|
+
agents: [],
|
|
5432
|
+
rules: []
|
|
5433
|
+
};
|
|
5434
|
+
for (const c of installedComponents) if (c.type === "skill") components.skills.push(c.name);
|
|
5435
|
+
else if (c.type === "agent-manifest" || c.type === "agent-deps-dir") components.agents.push(c.name);
|
|
5436
|
+
else if (c.type === "rule-manifest" || c.type === "rule-deps-dir") components.rules.push(c.name);
|
|
5437
|
+
const links = await collectPluginLinks(canonical, installedComponents, agentList, cwd);
|
|
5438
|
+
plugins.push({
|
|
5439
|
+
name: ledger.name,
|
|
5440
|
+
ref: ledger.ref,
|
|
5441
|
+
canonical,
|
|
5442
|
+
components,
|
|
5443
|
+
agents: uniqueAgents(links),
|
|
5444
|
+
links
|
|
5445
|
+
});
|
|
5446
|
+
}
|
|
5447
|
+
let skillDirs = [];
|
|
5448
|
+
try {
|
|
5449
|
+
skillDirs = (await readdir(skillsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
5450
|
+
} catch {}
|
|
5451
|
+
for (const dirName of skillDirs) {
|
|
5452
|
+
const canonical = join(skillsDir, dirName);
|
|
5453
|
+
const sidecarPath = join(canonical, ".forge-source.json");
|
|
5454
|
+
let sidecar;
|
|
5455
|
+
try {
|
|
5456
|
+
sidecar = JSON.parse(await readFile(sidecarPath, "utf-8"));
|
|
5457
|
+
} catch {
|
|
5458
|
+
continue;
|
|
5459
|
+
}
|
|
5460
|
+
if (sidecar.type && sidecar.type !== "skill") continue;
|
|
5461
|
+
if (sidecar.parent_plugin_ref) continue;
|
|
5462
|
+
if (!sidecar.name) continue;
|
|
5463
|
+
const links = await collectSkillLinks(canonical, sidecar.name, agentList, cwd);
|
|
5464
|
+
skills.push({
|
|
5465
|
+
name: sidecar.name,
|
|
5466
|
+
ref: sidecar.ref,
|
|
5467
|
+
canonical,
|
|
5468
|
+
agents: uniqueAgents(links),
|
|
5469
|
+
links
|
|
5470
|
+
});
|
|
5471
|
+
}
|
|
5472
|
+
plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
5473
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
5474
|
+
return {
|
|
5475
|
+
plugins,
|
|
5476
|
+
skills
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
function pluralize(count, singular, plural) {
|
|
5480
|
+
return count === 1 ? `${count} ${singular}` : `${count} ${plural ?? singular + "s"}`;
|
|
5481
|
+
}
|
|
5482
|
+
function pluginComponentSummary(plugin) {
|
|
5483
|
+
const parts = [];
|
|
5484
|
+
parts.push(pluralize(plugin.components.skills.length, "skill"));
|
|
5485
|
+
parts.push(pluralize(plugin.components.agents.length, "agent"));
|
|
5486
|
+
parts.push(pluralize(plugin.components.rules.length, "rule"));
|
|
5487
|
+
return parts.join(" · ");
|
|
5488
|
+
}
|
|
5213
5489
|
async function runList(args) {
|
|
5214
5490
|
const options = parseListOptions(args);
|
|
5215
|
-
const
|
|
5491
|
+
const cwd = process.cwd();
|
|
5216
5492
|
let agentFilter;
|
|
5217
5493
|
if (options.agent && options.agent.length > 0) {
|
|
5218
5494
|
const validAgents = Object.keys(agents);
|
|
5219
|
-
const
|
|
5220
|
-
if (
|
|
5221
|
-
console.log(`${YELLOW}Invalid agents: ${
|
|
5495
|
+
const invalid = options.agent.filter((a) => !validAgents.includes(a));
|
|
5496
|
+
if (invalid.length > 0) {
|
|
5497
|
+
console.log(`${YELLOW}Invalid agents: ${invalid.join(", ")}${RESET$1}`);
|
|
5222
5498
|
console.log(`${DIM$1}Valid agents: ${validAgents.join(", ")}${RESET$1}`);
|
|
5223
5499
|
process.exit(1);
|
|
5224
5500
|
}
|
|
5225
5501
|
agentFilter = options.agent;
|
|
5226
5502
|
}
|
|
5227
|
-
const
|
|
5228
|
-
global: scope,
|
|
5229
|
-
agentFilter
|
|
5230
|
-
});
|
|
5503
|
+
const inventory = await scanStore(cwd, agentFilter);
|
|
5231
5504
|
if (options.json) {
|
|
5232
|
-
const
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5505
|
+
const out = {
|
|
5506
|
+
plugins: inventory.plugins.map((p) => ({
|
|
5507
|
+
name: p.name,
|
|
5508
|
+
ref: p.ref,
|
|
5509
|
+
canonical: p.canonical,
|
|
5510
|
+
agents: p.agents,
|
|
5511
|
+
components: p.components,
|
|
5512
|
+
links: p.links
|
|
5513
|
+
})),
|
|
5514
|
+
skills: inventory.skills.map((s) => ({
|
|
5515
|
+
name: s.name,
|
|
5516
|
+
ref: s.ref ?? null,
|
|
5517
|
+
canonical: s.canonical,
|
|
5518
|
+
agents: s.agents,
|
|
5519
|
+
links: s.links
|
|
5520
|
+
}))
|
|
5521
|
+
};
|
|
5522
|
+
console.log(JSON.stringify(out, null, 2));
|
|
5239
5523
|
return;
|
|
5240
5524
|
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
if (installedSkills.length === 0) {
|
|
5245
|
-
if (options.json) {
|
|
5246
|
-
console.log("[]");
|
|
5247
|
-
return;
|
|
5248
|
-
}
|
|
5249
|
-
console.log(`${DIM$1}No ${scopeLabel.toLowerCase()} skills found.${RESET$1}`);
|
|
5250
|
-
if (scope) console.log(`${DIM$1}Try listing project skills without -g${RESET$1}`);
|
|
5251
|
-
else console.log(`${DIM$1}Try listing global skills with -g${RESET$1}`);
|
|
5525
|
+
if (inventory.plugins.length === 0 && inventory.skills.length === 0) {
|
|
5526
|
+
console.log(`${DIM$1}No forge-managed skills or plugins installed.${RESET$1}`);
|
|
5527
|
+
console.log(`${DIM$1}Run${RESET$1} ${TEXT$1}add <ref>${RESET$1} ${DIM$1}to install one.${RESET$1}`);
|
|
5252
5528
|
return;
|
|
5253
5529
|
}
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
const
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
|
|
5263
|
-
console.log();
|
|
5264
|
-
const groupedSkills = {};
|
|
5265
|
-
const ungroupedSkills = [];
|
|
5266
|
-
for (const skill of installedSkills) {
|
|
5267
|
-
const lockEntry = lockedSkills[skill.name];
|
|
5268
|
-
if (lockEntry?.pluginName) {
|
|
5269
|
-
const group = lockEntry.pluginName;
|
|
5270
|
-
if (!groupedSkills[group]) groupedSkills[group] = [];
|
|
5271
|
-
groupedSkills[group].push(skill);
|
|
5272
|
-
} else ungroupedSkills.push(skill);
|
|
5273
|
-
}
|
|
5274
|
-
if (Object.keys(groupedSkills).length > 0) {
|
|
5275
|
-
const sortedGroups = Object.keys(groupedSkills).sort();
|
|
5276
|
-
for (const group of sortedGroups) {
|
|
5277
|
-
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5278
|
-
console.log(`${BOLD$1}${title}${RESET$1}`);
|
|
5279
|
-
const skills = groupedSkills[group];
|
|
5280
|
-
if (skills) for (const skill of skills) printSkill(skill, true);
|
|
5530
|
+
if (inventory.plugins.length > 0) {
|
|
5531
|
+
console.log(`${BOLD$1}Plugins${RESET$1}`);
|
|
5532
|
+
for (const plugin of inventory.plugins) {
|
|
5533
|
+
const safeName = sanitizeMetadata(plugin.name);
|
|
5534
|
+
console.log(` ${CYAN}${safeName}${RESET$1} ${DIM$1}${plugin.ref}${RESET$1}`);
|
|
5535
|
+
console.log(` ${DIM$1}${shortenPath(plugin.canonical, cwd)}${RESET$1}`);
|
|
5536
|
+
console.log(` ${DIM$1}Components:${RESET$1} ${pluginComponentSummary(plugin)}`);
|
|
5537
|
+
printLinkedFrom(plugin.links, cwd);
|
|
5281
5538
|
console.log();
|
|
5282
5539
|
}
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5540
|
+
}
|
|
5541
|
+
if (inventory.skills.length > 0) {
|
|
5542
|
+
console.log(`${BOLD$1}Skills${RESET$1}`);
|
|
5543
|
+
for (const skill of inventory.skills) {
|
|
5544
|
+
const safeName = sanitizeMetadata(skill.name);
|
|
5545
|
+
const refTail = skill.ref ? ` ${DIM$1}${skill.ref}${RESET$1}` : "";
|
|
5546
|
+
console.log(` ${CYAN}${safeName}${RESET$1}${refTail}`);
|
|
5547
|
+
console.log(` ${DIM$1}${shortenPath(skill.canonical, cwd)}${RESET$1}`);
|
|
5548
|
+
printLinkedFrom(skill.links, cwd);
|
|
5286
5549
|
console.log();
|
|
5287
5550
|
}
|
|
5288
|
-
} else {
|
|
5289
|
-
for (const skill of installedSkills) printSkill(skill);
|
|
5290
|
-
console.log();
|
|
5291
5551
|
}
|
|
5292
5552
|
}
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5553
|
+
function printLinkedFrom(links, cwd) {
|
|
5554
|
+
if (links.length === 0) {
|
|
5555
|
+
console.log(` ${DIM$1}Linked from:${RESET$1} ${YELLOW}(none)${RESET$1}`);
|
|
5556
|
+
return;
|
|
5557
|
+
}
|
|
5558
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5559
|
+
for (const l of links) {
|
|
5560
|
+
const list = grouped.get(l.agent) ?? [];
|
|
5561
|
+
list.push(shortenPath(l.entryPath, cwd));
|
|
5562
|
+
grouped.set(l.agent, list);
|
|
5563
|
+
}
|
|
5564
|
+
if (grouped.size === 1) {
|
|
5565
|
+
const [agent, paths] = grouped.entries().next().value;
|
|
5566
|
+
if (paths.length === 1) {
|
|
5567
|
+
console.log(` ${DIM$1}Linked from:${RESET$1} ${agents[agent].displayName} ${DIM$1}${paths[0]}${RESET$1}`);
|
|
5568
|
+
return;
|
|
5306
5569
|
}
|
|
5307
|
-
const nonPluginNames = skillNames.filter((n) => !pluginRefRe.test(n));
|
|
5308
|
-
if (nonPluginNames.length === 0) return;
|
|
5309
|
-
skillNames = nonPluginNames;
|
|
5310
5570
|
}
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
const
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
if (err instanceof Error && err.code !== "ENOENT") M.warn(`Could not scan directory ${dir}: ${err.message}`);
|
|
5571
|
+
console.log(` ${DIM$1}Linked from:${RESET$1}`);
|
|
5572
|
+
const labelWidth = Math.max(...Array.from(grouped.keys()).map((a) => agents[a].displayName.length));
|
|
5573
|
+
for (const [agent, paths] of grouped) {
|
|
5574
|
+
const label = agents[agent].displayName.padEnd(labelWidth);
|
|
5575
|
+
if (paths.length === 1) console.log(` ${label} ${DIM$1}${paths[0]}${RESET$1}`);
|
|
5576
|
+
else {
|
|
5577
|
+
console.log(` ${label}`);
|
|
5578
|
+
for (const p of paths) console.log(` ${DIM$1}${p}${RESET$1}`);
|
|
5320
5579
|
}
|
|
5321
|
-
};
|
|
5322
|
-
if (isGlobal) {
|
|
5323
|
-
await scanDir(getCanonicalSkillsDir(true, cwd));
|
|
5324
|
-
for (const agent of Object.values(agents)) if (agent.globalSkillsDir !== void 0) await scanDir(agent.globalSkillsDir);
|
|
5325
|
-
} else {
|
|
5326
|
-
await scanDir(getCanonicalSkillsDir(false, cwd));
|
|
5327
|
-
for (const agent of Object.values(agents)) await scanDir(join(cwd, agent.skillsDir));
|
|
5328
5580
|
}
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5581
|
+
}
|
|
5582
|
+
const PLUGIN_REF_RE = /^@(?:public|teams\/[^/]+)\/plugins\/[^/]+/;
|
|
5583
|
+
function isPluginRef(s) {
|
|
5584
|
+
return PLUGIN_REF_RE.test(s);
|
|
5585
|
+
}
|
|
5586
|
+
function parseRemoveOptions(args) {
|
|
5587
|
+
const options = {};
|
|
5588
|
+
const skills = [];
|
|
5589
|
+
for (let i = 0; i < args.length; i++) {
|
|
5590
|
+
const arg = args[i];
|
|
5591
|
+
if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
5592
|
+
else if (arg === "--all") options.all = true;
|
|
5593
|
+
else if (arg === "-a" || arg === "--agent") {
|
|
5594
|
+
options.agent = options.agent || [];
|
|
5595
|
+
i++;
|
|
5596
|
+
let nextArg = args[i];
|
|
5597
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
5598
|
+
options.agent.push(nextArg);
|
|
5599
|
+
i++;
|
|
5600
|
+
nextArg = args[i];
|
|
5601
|
+
}
|
|
5602
|
+
i--;
|
|
5603
|
+
} else if (arg && !arg.startsWith("-")) skills.push(arg);
|
|
5334
5604
|
}
|
|
5605
|
+
return {
|
|
5606
|
+
skills,
|
|
5607
|
+
options
|
|
5608
|
+
};
|
|
5609
|
+
}
|
|
5610
|
+
function findOwningPlugin(name, inventory) {
|
|
5611
|
+
const lower = name.toLowerCase();
|
|
5612
|
+
for (const plugin of inventory.plugins) if (plugin.components.skills.some((s) => s.toLowerCase() === lower) || plugin.components.agents.some((a) => a.toLowerCase() === lower) || plugin.components.rules.some((r) => r.toLowerCase() === lower)) return plugin;
|
|
5613
|
+
return null;
|
|
5614
|
+
}
|
|
5615
|
+
async function removeCommand(skillNames, options) {
|
|
5616
|
+
const cwd = process.cwd();
|
|
5335
5617
|
if (options.agent && options.agent.length > 0) {
|
|
5336
5618
|
const validAgents = Object.keys(agents);
|
|
5337
|
-
const
|
|
5338
|
-
if (
|
|
5339
|
-
M.error(`Invalid agents: ${
|
|
5619
|
+
const invalid = options.agent.filter((a) => !validAgents.includes(a));
|
|
5620
|
+
if (invalid.length > 0) {
|
|
5621
|
+
M.error(`Invalid agents: ${invalid.join(", ")}`);
|
|
5340
5622
|
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
5341
5623
|
process.exit(1);
|
|
5342
5624
|
}
|
|
5343
5625
|
}
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5626
|
+
const inventory = await scanStore(cwd);
|
|
5627
|
+
const targets = [];
|
|
5628
|
+
const errors = [];
|
|
5629
|
+
const explicitPluginRefs = skillNames.filter(isPluginRef);
|
|
5630
|
+
const bareNames = skillNames.filter((n) => !isPluginRef(n));
|
|
5631
|
+
for (const ref of explicitPluginRefs) {
|
|
5632
|
+
const plugin = inventory.plugins.find((pl) => pl.ref === ref);
|
|
5633
|
+
if (plugin) targets.push({
|
|
5634
|
+
kind: "plugin",
|
|
5635
|
+
plugin
|
|
5636
|
+
});
|
|
5637
|
+
else errors.push(`No installed plugin matching ref ${ref}`);
|
|
5638
|
+
}
|
|
5639
|
+
if (options.all) {
|
|
5640
|
+
if (bareNames.length > 0) errors.push("--all cannot be combined with positional names");
|
|
5641
|
+
for (const plugin of inventory.plugins) targets.push({
|
|
5642
|
+
kind: "plugin",
|
|
5643
|
+
plugin
|
|
5644
|
+
});
|
|
5645
|
+
for (const skill of inventory.skills) targets.push({
|
|
5646
|
+
kind: "skill",
|
|
5647
|
+
skill
|
|
5648
|
+
});
|
|
5649
|
+
} else if (bareNames.length > 0) for (const name of bareNames) {
|
|
5650
|
+
const lower = name.toLowerCase();
|
|
5651
|
+
const plugin = inventory.plugins.find((pl) => pl.name.toLowerCase() === lower);
|
|
5652
|
+
if (plugin) {
|
|
5653
|
+
targets.push({
|
|
5654
|
+
kind: "plugin",
|
|
5655
|
+
plugin
|
|
5656
|
+
});
|
|
5657
|
+
continue;
|
|
5658
|
+
}
|
|
5659
|
+
const skill = inventory.skills.find((s) => s.name.toLowerCase() === lower);
|
|
5660
|
+
if (skill) {
|
|
5661
|
+
targets.push({
|
|
5662
|
+
kind: "skill",
|
|
5663
|
+
skill
|
|
5664
|
+
});
|
|
5665
|
+
continue;
|
|
5666
|
+
}
|
|
5667
|
+
const owner = findOwningPlugin(name, inventory);
|
|
5668
|
+
if (owner) {
|
|
5669
|
+
errors.push(`${name} belongs to plugin ${owner.ref}\n Cannot remove individual plugin components.\n Run: remove ${owner.ref}`);
|
|
5670
|
+
continue;
|
|
5671
|
+
}
|
|
5672
|
+
errors.push(`No forge-managed skill or plugin matching '${name}'.`);
|
|
5673
|
+
}
|
|
5674
|
+
else if (explicitPluginRefs.length === 0) {
|
|
5675
|
+
if (inventory.plugins.length === 0 && inventory.skills.length === 0) {
|
|
5676
|
+
Se(import_picocolors.default.yellow("No skills or plugins installed."));
|
|
5350
5677
|
return;
|
|
5351
5678
|
}
|
|
5352
|
-
|
|
5353
|
-
const
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5679
|
+
const choices = [];
|
|
5680
|
+
for (const plugin of inventory.plugins) {
|
|
5681
|
+
const summary = `${plugin.components.skills.length} skill(s), ${plugin.components.agents.length} agent(s), ${plugin.components.rules.length} rule(s)`;
|
|
5682
|
+
choices.push({
|
|
5683
|
+
value: {
|
|
5684
|
+
kind: "plugin",
|
|
5685
|
+
plugin
|
|
5686
|
+
},
|
|
5687
|
+
label: `[plugin] ${plugin.name}`,
|
|
5688
|
+
hint: summary
|
|
5689
|
+
});
|
|
5690
|
+
}
|
|
5691
|
+
for (const skill of inventory.skills) choices.push({
|
|
5692
|
+
value: {
|
|
5693
|
+
kind: "skill",
|
|
5694
|
+
skill
|
|
5695
|
+
},
|
|
5696
|
+
label: `[skill] ${skill.name}`
|
|
5697
|
+
});
|
|
5357
5698
|
const selected = await fe({
|
|
5358
|
-
message: `Select
|
|
5699
|
+
message: `Select what to remove ${import_picocolors.default.dim("(space to toggle)")}`,
|
|
5359
5700
|
options: choices,
|
|
5360
5701
|
required: true
|
|
5361
5702
|
});
|
|
@@ -5363,172 +5704,157 @@ async function removeCommand(skillNames, options) {
|
|
|
5363
5704
|
xe("Removal cancelled");
|
|
5364
5705
|
process.exit(0);
|
|
5365
5706
|
}
|
|
5366
|
-
|
|
5707
|
+
targets.push(...selected);
|
|
5367
5708
|
}
|
|
5368
|
-
|
|
5369
|
-
if (
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`);
|
|
5709
|
+
for (const err of errors) M.error(err);
|
|
5710
|
+
if (targets.length === 0) {
|
|
5711
|
+
if (errors.length === 0) Se(import_picocolors.default.yellow("Nothing to remove."));
|
|
5712
|
+
return;
|
|
5373
5713
|
}
|
|
5374
5714
|
if (!options.yes) {
|
|
5375
5715
|
console.log();
|
|
5376
|
-
M.info("
|
|
5377
|
-
for (const
|
|
5716
|
+
M.info("Planned removals:");
|
|
5717
|
+
for (const t of targets) {
|
|
5718
|
+
const tag = t.kind === "plugin" ? "[plugin]" : "[skill] ";
|
|
5719
|
+
const name = t.kind === "plugin" ? t.plugin?.name : t.skill?.name;
|
|
5720
|
+
const ref = t.kind === "plugin" ? t.plugin?.ref : t.skill?.ref;
|
|
5721
|
+
const refTail = ref ? ` ${import_picocolors.default.dim(ref)}` : "";
|
|
5722
|
+
M.message(` ${import_picocolors.default.red("•")} ${tag} ${name}${refTail}`);
|
|
5723
|
+
}
|
|
5378
5724
|
console.log();
|
|
5379
|
-
const confirmed = await ye({ message: `
|
|
5725
|
+
const confirmed = await ye({ message: `Uninstall ${targets.length} item(s)?` });
|
|
5380
5726
|
if (pD(confirmed) || !confirmed) {
|
|
5381
5727
|
xe("Removal cancelled");
|
|
5382
5728
|
process.exit(0);
|
|
5383
5729
|
}
|
|
5384
5730
|
}
|
|
5385
|
-
spinner
|
|
5731
|
+
const spinner = Y();
|
|
5732
|
+
spinner.start("Removing...");
|
|
5386
5733
|
const results = [];
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5734
|
+
const agentFilter = options.agent ?? null;
|
|
5735
|
+
for (const t of targets) if (t.kind === "plugin" && t.plugin) try {
|
|
5736
|
+
await uninstallPlugin(t.plugin, cwd, agentFilter);
|
|
5737
|
+
results.push({
|
|
5738
|
+
name: t.plugin.name,
|
|
5739
|
+
kind: "plugin",
|
|
5740
|
+
success: true
|
|
5391
5741
|
});
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
const pathsToCleanup = new Set([skillPath]);
|
|
5399
|
-
const sanitizedName = sanitizeName(skillName);
|
|
5400
|
-
if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
|
|
5401
|
-
else pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
|
|
5402
|
-
for (const pathToCleanup of pathsToCleanup) {
|
|
5403
|
-
if (pathToCleanup === canonicalPath) continue;
|
|
5404
|
-
try {
|
|
5405
|
-
if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {
|
|
5406
|
-
recursive: true,
|
|
5407
|
-
force: true
|
|
5408
|
-
});
|
|
5409
|
-
} catch (err) {
|
|
5410
|
-
M.warn(`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5411
|
-
}
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
|
|
5415
|
-
let isStillUsed = false;
|
|
5416
|
-
for (const agentKey of remainingAgents) if (await lstat(getInstallPath(skillName, agentKey, {
|
|
5417
|
-
global: isGlobal,
|
|
5418
|
-
cwd
|
|
5419
|
-
})).catch(() => null)) {
|
|
5420
|
-
isStillUsed = true;
|
|
5421
|
-
break;
|
|
5422
|
-
}
|
|
5423
|
-
if (!isStillUsed) await rm(canonicalPath, {
|
|
5424
|
-
recursive: true,
|
|
5425
|
-
force: true
|
|
5742
|
+
} catch (err) {
|
|
5743
|
+
results.push({
|
|
5744
|
+
name: t.plugin.name,
|
|
5745
|
+
kind: "plugin",
|
|
5746
|
+
success: false,
|
|
5747
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5426
5748
|
});
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
if (isGlobal) await removeSkillFromLock(skillName);
|
|
5749
|
+
}
|
|
5750
|
+
else if (t.kind === "skill" && t.skill) try {
|
|
5751
|
+
await uninstallStandaloneSkill(t.skill, cwd, agentFilter);
|
|
5431
5752
|
results.push({
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
sourceType: effectiveSourceType
|
|
5753
|
+
name: t.skill.name,
|
|
5754
|
+
kind: "skill",
|
|
5755
|
+
success: true
|
|
5436
5756
|
});
|
|
5437
5757
|
} catch (err) {
|
|
5438
5758
|
results.push({
|
|
5439
|
-
|
|
5759
|
+
name: t.skill.name,
|
|
5760
|
+
kind: "skill",
|
|
5440
5761
|
success: false,
|
|
5441
5762
|
error: err instanceof Error ? err.message : String(err)
|
|
5442
5763
|
});
|
|
5443
5764
|
}
|
|
5444
|
-
spinner.stop("
|
|
5765
|
+
spinner.stop("Done");
|
|
5445
5766
|
const successful = results.filter((r) => r.success);
|
|
5446
5767
|
const failed = results.filter((r) => !r.success);
|
|
5447
|
-
if (successful.length > 0) {
|
|
5448
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
5449
|
-
for (const r of successful) {
|
|
5450
|
-
const source = r.source || "local";
|
|
5451
|
-
const existing = bySource.get(source) || { skills: [] };
|
|
5452
|
-
existing.skills.push(r.skill);
|
|
5453
|
-
existing.sourceType = r.sourceType;
|
|
5454
|
-
bySource.set(source, existing);
|
|
5455
|
-
}
|
|
5456
|
-
for (const [source, data] of bySource) track({
|
|
5457
|
-
event: "remove",
|
|
5458
|
-
source,
|
|
5459
|
-
skills: data.skills.join(","),
|
|
5460
|
-
agents: targetAgents.join(","),
|
|
5461
|
-
...isGlobal && { global: "1" },
|
|
5462
|
-
sourceType: data.sourceType
|
|
5463
|
-
});
|
|
5464
|
-
}
|
|
5465
|
-
if (successful.length > 0) M.success(import_picocolors.default.green(`Successfully removed ${successful.length} skill(s)`));
|
|
5768
|
+
if (successful.length > 0) M.success(import_picocolors.default.green(`Successfully removed ${successful.length} item(s)`));
|
|
5466
5769
|
if (failed.length > 0) {
|
|
5467
|
-
M.error(import_picocolors.default.red(`Failed to remove ${failed.length}
|
|
5468
|
-
for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.
|
|
5770
|
+
M.error(import_picocolors.default.red(`Failed to remove ${failed.length} item(s)`));
|
|
5771
|
+
for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.name}: ${r.error}`);
|
|
5469
5772
|
}
|
|
5470
5773
|
console.log();
|
|
5471
5774
|
Se(import_picocolors.default.green("Done!"));
|
|
5472
5775
|
}
|
|
5473
|
-
function
|
|
5474
|
-
const
|
|
5475
|
-
const
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
else if (arg === "-a" || arg === "--agent") {
|
|
5482
|
-
options.agent = options.agent || [];
|
|
5483
|
-
i++;
|
|
5484
|
-
let nextArg = args[i];
|
|
5485
|
-
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
5486
|
-
options.agent.push(nextArg);
|
|
5487
|
-
i++;
|
|
5488
|
-
nextArg = args[i];
|
|
5489
|
-
}
|
|
5490
|
-
i--;
|
|
5491
|
-
} else if (arg && !arg.startsWith("-")) skills.push(arg);
|
|
5492
|
-
}
|
|
5493
|
-
return {
|
|
5494
|
-
skills,
|
|
5495
|
-
options
|
|
5496
|
-
};
|
|
5497
|
-
}
|
|
5498
|
-
async function uninstallPlugin(ref, scope, cwd) {
|
|
5499
|
-
const { scopeNamespace, pluginName } = parsePluginRef(ref);
|
|
5500
|
-
const storeRoot = getStoreRoot(scope, cwd);
|
|
5501
|
-
const pluginCanonical = join(storeRoot, "plugins", `${scopeNamespace}__${pluginName}`);
|
|
5502
|
-
const ledgerPath = join(pluginCanonical, ".forge-plugin.json");
|
|
5503
|
-
let ledger;
|
|
5504
|
-
try {
|
|
5505
|
-
ledger = JSON.parse(await readFile(ledgerPath, "utf-8"));
|
|
5506
|
-
} catch {
|
|
5507
|
-
throw new Error(`plugin not installed (no ledger at ${ledgerPath})`);
|
|
5508
|
-
}
|
|
5509
|
-
for (const agent of ledger.install_modes) for (const c of ledger.installed_components) {
|
|
5510
|
-
const entryPath = resolveEntryPath(agent, c, pluginCanonical, scope, cwd);
|
|
5511
|
-
if (!entryPath) continue;
|
|
5512
|
-
const status = await assertEntryReplaceable(entryPath, {
|
|
5513
|
-
intendedRef: ref,
|
|
5514
|
-
intendedCanonical: pluginCanonical,
|
|
5776
|
+
async function uninstallStandaloneSkill(skill, cwd, agentFilter) {
|
|
5777
|
+
const storeRoot = getStoreRoot();
|
|
5778
|
+
const intendedRef = skill.ref ?? `<store>/${skill.name}`;
|
|
5779
|
+
const candidates = standaloneSkillEntryCandidates(skill.name, cwd, agentFilter);
|
|
5780
|
+
for (const entry of candidates) try {
|
|
5781
|
+
const status = await assertEntryReplaceable(entry, {
|
|
5782
|
+
intendedRef,
|
|
5783
|
+
intendedCanonical: skill.canonical,
|
|
5515
5784
|
storeRoot
|
|
5516
5785
|
});
|
|
5517
|
-
if (status.kind === "
|
|
5518
|
-
|
|
5519
|
-
recursive: true,
|
|
5520
|
-
force: true
|
|
5521
|
-
});
|
|
5522
|
-
} catch {}
|
|
5523
|
-
else if (status.kind !== "absent") M.warn(`skipping ${entryPath} (${status.kind}) — not deleted (preserved by forge)`);
|
|
5524
|
-
}
|
|
5525
|
-
if (ledger.install_modes.includes("codex")) await cleanupCodexConfig(ledger, scope, cwd, scopeNamespace, pluginName);
|
|
5526
|
-
try {
|
|
5527
|
-
await rm(pluginCanonical, {
|
|
5786
|
+
if (status.kind === "absent") continue;
|
|
5787
|
+
if (status.kind === "forge-self" || status.kind === "forge-other") await rm(entry, {
|
|
5528
5788
|
recursive: true,
|
|
5529
5789
|
force: true
|
|
5530
5790
|
});
|
|
5791
|
+
else M.warn(`skipping ${entry} (${status.kind}) — not deleted (preserved by forge)`);
|
|
5531
5792
|
} catch {}
|
|
5793
|
+
await rm(skill.canonical, {
|
|
5794
|
+
recursive: true,
|
|
5795
|
+
force: true
|
|
5796
|
+
});
|
|
5797
|
+
try {
|
|
5798
|
+
await removeSkillFromLock(skill.name);
|
|
5799
|
+
} catch {}
|
|
5800
|
+
try {
|
|
5801
|
+
await removeSkillFromLocalLock(skill.name, cwd);
|
|
5802
|
+
} catch {}
|
|
5803
|
+
}
|
|
5804
|
+
function standaloneSkillEntryCandidates(skillName, cwd, agentFilter) {
|
|
5805
|
+
const home = homedir();
|
|
5806
|
+
const list = agentFilter ?? Object.keys(agents);
|
|
5807
|
+
const out = [];
|
|
5808
|
+
for (const ag of list) if (ag === "claude-code") {
|
|
5809
|
+
out.push(join(cwd, ".claude", "skills", skillName));
|
|
5810
|
+
out.push(join(home, ".claude", "skills", skillName));
|
|
5811
|
+
} else if (ag === "cursor") {
|
|
5812
|
+
out.push(join(cwd, ".cursor", "skills", skillName));
|
|
5813
|
+
out.push(join(home, ".cursor", "skills", skillName));
|
|
5814
|
+
} else if (ag === "codex") {
|
|
5815
|
+
out.push(join(cwd, ".agents", "skills", skillName));
|
|
5816
|
+
out.push(join(home, ".agents", "skills", skillName));
|
|
5817
|
+
}
|
|
5818
|
+
return out;
|
|
5819
|
+
}
|
|
5820
|
+
async function uninstallPlugin(plugin, cwd, agentFilter) {
|
|
5821
|
+
const storeRoot = getStoreRoot();
|
|
5822
|
+
const ledgerPath = join(plugin.canonical, ".forge-plugin.json");
|
|
5823
|
+
let ledger;
|
|
5824
|
+
try {
|
|
5825
|
+
ledger = JSON.parse(await readFile(ledgerPath, "utf-8"));
|
|
5826
|
+
} catch {
|
|
5827
|
+
throw new Error(`plugin ledger missing or unreadable: ${ledgerPath}`);
|
|
5828
|
+
}
|
|
5829
|
+
const installModes = agentFilter ? ledger.install_modes.filter((a) => agentFilter.includes(a)) : ledger.install_modes;
|
|
5830
|
+
for (const agent of installModes) for (const c of ledger.installed_components) {
|
|
5831
|
+
const comp = ledgerToComponent(c, plugin.canonical);
|
|
5832
|
+
if (!comp) continue;
|
|
5833
|
+
for (const scope of ["project", "global"]) {
|
|
5834
|
+
const t = getInstallTargets(agent, comp, scope, cwd);
|
|
5835
|
+
if (!t.entry) continue;
|
|
5836
|
+
const intendedRef = `${plugin.ref}/${componentSubRef(comp)}`;
|
|
5837
|
+
const status = await assertEntryReplaceable(t.entry, {
|
|
5838
|
+
intendedRef,
|
|
5839
|
+
intendedCanonical: t.canonical,
|
|
5840
|
+
storeRoot
|
|
5841
|
+
});
|
|
5842
|
+
if (status.kind === "absent") continue;
|
|
5843
|
+
if (status.kind === "forge-self") try {
|
|
5844
|
+
await rm(t.entry, {
|
|
5845
|
+
recursive: true,
|
|
5846
|
+
force: true
|
|
5847
|
+
});
|
|
5848
|
+
} catch {}
|
|
5849
|
+
else M.warn(`skipping ${t.entry} (${status.kind}) — not deleted (preserved by forge)`);
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
if (installModes.includes("codex")) for (const scope of ["project", "global"]) await cleanupCodexConfigToml(ledger, scope, cwd);
|
|
5853
|
+
await rm(plugin.canonical, {
|
|
5854
|
+
recursive: true,
|
|
5855
|
+
force: true
|
|
5856
|
+
});
|
|
5857
|
+
const { pluginName } = parsePluginRef(plugin.ref);
|
|
5532
5858
|
try {
|
|
5533
5859
|
await removePluginFromLock(pluginName);
|
|
5534
5860
|
} catch {}
|
|
@@ -5536,75 +5862,63 @@ async function uninstallPlugin(ref, scope, cwd) {
|
|
|
5536
5862
|
await removePluginFromLocalLock(pluginName, cwd);
|
|
5537
5863
|
} catch {}
|
|
5538
5864
|
}
|
|
5539
|
-
function
|
|
5540
|
-
|
|
5865
|
+
function componentSubRef(c) {
|
|
5866
|
+
switch (c.kind) {
|
|
5867
|
+
case "plugin-skill": return `skills/${c.skillName}`;
|
|
5868
|
+
case "plugin-agent-manifest": return `agents/${c.filename}`;
|
|
5869
|
+
case "plugin-agent-deps-dir": return `agents/${c.dirname}`;
|
|
5870
|
+
case "plugin-rule-manifest": return `rules/${c.filename}`;
|
|
5871
|
+
case "plugin-rule-deps-dir": return `rules/${c.dirname}`;
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
function ledgerToComponent(c, pluginCanonical) {
|
|
5541
5875
|
switch (c.type) {
|
|
5542
|
-
case "skill":
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
};
|
|
5569
|
-
break;
|
|
5570
|
-
case "rule-deps-dir":
|
|
5571
|
-
component = {
|
|
5572
|
-
kind: "plugin-rule-deps-dir",
|
|
5573
|
-
pluginCanonical,
|
|
5574
|
-
dirname: c.name
|
|
5575
|
-
};
|
|
5576
|
-
break;
|
|
5577
|
-
default: return "";
|
|
5876
|
+
case "skill": return {
|
|
5877
|
+
kind: "plugin-skill",
|
|
5878
|
+
pluginCanonical,
|
|
5879
|
+
skillName: c.name
|
|
5880
|
+
};
|
|
5881
|
+
case "agent-manifest": return {
|
|
5882
|
+
kind: "plugin-agent-manifest",
|
|
5883
|
+
pluginCanonical,
|
|
5884
|
+
filename: c.name
|
|
5885
|
+
};
|
|
5886
|
+
case "agent-deps-dir": return {
|
|
5887
|
+
kind: "plugin-agent-deps-dir",
|
|
5888
|
+
pluginCanonical,
|
|
5889
|
+
dirname: c.name
|
|
5890
|
+
};
|
|
5891
|
+
case "rule-manifest": return {
|
|
5892
|
+
kind: "plugin-rule-manifest",
|
|
5893
|
+
pluginCanonical,
|
|
5894
|
+
filename: c.name
|
|
5895
|
+
};
|
|
5896
|
+
case "rule-deps-dir": return {
|
|
5897
|
+
kind: "plugin-rule-deps-dir",
|
|
5898
|
+
pluginCanonical,
|
|
5899
|
+
dirname: c.name
|
|
5900
|
+
};
|
|
5901
|
+
default: return null;
|
|
5578
5902
|
}
|
|
5579
|
-
return getInstallTargets(agent, component, scope, cwd).entry;
|
|
5580
5903
|
}
|
|
5581
|
-
async function
|
|
5582
|
-
const configToml = join(scope === "global" ? getAgentHome("codex") : join(cwd
|
|
5904
|
+
async function cleanupCodexConfigToml(ledger, scope, cwd) {
|
|
5905
|
+
const configToml = join(scope === "global" ? getAgentHome("codex") : join(cwd, ".codex"), "config.toml");
|
|
5583
5906
|
let tomlRaw = "";
|
|
5584
5907
|
try {
|
|
5585
5908
|
tomlRaw = await readFile(configToml, "utf-8");
|
|
5586
|
-
} catch {}
|
|
5587
|
-
if (tomlRaw) {
|
|
5588
|
-
let changed = false;
|
|
5589
|
-
for (const c of ledger.installed_components) if (c.type === "agent-manifest") {
|
|
5590
|
-
const id = c.name.replace(/\.md$/, "");
|
|
5591
|
-
const updated = removeCodexAgent(tomlRaw, id);
|
|
5592
|
-
if (updated !== tomlRaw) {
|
|
5593
|
-
tomlRaw = updated;
|
|
5594
|
-
changed = true;
|
|
5595
|
-
}
|
|
5596
|
-
}
|
|
5597
|
-
if (changed) await writeFile(configToml, tomlRaw, "utf-8");
|
|
5598
|
-
}
|
|
5599
|
-
const canonicalAgentsMd = join(getStoreRoot(scope, cwd), "codex-agents.md");
|
|
5600
|
-
let agentsRaw = "";
|
|
5601
|
-
try {
|
|
5602
|
-
agentsRaw = await readFile(canonicalAgentsMd, "utf-8");
|
|
5603
5909
|
} catch {
|
|
5604
5910
|
return;
|
|
5605
5911
|
}
|
|
5606
|
-
|
|
5607
|
-
|
|
5912
|
+
let changed = false;
|
|
5913
|
+
for (const c of ledger.installed_components) if (c.type === "agent-manifest") {
|
|
5914
|
+
const id = c.name.replace(/\.md$/, "");
|
|
5915
|
+
const updated = removeCodexAgent(tomlRaw, id);
|
|
5916
|
+
if (updated !== tomlRaw) {
|
|
5917
|
+
tomlRaw = updated;
|
|
5918
|
+
changed = true;
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
if (changed) await writeFile(configToml, tomlRaw, "utf-8");
|
|
5608
5922
|
}
|
|
5609
5923
|
function parsePluginRef(ref) {
|
|
5610
5924
|
let m = /^@public\/plugins\/(.+)$/.exec(ref);
|
|
@@ -5619,202 +5933,6 @@ function parsePluginRef(ref) {
|
|
|
5619
5933
|
};
|
|
5620
5934
|
throw new Error(`invalid plugin ref: ${ref}`);
|
|
5621
5935
|
}
|
|
5622
|
-
function formatSourceInput(sourceUrl, ref) {
|
|
5623
|
-
if (!ref) return sourceUrl;
|
|
5624
|
-
return `${sourceUrl}#${ref}`;
|
|
5625
|
-
}
|
|
5626
|
-
function deriveSkillFolder(skillPath) {
|
|
5627
|
-
let folder = skillPath;
|
|
5628
|
-
if (folder.endsWith("/SKILL.md")) folder = folder.slice(0, -9);
|
|
5629
|
-
else if (folder.endsWith("SKILL.md")) folder = folder.slice(0, -8);
|
|
5630
|
-
if (folder.endsWith("/")) folder = folder.slice(0, -1);
|
|
5631
|
-
return folder;
|
|
5632
|
-
}
|
|
5633
|
-
function appendFolderAndRef(source, skillPath, ref) {
|
|
5634
|
-
const folder = deriveSkillFolder(skillPath);
|
|
5635
|
-
const withFolder = folder ? `${source}/${folder}` : source;
|
|
5636
|
-
return ref ? `${withFolder}#${ref}` : withFolder;
|
|
5637
|
-
}
|
|
5638
|
-
function buildUpdateInstallSource(entry) {
|
|
5639
|
-
if (!entry.skillPath) return formatSourceInput(entry.sourceUrl, entry.ref);
|
|
5640
|
-
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
5641
|
-
}
|
|
5642
|
-
function buildLocalUpdateSource(entry) {
|
|
5643
|
-
if (!entry.skillPath) return formatSourceInput(entry.source, entry.ref);
|
|
5644
|
-
return appendFolderAndRef(entry.source, entry.skillPath, entry.ref);
|
|
5645
|
-
}
|
|
5646
|
-
function getRandomPort() {
|
|
5647
|
-
return new Promise((resolve, reject) => {
|
|
5648
|
-
const server = createServer();
|
|
5649
|
-
server.listen(0, "127.0.0.1", () => {
|
|
5650
|
-
const address = server.address();
|
|
5651
|
-
if (address && typeof address !== "string" && "port" in address) {
|
|
5652
|
-
const port = address.port;
|
|
5653
|
-
server.close(() => resolve(port));
|
|
5654
|
-
} else server.close(() => reject(/* @__PURE__ */ new Error("Failed to get port")));
|
|
5655
|
-
});
|
|
5656
|
-
server.on("error", reject);
|
|
5657
|
-
});
|
|
5658
|
-
}
|
|
5659
|
-
function getSuccessHtml() {
|
|
5660
|
-
return `
|
|
5661
|
-
<!DOCTYPE html>
|
|
5662
|
-
<html>
|
|
5663
|
-
<head>
|
|
5664
|
-
<meta charset="utf-8">
|
|
5665
|
-
<title>Login Successful</title>
|
|
5666
|
-
<style>
|
|
5667
|
-
body { font-family: system-ui, -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #10b981; color: white; }
|
|
5668
|
-
.container { text-align: center; padding: 2rem; }
|
|
5669
|
-
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
5670
|
-
p { opacity: 0.9; }
|
|
5671
|
-
.spinner { width: 24px; height: 24px; border: 2px solid rgba(255,255,255,0.3); border-top-color: white; border-radius: 50%; animation: spin 1s linear infinite; margin: 1rem auto; }
|
|
5672
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
5673
|
-
</style>
|
|
5674
|
-
</head>
|
|
5675
|
-
<body>
|
|
5676
|
-
<div class="container">
|
|
5677
|
-
<h1>Login Successful!</h1>
|
|
5678
|
-
<p>You can close this window and return to the CLI.</p>
|
|
5679
|
-
<div class="spinner"></div>
|
|
5680
|
-
</div>
|
|
5681
|
-
</body>
|
|
5682
|
-
</html>
|
|
5683
|
-
`.trim();
|
|
5684
|
-
}
|
|
5685
|
-
function getErrorHtml(error) {
|
|
5686
|
-
return `
|
|
5687
|
-
<!DOCTYPE html>
|
|
5688
|
-
<html>
|
|
5689
|
-
<head>
|
|
5690
|
-
<meta charset="utf-8">
|
|
5691
|
-
<title>Login Failed</title>
|
|
5692
|
-
<style>
|
|
5693
|
-
body { font-family: system-ui, -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #ef4444; color: white; }
|
|
5694
|
-
.container { text-align: center; padding: 2rem; }
|
|
5695
|
-
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
5696
|
-
p { opacity: 0.9; }
|
|
5697
|
-
</style>
|
|
5698
|
-
</head>
|
|
5699
|
-
<body>
|
|
5700
|
-
<div class="container">
|
|
5701
|
-
<h1>Login Failed</h1>
|
|
5702
|
-
<p>${import_picocolors.default.red(error)}</p>
|
|
5703
|
-
<p>Please try again.</p>
|
|
5704
|
-
</div>
|
|
5705
|
-
</body>
|
|
5706
|
-
</html>
|
|
5707
|
-
`.trim();
|
|
5708
|
-
}
|
|
5709
|
-
let callbackResolve = null;
|
|
5710
|
-
async function runLogin(options = {}) {
|
|
5711
|
-
const cfg = loadConfig();
|
|
5712
|
-
if (options.logout) {
|
|
5713
|
-
if (cfg.token) {
|
|
5714
|
-
saveConfig({
|
|
5715
|
-
token: void 0,
|
|
5716
|
-
token_name: void 0
|
|
5717
|
-
});
|
|
5718
|
-
M.success("Logged out successfully.");
|
|
5719
|
-
M.message(import_picocolors.default.dim("Your token has been removed from the config."));
|
|
5720
|
-
} else M.message("You are not logged in.");
|
|
5721
|
-
return;
|
|
5722
|
-
}
|
|
5723
|
-
if (cfg.token) {
|
|
5724
|
-
M.message("You are already logged in.");
|
|
5725
|
-
M.info(import_picocolors.default.dim(`Token: ${cfg.token_name || "default"}`));
|
|
5726
|
-
const shouldReauth = await ye({
|
|
5727
|
-
message: "Re-authenticate?",
|
|
5728
|
-
initialValue: false
|
|
5729
|
-
});
|
|
5730
|
-
if (isCancelled(shouldReauth)) {
|
|
5731
|
-
Se(import_picocolors.default.dim("Cancelled"));
|
|
5732
|
-
return;
|
|
5733
|
-
}
|
|
5734
|
-
if (!shouldReauth) return;
|
|
5735
|
-
}
|
|
5736
|
-
console.log();
|
|
5737
|
-
Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" login ")));
|
|
5738
|
-
const portalUrl = cfg.portal;
|
|
5739
|
-
const port = await getRandomPort();
|
|
5740
|
-
const redirectUrl = `http://127.0.0.1:${port}/callback`;
|
|
5741
|
-
const authUrl = `${portalUrl}/cli-auth?redirect=${encodeURIComponent(redirectUrl)}`;
|
|
5742
|
-
M.message(`Opening browser for authentication...`);
|
|
5743
|
-
M.info(import_picocolors.default.dim(authUrl));
|
|
5744
|
-
await new Promise((resolve, reject) => {
|
|
5745
|
-
exec(`open "${authUrl}"`, (err) => {
|
|
5746
|
-
if (err) reject(err);
|
|
5747
|
-
else resolve();
|
|
5748
|
-
});
|
|
5749
|
-
});
|
|
5750
|
-
const result = await new Promise((resolve) => {
|
|
5751
|
-
callbackResolve = resolve;
|
|
5752
|
-
const server = createServer((req, res) => {
|
|
5753
|
-
const url = new URL$1(req.url || "", `http://127.0.0.1:${port}`);
|
|
5754
|
-
if (url.pathname === "/callback") {
|
|
5755
|
-
const token = url.searchParams.get("token");
|
|
5756
|
-
const tokenName = url.searchParams.get("token_name");
|
|
5757
|
-
const error = url.searchParams.get("error");
|
|
5758
|
-
if (error) {
|
|
5759
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
5760
|
-
res.end(getErrorHtml(error));
|
|
5761
|
-
resolve({
|
|
5762
|
-
success: false,
|
|
5763
|
-
error
|
|
5764
|
-
});
|
|
5765
|
-
} else if (token) {
|
|
5766
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
5767
|
-
res.end(getSuccessHtml());
|
|
5768
|
-
resolve({
|
|
5769
|
-
success: true,
|
|
5770
|
-
token,
|
|
5771
|
-
tokenName: tokenName || "default"
|
|
5772
|
-
});
|
|
5773
|
-
} else {
|
|
5774
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
5775
|
-
res.end(getErrorHtml("No token received"));
|
|
5776
|
-
resolve({
|
|
5777
|
-
success: false,
|
|
5778
|
-
error: "No token received"
|
|
5779
|
-
});
|
|
5780
|
-
}
|
|
5781
|
-
} else {
|
|
5782
|
-
res.writeHead(404);
|
|
5783
|
-
res.end("Not found");
|
|
5784
|
-
}
|
|
5785
|
-
});
|
|
5786
|
-
server.listen(port, "127.0.0.1", () => {});
|
|
5787
|
-
setTimeout(() => {
|
|
5788
|
-
server.close();
|
|
5789
|
-
if (callbackResolve) callbackResolve({
|
|
5790
|
-
success: false,
|
|
5791
|
-
error: "Login timed out"
|
|
5792
|
-
});
|
|
5793
|
-
}, 12e4);
|
|
5794
|
-
});
|
|
5795
|
-
if (!result.success) {
|
|
5796
|
-
Se(import_picocolors.default.red("Login failed: " + result.error));
|
|
5797
|
-
process.exit(1);
|
|
5798
|
-
}
|
|
5799
|
-
saveConfig({
|
|
5800
|
-
token: result.token,
|
|
5801
|
-
token_name: result.tokenName
|
|
5802
|
-
});
|
|
5803
|
-
M.success("Login successful!");
|
|
5804
|
-
M.message(import_picocolors.default.dim(`Token saved as: ${result.tokenName}`));
|
|
5805
|
-
Se(import_picocolors.default.green("You can now publish skills."));
|
|
5806
|
-
}
|
|
5807
|
-
function isCancelled(value) {
|
|
5808
|
-
return typeof value === "symbol";
|
|
5809
|
-
}
|
|
5810
|
-
function parseLoginOptions(args) {
|
|
5811
|
-
const options = {};
|
|
5812
|
-
for (let i = 0; i < args.length; i++) {
|
|
5813
|
-
const arg = args[i];
|
|
5814
|
-
if (arg === "--logout" || arg === "-l") options.logout = true;
|
|
5815
|
-
}
|
|
5816
|
-
return options;
|
|
5817
|
-
}
|
|
5818
5936
|
var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
|
|
5819
5937
|
async function getBootstrap(opts = {}) {
|
|
5820
5938
|
const cfg = loadConfig();
|
|
@@ -5979,11 +6097,14 @@ function validatePath(path) {
|
|
|
5979
6097
|
return { valid: true };
|
|
5980
6098
|
}
|
|
5981
6099
|
async function runPublish(paths, options = {}) {
|
|
5982
|
-
|
|
6100
|
+
let cfg = loadConfig();
|
|
5983
6101
|
if (!cfg.token) {
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
6102
|
+
await runLogin({ force: true });
|
|
6103
|
+
cfg = loadConfig();
|
|
6104
|
+
if (!cfg.token) {
|
|
6105
|
+
M.error("Login required to publish.");
|
|
6106
|
+
process.exit(1);
|
|
6107
|
+
}
|
|
5987
6108
|
}
|
|
5988
6109
|
console.log();
|
|
5989
6110
|
Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" publish ")));
|
|
@@ -6032,14 +6153,25 @@ async function runPublish(paths, options = {}) {
|
|
|
6032
6153
|
form.set("asset_type", assetType);
|
|
6033
6154
|
form.set("file", blob, filename);
|
|
6034
6155
|
try {
|
|
6035
|
-
const
|
|
6156
|
+
const sendRequest = (token) => fetch(contributionsUrl, {
|
|
6036
6157
|
method: "POST",
|
|
6037
6158
|
headers: {
|
|
6038
|
-
Authorization: `Bearer ${
|
|
6159
|
+
Authorization: `Bearer ${token}`,
|
|
6039
6160
|
"User-Agent": "shoplazza-ai-dev-cli"
|
|
6040
6161
|
},
|
|
6041
6162
|
body: form
|
|
6042
6163
|
});
|
|
6164
|
+
let resp = await sendRequest(cfg.token);
|
|
6165
|
+
if (resp.status === 401) {
|
|
6166
|
+
await resp.arrayBuffer().catch(() => void 0);
|
|
6167
|
+
await runLogin({ force: true });
|
|
6168
|
+
cfg = loadConfig();
|
|
6169
|
+
if (!cfg.token) {
|
|
6170
|
+
M.error(`Failed to publish ${name}: login required`);
|
|
6171
|
+
continue;
|
|
6172
|
+
}
|
|
6173
|
+
resp = await sendRequest(cfg.token);
|
|
6174
|
+
}
|
|
6043
6175
|
if (!resp.ok) {
|
|
6044
6176
|
const errorText = await resp.text();
|
|
6045
6177
|
M.error(`Failed to publish ${name}: ${resp.status} ${errorText}`);
|
|
@@ -6049,12 +6181,6 @@ async function runPublish(paths, options = {}) {
|
|
|
6049
6181
|
M.success(`Published: ${import_picocolors.default.green(name)}`);
|
|
6050
6182
|
if (result?.asset_ref) M.message(import_picocolors.default.dim(`Asset: ${result.asset_ref}`));
|
|
6051
6183
|
if (result?.mr_iid) M.message(import_picocolors.default.dim(`MR !${result.mr_iid}`));
|
|
6052
|
-
track({
|
|
6053
|
-
event_type: "publish",
|
|
6054
|
-
asset_ref: result?.asset_ref ?? name,
|
|
6055
|
-
visibility,
|
|
6056
|
-
team_id: scope.startsWith("team:") ? scope.slice(5) : void 0
|
|
6057
|
-
});
|
|
6058
6184
|
} catch (err) {
|
|
6059
6185
|
const message = err instanceof Error ? err.message : String(err);
|
|
6060
6186
|
M.error(`Failed to publish ${name}: ${message}`);
|
|
@@ -6136,12 +6262,12 @@ function showBanner() {
|
|
|
6136
6262
|
console.log();
|
|
6137
6263
|
console.log(`${DIM}Shoplazza Forge skill CLI${RESET}`);
|
|
6138
6264
|
console.log();
|
|
6139
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli add ${DIM}<source>${RESET} ${DIM}Add a skill${RESET}`);
|
|
6140
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
6141
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli list${RESET} ${DIM}List installed skills${RESET}`);
|
|
6265
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli add ${DIM}<source>${RESET} ${DIM}Add a skill or plugin${RESET}`);
|
|
6266
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli remove${RESET} ${DIM}Remove installed skills or plugins${RESET}`);
|
|
6267
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli list${RESET} ${DIM}List installed skills and plugins${RESET}`);
|
|
6142
6268
|
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
|
|
6143
6269
|
console.log();
|
|
6144
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli update${RESET} ${DIM}
|
|
6270
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli update${RESET} ${DIM}Refresh installed skills and plugins${RESET}`);
|
|
6145
6271
|
console.log();
|
|
6146
6272
|
console.log(` ${DIM}$${RESET} ${TEXT}npx shoplazza-ai-dev-cli init ${DIM}[name]${RESET} ${DIM}Initialize a skill${RESET}`);
|
|
6147
6273
|
console.log();
|
|
@@ -6153,29 +6279,24 @@ function showHelp() {
|
|
|
6153
6279
|
${BOLD}Usage:${RESET} shoplazza-ai-dev-cli <command> [options]
|
|
6154
6280
|
|
|
6155
6281
|
${BOLD}Manage Skills:${RESET}
|
|
6156
|
-
add <source> Add a skill (alias: a)
|
|
6282
|
+
add <source> Add a skill or plugin (alias: a)
|
|
6157
6283
|
e.g. @public/skills --skill <name>
|
|
6158
6284
|
@teams/<scope>/skills --skill <name>
|
|
6159
|
-
@public/
|
|
6285
|
+
@public/plugins/<name>
|
|
6160
6286
|
https://gitlab.shoplazza.site/.../<repo>.git
|
|
6161
6287
|
add --team <scope> Install every skill owned by a team
|
|
6162
|
-
remove [
|
|
6163
|
-
list, ls List installed skills
|
|
6288
|
+
remove [names...] Remove installed skills or plugins
|
|
6289
|
+
list, ls List installed skills and plugins
|
|
6164
6290
|
find [query] Search for skills interactively
|
|
6165
6291
|
|
|
6166
6292
|
${BOLD}Updates:${RESET}
|
|
6167
|
-
update [
|
|
6168
|
-
|
|
6169
|
-
${BOLD}Update Options:${RESET}
|
|
6170
|
-
-g, --global Update global skills only
|
|
6171
|
-
-p, --project Update project skills only
|
|
6172
|
-
-y, --yes Skip scope prompt (auto-detect: project if in a project, else global)
|
|
6293
|
+
update [names...] Refresh installed skills/plugins from source (alias: upgrade)
|
|
6173
6294
|
|
|
6174
6295
|
${BOLD}Project:${RESET}
|
|
6175
6296
|
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
6176
6297
|
|
|
6177
6298
|
${BOLD}Add Options:${RESET}
|
|
6178
|
-
-g, --global Install
|
|
6299
|
+
-g, --global Install at global scope (entry symlinks under ~/)
|
|
6179
6300
|
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
6180
6301
|
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
|
|
6181
6302
|
-l, --list List available skills in the source without installing
|
|
@@ -6187,62 +6308,62 @@ ${BOLD}Add Options:${RESET}
|
|
|
6187
6308
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
6188
6309
|
|
|
6189
6310
|
${BOLD}Remove Options:${RESET}
|
|
6190
|
-
-
|
|
6191
|
-
-a, --agent <agents> Remove from specific agents (use '*' for all agents)
|
|
6192
|
-
-s, --skill <skills> Specify skills to remove (use '*' for all skills)
|
|
6311
|
+
-a, --agent <agents> Restrict cleanup to specific agents
|
|
6193
6312
|
-y, --yes Skip confirmation prompts
|
|
6194
|
-
--all
|
|
6313
|
+
--all Remove every installed skill and plugin
|
|
6195
6314
|
|
|
6196
6315
|
${BOLD}List Options:${RESET}
|
|
6197
|
-
-
|
|
6198
|
-
-a, --agent <agents> Filter by specific agents
|
|
6316
|
+
-a, --agent <agents> Filter the "Linked from" column by agent
|
|
6199
6317
|
--json Output as JSON (machine-readable, no ANSI codes)
|
|
6200
6318
|
|
|
6319
|
+
${BOLD}Update Options:${RESET}
|
|
6320
|
+
-y, --yes Skip confirmation prompts
|
|
6321
|
+
--json Machine-readable summary
|
|
6322
|
+
|
|
6201
6323
|
${BOLD}Options:${RESET}
|
|
6202
6324
|
--help, -h Show this help message
|
|
6203
6325
|
--version, -v Show version number
|
|
6204
6326
|
|
|
6205
6327
|
${BOLD}Examples:${RESET}
|
|
6206
6328
|
${DIM}$${RESET} shoplazza-ai-dev-cli add @public/skills --skill systematic-debugging
|
|
6207
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli add @public/
|
|
6208
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli add @public/skills --skill <name> --agent claude-code cursor
|
|
6329
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli add @public/plugins/forge-demo-plugin
|
|
6209
6330
|
${DIM}$${RESET} shoplazza-ai-dev-cli add @teams/infra/skills --skill <name>
|
|
6210
6331
|
${DIM}$${RESET} shoplazza-ai-dev-cli remove ${DIM}# interactive${RESET}
|
|
6211
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove my-skill ${DIM}#
|
|
6212
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli
|
|
6213
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli
|
|
6214
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli
|
|
6215
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli find typescript ${DIM}# search by keyword${RESET}
|
|
6332
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove my-skill ${DIM}# bare-name → resolved against store${RESET}
|
|
6333
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove @public/plugins/foo ${DIM}# explicit plugin ref${RESET}
|
|
6334
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli list ${DIM}# list everything in store${RESET}
|
|
6335
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli list --json
|
|
6216
6336
|
${DIM}$${RESET} shoplazza-ai-dev-cli update
|
|
6337
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli update some-skill
|
|
6217
6338
|
${DIM}$${RESET} shoplazza-ai-dev-cli init my-skill
|
|
6218
6339
|
`);
|
|
6219
6340
|
}
|
|
6220
6341
|
function showRemoveHelp() {
|
|
6221
6342
|
console.log(`
|
|
6222
|
-
${BOLD}Usage:${RESET} shoplazza-ai-dev-cli remove [
|
|
6343
|
+
${BOLD}Usage:${RESET} shoplazza-ai-dev-cli remove [names...] [options]
|
|
6223
6344
|
|
|
6224
6345
|
${BOLD}Description:${RESET}
|
|
6225
|
-
|
|
6226
|
-
|
|
6346
|
+
Resolves each name against the forge store (~/.ai-dev-cli/store):
|
|
6347
|
+
- explicit plugin ref or bare plugin name → cascades full plugin uninstall
|
|
6348
|
+
- bare standalone-skill name → uninstalls just that skill
|
|
6349
|
+
- bare component name owned by a plugin → refused (uninstall the plugin instead)
|
|
6227
6350
|
|
|
6228
6351
|
${BOLD}Arguments:${RESET}
|
|
6229
|
-
|
|
6352
|
+
names Optional skill / plugin names (space-separated). Empty triggers an
|
|
6353
|
+
interactive multiselect over everything currently in the store.
|
|
6230
6354
|
|
|
6231
6355
|
${BOLD}Options:${RESET}
|
|
6232
|
-
-
|
|
6233
|
-
-
|
|
6234
|
-
|
|
6235
|
-
-y, --yes Skip confirmation prompts
|
|
6236
|
-
--all Shorthand for --skill '*' --agent '*' -y
|
|
6356
|
+
-a, --agent Restrict entry cleanup to specific IDE agents
|
|
6357
|
+
-y, --yes Skip confirmation prompts
|
|
6358
|
+
--all Remove every installed skill and plugin
|
|
6237
6359
|
|
|
6238
6360
|
${BOLD}Examples:${RESET}
|
|
6239
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove ${DIM}# interactive
|
|
6240
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove my-skill
|
|
6241
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove skill1 skill2 -y
|
|
6242
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove
|
|
6243
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli rm --agent claude-code my-skill
|
|
6244
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove --all
|
|
6245
|
-
${DIM}$${RESET} shoplazza-ai-dev-cli remove --skill '*' -a cursor ${DIM}# remove all skills from cursor${RESET}
|
|
6361
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove ${DIM}# interactive${RESET}
|
|
6362
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove my-skill ${DIM}# resolve and remove${RESET}
|
|
6363
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove skill1 skill2 -y ${DIM}# remove several${RESET}
|
|
6364
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove @public/plugins/foo ${DIM}# explicit plugin ref${RESET}
|
|
6365
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli rm --agent claude-code my-skill ${DIM}# clean only Claude Code entry${RESET}
|
|
6366
|
+
${DIM}$${RESET} shoplazza-ai-dev-cli remove --all ${DIM}# remove everything${RESET}
|
|
6246
6367
|
`);
|
|
6247
6368
|
}
|
|
6248
6369
|
function runInit(args) {
|
|
@@ -6289,383 +6410,155 @@ Describe when this skill should be used.
|
|
|
6289
6410
|
console.log(` ${DIM}Forge:${RESET} ${TEXT}shoplazza-ai-dev-cli publish ${displayPath.replace("/SKILL.md", "")}${RESET}`);
|
|
6290
6411
|
console.log();
|
|
6291
6412
|
}
|
|
6292
|
-
const AGENTS_DIR = ".agents";
|
|
6293
|
-
const LOCK_FILE = ".skill-lock.json";
|
|
6294
|
-
const CURRENT_LOCK_VERSION = 3;
|
|
6295
|
-
function getSkillLockPath() {
|
|
6296
|
-
const xdgStateHome = process.env.XDG_STATE_HOME;
|
|
6297
|
-
if (xdgStateHome) return join(xdgStateHome, "skills", LOCK_FILE);
|
|
6298
|
-
return join(homedir(), AGENTS_DIR, LOCK_FILE);
|
|
6299
|
-
}
|
|
6300
|
-
function readSkillLock() {
|
|
6301
|
-
const lockPath = getSkillLockPath();
|
|
6302
|
-
try {
|
|
6303
|
-
const content = readFileSync(lockPath, "utf-8");
|
|
6304
|
-
const parsed = JSON.parse(content);
|
|
6305
|
-
if (typeof parsed.version !== "number" || !parsed.skills) return {
|
|
6306
|
-
version: CURRENT_LOCK_VERSION,
|
|
6307
|
-
skills: {}
|
|
6308
|
-
};
|
|
6309
|
-
if (parsed.version < CURRENT_LOCK_VERSION) return {
|
|
6310
|
-
version: CURRENT_LOCK_VERSION,
|
|
6311
|
-
skills: {}
|
|
6312
|
-
};
|
|
6313
|
-
return parsed;
|
|
6314
|
-
} catch {
|
|
6315
|
-
return {
|
|
6316
|
-
version: CURRENT_LOCK_VERSION,
|
|
6317
|
-
skills: {}
|
|
6318
|
-
};
|
|
6319
|
-
}
|
|
6320
|
-
}
|
|
6321
6413
|
function parseUpdateOptions(args) {
|
|
6322
6414
|
const options = {};
|
|
6323
|
-
const
|
|
6324
|
-
for (const arg of args) if (arg === "-
|
|
6325
|
-
else if (arg === "
|
|
6326
|
-
else if (arg
|
|
6327
|
-
|
|
6328
|
-
if (positional.length > 0) options.skills = positional;
|
|
6415
|
+
const names = [];
|
|
6416
|
+
for (const arg of args) if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
6417
|
+
else if (arg === "--json") options.json = true;
|
|
6418
|
+
else if (!arg.startsWith("-")) names.push(arg);
|
|
6419
|
+
if (names.length > 0) options.names = names;
|
|
6329
6420
|
return options;
|
|
6330
6421
|
}
|
|
6331
|
-
function
|
|
6332
|
-
const
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6422
|
+
function hasProjectEntryFor(canonical, cwd) {
|
|
6423
|
+
const candidates = [
|
|
6424
|
+
join(cwd, ".claude", "skills"),
|
|
6425
|
+
join(cwd, ".cursor", "skills"),
|
|
6426
|
+
join(cwd, ".agents", "skills"),
|
|
6427
|
+
join(cwd, ".claude", "agents"),
|
|
6428
|
+
join(cwd, ".cursor", "agents"),
|
|
6429
|
+
join(cwd, ".codex", "agents"),
|
|
6430
|
+
join(cwd, ".claude", "rules"),
|
|
6431
|
+
join(cwd, ".cursor", "rules")
|
|
6432
|
+
];
|
|
6433
|
+
for (const dir of candidates) {
|
|
6434
|
+
if (!existsSync(dir)) continue;
|
|
6435
|
+
let entries;
|
|
6436
|
+
try {
|
|
6437
|
+
entries = readdirSyncSafe(dir);
|
|
6438
|
+
} catch {
|
|
6439
|
+
continue;
|
|
6339
6440
|
}
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
if (options.project) return "project";
|
|
6347
|
-
return "both";
|
|
6348
|
-
}
|
|
6349
|
-
if (options.global && options.project) return "both";
|
|
6350
|
-
if (options.global) return "global";
|
|
6351
|
-
if (options.project) return "project";
|
|
6352
|
-
if (options.yes || !process.stdin.isTTY) return hasProjectSkills() ? "project" : "global";
|
|
6353
|
-
const scope = await ve({
|
|
6354
|
-
message: "Update scope",
|
|
6355
|
-
options: [
|
|
6356
|
-
{
|
|
6357
|
-
value: "project",
|
|
6358
|
-
label: "Project",
|
|
6359
|
-
hint: "Update skills in current directory"
|
|
6360
|
-
},
|
|
6361
|
-
{
|
|
6362
|
-
value: "global",
|
|
6363
|
-
label: "Global",
|
|
6364
|
-
hint: "Update skills in home directory"
|
|
6365
|
-
},
|
|
6366
|
-
{
|
|
6367
|
-
value: "both",
|
|
6368
|
-
label: "Both",
|
|
6369
|
-
hint: "Update all skills"
|
|
6370
|
-
}
|
|
6371
|
-
]
|
|
6372
|
-
});
|
|
6373
|
-
if (pD(scope)) {
|
|
6374
|
-
xe("Cancelled");
|
|
6375
|
-
process.exit(0);
|
|
6376
|
-
}
|
|
6377
|
-
return scope;
|
|
6378
|
-
}
|
|
6379
|
-
function matchesSkillFilter(name, filter) {
|
|
6380
|
-
if (!filter || filter.length === 0) return true;
|
|
6381
|
-
const lower = name.toLowerCase();
|
|
6382
|
-
return filter.some((f) => f.toLowerCase() === lower);
|
|
6383
|
-
}
|
|
6384
|
-
function getSkipReason(entry) {
|
|
6385
|
-
if (entry.sourceType === "local") return "Local path";
|
|
6386
|
-
if (entry.sourceType === "git") return "Git URL";
|
|
6387
|
-
if (entry.sourceType === "well-known") return "Well-known skill";
|
|
6388
|
-
if (!entry.skillFolderHash) return "Private or deleted repo";
|
|
6389
|
-
if (!entry.skillPath) return "No skill path recorded";
|
|
6390
|
-
return "No version tracking";
|
|
6391
|
-
}
|
|
6392
|
-
function getInstallSource(skill) {
|
|
6393
|
-
let url = skill.sourceUrl;
|
|
6394
|
-
if (skill.sourceType === "well-known") {
|
|
6395
|
-
const idx = url.indexOf("/.well-known/");
|
|
6396
|
-
if (idx !== -1) url = url.slice(0, idx);
|
|
6397
|
-
}
|
|
6398
|
-
return formatSourceInput(url, skill.ref);
|
|
6399
|
-
}
|
|
6400
|
-
function printSkippedSkills(skipped) {
|
|
6401
|
-
if (skipped.length === 0) return;
|
|
6402
|
-
console.log();
|
|
6403
|
-
console.log(`${DIM}${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
|
|
6404
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
6405
|
-
for (const skill of skipped) {
|
|
6406
|
-
const source = getInstallSource(skill);
|
|
6407
|
-
const existing = grouped.get(source) || [];
|
|
6408
|
-
existing.push(skill);
|
|
6409
|
-
grouped.set(source, existing);
|
|
6410
|
-
}
|
|
6411
|
-
for (const [source, skills] of grouped) {
|
|
6412
|
-
if (skills.length === 1) {
|
|
6413
|
-
const skill = skills[0];
|
|
6414
|
-
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)} ${DIM}(${skill.reason})${RESET}`);
|
|
6415
|
-
} else {
|
|
6416
|
-
const reason = skills[0].reason;
|
|
6417
|
-
const names = skills.map((s) => sanitizeMetadata(s.name)).join(", ");
|
|
6418
|
-
console.log(` ${TEXT}•${RESET} ${names} ${DIM}(${reason})${RESET}`);
|
|
6441
|
+
for (const name of entries) {
|
|
6442
|
+
const entryPath = join(dir, name);
|
|
6443
|
+
try {
|
|
6444
|
+
const real = realpathSync(entryPath);
|
|
6445
|
+
if (real === canonical || real.startsWith(canonical + sep)) return true;
|
|
6446
|
+
} catch {}
|
|
6419
6447
|
}
|
|
6420
|
-
console.log(` ${DIM}To update: ${TEXT}npx skills add ${source} -g -y${RESET}`);
|
|
6421
6448
|
}
|
|
6449
|
+
return false;
|
|
6422
6450
|
}
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
if (entry.sourceType === "node_modules" || entry.sourceType === "local") continue;
|
|
6429
|
-
skills.push({
|
|
6430
|
-
name,
|
|
6431
|
-
source: entry.source,
|
|
6432
|
-
entry
|
|
6433
|
-
});
|
|
6451
|
+
function readdirSyncSafe(dir) {
|
|
6452
|
+
try {
|
|
6453
|
+
return readdirSync(dir);
|
|
6454
|
+
} catch {
|
|
6455
|
+
return [];
|
|
6434
6456
|
}
|
|
6435
|
-
return skills;
|
|
6436
6457
|
}
|
|
6437
|
-
async function
|
|
6438
|
-
const
|
|
6439
|
-
const
|
|
6440
|
-
|
|
6441
|
-
let
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6458
|
+
async function runUpdate(args = []) {
|
|
6459
|
+
const options = parseUpdateOptions(args);
|
|
6460
|
+
const cwd = process.cwd();
|
|
6461
|
+
const inventory = await scanStore(cwd);
|
|
6462
|
+
let targets = [...inventory.plugins.map((pl) => ({
|
|
6463
|
+
kind: "plugin",
|
|
6464
|
+
name: pl.name,
|
|
6465
|
+
ref: pl.ref,
|
|
6466
|
+
canonical: pl.canonical,
|
|
6467
|
+
hasProjectEntry: hasProjectEntryFor(pl.canonical, cwd)
|
|
6468
|
+
})), ...inventory.skills.filter((s) => Boolean(s.ref)).map((s) => ({
|
|
6469
|
+
kind: "skill",
|
|
6470
|
+
name: s.name,
|
|
6471
|
+
ref: s.ref,
|
|
6472
|
+
canonical: s.canonical,
|
|
6473
|
+
hasProjectEntry: hasProjectEntryFor(s.canonical, cwd)
|
|
6474
|
+
}))];
|
|
6475
|
+
if (options.names && options.names.length > 0) {
|
|
6476
|
+
const wanted = new Set(options.names.map((n) => n.toLowerCase()));
|
|
6477
|
+
targets = targets.filter((t) => wanted.has(t.name.toLowerCase()));
|
|
6452
6478
|
}
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
skipped.push({
|
|
6463
|
-
name: skillName,
|
|
6464
|
-
reason: getSkipReason(entry),
|
|
6465
|
-
sourceUrl: entry.sourceUrl,
|
|
6466
|
-
sourceType: entry.sourceType,
|
|
6467
|
-
ref: entry.ref
|
|
6468
|
-
});
|
|
6469
|
-
continue;
|
|
6479
|
+
if (options.json) {
|
|
6480
|
+
const out = { targets: targets.map((t) => ({
|
|
6481
|
+
kind: t.kind,
|
|
6482
|
+
name: t.name,
|
|
6483
|
+
ref: t.ref
|
|
6484
|
+
})) };
|
|
6485
|
+
if (targets.length === 0) {
|
|
6486
|
+
console.log(JSON.stringify(out, null, 2));
|
|
6487
|
+
return;
|
|
6470
6488
|
}
|
|
6471
|
-
checkable.push({
|
|
6472
|
-
name: skillName,
|
|
6473
|
-
entry
|
|
6474
|
-
});
|
|
6475
6489
|
}
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
source: entry.source,
|
|
6484
|
-
entry
|
|
6485
|
-
});
|
|
6486
|
-
} catch {}
|
|
6487
|
-
}
|
|
6488
|
-
if (checkable.length > 0) process.stdout.write("\r\x1B[K");
|
|
6489
|
-
const checkedCount = checkable.length + skipped.length;
|
|
6490
|
-
if (checkable.length === 0 && skipped.length === 0) {
|
|
6491
|
-
if (!skillFilter) console.log(`${DIM}No global skills to check.${RESET}`);
|
|
6492
|
-
return {
|
|
6493
|
-
successCount,
|
|
6494
|
-
failCount,
|
|
6495
|
-
checkedCount: 0
|
|
6496
|
-
};
|
|
6490
|
+
if (targets.length === 0) {
|
|
6491
|
+
if (options.names && options.names.length > 0) console.log(`${DIM}No installed skills/plugins matching: ${options.names.join(", ")}${RESET}`);
|
|
6492
|
+
else {
|
|
6493
|
+
console.log(`${DIM}Nothing to update — store is empty.${RESET}`);
|
|
6494
|
+
console.log(`${DIM}Install something with${RESET} ${TEXT}npx skills add <ref>${RESET}`);
|
|
6495
|
+
}
|
|
6496
|
+
return;
|
|
6497
6497
|
}
|
|
6498
|
-
if (
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
successCount,
|
|
6502
|
-
failCount,
|
|
6503
|
-
checkedCount
|
|
6504
|
-
};
|
|
6498
|
+
if (!options.yes && !options.json) {
|
|
6499
|
+
console.log(`${TEXT}Updating ${targets.length} entr${targets.length === 1 ? "y" : "ies"}...${RESET}`);
|
|
6500
|
+
console.log();
|
|
6505
6501
|
}
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
checkedCount
|
|
6512
|
-
};
|
|
6502
|
+
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
6503
|
+
if (!existsSync(cliEntry)) {
|
|
6504
|
+
console.error(`${BOLD}✗${RESET} CLI entrypoint not found at ${cliEntry}`);
|
|
6505
|
+
process.exitCode = 1;
|
|
6506
|
+
return;
|
|
6513
6507
|
}
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
const
|
|
6521
|
-
if (!existsSync(cliEntry)) {
|
|
6522
|
-
failCount++;
|
|
6523
|
-
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
6524
|
-
continue;
|
|
6525
|
-
}
|
|
6526
|
-
if (spawnSync(process.execPath, [
|
|
6527
|
-
cliEntry,
|
|
6508
|
+
let success = 0;
|
|
6509
|
+
let fail = 0;
|
|
6510
|
+
const results = [];
|
|
6511
|
+
for (const t of targets) {
|
|
6512
|
+
const safeName = sanitizeMetadata(t.name);
|
|
6513
|
+
if (!options.json) console.log(`${TEXT}Updating ${safeName}...${RESET} ${DIM}(${t.ref})${RESET}`);
|
|
6514
|
+
const passthrough = [
|
|
6528
6515
|
"add",
|
|
6529
|
-
|
|
6530
|
-
"-g",
|
|
6516
|
+
t.ref,
|
|
6531
6517
|
"-y"
|
|
6532
|
-
]
|
|
6533
|
-
|
|
6534
|
-
|
|
6518
|
+
];
|
|
6519
|
+
if (!t.hasProjectEntry) passthrough.push("-g");
|
|
6520
|
+
const ok = spawnSync(process.execPath, [cliEntry, ...passthrough], {
|
|
6521
|
+
stdio: options.json ? [
|
|
6522
|
+
"ignore",
|
|
6535
6523
|
"pipe",
|
|
6536
6524
|
"pipe"
|
|
6537
|
-
]
|
|
6538
|
-
encoding: "utf-8",
|
|
6539
|
-
shell: process.platform === "win32"
|
|
6540
|
-
}).status === 0) {
|
|
6541
|
-
successCount++;
|
|
6542
|
-
console.log(` ${TEXT}✓${RESET} Updated ${safeName}`);
|
|
6543
|
-
} else {
|
|
6544
|
-
failCount++;
|
|
6545
|
-
console.log(` ${DIM}✗ Failed to update ${safeName}${RESET}`);
|
|
6546
|
-
}
|
|
6547
|
-
}
|
|
6548
|
-
printSkippedSkills(skipped);
|
|
6549
|
-
return {
|
|
6550
|
-
successCount,
|
|
6551
|
-
failCount,
|
|
6552
|
-
checkedCount
|
|
6553
|
-
};
|
|
6554
|
-
}
|
|
6555
|
-
async function updateProjectSkills(skillFilter) {
|
|
6556
|
-
const projectSkills = await getProjectSkillsForUpdate(skillFilter);
|
|
6557
|
-
let successCount = 0;
|
|
6558
|
-
let failCount = 0;
|
|
6559
|
-
if (projectSkills.length === 0) {
|
|
6560
|
-
if (!skillFilter) {
|
|
6561
|
-
console.log(`${DIM}No project skills to update.${RESET}`);
|
|
6562
|
-
console.log(`${DIM}Install project skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
|
|
6563
|
-
}
|
|
6564
|
-
return {
|
|
6565
|
-
successCount,
|
|
6566
|
-
failCount,
|
|
6567
|
-
foundCount: 0
|
|
6568
|
-
};
|
|
6569
|
-
}
|
|
6570
|
-
const updatable = projectSkills.filter((s) => s.entry.skillPath);
|
|
6571
|
-
const legacy = projectSkills.filter((s) => !s.entry.skillPath);
|
|
6572
|
-
if (updatable.length === 0) {
|
|
6573
|
-
console.log(`${DIM}No project skills can be updated in place.${RESET}`);
|
|
6574
|
-
printLegacyProjectSkills(legacy);
|
|
6575
|
-
return {
|
|
6576
|
-
successCount,
|
|
6577
|
-
failCount,
|
|
6578
|
-
foundCount: projectSkills.length
|
|
6579
|
-
};
|
|
6580
|
-
}
|
|
6581
|
-
console.log(`${TEXT}Refreshing ${updatable.length} project skill(s)...${RESET}`);
|
|
6582
|
-
console.log();
|
|
6583
|
-
for (const skill of updatable) {
|
|
6584
|
-
const safeName = sanitizeMetadata(skill.name);
|
|
6585
|
-
console.log(`${TEXT}Updating ${safeName}...${RESET}`);
|
|
6586
|
-
const installUrl = buildLocalUpdateSource(skill.entry);
|
|
6587
|
-
const cliEntry = join(__dirname, "..", "bin", "cli.mjs");
|
|
6588
|
-
if (!existsSync(cliEntry)) {
|
|
6589
|
-
failCount++;
|
|
6590
|
-
console.log(` ${DIM}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET}`);
|
|
6591
|
-
continue;
|
|
6592
|
-
}
|
|
6593
|
-
if (spawnSync(process.execPath, [
|
|
6594
|
-
cliEntry,
|
|
6595
|
-
"add",
|
|
6596
|
-
installUrl,
|
|
6597
|
-
"--skill",
|
|
6598
|
-
skill.name,
|
|
6599
|
-
"-y"
|
|
6600
|
-
], {
|
|
6601
|
-
stdio: [
|
|
6525
|
+
] : [
|
|
6602
6526
|
"inherit",
|
|
6603
6527
|
"pipe",
|
|
6604
6528
|
"pipe"
|
|
6605
6529
|
],
|
|
6606
6530
|
encoding: "utf-8",
|
|
6607
6531
|
shell: process.platform === "win32"
|
|
6608
|
-
}).status === 0
|
|
6609
|
-
|
|
6610
|
-
|
|
6532
|
+
}).status === 0;
|
|
6533
|
+
if (ok) {
|
|
6534
|
+
success++;
|
|
6535
|
+
if (!options.json) console.log(` ${TEXT}✓${RESET} updated ${safeName}`);
|
|
6611
6536
|
} else {
|
|
6612
|
-
|
|
6613
|
-
console.log(` ${DIM}✗
|
|
6537
|
+
fail++;
|
|
6538
|
+
if (!options.json) console.log(` ${DIM}✗ failed ${safeName}${RESET}`);
|
|
6614
6539
|
}
|
|
6540
|
+
results.push({
|
|
6541
|
+
name: t.name,
|
|
6542
|
+
kind: t.kind,
|
|
6543
|
+
ok
|
|
6544
|
+
});
|
|
6615
6545
|
}
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
}
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
const reinstall = formatSourceInput(skill.entry.source, skill.entry.ref);
|
|
6629
|
-
console.log(` ${TEXT}•${RESET} ${sanitizeMetadata(skill.name)}`);
|
|
6630
|
-
console.log(` ${DIM}To refresh: ${TEXT}npx skills add ${reinstall} -y${RESET}`);
|
|
6546
|
+
if (options.json) {
|
|
6547
|
+
console.log(JSON.stringify({
|
|
6548
|
+
targets: targets.map((t) => ({
|
|
6549
|
+
kind: t.kind,
|
|
6550
|
+
name: t.name,
|
|
6551
|
+
ref: t.ref
|
|
6552
|
+
})),
|
|
6553
|
+
results,
|
|
6554
|
+
success,
|
|
6555
|
+
fail
|
|
6556
|
+
}, null, 2));
|
|
6557
|
+
return;
|
|
6631
6558
|
}
|
|
6632
|
-
}
|
|
6633
|
-
async function runUpdate(args = []) {
|
|
6634
|
-
const options = parseUpdateOptions(args);
|
|
6635
|
-
const scope = await resolveUpdateScope(options);
|
|
6636
|
-
if (options.skills) console.log(`${TEXT}Updating ${options.skills.join(", ")}...${RESET}`);
|
|
6637
|
-
else console.log(`${TEXT}Checking for skill updates...${RESET}`);
|
|
6638
6559
|
console.log();
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
let totalFound = 0;
|
|
6642
|
-
if (scope === "global" || scope === "both") {
|
|
6643
|
-
if (scope === "both" && !options.skills) console.log(`${BOLD}Global Skills${RESET}`);
|
|
6644
|
-
const { successCount, failCount, checkedCount } = await updateGlobalSkills(options.skills);
|
|
6645
|
-
totalSuccess += successCount;
|
|
6646
|
-
totalFail += failCount;
|
|
6647
|
-
totalFound += checkedCount;
|
|
6648
|
-
if (scope === "both" && !options.skills) console.log();
|
|
6649
|
-
}
|
|
6650
|
-
if (scope === "project" || scope === "both") {
|
|
6651
|
-
if (scope === "both" && !options.skills) console.log(`${BOLD}Project Skills${RESET}`);
|
|
6652
|
-
const { successCount, failCount, foundCount } = await updateProjectSkills(options.skills);
|
|
6653
|
-
totalSuccess += successCount;
|
|
6654
|
-
totalFail += failCount;
|
|
6655
|
-
totalFound += foundCount;
|
|
6656
|
-
}
|
|
6657
|
-
if (options.skills && totalFound === 0) console.log(`${DIM}No installed skills found matching: ${options.skills.join(", ")}${RESET}`);
|
|
6658
|
-
console.log();
|
|
6659
|
-
if (totalSuccess > 0) console.log(`${TEXT}✓ Updated ${totalSuccess} skill(s)${RESET}`);
|
|
6660
|
-
if (totalFail > 0) console.log(`${DIM}Failed to update ${totalFail} skill(s)${RESET}`);
|
|
6661
|
-
if (totalSuccess === 0 && totalFail === 0) {}
|
|
6662
|
-
track({
|
|
6663
|
-
event: "update",
|
|
6664
|
-
scope,
|
|
6665
|
-
skillCount: String(totalSuccess + totalFail),
|
|
6666
|
-
successCount: String(totalSuccess),
|
|
6667
|
-
failCount: String(totalFail)
|
|
6668
|
-
});
|
|
6560
|
+
if (success > 0) console.log(`${TEXT}✓ Updated ${success} entr${success === 1 ? "y" : "ies"}${RESET}`);
|
|
6561
|
+
if (fail > 0) console.log(`${DIM}Failed to update ${fail} entr${fail === 1 ? "y" : "ies"}${RESET}`);
|
|
6669
6562
|
console.log();
|
|
6670
6563
|
}
|
|
6671
6564
|
async function main() {
|