@anytio/pspm 0.0.7 → 0.1.0
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/LICENSE +149 -0
- package/README.md +53 -13
- package/dist/index.js +1341 -175
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,9 +5,11 @@ import { fileURLToPath, URL as URL$1 } from 'url';
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { createHash, randomBytes } from 'crypto';
|
|
7
7
|
import * as semver from 'semver';
|
|
8
|
-
import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, unlink } from 'fs/promises';
|
|
8
|
+
import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, lstat, cp, unlink, readlink, symlink } from 'fs/promises';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import * as ini from 'ini';
|
|
11
|
+
import { checkbox } from '@inquirer/prompts';
|
|
12
|
+
import { createInterface } from 'readline';
|
|
11
13
|
import http from 'http';
|
|
12
14
|
import open from 'open';
|
|
13
15
|
import { exec as exec$1 } from 'child_process';
|
|
@@ -25,7 +27,7 @@ var DEFAULT_SKILL_FILES = [
|
|
|
25
27
|
"scripts",
|
|
26
28
|
"data"
|
|
27
29
|
];
|
|
28
|
-
var PSPM_SCHEMA_URL = "https://pspm.dev/schema/pspm.json";
|
|
30
|
+
var PSPM_SCHEMA_URL = "https://pspm.dev/schema/v1/pspm.json";
|
|
29
31
|
function validateManifest(manifest) {
|
|
30
32
|
if (!manifest.name) {
|
|
31
33
|
return { valid: false, error: "Manifest must have a 'name' field" };
|
|
@@ -61,6 +63,41 @@ function parseSkillSpecifier(specifier) {
|
|
|
61
63
|
versionRange: match[3]
|
|
62
64
|
};
|
|
63
65
|
}
|
|
66
|
+
var GITHUB_SPECIFIER_PATTERN = /^github:([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)(\/[^@]+)?(?:@(.+))?$/;
|
|
67
|
+
function parseGitHubSpecifier(specifier) {
|
|
68
|
+
const match = specifier.match(GITHUB_SPECIFIER_PATTERN);
|
|
69
|
+
if (!match) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const [, owner, repo, pathWithSlash, ref] = match;
|
|
73
|
+
return {
|
|
74
|
+
owner,
|
|
75
|
+
repo,
|
|
76
|
+
// Remove leading slash from path
|
|
77
|
+
path: pathWithSlash ? pathWithSlash.slice(1) : void 0,
|
|
78
|
+
ref: ref || void 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function formatGitHubSpecifier(spec) {
|
|
82
|
+
let result = `github:${spec.owner}/${spec.repo}`;
|
|
83
|
+
if (spec.path) {
|
|
84
|
+
result += `/${spec.path}`;
|
|
85
|
+
}
|
|
86
|
+
if (spec.ref) {
|
|
87
|
+
result += `@${spec.ref}`;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function getGitHubSkillName(spec) {
|
|
92
|
+
if (spec.path) {
|
|
93
|
+
const segments = spec.path.split("/").filter(Boolean);
|
|
94
|
+
return segments[segments.length - 1];
|
|
95
|
+
}
|
|
96
|
+
return spec.repo;
|
|
97
|
+
}
|
|
98
|
+
function isGitHubSpecifier(specifier) {
|
|
99
|
+
return specifier.startsWith("github:");
|
|
100
|
+
}
|
|
64
101
|
function resolveVersion(range, availableVersions) {
|
|
65
102
|
const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
|
|
66
103
|
if (!range || range === "latest" || range === "*") {
|
|
@@ -656,19 +693,19 @@ async function access(specifier, options) {
|
|
|
656
693
|
}
|
|
657
694
|
packageName = parsed.name;
|
|
658
695
|
} else {
|
|
659
|
-
const { readFile:
|
|
660
|
-
const { join:
|
|
696
|
+
const { readFile: readFile7 } = await import('fs/promises');
|
|
697
|
+
const { join: join13 } = await import('path');
|
|
661
698
|
let manifest = null;
|
|
662
699
|
try {
|
|
663
|
-
const content = await
|
|
664
|
-
|
|
700
|
+
const content = await readFile7(
|
|
701
|
+
join13(process.cwd(), "pspm.json"),
|
|
665
702
|
"utf-8"
|
|
666
703
|
);
|
|
667
704
|
manifest = JSON.parse(content);
|
|
668
705
|
} catch {
|
|
669
706
|
try {
|
|
670
|
-
const content = await
|
|
671
|
-
|
|
707
|
+
const content = await readFile7(
|
|
708
|
+
join13(process.cwd(), "package.json"),
|
|
672
709
|
"utf-8"
|
|
673
710
|
);
|
|
674
711
|
manifest = JSON.parse(content);
|
|
@@ -712,6 +749,243 @@ async function access(specifier, options) {
|
|
|
712
749
|
process.exit(1);
|
|
713
750
|
}
|
|
714
751
|
}
|
|
752
|
+
var AGENT_INFO = {
|
|
753
|
+
"claude-code": {
|
|
754
|
+
displayName: "Claude Code",
|
|
755
|
+
skillsDir: ".claude/skills"
|
|
756
|
+
},
|
|
757
|
+
codex: {
|
|
758
|
+
displayName: "Codex",
|
|
759
|
+
skillsDir: ".codex/skills"
|
|
760
|
+
},
|
|
761
|
+
cursor: {
|
|
762
|
+
displayName: "Cursor",
|
|
763
|
+
skillsDir: ".cursor/skills"
|
|
764
|
+
},
|
|
765
|
+
gemini: {
|
|
766
|
+
displayName: "Gemini CLI",
|
|
767
|
+
skillsDir: ".gemini/skills"
|
|
768
|
+
},
|
|
769
|
+
kiro: {
|
|
770
|
+
displayName: "Kiro CLI",
|
|
771
|
+
skillsDir: ".kiro/skills"
|
|
772
|
+
},
|
|
773
|
+
opencode: {
|
|
774
|
+
displayName: "OpenCode",
|
|
775
|
+
skillsDir: ".opencode/skills"
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
var DEFAULT_AGENT_CONFIGS = {
|
|
779
|
+
"claude-code": { skillsDir: AGENT_INFO["claude-code"].skillsDir },
|
|
780
|
+
codex: { skillsDir: AGENT_INFO.codex.skillsDir },
|
|
781
|
+
cursor: { skillsDir: AGENT_INFO.cursor.skillsDir },
|
|
782
|
+
gemini: { skillsDir: AGENT_INFO.gemini.skillsDir },
|
|
783
|
+
kiro: { skillsDir: AGENT_INFO.kiro.skillsDir },
|
|
784
|
+
opencode: { skillsDir: AGENT_INFO.opencode.skillsDir }
|
|
785
|
+
};
|
|
786
|
+
var DEFAULT_AGENT = "claude-code";
|
|
787
|
+
var ALL_AGENTS = [
|
|
788
|
+
"claude-code",
|
|
789
|
+
"codex",
|
|
790
|
+
"cursor",
|
|
791
|
+
"gemini",
|
|
792
|
+
"kiro",
|
|
793
|
+
"opencode"
|
|
794
|
+
];
|
|
795
|
+
function resolveAgentConfig(name, overrides) {
|
|
796
|
+
if (overrides?.[name]) {
|
|
797
|
+
return overrides[name];
|
|
798
|
+
}
|
|
799
|
+
if (name in DEFAULT_AGENT_CONFIGS) {
|
|
800
|
+
return DEFAULT_AGENT_CONFIGS[name];
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function parseAgentArg(agentArg) {
|
|
805
|
+
if (!agentArg) {
|
|
806
|
+
return [DEFAULT_AGENT];
|
|
807
|
+
}
|
|
808
|
+
if (agentArg === "none") {
|
|
809
|
+
return ["none"];
|
|
810
|
+
}
|
|
811
|
+
return agentArg.split(",").map((a) => a.trim()).filter(Boolean);
|
|
812
|
+
}
|
|
813
|
+
function getAvailableAgents(overrides) {
|
|
814
|
+
const builtIn = Object.keys(DEFAULT_AGENT_CONFIGS);
|
|
815
|
+
const custom = overrides ? Object.keys(overrides) : [];
|
|
816
|
+
return [.../* @__PURE__ */ new Set([...builtIn, ...custom])];
|
|
817
|
+
}
|
|
818
|
+
async function promptForAgents() {
|
|
819
|
+
const choices = ALL_AGENTS.map((agent) => ({
|
|
820
|
+
name: `${AGENT_INFO[agent].displayName} (${AGENT_INFO[agent].skillsDir})`,
|
|
821
|
+
value: agent,
|
|
822
|
+
checked: true
|
|
823
|
+
// All selected by default
|
|
824
|
+
}));
|
|
825
|
+
const selected = await checkbox({
|
|
826
|
+
message: "Select agents to install skills to",
|
|
827
|
+
choices
|
|
828
|
+
});
|
|
829
|
+
if (selected.length === 0) {
|
|
830
|
+
return ["none"];
|
|
831
|
+
}
|
|
832
|
+
return selected;
|
|
833
|
+
}
|
|
834
|
+
var GitHubRateLimitError = class extends Error {
|
|
835
|
+
constructor() {
|
|
836
|
+
super(
|
|
837
|
+
"GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable for higher limits."
|
|
838
|
+
);
|
|
839
|
+
this.name = "GitHubRateLimitError";
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var GitHubNotFoundError = class extends Error {
|
|
843
|
+
constructor(spec) {
|
|
844
|
+
const path = spec.path ? `/${spec.path}` : "";
|
|
845
|
+
const ref = spec.ref ? `@${spec.ref}` : "";
|
|
846
|
+
super(
|
|
847
|
+
`GitHub repository not found: ${spec.owner}/${spec.repo}${path}${ref}`
|
|
848
|
+
);
|
|
849
|
+
this.name = "GitHubNotFoundError";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
var GitHubPathNotFoundError = class extends Error {
|
|
853
|
+
constructor(spec, availablePaths) {
|
|
854
|
+
const pathInfo = availablePaths?.length ? `
|
|
855
|
+
Available paths in repository root:
|
|
856
|
+
${availablePaths.join("\n ")}` : "";
|
|
857
|
+
super(
|
|
858
|
+
`Path "${spec.path}" not found in ${spec.owner}/${spec.repo}${pathInfo}`
|
|
859
|
+
);
|
|
860
|
+
this.name = "GitHubPathNotFoundError";
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
function getGitHubHeaders() {
|
|
864
|
+
const headers = {
|
|
865
|
+
Accept: "application/vnd.github+json",
|
|
866
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
867
|
+
"User-Agent": "pspm-cli"
|
|
868
|
+
};
|
|
869
|
+
const token = process.env.GITHUB_TOKEN;
|
|
870
|
+
if (token) {
|
|
871
|
+
headers.Authorization = `Bearer ${token}`;
|
|
872
|
+
}
|
|
873
|
+
return headers;
|
|
874
|
+
}
|
|
875
|
+
async function resolveGitHubRef(owner, repo, ref) {
|
|
876
|
+
const headers = getGitHubHeaders();
|
|
877
|
+
if (!ref || ref === "latest") {
|
|
878
|
+
const repoUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
|
879
|
+
const repoResponse = await fetch(repoUrl, { headers });
|
|
880
|
+
if (repoResponse.status === 404) {
|
|
881
|
+
throw new GitHubNotFoundError({ owner, repo });
|
|
882
|
+
}
|
|
883
|
+
if (repoResponse.status === 403) {
|
|
884
|
+
const remaining = repoResponse.headers.get("x-ratelimit-remaining");
|
|
885
|
+
if (remaining === "0") {
|
|
886
|
+
throw new GitHubRateLimitError();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (!repoResponse.ok) {
|
|
890
|
+
throw new Error(`GitHub API error: ${repoResponse.status}`);
|
|
891
|
+
}
|
|
892
|
+
const repoData = await repoResponse.json();
|
|
893
|
+
ref = repoData.default_branch;
|
|
894
|
+
}
|
|
895
|
+
const commitUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${ref}`;
|
|
896
|
+
const commitResponse = await fetch(commitUrl, { headers });
|
|
897
|
+
if (commitResponse.status === 404) {
|
|
898
|
+
throw new GitHubNotFoundError({ owner, repo, ref });
|
|
899
|
+
}
|
|
900
|
+
if (commitResponse.status === 403) {
|
|
901
|
+
const remaining = commitResponse.headers.get("x-ratelimit-remaining");
|
|
902
|
+
if (remaining === "0") {
|
|
903
|
+
throw new GitHubRateLimitError();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (!commitResponse.ok) {
|
|
907
|
+
throw new Error(`GitHub API error: ${commitResponse.status}`);
|
|
908
|
+
}
|
|
909
|
+
const commitData = await commitResponse.json();
|
|
910
|
+
return commitData.sha;
|
|
911
|
+
}
|
|
912
|
+
async function downloadGitHubPackage(spec) {
|
|
913
|
+
const headers = getGitHubHeaders();
|
|
914
|
+
const commit = await resolveGitHubRef(spec.owner, spec.repo, spec.ref);
|
|
915
|
+
const tarballUrl = `https://api.github.com/repos/${spec.owner}/${spec.repo}/tarball/${commit}`;
|
|
916
|
+
const response = await fetch(tarballUrl, {
|
|
917
|
+
headers,
|
|
918
|
+
redirect: "follow"
|
|
919
|
+
});
|
|
920
|
+
if (response.status === 404) {
|
|
921
|
+
throw new GitHubNotFoundError(spec);
|
|
922
|
+
}
|
|
923
|
+
if (response.status === 403) {
|
|
924
|
+
const remaining = response.headers.get("x-ratelimit-remaining");
|
|
925
|
+
if (remaining === "0") {
|
|
926
|
+
throw new GitHubRateLimitError();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (!response.ok) {
|
|
930
|
+
throw new Error(`Failed to download GitHub tarball: ${response.status}`);
|
|
931
|
+
}
|
|
932
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
933
|
+
const integrity = calculateIntegrity(buffer);
|
|
934
|
+
return { buffer, commit, integrity };
|
|
935
|
+
}
|
|
936
|
+
async function extractGitHubPackage(spec, buffer, skillsDir) {
|
|
937
|
+
const destPath = spec.path ? join(skillsDir, "_github", spec.owner, spec.repo, spec.path) : join(skillsDir, "_github", spec.owner, spec.repo);
|
|
938
|
+
const tempDir = join(skillsDir, "_github", ".temp", `${Date.now()}`);
|
|
939
|
+
await mkdir(tempDir, { recursive: true });
|
|
940
|
+
const tempFile = join(tempDir, "archive.tgz");
|
|
941
|
+
try {
|
|
942
|
+
await writeFile(tempFile, buffer);
|
|
943
|
+
const { exec: exec2 } = await import('child_process');
|
|
944
|
+
const { promisify: promisify2 } = await import('util');
|
|
945
|
+
const execAsync = promisify2(exec2);
|
|
946
|
+
await execAsync(`tar -xzf "${tempFile}" -C "${tempDir}"`);
|
|
947
|
+
const entries = await readdir(tempDir);
|
|
948
|
+
const extractedDir = entries.find(
|
|
949
|
+
(e) => e !== "archive.tgz" && !e.startsWith(".")
|
|
950
|
+
);
|
|
951
|
+
if (!extractedDir) {
|
|
952
|
+
throw new Error("Failed to find extracted directory in tarball");
|
|
953
|
+
}
|
|
954
|
+
const sourcePath = join(tempDir, extractedDir);
|
|
955
|
+
const copySource = spec.path ? join(sourcePath, spec.path) : sourcePath;
|
|
956
|
+
if (spec.path) {
|
|
957
|
+
const pathExists = await lstat(copySource).catch(() => null);
|
|
958
|
+
if (!pathExists) {
|
|
959
|
+
const rootEntries = await readdir(sourcePath);
|
|
960
|
+
const dirs = [];
|
|
961
|
+
for (const entry of rootEntries) {
|
|
962
|
+
const stat7 = await lstat(join(sourcePath, entry)).catch(() => null);
|
|
963
|
+
if (stat7?.isDirectory() && !entry.startsWith(".")) {
|
|
964
|
+
dirs.push(entry);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
throw new GitHubPathNotFoundError(spec, dirs);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
await rm(destPath, { recursive: true, force: true });
|
|
971
|
+
await mkdir(destPath, { recursive: true });
|
|
972
|
+
await cp(copySource, destPath, { recursive: true });
|
|
973
|
+
return spec.path ? `.pspm/skills/_github/${spec.owner}/${spec.repo}/${spec.path}` : `.pspm/skills/_github/${spec.owner}/${spec.repo}`;
|
|
974
|
+
} finally {
|
|
975
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function getGitHubDisplayName(spec, commit) {
|
|
979
|
+
let name = `github:${spec.owner}/${spec.repo}`;
|
|
980
|
+
if (spec.path) {
|
|
981
|
+
name += `/${spec.path}`;
|
|
982
|
+
}
|
|
983
|
+
if (spec.ref || commit) {
|
|
984
|
+
const ref = spec.ref || "HEAD";
|
|
985
|
+
name += ` (${ref}${""})`;
|
|
986
|
+
}
|
|
987
|
+
return name;
|
|
988
|
+
}
|
|
715
989
|
async function hasLegacyLockfile() {
|
|
716
990
|
try {
|
|
717
991
|
await stat(getLegacyLockfilePath());
|
|
@@ -783,17 +1057,20 @@ async function writeLockfile(lockfile) {
|
|
|
783
1057
|
const lockfilePath = getLockfilePath();
|
|
784
1058
|
await mkdir(dirname(lockfilePath), { recursive: true });
|
|
785
1059
|
const normalized = {
|
|
786
|
-
lockfileVersion:
|
|
1060
|
+
lockfileVersion: 3,
|
|
787
1061
|
registryUrl: lockfile.registryUrl,
|
|
788
1062
|
packages: lockfile.packages ?? lockfile.skills ?? {}
|
|
789
1063
|
};
|
|
1064
|
+
if (lockfile.githubPackages && Object.keys(lockfile.githubPackages).length > 0) {
|
|
1065
|
+
normalized.githubPackages = lockfile.githubPackages;
|
|
1066
|
+
}
|
|
790
1067
|
await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}
|
|
791
1068
|
`);
|
|
792
1069
|
}
|
|
793
1070
|
async function createEmptyLockfile() {
|
|
794
1071
|
const registryUrl = await getRegistryUrl();
|
|
795
1072
|
return {
|
|
796
|
-
lockfileVersion:
|
|
1073
|
+
lockfileVersion: 3,
|
|
797
1074
|
registryUrl,
|
|
798
1075
|
packages: {}
|
|
799
1076
|
};
|
|
@@ -836,9 +1113,220 @@ async function listLockfileSkills() {
|
|
|
836
1113
|
entry
|
|
837
1114
|
}));
|
|
838
1115
|
}
|
|
1116
|
+
async function addGitHubToLockfile(specifier, entry) {
|
|
1117
|
+
let lockfile = await readLockfile();
|
|
1118
|
+
if (!lockfile) {
|
|
1119
|
+
lockfile = await createEmptyLockfile();
|
|
1120
|
+
}
|
|
1121
|
+
if (!lockfile.githubPackages) {
|
|
1122
|
+
lockfile.githubPackages = {};
|
|
1123
|
+
}
|
|
1124
|
+
lockfile.githubPackages[specifier] = entry;
|
|
1125
|
+
await writeLockfile(lockfile);
|
|
1126
|
+
}
|
|
1127
|
+
async function removeGitHubFromLockfile(specifier) {
|
|
1128
|
+
const lockfile = await readLockfile();
|
|
1129
|
+
if (!lockfile?.githubPackages?.[specifier]) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
delete lockfile.githubPackages[specifier];
|
|
1133
|
+
await writeLockfile(lockfile);
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1136
|
+
async function listLockfileGitHubPackages() {
|
|
1137
|
+
const lockfile = await readLockfile();
|
|
1138
|
+
if (!lockfile?.githubPackages) {
|
|
1139
|
+
return [];
|
|
1140
|
+
}
|
|
1141
|
+
return Object.entries(lockfile.githubPackages).map(([specifier, entry]) => ({
|
|
1142
|
+
specifier,
|
|
1143
|
+
entry
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1146
|
+
function getManifestPath() {
|
|
1147
|
+
return join(process.cwd(), "pspm.json");
|
|
1148
|
+
}
|
|
1149
|
+
async function readManifest() {
|
|
1150
|
+
try {
|
|
1151
|
+
const content = await readFile(getManifestPath(), "utf-8");
|
|
1152
|
+
return JSON.parse(content);
|
|
1153
|
+
} catch {
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function writeManifest(manifest) {
|
|
1158
|
+
const content = JSON.stringify(manifest, null, 2);
|
|
1159
|
+
await writeFile(getManifestPath(), `${content}
|
|
1160
|
+
`);
|
|
1161
|
+
}
|
|
1162
|
+
async function createMinimalManifest() {
|
|
1163
|
+
return {
|
|
1164
|
+
dependencies: {}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
async function ensureManifest() {
|
|
1168
|
+
let manifest = await readManifest();
|
|
1169
|
+
if (!manifest) {
|
|
1170
|
+
manifest = await createMinimalManifest();
|
|
1171
|
+
await writeManifest(manifest);
|
|
1172
|
+
}
|
|
1173
|
+
return manifest;
|
|
1174
|
+
}
|
|
1175
|
+
async function addDependency(skillName, versionRange) {
|
|
1176
|
+
const manifest = await ensureManifest();
|
|
1177
|
+
if (!manifest.dependencies) {
|
|
1178
|
+
manifest.dependencies = {};
|
|
1179
|
+
}
|
|
1180
|
+
manifest.dependencies[skillName] = versionRange;
|
|
1181
|
+
await writeManifest(manifest);
|
|
1182
|
+
}
|
|
1183
|
+
async function removeDependency(skillName) {
|
|
1184
|
+
const manifest = await readManifest();
|
|
1185
|
+
if (!manifest?.dependencies?.[skillName]) {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
delete manifest.dependencies[skillName];
|
|
1189
|
+
await writeManifest(manifest);
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1192
|
+
async function getDependencies() {
|
|
1193
|
+
const manifest = await readManifest();
|
|
1194
|
+
return manifest?.dependencies ?? {};
|
|
1195
|
+
}
|
|
1196
|
+
async function getGitHubDependencies() {
|
|
1197
|
+
const manifest = await readManifest();
|
|
1198
|
+
return manifest?.githubDependencies ?? {};
|
|
1199
|
+
}
|
|
1200
|
+
async function addGitHubDependency(specifier, ref) {
|
|
1201
|
+
const manifest = await ensureManifest();
|
|
1202
|
+
if (!manifest.githubDependencies) {
|
|
1203
|
+
manifest.githubDependencies = {};
|
|
1204
|
+
}
|
|
1205
|
+
manifest.githubDependencies[specifier] = ref;
|
|
1206
|
+
await writeManifest(manifest);
|
|
1207
|
+
}
|
|
1208
|
+
async function removeGitHubDependency(specifier) {
|
|
1209
|
+
const manifest = await readManifest();
|
|
1210
|
+
if (!manifest?.githubDependencies?.[specifier]) {
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
delete manifest.githubDependencies[specifier];
|
|
1214
|
+
await writeManifest(manifest);
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1217
|
+
async function createAgentSymlinks(skills, options) {
|
|
1218
|
+
const { agents, projectRoot, agentConfigs } = options;
|
|
1219
|
+
if (agents.length === 1 && agents[0] === "none") {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
for (const agentName of agents) {
|
|
1223
|
+
const config2 = resolveAgentConfig(agentName, agentConfigs);
|
|
1224
|
+
if (!config2) {
|
|
1225
|
+
console.warn(`Warning: Unknown agent "${agentName}", skipping symlinks`);
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const agentSkillsDir = join(projectRoot, config2.skillsDir);
|
|
1229
|
+
await mkdir(agentSkillsDir, { recursive: true });
|
|
1230
|
+
for (const skill of skills) {
|
|
1231
|
+
const symlinkPath = join(agentSkillsDir, skill.name);
|
|
1232
|
+
const targetPath = join(projectRoot, skill.sourcePath);
|
|
1233
|
+
const relativeTarget = relative(dirname(symlinkPath), targetPath);
|
|
1234
|
+
await createSymlink(symlinkPath, relativeTarget, skill.name);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
async function createSymlink(symlinkPath, target, skillName) {
|
|
1239
|
+
try {
|
|
1240
|
+
const stats = await lstat(symlinkPath).catch(() => null);
|
|
1241
|
+
if (stats) {
|
|
1242
|
+
if (stats.isSymbolicLink()) {
|
|
1243
|
+
const existingTarget = await readlink(symlinkPath);
|
|
1244
|
+
if (existingTarget === target) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
await rm(symlinkPath);
|
|
1248
|
+
} else {
|
|
1249
|
+
console.warn(
|
|
1250
|
+
`Warning: File exists at symlink path for "${skillName}", skipping: ${symlinkPath}`
|
|
1251
|
+
);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
await symlink(target, symlinkPath);
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1258
|
+
console.warn(
|
|
1259
|
+
`Warning: Failed to create symlink for "${skillName}": ${message}`
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
async function removeAgentSymlinks(skillName, options) {
|
|
1264
|
+
const { agents, projectRoot, agentConfigs } = options;
|
|
1265
|
+
if (agents.length === 1 && agents[0] === "none") {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
for (const agentName of agents) {
|
|
1269
|
+
const config2 = resolveAgentConfig(agentName, agentConfigs);
|
|
1270
|
+
if (!config2) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
|
|
1274
|
+
try {
|
|
1275
|
+
const stats = await lstat(symlinkPath).catch(() => null);
|
|
1276
|
+
if (stats?.isSymbolicLink()) {
|
|
1277
|
+
await rm(symlinkPath);
|
|
1278
|
+
}
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function getRegistrySkillPath(username, skillName) {
|
|
1284
|
+
return `.pspm/skills/${username}/${skillName}`;
|
|
1285
|
+
}
|
|
1286
|
+
function getGitHubSkillPath(owner, repo, path) {
|
|
1287
|
+
if (path) {
|
|
1288
|
+
return `.pspm/skills/_github/${owner}/${repo}/${path}`;
|
|
1289
|
+
}
|
|
1290
|
+
return `.pspm/skills/_github/${owner}/${repo}`;
|
|
1291
|
+
}
|
|
1292
|
+
async function getLinkedAgents(skillName, agents, projectRoot, agentConfigs) {
|
|
1293
|
+
const linkedAgents = [];
|
|
1294
|
+
for (const agentName of agents) {
|
|
1295
|
+
const config2 = resolveAgentConfig(agentName, agentConfigs);
|
|
1296
|
+
if (!config2) continue;
|
|
1297
|
+
const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
|
|
1298
|
+
try {
|
|
1299
|
+
const stats = await lstat(symlinkPath);
|
|
1300
|
+
if (stats.isSymbolicLink()) {
|
|
1301
|
+
linkedAgents.push(agentName);
|
|
1302
|
+
}
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return linkedAgents;
|
|
1307
|
+
}
|
|
839
1308
|
|
|
840
1309
|
// src/commands/add.ts
|
|
841
|
-
async function add(specifier,
|
|
1310
|
+
async function add(specifier, options) {
|
|
1311
|
+
let agents;
|
|
1312
|
+
const manifest = await readManifest();
|
|
1313
|
+
if (options.agent) {
|
|
1314
|
+
agents = parseAgentArg(options.agent);
|
|
1315
|
+
} else if (manifest) {
|
|
1316
|
+
agents = parseAgentArg(void 0);
|
|
1317
|
+
} else if (options.yes) {
|
|
1318
|
+
agents = parseAgentArg(void 0);
|
|
1319
|
+
} else {
|
|
1320
|
+
console.log("No pspm.json found. Let's set up your project.\n");
|
|
1321
|
+
agents = await promptForAgents();
|
|
1322
|
+
}
|
|
1323
|
+
if (isGitHubSpecifier(specifier)) {
|
|
1324
|
+
await addGitHub(specifier, { ...options, resolvedAgents: agents });
|
|
1325
|
+
} else {
|
|
1326
|
+
await addRegistry(specifier, { ...options, resolvedAgents: agents });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
async function addRegistry(specifier, options) {
|
|
842
1330
|
try {
|
|
843
1331
|
const config2 = await resolveConfig();
|
|
844
1332
|
const registryUrl = config2.registryUrl;
|
|
@@ -925,16 +1413,16 @@ async function add(specifier, _options) {
|
|
|
925
1413
|
const skillsDir = getSkillsDir();
|
|
926
1414
|
const destDir = join(skillsDir, username, name);
|
|
927
1415
|
await mkdir(destDir, { recursive: true });
|
|
928
|
-
const { writeFile:
|
|
1416
|
+
const { writeFile: writeFile8 } = await import('fs/promises');
|
|
929
1417
|
const tempFile = join(destDir, ".temp.tgz");
|
|
930
|
-
await
|
|
1418
|
+
await writeFile8(tempFile, tarballBuffer);
|
|
931
1419
|
const { exec: exec2 } = await import('child_process');
|
|
932
1420
|
const { promisify: promisify2 } = await import('util');
|
|
933
1421
|
const execAsync = promisify2(exec2);
|
|
934
1422
|
try {
|
|
935
1423
|
await rm(destDir, { recursive: true, force: true });
|
|
936
1424
|
await mkdir(destDir, { recursive: true });
|
|
937
|
-
await
|
|
1425
|
+
await writeFile8(tempFile, tarballBuffer);
|
|
938
1426
|
await execAsync(
|
|
939
1427
|
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
940
1428
|
);
|
|
@@ -947,6 +1435,21 @@ async function add(specifier, _options) {
|
|
|
947
1435
|
resolved: versionInfo.downloadUrl,
|
|
948
1436
|
integrity
|
|
949
1437
|
});
|
|
1438
|
+
const dependencyRange = versionRange || `^${resolved}`;
|
|
1439
|
+
await addDependency(fullName, dependencyRange);
|
|
1440
|
+
const agents = options.resolvedAgents;
|
|
1441
|
+
if (agents[0] !== "none") {
|
|
1442
|
+
const skillManifest = await readManifest();
|
|
1443
|
+
const skillInfo = {
|
|
1444
|
+
name,
|
|
1445
|
+
sourcePath: getRegistrySkillPath(username, name)
|
|
1446
|
+
};
|
|
1447
|
+
await createAgentSymlinks([skillInfo], {
|
|
1448
|
+
agents,
|
|
1449
|
+
projectRoot: process.cwd(),
|
|
1450
|
+
agentConfigs: skillManifest?.agents
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
950
1453
|
console.log(`Installed @user/${username}/${name}@${resolved}`);
|
|
951
1454
|
console.log(`Location: ${destDir}`);
|
|
952
1455
|
} catch (error) {
|
|
@@ -955,6 +1458,72 @@ async function add(specifier, _options) {
|
|
|
955
1458
|
process.exit(1);
|
|
956
1459
|
}
|
|
957
1460
|
}
|
|
1461
|
+
async function addGitHub(specifier, options) {
|
|
1462
|
+
try {
|
|
1463
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
1464
|
+
if (!parsed) {
|
|
1465
|
+
console.error(
|
|
1466
|
+
`Error: Invalid GitHub specifier "${specifier}". Use format: github:{owner}/{repo}[/{path}][@{ref}]`
|
|
1467
|
+
);
|
|
1468
|
+
process.exit(1);
|
|
1469
|
+
}
|
|
1470
|
+
const ref = parsed.ref || "HEAD";
|
|
1471
|
+
console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
|
|
1472
|
+
const result = await downloadGitHubPackage(parsed);
|
|
1473
|
+
console.log(
|
|
1474
|
+
`Installing ${specifier} (${ref}@${result.commit.slice(0, 7)})...`
|
|
1475
|
+
);
|
|
1476
|
+
const skillsDir = getSkillsDir();
|
|
1477
|
+
const destPath = await extractGitHubPackage(
|
|
1478
|
+
parsed,
|
|
1479
|
+
result.buffer,
|
|
1480
|
+
skillsDir
|
|
1481
|
+
);
|
|
1482
|
+
const lockfileSpecifier = formatGitHubSpecifier({
|
|
1483
|
+
owner: parsed.owner,
|
|
1484
|
+
repo: parsed.repo,
|
|
1485
|
+
path: parsed.path
|
|
1486
|
+
// Don't include ref in the specifier key, it's stored in gitRef
|
|
1487
|
+
});
|
|
1488
|
+
const entry = {
|
|
1489
|
+
version: result.commit.slice(0, 7),
|
|
1490
|
+
resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
|
|
1491
|
+
integrity: result.integrity,
|
|
1492
|
+
gitCommit: result.commit,
|
|
1493
|
+
gitRef: ref
|
|
1494
|
+
};
|
|
1495
|
+
await addGitHubToLockfile(lockfileSpecifier, entry);
|
|
1496
|
+
await addGitHubDependency(lockfileSpecifier, ref);
|
|
1497
|
+
const agents = options.resolvedAgents;
|
|
1498
|
+
if (agents[0] !== "none") {
|
|
1499
|
+
const manifest = await readManifest();
|
|
1500
|
+
const skillName = getGitHubSkillName(parsed);
|
|
1501
|
+
const skillInfo = {
|
|
1502
|
+
name: skillName,
|
|
1503
|
+
sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
|
|
1504
|
+
};
|
|
1505
|
+
await createAgentSymlinks([skillInfo], {
|
|
1506
|
+
agents,
|
|
1507
|
+
projectRoot: process.cwd(),
|
|
1508
|
+
agentConfigs: manifest?.agents
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
console.log(`Installed ${specifier} (${ref}@${result.commit.slice(0, 7)})`);
|
|
1512
|
+
console.log(`Location: ${destPath}`);
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
if (error instanceof GitHubRateLimitError) {
|
|
1515
|
+
console.error(`Error: ${error.message}`);
|
|
1516
|
+
} else if (error instanceof GitHubPathNotFoundError) {
|
|
1517
|
+
console.error(`Error: ${error.message}`);
|
|
1518
|
+
} else if (error instanceof GitHubNotFoundError) {
|
|
1519
|
+
console.error(`Error: ${error.message}`);
|
|
1520
|
+
} else {
|
|
1521
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1522
|
+
console.error(`Error: ${message}`);
|
|
1523
|
+
}
|
|
1524
|
+
process.exit(1);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
958
1527
|
async function configInit(options) {
|
|
959
1528
|
try {
|
|
960
1529
|
const configPath = join(process.cwd(), ".pspmrc");
|
|
@@ -1078,6 +1647,14 @@ async function deprecate(specifier, message, options) {
|
|
|
1078
1647
|
process.exit(1);
|
|
1079
1648
|
}
|
|
1080
1649
|
}
|
|
1650
|
+
function prompt(rl, question, defaultValue) {
|
|
1651
|
+
return new Promise((resolve) => {
|
|
1652
|
+
const displayDefault = defaultValue ? ` (${defaultValue})` : "";
|
|
1653
|
+
rl.question(`${question}${displayDefault} `, (answer) => {
|
|
1654
|
+
resolve(answer.trim() || defaultValue);
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1081
1658
|
async function readExistingPackageJson() {
|
|
1082
1659
|
try {
|
|
1083
1660
|
const content = await readFile(
|
|
@@ -1096,52 +1673,177 @@ async function readExistingPackageJson() {
|
|
|
1096
1673
|
return null;
|
|
1097
1674
|
}
|
|
1098
1675
|
}
|
|
1676
|
+
async function getGitAuthor() {
|
|
1677
|
+
try {
|
|
1678
|
+
const { exec: exec2 } = await import('child_process');
|
|
1679
|
+
const { promisify: promisify2 } = await import('util');
|
|
1680
|
+
const execAsync = promisify2(exec2);
|
|
1681
|
+
const [nameResult, emailResult] = await Promise.all([
|
|
1682
|
+
execAsync("git config user.name").catch(() => ({ stdout: "" })),
|
|
1683
|
+
execAsync("git config user.email").catch(() => ({ stdout: "" }))
|
|
1684
|
+
]);
|
|
1685
|
+
const name = nameResult.stdout.trim();
|
|
1686
|
+
const email = emailResult.stdout.trim();
|
|
1687
|
+
if (name && email) {
|
|
1688
|
+
return `${name} <${email}>`;
|
|
1689
|
+
}
|
|
1690
|
+
if (name) {
|
|
1691
|
+
return name;
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
} catch {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1099
1698
|
function sanitizeName(name) {
|
|
1100
1699
|
const withoutScope = name.replace(/^@[^/]+\//, "");
|
|
1101
1700
|
return withoutScope.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
1102
1701
|
}
|
|
1702
|
+
function isValidName(name) {
|
|
1703
|
+
return /^[a-z][a-z0-9_-]*$/.test(name);
|
|
1704
|
+
}
|
|
1705
|
+
function isValidVersion(version2) {
|
|
1706
|
+
return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(version2);
|
|
1707
|
+
}
|
|
1103
1708
|
async function init(options) {
|
|
1104
1709
|
try {
|
|
1105
1710
|
const pspmJsonPath = join(process.cwd(), "pspm.json");
|
|
1711
|
+
let exists = false;
|
|
1106
1712
|
try {
|
|
1107
1713
|
await stat(pspmJsonPath);
|
|
1714
|
+
exists = true;
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
if (exists && !options.force) {
|
|
1108
1718
|
console.error("Error: pspm.json already exists in this directory.");
|
|
1109
|
-
console.error("Use --force to overwrite
|
|
1719
|
+
console.error("Use --force to overwrite.");
|
|
1110
1720
|
process.exit(1);
|
|
1111
|
-
} catch {
|
|
1112
1721
|
}
|
|
1113
1722
|
const existingPkg = await readExistingPackageJson();
|
|
1114
|
-
const
|
|
1723
|
+
const gitAuthor = await getGitAuthor();
|
|
1724
|
+
const defaultName = sanitizeName(
|
|
1725
|
+
options.name || existingPkg?.name || basename(process.cwd())
|
|
1726
|
+
);
|
|
1115
1727
|
const defaultVersion = existingPkg?.version || "0.1.0";
|
|
1116
1728
|
const defaultDescription = options.description || existingPkg?.description || "";
|
|
1117
|
-
const defaultAuthor = options.author || existingPkg?.author || "";
|
|
1729
|
+
const defaultAuthor = options.author || existingPkg?.author || gitAuthor || "";
|
|
1118
1730
|
const defaultLicense = existingPkg?.license || "MIT";
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1731
|
+
const defaultMain = "SKILL.md";
|
|
1732
|
+
const defaultCapabilities = "";
|
|
1733
|
+
let manifest;
|
|
1734
|
+
if (options.yes) {
|
|
1735
|
+
manifest = {
|
|
1736
|
+
$schema: PSPM_SCHEMA_URL,
|
|
1737
|
+
name: defaultName,
|
|
1738
|
+
version: defaultVersion,
|
|
1739
|
+
description: defaultDescription || void 0,
|
|
1740
|
+
author: defaultAuthor || void 0,
|
|
1741
|
+
license: defaultLicense,
|
|
1742
|
+
type: "skill",
|
|
1743
|
+
capabilities: [],
|
|
1744
|
+
main: defaultMain,
|
|
1745
|
+
requirements: {
|
|
1746
|
+
pspm: ">=0.1.0"
|
|
1747
|
+
},
|
|
1748
|
+
files: [...DEFAULT_SKILL_FILES],
|
|
1749
|
+
dependencies: {},
|
|
1750
|
+
private: false
|
|
1751
|
+
};
|
|
1752
|
+
} else {
|
|
1753
|
+
console.log(
|
|
1754
|
+
"This utility will walk you through creating a pspm.json file."
|
|
1755
|
+
);
|
|
1756
|
+
console.log(
|
|
1757
|
+
"It only covers the most common items, and tries to guess sensible defaults."
|
|
1758
|
+
);
|
|
1759
|
+
console.log("");
|
|
1760
|
+
console.log(
|
|
1761
|
+
"See `pspm init --help` for definitive documentation on these fields"
|
|
1762
|
+
);
|
|
1763
|
+
console.log("and exactly what they do.");
|
|
1764
|
+
console.log("");
|
|
1765
|
+
console.log("Press ^C at any time to quit.");
|
|
1766
|
+
const rl = createInterface({
|
|
1767
|
+
input: process.stdin,
|
|
1768
|
+
output: process.stdout
|
|
1769
|
+
});
|
|
1770
|
+
try {
|
|
1771
|
+
let name = await prompt(rl, "skill name:", defaultName);
|
|
1772
|
+
while (!isValidName(name)) {
|
|
1773
|
+
console.log(
|
|
1774
|
+
" Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores."
|
|
1775
|
+
);
|
|
1776
|
+
name = await prompt(rl, "skill name:", sanitizeName(name));
|
|
1777
|
+
}
|
|
1778
|
+
let version2 = await prompt(rl, "version:", defaultVersion);
|
|
1779
|
+
while (!isValidVersion(version2)) {
|
|
1780
|
+
console.log(" Version must be valid semver (e.g., 1.0.0)");
|
|
1781
|
+
version2 = await prompt(rl, "version:", "0.1.0");
|
|
1782
|
+
}
|
|
1783
|
+
const description = await prompt(
|
|
1784
|
+
rl,
|
|
1785
|
+
"description:",
|
|
1786
|
+
defaultDescription
|
|
1787
|
+
);
|
|
1788
|
+
const main = await prompt(rl, "entry point:", defaultMain);
|
|
1789
|
+
const capabilitiesStr = await prompt(
|
|
1790
|
+
rl,
|
|
1791
|
+
"capabilities (comma-separated):",
|
|
1792
|
+
defaultCapabilities
|
|
1793
|
+
);
|
|
1794
|
+
const author = await prompt(rl, "author:", defaultAuthor);
|
|
1795
|
+
const license = await prompt(rl, "license:", defaultLicense);
|
|
1796
|
+
rl.close();
|
|
1797
|
+
const capabilities = capabilitiesStr ? capabilitiesStr.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
1798
|
+
manifest = {
|
|
1799
|
+
$schema: PSPM_SCHEMA_URL,
|
|
1800
|
+
name,
|
|
1801
|
+
version: version2,
|
|
1802
|
+
description: description || void 0,
|
|
1803
|
+
author: author || void 0,
|
|
1804
|
+
license,
|
|
1805
|
+
type: "skill",
|
|
1806
|
+
capabilities,
|
|
1807
|
+
main,
|
|
1808
|
+
requirements: {
|
|
1809
|
+
pspm: ">=0.1.0"
|
|
1810
|
+
},
|
|
1811
|
+
files: [...DEFAULT_SKILL_FILES],
|
|
1812
|
+
dependencies: {},
|
|
1813
|
+
private: false
|
|
1814
|
+
};
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
rl.close();
|
|
1817
|
+
if (error instanceof Error && error.message.includes("readline was closed")) {
|
|
1818
|
+
console.log("\nAborted.");
|
|
1819
|
+
process.exit(0);
|
|
1820
|
+
}
|
|
1821
|
+
throw error;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1136
1824
|
if (!manifest.description) delete manifest.description;
|
|
1137
1825
|
if (!manifest.author) delete manifest.author;
|
|
1826
|
+
if (manifest.capabilities?.length === 0) delete manifest.capabilities;
|
|
1138
1827
|
const content = JSON.stringify(manifest, null, 2);
|
|
1139
|
-
|
|
1140
|
-
`);
|
|
1141
|
-
console.log("Created pspm.json:");
|
|
1828
|
+
console.log("");
|
|
1829
|
+
console.log(`About to write to ${pspmJsonPath}:`);
|
|
1142
1830
|
console.log("");
|
|
1143
1831
|
console.log(content);
|
|
1144
1832
|
console.log("");
|
|
1833
|
+
if (!options.yes) {
|
|
1834
|
+
const rl = createInterface({
|
|
1835
|
+
input: process.stdin,
|
|
1836
|
+
output: process.stdout
|
|
1837
|
+
});
|
|
1838
|
+
const confirm = await prompt(rl, "Is this OK?", "yes");
|
|
1839
|
+
rl.close();
|
|
1840
|
+
if (confirm.toLowerCase() !== "yes" && confirm.toLowerCase() !== "y") {
|
|
1841
|
+
console.log("Aborted.");
|
|
1842
|
+
process.exit(0);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
await writeFile(pspmJsonPath, `${content}
|
|
1846
|
+
`);
|
|
1145
1847
|
try {
|
|
1146
1848
|
await stat(join(process.cwd(), "SKILL.md"));
|
|
1147
1849
|
} catch {
|
|
@@ -1194,108 +1896,431 @@ async function writeToCache(cacheDir, integrity, data) {
|
|
|
1194
1896
|
async function install(options) {
|
|
1195
1897
|
try {
|
|
1196
1898
|
const config2 = await resolveConfig();
|
|
1197
|
-
const
|
|
1899
|
+
const registryUrl = config2.registryUrl;
|
|
1900
|
+
const apiKey = getTokenForRegistry(config2, registryUrl);
|
|
1198
1901
|
const skillsDir = options.dir || getSkillsDir();
|
|
1199
1902
|
const cacheDir = getCacheDir();
|
|
1903
|
+
const manifest = await readManifest();
|
|
1904
|
+
const agentConfigs = manifest?.agents;
|
|
1905
|
+
let agents;
|
|
1906
|
+
if (options.agent) {
|
|
1907
|
+
agents = parseAgentArg(options.agent);
|
|
1908
|
+
} else if (manifest) {
|
|
1909
|
+
agents = parseAgentArg(void 0);
|
|
1910
|
+
} else if (options.yes) {
|
|
1911
|
+
agents = parseAgentArg(void 0);
|
|
1912
|
+
} else {
|
|
1913
|
+
console.log("No pspm.json found. Let's set up your project.\n");
|
|
1914
|
+
agents = await promptForAgents();
|
|
1915
|
+
}
|
|
1200
1916
|
await migrateLockfileIfNeeded();
|
|
1201
|
-
|
|
1202
|
-
|
|
1917
|
+
let lockfile = await readLockfile();
|
|
1918
|
+
const manifestDeps = await getDependencies();
|
|
1919
|
+
const manifestGitHubDeps = await getGitHubDependencies();
|
|
1920
|
+
const lockfilePackages = lockfile?.packages ?? lockfile?.skills ?? {};
|
|
1921
|
+
const lockfileGitHubPackages = lockfile?.githubPackages ?? {};
|
|
1922
|
+
const installedSkills = [];
|
|
1923
|
+
const missingDeps = [];
|
|
1924
|
+
for (const [fullName, versionRange] of Object.entries(manifestDeps)) {
|
|
1925
|
+
if (!lockfilePackages[fullName]) {
|
|
1926
|
+
missingDeps.push({ fullName, versionRange });
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if (missingDeps.length > 0) {
|
|
1203
1930
|
if (options.frozenLockfile) {
|
|
1204
1931
|
console.error(
|
|
1205
|
-
"Error:
|
|
1932
|
+
"Error: Dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
|
|
1206
1933
|
);
|
|
1934
|
+
console.error("Missing dependencies:");
|
|
1935
|
+
for (const dep of missingDeps) {
|
|
1936
|
+
console.error(` - ${dep.fullName}@${dep.versionRange}`);
|
|
1937
|
+
}
|
|
1207
1938
|
process.exit(1);
|
|
1208
1939
|
}
|
|
1209
|
-
console.log(
|
|
1210
|
-
return;
|
|
1211
|
-
}
|
|
1212
|
-
const skillCount = Object.keys(
|
|
1213
|
-
lockfile.packages ?? lockfile.skills ?? {}
|
|
1214
|
-
).length;
|
|
1215
|
-
if (skillCount === 0) {
|
|
1216
|
-
console.log("No skills in lockfile. Nothing to install.");
|
|
1217
|
-
return;
|
|
1218
|
-
}
|
|
1219
|
-
console.log(`Installing ${skillCount} skill(s)...
|
|
1940
|
+
console.log(`Resolving ${missingDeps.length} new dependency(ies)...
|
|
1220
1941
|
`);
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1942
|
+
configure2({ registryUrl, apiKey: apiKey ?? "" });
|
|
1943
|
+
for (const { fullName, versionRange } of missingDeps) {
|
|
1944
|
+
const parsed = parseSkillSpecifier(fullName);
|
|
1945
|
+
if (!parsed) {
|
|
1946
|
+
console.error(`Error: Invalid dependency specifier: ${fullName}`);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const { username, name } = parsed;
|
|
1950
|
+
console.log(`Resolving ${fullName}@${versionRange}...`);
|
|
1951
|
+
const versionsResponse = await listSkillVersions(username, name);
|
|
1952
|
+
if (versionsResponse.status !== 200) {
|
|
1953
|
+
const errorMessage = extractApiErrorMessage(
|
|
1954
|
+
versionsResponse,
|
|
1955
|
+
`Skill ${fullName} not found`
|
|
1956
|
+
);
|
|
1957
|
+
console.error(`Error: ${errorMessage}`);
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
const versions = versionsResponse.data;
|
|
1961
|
+
if (versions.length === 0) {
|
|
1962
|
+
console.error(`Error: Skill ${fullName} not found`);
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
const versionStrings = versions.map(
|
|
1966
|
+
(v) => v.version
|
|
1967
|
+
);
|
|
1968
|
+
const resolved = resolveVersion(versionRange || "*", versionStrings);
|
|
1969
|
+
if (!resolved) {
|
|
1970
|
+
console.error(
|
|
1971
|
+
`Error: No version matching "${versionRange}" for ${fullName}`
|
|
1972
|
+
);
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
const versionResponse = await getSkillVersion(username, name, resolved);
|
|
1976
|
+
if (versionResponse.status !== 200 || !versionResponse.data) {
|
|
1977
|
+
const errorMessage = extractApiErrorMessage(
|
|
1978
|
+
versionResponse,
|
|
1979
|
+
`Version ${resolved} not found`
|
|
1980
|
+
);
|
|
1981
|
+
console.error(`Error: ${errorMessage}`);
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
const versionInfo = versionResponse.data;
|
|
1985
|
+
const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
|
|
1239
1986
|
const downloadHeaders = {};
|
|
1240
1987
|
if (!isPresignedUrl && apiKey) {
|
|
1241
1988
|
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
1242
1989
|
}
|
|
1243
|
-
const
|
|
1990
|
+
const tarballResponse = await fetch(versionInfo.downloadUrl, {
|
|
1244
1991
|
headers: downloadHeaders,
|
|
1245
1992
|
redirect: "follow"
|
|
1246
1993
|
});
|
|
1247
|
-
if (!
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1994
|
+
if (!tarballResponse.ok) {
|
|
1995
|
+
console.error(
|
|
1996
|
+
`Error: Failed to download tarball for ${fullName} (${tarballResponse.status})`
|
|
1997
|
+
);
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
|
|
2001
|
+
const integrity = calculateIntegrity(tarballBuffer);
|
|
2002
|
+
await addToLockfile(fullName, {
|
|
2003
|
+
version: resolved,
|
|
2004
|
+
resolved: versionInfo.downloadUrl,
|
|
2005
|
+
integrity
|
|
2006
|
+
});
|
|
2007
|
+
await writeToCache(cacheDir, integrity, tarballBuffer);
|
|
2008
|
+
console.log(` Resolved ${fullName}@${resolved}`);
|
|
2009
|
+
}
|
|
2010
|
+
lockfile = await readLockfile();
|
|
2011
|
+
}
|
|
2012
|
+
const missingGitHubDeps = [];
|
|
2013
|
+
for (const [specifier, ref] of Object.entries(manifestGitHubDeps)) {
|
|
2014
|
+
if (!lockfileGitHubPackages[specifier]) {
|
|
2015
|
+
missingGitHubDeps.push({ specifier, ref });
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
if (missingGitHubDeps.length > 0) {
|
|
2019
|
+
if (options.frozenLockfile) {
|
|
2020
|
+
console.error(
|
|
2021
|
+
"Error: GitHub dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
|
|
2022
|
+
);
|
|
2023
|
+
console.error("Missing GitHub dependencies:");
|
|
2024
|
+
for (const dep of missingGitHubDeps) {
|
|
2025
|
+
console.error(` - ${dep.specifier}@${dep.ref}`);
|
|
2026
|
+
}
|
|
2027
|
+
process.exit(1);
|
|
2028
|
+
}
|
|
2029
|
+
console.log(
|
|
2030
|
+
`
|
|
2031
|
+
Resolving ${missingGitHubDeps.length} GitHub dependency(ies)...
|
|
2032
|
+
`
|
|
2033
|
+
);
|
|
2034
|
+
for (const { specifier, ref } of missingGitHubDeps) {
|
|
2035
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2036
|
+
if (!parsed) {
|
|
2037
|
+
console.error(`Error: Invalid GitHub specifier: ${specifier}`);
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
parsed.ref = parsed.ref || ref;
|
|
2041
|
+
console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
|
|
2042
|
+
try {
|
|
2043
|
+
const result = await downloadGitHubPackage(parsed);
|
|
2044
|
+
await extractGitHubPackage(parsed, result.buffer, skillsDir);
|
|
2045
|
+
const entry = {
|
|
2046
|
+
version: result.commit.slice(0, 7),
|
|
2047
|
+
resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
|
|
2048
|
+
integrity: result.integrity,
|
|
2049
|
+
gitCommit: result.commit,
|
|
2050
|
+
gitRef: ref || "HEAD"
|
|
2051
|
+
};
|
|
2052
|
+
await addGitHubToLockfile(specifier, entry);
|
|
2053
|
+
await writeToCache(cacheDir, result.integrity, result.buffer);
|
|
2054
|
+
console.log(
|
|
2055
|
+
` Resolved ${specifier} (${ref}@${result.commit.slice(0, 7)})`
|
|
2056
|
+
);
|
|
2057
|
+
} catch (error) {
|
|
2058
|
+
if (error instanceof GitHubRateLimitError) {
|
|
2059
|
+
console.error(`Error: ${error.message}`);
|
|
2060
|
+
} else if (error instanceof GitHubPathNotFoundError) {
|
|
2061
|
+
console.error(`Error: ${error.message}`);
|
|
2062
|
+
} else if (error instanceof GitHubNotFoundError) {
|
|
2063
|
+
console.error(`Error: ${error.message}`);
|
|
2064
|
+
} else {
|
|
2065
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2066
|
+
console.error(`Error resolving ${specifier}: ${message}`);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
lockfile = await readLockfile();
|
|
2071
|
+
}
|
|
2072
|
+
const packages = lockfile?.packages ?? lockfile?.skills ?? {};
|
|
2073
|
+
const packageCount = Object.keys(packages).length;
|
|
2074
|
+
if (packageCount > 0) {
|
|
2075
|
+
console.log(`
|
|
2076
|
+
Installing ${packageCount} registry skill(s)...
|
|
2077
|
+
`);
|
|
2078
|
+
const entries = Object.entries(packages);
|
|
2079
|
+
for (const [fullName, entry] of entries) {
|
|
2080
|
+
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
2081
|
+
if (!match) {
|
|
2082
|
+
console.warn(`Warning: Invalid skill name in lockfile: ${fullName}`);
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
const [, username, name] = match;
|
|
2086
|
+
console.log(`Installing ${fullName}@${entry.version}...`);
|
|
2087
|
+
let tarballBuffer;
|
|
2088
|
+
let fromCache = false;
|
|
2089
|
+
const cachedTarball = await readFromCache(cacheDir, entry.integrity);
|
|
2090
|
+
if (cachedTarball) {
|
|
2091
|
+
tarballBuffer = cachedTarball;
|
|
2092
|
+
fromCache = true;
|
|
2093
|
+
} else {
|
|
2094
|
+
const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
|
|
2095
|
+
const downloadHeaders = {};
|
|
2096
|
+
if (!isPresignedUrl && apiKey) {
|
|
2097
|
+
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
2098
|
+
}
|
|
2099
|
+
const response = await fetch(entry.resolved, {
|
|
2100
|
+
headers: downloadHeaders,
|
|
2101
|
+
redirect: "follow"
|
|
2102
|
+
});
|
|
2103
|
+
if (!response.ok) {
|
|
2104
|
+
if (response.status === 401) {
|
|
2105
|
+
if (!apiKey) {
|
|
2106
|
+
console.error(
|
|
2107
|
+
` Error: ${fullName} requires authentication. Run 'pspm login' first.`
|
|
2108
|
+
);
|
|
2109
|
+
} else {
|
|
2110
|
+
console.error(
|
|
2111
|
+
` Error: Access denied to ${fullName}. You may not have permission to access this private package.`
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
1253
2114
|
} else {
|
|
1254
2115
|
console.error(
|
|
1255
|
-
` Error:
|
|
2116
|
+
` Error: Failed to download ${fullName} (${response.status})`
|
|
1256
2117
|
);
|
|
1257
2118
|
}
|
|
1258
|
-
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
tarballBuffer = Buffer.from(await response.arrayBuffer());
|
|
2122
|
+
const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
|
|
2123
|
+
if (actualIntegrity !== entry.integrity) {
|
|
1259
2124
|
console.error(
|
|
1260
|
-
` Error:
|
|
2125
|
+
` Error: Checksum verification failed for ${fullName}`
|
|
1261
2126
|
);
|
|
2127
|
+
if (options.frozenLockfile) {
|
|
2128
|
+
process.exit(1);
|
|
2129
|
+
}
|
|
2130
|
+
continue;
|
|
1262
2131
|
}
|
|
1263
|
-
|
|
2132
|
+
await writeToCache(cacheDir, entry.integrity, tarballBuffer);
|
|
1264
2133
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
2134
|
+
const destDir = join(skillsDir, username, name);
|
|
2135
|
+
await rm(destDir, { recursive: true, force: true });
|
|
2136
|
+
await mkdir(destDir, { recursive: true });
|
|
2137
|
+
const tempFile = join(destDir, ".temp.tgz");
|
|
2138
|
+
await writeFile(tempFile, tarballBuffer);
|
|
2139
|
+
const { exec: exec2 } = await import('child_process');
|
|
2140
|
+
const { promisify: promisify2 } = await import('util');
|
|
2141
|
+
const execAsync = promisify2(exec2);
|
|
2142
|
+
try {
|
|
2143
|
+
await execAsync(
|
|
2144
|
+
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
2145
|
+
);
|
|
2146
|
+
} finally {
|
|
2147
|
+
await rm(tempFile, { force: true });
|
|
2148
|
+
}
|
|
2149
|
+
console.log(
|
|
2150
|
+
` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
|
|
2151
|
+
);
|
|
2152
|
+
installedSkills.push({
|
|
2153
|
+
name,
|
|
2154
|
+
sourcePath: getRegistrySkillPath(username, name)
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
const githubPackages = lockfile?.githubPackages ?? {};
|
|
2159
|
+
const githubCount = Object.keys(githubPackages).length;
|
|
2160
|
+
if (githubCount > 0) {
|
|
2161
|
+
console.log(`
|
|
2162
|
+
Installing ${githubCount} GitHub skill(s)...
|
|
2163
|
+
`);
|
|
2164
|
+
for (const [specifier, entry] of Object.entries(githubPackages)) {
|
|
2165
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2166
|
+
if (!parsed) {
|
|
2167
|
+
console.warn(
|
|
2168
|
+
`Warning: Invalid GitHub specifier in lockfile: ${specifier}`
|
|
1270
2169
|
);
|
|
1271
|
-
if (options.frozenLockfile) {
|
|
1272
|
-
process.exit(1);
|
|
1273
|
-
}
|
|
1274
2170
|
continue;
|
|
1275
2171
|
}
|
|
1276
|
-
|
|
2172
|
+
const ghEntry = entry;
|
|
2173
|
+
console.log(
|
|
2174
|
+
`Installing ${specifier} (${ghEntry.gitRef}@${ghEntry.gitCommit.slice(0, 7)})...`
|
|
2175
|
+
);
|
|
2176
|
+
let tarballBuffer;
|
|
2177
|
+
let fromCache = false;
|
|
2178
|
+
const cachedTarball = await readFromCache(cacheDir, ghEntry.integrity);
|
|
2179
|
+
if (cachedTarball) {
|
|
2180
|
+
tarballBuffer = cachedTarball;
|
|
2181
|
+
fromCache = true;
|
|
2182
|
+
} else {
|
|
2183
|
+
try {
|
|
2184
|
+
const specWithCommit = { ...parsed, ref: ghEntry.gitCommit };
|
|
2185
|
+
const result = await downloadGitHubPackage(specWithCommit);
|
|
2186
|
+
tarballBuffer = result.buffer;
|
|
2187
|
+
if (result.integrity !== ghEntry.integrity) {
|
|
2188
|
+
console.error(
|
|
2189
|
+
` Error: Checksum verification failed for ${specifier}`
|
|
2190
|
+
);
|
|
2191
|
+
if (options.frozenLockfile) {
|
|
2192
|
+
process.exit(1);
|
|
2193
|
+
}
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
await writeToCache(cacheDir, ghEntry.integrity, tarballBuffer);
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
if (error instanceof GitHubRateLimitError) {
|
|
2199
|
+
console.error(` Error: ${error.message}`);
|
|
2200
|
+
} else if (error instanceof GitHubPathNotFoundError) {
|
|
2201
|
+
console.error(` Error: ${error.message}`);
|
|
2202
|
+
} else if (error instanceof GitHubNotFoundError) {
|
|
2203
|
+
console.error(` Error: ${error.message}`);
|
|
2204
|
+
} else {
|
|
2205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2206
|
+
console.error(` Error downloading ${specifier}: ${message}`);
|
|
2207
|
+
}
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
try {
|
|
2212
|
+
const destPath = await extractGitHubPackage(
|
|
2213
|
+
parsed,
|
|
2214
|
+
tarballBuffer,
|
|
2215
|
+
skillsDir
|
|
2216
|
+
);
|
|
2217
|
+
console.log(
|
|
2218
|
+
` Installed to ${destPath}${fromCache ? " (from cache)" : ""}`
|
|
2219
|
+
);
|
|
2220
|
+
const skillName = getGitHubSkillName(parsed);
|
|
2221
|
+
installedSkills.push({
|
|
2222
|
+
name: skillName,
|
|
2223
|
+
sourcePath: getGitHubSkillPath(
|
|
2224
|
+
parsed.owner,
|
|
2225
|
+
parsed.repo,
|
|
2226
|
+
parsed.path
|
|
2227
|
+
)
|
|
2228
|
+
});
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2231
|
+
console.error(` Error extracting ${specifier}: ${message}`);
|
|
2232
|
+
}
|
|
1277
2233
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
await
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
2234
|
+
}
|
|
2235
|
+
if (installedSkills.length > 0 && agents[0] !== "none") {
|
|
2236
|
+
console.log(`
|
|
2237
|
+
Creating symlinks for agent(s): ${agents.join(", ")}...`);
|
|
2238
|
+
await createAgentSymlinks(installedSkills, {
|
|
2239
|
+
agents,
|
|
2240
|
+
projectRoot: process.cwd(),
|
|
2241
|
+
agentConfigs
|
|
2242
|
+
});
|
|
2243
|
+
console.log(" Symlinks created.");
|
|
2244
|
+
}
|
|
2245
|
+
const totalCount = packageCount + githubCount;
|
|
2246
|
+
if (totalCount === 0) {
|
|
2247
|
+
console.log("No skills to install.");
|
|
2248
|
+
} else {
|
|
2249
|
+
console.log(`
|
|
2250
|
+
All ${totalCount} skill(s) installed.`);
|
|
2251
|
+
}
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2254
|
+
console.error(`Error: ${message}`);
|
|
2255
|
+
process.exit(1);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// src/commands/link.ts
|
|
2260
|
+
async function link(options) {
|
|
2261
|
+
try {
|
|
2262
|
+
const manifest = await readManifest();
|
|
2263
|
+
const agentConfigs = manifest?.agents;
|
|
2264
|
+
let agents;
|
|
2265
|
+
if (options.agent) {
|
|
2266
|
+
agents = parseAgentArg(options.agent);
|
|
2267
|
+
} else if (manifest) {
|
|
2268
|
+
agents = parseAgentArg(void 0);
|
|
2269
|
+
} else if (options.yes) {
|
|
2270
|
+
agents = parseAgentArg(void 0);
|
|
2271
|
+
} else {
|
|
2272
|
+
console.log("No pspm.json found. Let's set up your project.\n");
|
|
2273
|
+
agents = await promptForAgents();
|
|
2274
|
+
}
|
|
2275
|
+
if (agents.length === 1 && agents[0] === "none") {
|
|
2276
|
+
console.log("Skipping symlink creation (--agent none)");
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
const skills = [];
|
|
2280
|
+
const registrySkills = await listLockfileSkills();
|
|
2281
|
+
for (const { name } of registrySkills) {
|
|
2282
|
+
const parsed = parseSkillSpecifier(name);
|
|
2283
|
+
if (!parsed) {
|
|
2284
|
+
console.warn(`Warning: Invalid skill name in lockfile: ${name}`);
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
skills.push({
|
|
2288
|
+
name: parsed.name,
|
|
2289
|
+
sourcePath: getRegistrySkillPath(parsed.username, parsed.name)
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
const githubSkills = await listLockfileGitHubPackages();
|
|
2293
|
+
for (const { specifier } of githubSkills) {
|
|
2294
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2295
|
+
if (!parsed) {
|
|
2296
|
+
console.warn(
|
|
2297
|
+
`Warning: Invalid GitHub specifier in lockfile: ${specifier}`
|
|
1289
2298
|
);
|
|
1290
|
-
|
|
1291
|
-
await rm(tempFile, { force: true });
|
|
2299
|
+
continue;
|
|
1292
2300
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
2301
|
+
const skillName = getGitHubSkillName(parsed);
|
|
2302
|
+
skills.push({
|
|
2303
|
+
name: skillName,
|
|
2304
|
+
sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
if (skills.length === 0) {
|
|
2308
|
+
console.log("No skills found in lockfile. Nothing to link.");
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
console.log(
|
|
2312
|
+
`Creating symlinks for ${skills.length} skill(s) to agent(s): ${agents.join(", ")}...`
|
|
2313
|
+
);
|
|
2314
|
+
await createAgentSymlinks(skills, {
|
|
2315
|
+
agents,
|
|
2316
|
+
projectRoot: process.cwd(),
|
|
2317
|
+
agentConfigs
|
|
2318
|
+
});
|
|
2319
|
+
console.log("Symlinks created successfully.");
|
|
2320
|
+
console.log("\nLinked skills:");
|
|
2321
|
+
for (const skill of skills) {
|
|
2322
|
+
console.log(` ${skill.name} -> ${skill.sourcePath}`);
|
|
1296
2323
|
}
|
|
1297
|
-
console.log(`
|
|
1298
|
-
All ${skillCount} skill(s) installed.`);
|
|
1299
2324
|
} catch (error) {
|
|
1300
2325
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1301
2326
|
console.error(`Error: ${message}`);
|
|
@@ -1304,7 +2329,76 @@ All ${skillCount} skill(s) installed.`);
|
|
|
1304
2329
|
}
|
|
1305
2330
|
async function list(options) {
|
|
1306
2331
|
try {
|
|
1307
|
-
const
|
|
2332
|
+
const registrySkills = await listLockfileSkills();
|
|
2333
|
+
const githubSkills = await listLockfileGitHubPackages();
|
|
2334
|
+
const manifest = await readManifest();
|
|
2335
|
+
const agentConfigs = manifest?.agents;
|
|
2336
|
+
const availableAgents = getAvailableAgents(agentConfigs);
|
|
2337
|
+
const projectRoot = process.cwd();
|
|
2338
|
+
const skills = [];
|
|
2339
|
+
for (const { name: fullName, entry } of registrySkills) {
|
|
2340
|
+
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
2341
|
+
if (!match) continue;
|
|
2342
|
+
const [, username, skillName] = match;
|
|
2343
|
+
const sourcePath = getRegistrySkillPath(username, skillName);
|
|
2344
|
+
const absolutePath = join(projectRoot, sourcePath);
|
|
2345
|
+
let status = "installed";
|
|
2346
|
+
try {
|
|
2347
|
+
await access$1(absolutePath);
|
|
2348
|
+
} catch {
|
|
2349
|
+
status = "missing";
|
|
2350
|
+
}
|
|
2351
|
+
const linkedAgents = await getLinkedAgents(
|
|
2352
|
+
skillName,
|
|
2353
|
+
availableAgents,
|
|
2354
|
+
projectRoot,
|
|
2355
|
+
agentConfigs
|
|
2356
|
+
);
|
|
2357
|
+
skills.push({
|
|
2358
|
+
name: skillName,
|
|
2359
|
+
fullName,
|
|
2360
|
+
version: entry.version,
|
|
2361
|
+
source: "registry",
|
|
2362
|
+
sourcePath,
|
|
2363
|
+
status,
|
|
2364
|
+
linkedAgents
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
for (const { specifier, entry } of githubSkills) {
|
|
2368
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2369
|
+
if (!parsed) continue;
|
|
2370
|
+
const ghEntry = entry;
|
|
2371
|
+
const skillName = getGitHubSkillName(parsed);
|
|
2372
|
+
const sourcePath = getGitHubSkillPath(
|
|
2373
|
+
parsed.owner,
|
|
2374
|
+
parsed.repo,
|
|
2375
|
+
parsed.path
|
|
2376
|
+
);
|
|
2377
|
+
const absolutePath = join(projectRoot, sourcePath);
|
|
2378
|
+
let status = "installed";
|
|
2379
|
+
try {
|
|
2380
|
+
await access$1(absolutePath);
|
|
2381
|
+
} catch {
|
|
2382
|
+
status = "missing";
|
|
2383
|
+
}
|
|
2384
|
+
const linkedAgents = await getLinkedAgents(
|
|
2385
|
+
skillName,
|
|
2386
|
+
availableAgents,
|
|
2387
|
+
projectRoot,
|
|
2388
|
+
agentConfigs
|
|
2389
|
+
);
|
|
2390
|
+
skills.push({
|
|
2391
|
+
name: skillName,
|
|
2392
|
+
fullName: specifier,
|
|
2393
|
+
version: ghEntry.gitCommit.slice(0, 7),
|
|
2394
|
+
source: "github",
|
|
2395
|
+
sourcePath,
|
|
2396
|
+
status,
|
|
2397
|
+
linkedAgents,
|
|
2398
|
+
gitRef: ghEntry.gitRef,
|
|
2399
|
+
gitCommit: ghEntry.gitCommit
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
1308
2402
|
if (skills.length === 0) {
|
|
1309
2403
|
console.log("No skills installed.");
|
|
1310
2404
|
return;
|
|
@@ -1313,26 +2407,33 @@ async function list(options) {
|
|
|
1313
2407
|
console.log(JSON.stringify(skills, null, 2));
|
|
1314
2408
|
return;
|
|
1315
2409
|
}
|
|
1316
|
-
const skillsDir = getSkillsDir();
|
|
1317
2410
|
console.log("Installed skills:\n");
|
|
1318
|
-
for (const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
try {
|
|
1325
|
-
await access$1(skillPath);
|
|
1326
|
-
} catch {
|
|
1327
|
-
status = "missing";
|
|
2411
|
+
for (const skill of skills) {
|
|
2412
|
+
if (skill.source === "registry") {
|
|
2413
|
+
console.log(` ${skill.fullName}@${skill.version} (registry)`);
|
|
2414
|
+
} else {
|
|
2415
|
+
const refInfo = skill.gitRef ? `${skill.gitRef}@${skill.gitCommit?.slice(0, 7)}` : skill.version;
|
|
2416
|
+
console.log(` ${skill.fullName} (${refInfo})`);
|
|
1328
2417
|
}
|
|
1329
|
-
|
|
1330
|
-
if (status === "missing") {
|
|
2418
|
+
if (skill.status === "missing") {
|
|
1331
2419
|
console.log(` Status: MISSING (run 'pspm install' to restore)`);
|
|
1332
2420
|
}
|
|
2421
|
+
if (skill.linkedAgents.length > 0) {
|
|
2422
|
+
for (const agent of skill.linkedAgents) {
|
|
2423
|
+
const config2 = resolveAgentConfig(agent, agentConfigs);
|
|
2424
|
+
if (config2) {
|
|
2425
|
+
console.log(` -> ${config2.skillsDir}/${skill.name}`);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
1333
2429
|
}
|
|
2430
|
+
const registryCount = skills.filter((s) => s.source === "registry").length;
|
|
2431
|
+
const githubCount = skills.filter((s) => s.source === "github").length;
|
|
2432
|
+
const parts = [];
|
|
2433
|
+
if (registryCount > 0) parts.push(`${registryCount} registry`);
|
|
2434
|
+
if (githubCount > 0) parts.push(`${githubCount} github`);
|
|
1334
2435
|
console.log(`
|
|
1335
|
-
Total: ${skills.length} skill(s)`);
|
|
2436
|
+
Total: ${skills.length} skill(s) (${parts.join(", ")})`);
|
|
1336
2437
|
} catch (error) {
|
|
1337
2438
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1338
2439
|
console.error(`Error: ${message}`);
|
|
@@ -1837,59 +2938,103 @@ Setting visibility to ${options.access}...`);
|
|
|
1837
2938
|
}
|
|
1838
2939
|
async function remove(nameOrSpecifier) {
|
|
1839
2940
|
try {
|
|
1840
|
-
await
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
console.error(`Error: Invalid skill specifier: ${nameOrSpecifier}`);
|
|
1848
|
-
process.exit(1);
|
|
1849
|
-
}
|
|
1850
|
-
fullName = `@user/${match[1]}/${match[2]}`;
|
|
1851
|
-
username = match[1];
|
|
1852
|
-
name = match[2];
|
|
2941
|
+
const manifest = await readManifest();
|
|
2942
|
+
const agentConfigs = manifest?.agents;
|
|
2943
|
+
const agents = getAvailableAgents(agentConfigs);
|
|
2944
|
+
if (isGitHubSpecifier(nameOrSpecifier)) {
|
|
2945
|
+
await removeGitHub(nameOrSpecifier, agents, agentConfigs);
|
|
2946
|
+
} else if (nameOrSpecifier.startsWith("@user/")) {
|
|
2947
|
+
await removeRegistry(nameOrSpecifier, agents, agentConfigs);
|
|
1853
2948
|
} else {
|
|
1854
|
-
|
|
1855
|
-
const found = skills.find((s) => {
|
|
1856
|
-
const match2 = s.name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1857
|
-
return match2 && match2[2] === nameOrSpecifier;
|
|
1858
|
-
});
|
|
1859
|
-
if (!found) {
|
|
1860
|
-
console.error(
|
|
1861
|
-
`Error: Skill "${nameOrSpecifier}" not found in lockfile`
|
|
1862
|
-
);
|
|
1863
|
-
process.exit(1);
|
|
1864
|
-
}
|
|
1865
|
-
fullName = found.name;
|
|
1866
|
-
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1867
|
-
if (!match) {
|
|
1868
|
-
console.error(`Error: Invalid skill name in lockfile: ${fullName}`);
|
|
1869
|
-
process.exit(1);
|
|
1870
|
-
}
|
|
1871
|
-
username = match[1];
|
|
1872
|
-
name = match[2];
|
|
1873
|
-
}
|
|
1874
|
-
console.log(`Removing ${fullName}...`);
|
|
1875
|
-
const removed = await removeFromLockfile(fullName);
|
|
1876
|
-
if (!removed) {
|
|
1877
|
-
console.error(`Error: ${fullName} not found in lockfile`);
|
|
1878
|
-
process.exit(1);
|
|
1879
|
-
}
|
|
1880
|
-
const skillsDir = getSkillsDir();
|
|
1881
|
-
const destDir = join(skillsDir, username, name);
|
|
1882
|
-
try {
|
|
1883
|
-
await rm(destDir, { recursive: true, force: true });
|
|
1884
|
-
} catch {
|
|
2949
|
+
await removeByShortName(nameOrSpecifier, agents, agentConfigs);
|
|
1885
2950
|
}
|
|
1886
|
-
console.log(`Removed ${fullName}`);
|
|
1887
2951
|
} catch (error) {
|
|
1888
2952
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1889
2953
|
console.error(`Error: ${message}`);
|
|
1890
2954
|
process.exit(1);
|
|
1891
2955
|
}
|
|
1892
2956
|
}
|
|
2957
|
+
async function removeRegistry(specifier, agents, agentConfigs) {
|
|
2958
|
+
const match = specifier.match(/^@user\/([^/]+)\/([^@/]+)/);
|
|
2959
|
+
if (!match) {
|
|
2960
|
+
console.error(`Error: Invalid skill specifier: ${specifier}`);
|
|
2961
|
+
process.exit(1);
|
|
2962
|
+
}
|
|
2963
|
+
const fullName = `@user/${match[1]}/${match[2]}`;
|
|
2964
|
+
const username = match[1];
|
|
2965
|
+
const name = match[2];
|
|
2966
|
+
console.log(`Removing ${fullName}...`);
|
|
2967
|
+
const removedFromLockfile = await removeFromLockfile(fullName);
|
|
2968
|
+
const removedFromManifest = await removeDependency(fullName);
|
|
2969
|
+
if (!removedFromLockfile && !removedFromManifest) {
|
|
2970
|
+
console.error(`Error: ${fullName} not found in lockfile or pspm.json`);
|
|
2971
|
+
process.exit(1);
|
|
2972
|
+
}
|
|
2973
|
+
await removeAgentSymlinks(name, {
|
|
2974
|
+
agents,
|
|
2975
|
+
projectRoot: process.cwd(),
|
|
2976
|
+
agentConfigs
|
|
2977
|
+
});
|
|
2978
|
+
const skillsDir = getSkillsDir();
|
|
2979
|
+
const destDir = join(skillsDir, username, name);
|
|
2980
|
+
try {
|
|
2981
|
+
await rm(destDir, { recursive: true, force: true });
|
|
2982
|
+
} catch {
|
|
2983
|
+
}
|
|
2984
|
+
console.log(`Removed ${fullName}`);
|
|
2985
|
+
}
|
|
2986
|
+
async function removeGitHub(specifier, agents, agentConfigs) {
|
|
2987
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2988
|
+
if (!parsed) {
|
|
2989
|
+
console.error(`Error: Invalid GitHub specifier: ${specifier}`);
|
|
2990
|
+
process.exit(1);
|
|
2991
|
+
}
|
|
2992
|
+
const lockfileKey = parsed.path ? `github:${parsed.owner}/${parsed.repo}/${parsed.path}` : `github:${parsed.owner}/${parsed.repo}`;
|
|
2993
|
+
console.log(`Removing ${lockfileKey}...`);
|
|
2994
|
+
const removedFromLockfile = await removeGitHubFromLockfile(lockfileKey);
|
|
2995
|
+
const removedFromManifest = await removeGitHubDependency(lockfileKey);
|
|
2996
|
+
if (!removedFromLockfile && !removedFromManifest) {
|
|
2997
|
+
console.error(`Error: ${lockfileKey} not found in lockfile or pspm.json`);
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
const skillName = getGitHubSkillName(parsed);
|
|
3001
|
+
await removeAgentSymlinks(skillName, {
|
|
3002
|
+
agents,
|
|
3003
|
+
projectRoot: process.cwd(),
|
|
3004
|
+
agentConfigs
|
|
3005
|
+
});
|
|
3006
|
+
const skillsDir = getSkillsDir();
|
|
3007
|
+
const destPath = getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path);
|
|
3008
|
+
const destDir = join(skillsDir, "..", destPath);
|
|
3009
|
+
try {
|
|
3010
|
+
await rm(destDir, { recursive: true, force: true });
|
|
3011
|
+
} catch {
|
|
3012
|
+
}
|
|
3013
|
+
console.log(`Removed ${lockfileKey}`);
|
|
3014
|
+
}
|
|
3015
|
+
async function removeByShortName(shortName, agents, agentConfigs) {
|
|
3016
|
+
const registrySkills = await listLockfileSkills();
|
|
3017
|
+
const foundRegistry = registrySkills.find((s) => {
|
|
3018
|
+
const match = s.name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
3019
|
+
return match && match[2] === shortName;
|
|
3020
|
+
});
|
|
3021
|
+
if (foundRegistry) {
|
|
3022
|
+
await removeRegistry(foundRegistry.name, agents, agentConfigs);
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
const githubSkills = await listLockfileGitHubPackages();
|
|
3026
|
+
const foundGitHub = githubSkills.find((s) => {
|
|
3027
|
+
const parsed = parseGitHubSpecifier(s.specifier);
|
|
3028
|
+
if (!parsed) return false;
|
|
3029
|
+
return getGitHubSkillName(parsed) === shortName;
|
|
3030
|
+
});
|
|
3031
|
+
if (foundGitHub) {
|
|
3032
|
+
await removeGitHub(foundGitHub.specifier, agents, agentConfigs);
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
console.error(`Error: Skill "${shortName}" not found in lockfile`);
|
|
3036
|
+
process.exit(1);
|
|
3037
|
+
}
|
|
1893
3038
|
|
|
1894
3039
|
// src/commands/unpublish.ts
|
|
1895
3040
|
async function unpublish(specifier, options) {
|
|
@@ -2075,12 +3220,13 @@ program.command("logout").description("Log out and clear stored credentials").ac
|
|
|
2075
3220
|
program.command("whoami").description("Show current user information").action(async () => {
|
|
2076
3221
|
await whoami();
|
|
2077
3222
|
});
|
|
2078
|
-
program.command("init").description("Create a new pspm.json manifest in the current directory").option("-n, --name <name>", "Skill name").option("-d, --description <desc>", "Skill description").option("-a, --author <author>", "Author name").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
|
|
3223
|
+
program.command("init").description("Create a new pspm.json manifest in the current directory").option("-n, --name <name>", "Skill name").option("-d, --description <desc>", "Skill description").option("-a, --author <author>", "Author name").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Overwrite existing pspm.json").action(async (options) => {
|
|
2079
3224
|
await init({
|
|
2080
3225
|
name: options.name,
|
|
2081
3226
|
description: options.description,
|
|
2082
3227
|
author: options.author,
|
|
2083
|
-
yes: options.yes
|
|
3228
|
+
yes: options.yes,
|
|
3229
|
+
force: options.force
|
|
2084
3230
|
});
|
|
2085
3231
|
});
|
|
2086
3232
|
program.command("migrate").description(
|
|
@@ -2088,8 +3234,17 @@ program.command("migrate").description(
|
|
|
2088
3234
|
).option("--dry-run", "Show what would be migrated without making changes").action(async (options) => {
|
|
2089
3235
|
await migrate({ dryRun: options.dryRun });
|
|
2090
3236
|
});
|
|
2091
|
-
program.command("add <specifier>").description(
|
|
2092
|
-
|
|
3237
|
+
program.command("add <specifier>").description(
|
|
3238
|
+
"Add a skill (e.g., @user/bsheng/vite_slides@^2.0.0 or github:owner/repo/path@ref)"
|
|
3239
|
+
).option("--save", "Save to lockfile (default)").option(
|
|
3240
|
+
"--agent <agents>",
|
|
3241
|
+
'Comma-separated agents for symlinks (default: "claude-code", use "none" to skip)'
|
|
3242
|
+
).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (specifier, options) => {
|
|
3243
|
+
await add(specifier, {
|
|
3244
|
+
save: options.save ?? true,
|
|
3245
|
+
agent: options.agent,
|
|
3246
|
+
yes: options.yes
|
|
3247
|
+
});
|
|
2093
3248
|
});
|
|
2094
3249
|
program.command("remove <name>").alias("rm").description("Remove an installed skill").action(async (name) => {
|
|
2095
3250
|
await remove(name);
|
|
@@ -2097,12 +3252,23 @@ program.command("remove <name>").alias("rm").description("Remove an installed sk
|
|
|
2097
3252
|
program.command("list").alias("ls").description("List installed skills").option("--json", "Output as JSON").action(async (options) => {
|
|
2098
3253
|
await list({ json: options.json });
|
|
2099
3254
|
});
|
|
2100
|
-
program.command("install").alias("i").description("Install all skills from lockfile").option("--frozen-lockfile", "Fail if lockfile is missing or outdated").option("--dir <path>", "Install skills to a specific directory").
|
|
3255
|
+
program.command("install").alias("i").description("Install all skills from lockfile").option("--frozen-lockfile", "Fail if lockfile is missing or outdated").option("--dir <path>", "Install skills to a specific directory").option(
|
|
3256
|
+
"--agent <agents>",
|
|
3257
|
+
'Comma-separated agents for symlinks (default: "claude-code", use "none" to skip)'
|
|
3258
|
+
).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (options) => {
|
|
2101
3259
|
await install({
|
|
2102
3260
|
frozenLockfile: options.frozenLockfile,
|
|
2103
|
-
dir: options.dir
|
|
3261
|
+
dir: options.dir,
|
|
3262
|
+
agent: options.agent,
|
|
3263
|
+
yes: options.yes
|
|
2104
3264
|
});
|
|
2105
3265
|
});
|
|
3266
|
+
program.command("link").description("Recreate agent symlinks without reinstalling").option(
|
|
3267
|
+
"--agent <agents>",
|
|
3268
|
+
'Comma-separated agents for symlinks (default: "claude-code", use "none" to skip)'
|
|
3269
|
+
).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (options) => {
|
|
3270
|
+
await link({ agent: options.agent, yes: options.yes });
|
|
3271
|
+
});
|
|
2106
3272
|
program.command("update").description("Update all skills to latest compatible versions").option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
|
|
2107
3273
|
await update({ dryRun: options.dryRun });
|
|
2108
3274
|
});
|