@anytio/pspm 0.0.7 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,31 +1,39 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from 'fs';
3
- import { dirname, join, basename, relative } from 'path';
4
- import { fileURLToPath, URL as URL$1 } from 'url';
5
- import { Command } from 'commander';
6
2
  import { createHash, randomBytes } from 'crypto';
7
3
  import * as semver from 'semver';
8
- import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, unlink } from 'fs/promises';
4
+ import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, lstat, unlink, cp, readlink, symlink } from 'fs/promises';
9
5
  import { homedir } from 'os';
6
+ import { dirname, join, basename, relative } from 'path';
10
7
  import * as ini from 'ini';
8
+ import { checkbox } from '@inquirer/prompts';
9
+ import { readFileSync } from 'fs';
10
+ import { fileURLToPath, URL as URL$1 } from 'url';
11
+ import { Command } from 'commander';
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';
14
16
  import { promisify } from 'util';
15
17
 
18
+ var __defProp = Object.defineProperty;
19
+ var __getOwnPropNames = Object.getOwnPropertyNames;
20
+ var __esm = (fn, res) => function __init() {
21
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
22
+ };
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
16
27
  function calculateIntegrity(data) {
17
28
  const hash = createHash("sha256").update(data).digest("base64");
18
29
  return `sha256-${hash}`;
19
30
  }
31
+ var init_integrity = __esm({
32
+ "../../packages/shared/pspm-types/src/integrity.ts"() {
33
+ }
34
+ });
20
35
 
21
36
  // ../../packages/shared/pspm-types/src/manifest.ts
22
- var DEFAULT_SKILL_FILES = [
23
- "SKILL.md",
24
- "runtime",
25
- "scripts",
26
- "data"
27
- ];
28
- var PSPM_SCHEMA_URL = "https://pspm.dev/schema/pspm.json";
29
37
  function validateManifest(manifest) {
30
38
  if (!manifest.name) {
31
39
  return { valid: false, error: "Manifest must have a 'name' field" };
@@ -47,9 +55,20 @@ function validateManifest(manifest) {
47
55
  }
48
56
  return { valid: true };
49
57
  }
58
+ var DEFAULT_SKILL_FILES, PSPM_SCHEMA_URL;
59
+ var init_manifest = __esm({
60
+ "../../packages/shared/pspm-types/src/manifest.ts"() {
61
+ DEFAULT_SKILL_FILES = [
62
+ "SKILL.md",
63
+ "runtime",
64
+ "scripts",
65
+ "data"
66
+ ];
67
+ PSPM_SCHEMA_URL = "https://pspm.dev/schema/v1/pspm.json";
68
+ }
69
+ });
50
70
 
51
71
  // ../../packages/shared/pspm-types/src/specifier.ts
52
- var SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
53
72
  function parseSkillSpecifier(specifier) {
54
73
  const match = specifier.match(SPECIFIER_PATTERN);
55
74
  if (!match) {
@@ -61,6 +80,47 @@ function parseSkillSpecifier(specifier) {
61
80
  versionRange: match[3]
62
81
  };
63
82
  }
83
+ function parseGitHubSpecifier(specifier) {
84
+ const match = specifier.match(GITHUB_SPECIFIER_PATTERN);
85
+ if (!match) {
86
+ return null;
87
+ }
88
+ const [, owner, repo, pathWithSlash, ref] = match;
89
+ return {
90
+ owner,
91
+ repo,
92
+ // Remove leading slash from path
93
+ path: pathWithSlash ? pathWithSlash.slice(1) : void 0,
94
+ ref: ref || void 0
95
+ };
96
+ }
97
+ function formatGitHubSpecifier(spec) {
98
+ let result = `github:${spec.owner}/${spec.repo}`;
99
+ if (spec.path) {
100
+ result += `/${spec.path}`;
101
+ }
102
+ if (spec.ref) {
103
+ result += `@${spec.ref}`;
104
+ }
105
+ return result;
106
+ }
107
+ function getGitHubSkillName(spec) {
108
+ if (spec.path) {
109
+ const segments = spec.path.split("/").filter(Boolean);
110
+ return segments[segments.length - 1];
111
+ }
112
+ return spec.repo;
113
+ }
114
+ function isGitHubSpecifier(specifier) {
115
+ return specifier.startsWith("github:");
116
+ }
117
+ var SPECIFIER_PATTERN, GITHUB_SPECIFIER_PATTERN;
118
+ var init_specifier = __esm({
119
+ "../../packages/shared/pspm-types/src/specifier.ts"() {
120
+ SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
121
+ GITHUB_SPECIFIER_PATTERN = /^github:([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)(\/[^@]+)?(?:@(.+))?$/;
122
+ }
123
+ });
64
124
  function resolveVersion(range, availableVersions) {
65
125
  const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
66
126
  if (!range || range === "latest" || range === "*") {
@@ -68,9 +128,22 @@ function resolveVersion(range, availableVersions) {
68
128
  }
69
129
  return semver.maxSatisfying(sorted, range);
70
130
  }
131
+ var init_version = __esm({
132
+ "../../packages/shared/pspm-types/src/version.ts"() {
133
+ }
134
+ });
135
+
136
+ // ../../packages/shared/pspm-types/src/index.ts
137
+ var init_src = __esm({
138
+ "../../packages/shared/pspm-types/src/index.ts"() {
139
+ init_integrity();
140
+ init_manifest();
141
+ init_specifier();
142
+ init_version();
143
+ }
144
+ });
71
145
 
72
146
  // ../../packages/sdk/src/fetchers/cli-fetcher.ts
73
- var config = null;
74
147
  function configure(options) {
75
148
  config = options;
76
149
  }
@@ -109,84 +182,104 @@ async function customFetch(url, options) {
109
182
  headers: response.headers
110
183
  };
111
184
  }
185
+ var config;
186
+ var init_cli_fetcher = __esm({
187
+ "../../packages/sdk/src/fetchers/cli-fetcher.ts"() {
188
+ config = null;
189
+ }
190
+ });
112
191
 
113
192
  // ../../packages/sdk/src/generated/fetch/index.ts
114
- var getMeUrl = () => {
115
- return `/api/skills/me`;
116
- };
117
- var me = async (options) => {
118
- return customFetch(
119
- getMeUrl(),
120
- {
121
- ...options,
122
- method: "GET"
123
- }
124
- );
125
- };
126
- var getListSkillVersionsUrl = (username, name) => {
127
- return `/api/skills/@user/${username}/${name}/versions`;
128
- };
129
- var listSkillVersions = async (username, name, options) => {
130
- return customFetch(
131
- getListSkillVersionsUrl(username, name),
132
- {
133
- ...options,
134
- method: "GET"
135
- }
136
- );
137
- };
138
- var getGetSkillVersionUrl = (username, name, version2) => {
139
- return `/api/skills/@user/${username}/${name}/${version2}`;
140
- };
141
- var getSkillVersion = async (username, name, version2, options) => {
142
- return customFetch(
143
- getGetSkillVersionUrl(username, name, version2),
144
- {
145
- ...options,
146
- method: "GET"
147
- }
148
- );
149
- };
150
- var getPublishSkillUrl = () => {
151
- return `/api/skills/publish`;
152
- };
153
- var publishSkill = async (publishSkillInput, options) => {
154
- return customFetch(
155
- getPublishSkillUrl(),
156
- {
157
- ...options,
158
- method: "POST",
159
- headers: { "Content-Type": "application/json", ...options?.headers },
160
- body: JSON.stringify(
161
- publishSkillInput
162
- )
163
- }
164
- );
165
- };
166
- var getDeleteSkillUrl = (name) => {
167
- return `/api/skills/${name}`;
168
- };
169
- var deleteSkill = async (name, options) => {
170
- return customFetch(
171
- getDeleteSkillUrl(name),
172
- {
173
- ...options,
174
- method: "DELETE"
175
- }
176
- );
177
- };
178
- var getDeleteSkillVersionUrl = (name, version2) => {
179
- return `/api/skills/${name}/${version2}`;
180
- };
181
- var deleteSkillVersion = async (name, version2, options) => {
182
- return customFetch(
183
- getDeleteSkillVersionUrl(name, version2),
184
- {
185
- ...options,
186
- method: "DELETE"
187
- }
188
- );
189
- };
193
+ var getMeUrl, me, getListSkillVersionsUrl, listSkillVersions, getGetSkillVersionUrl, getSkillVersion, getPublishSkillUrl, publishSkill, getDeleteSkillUrl, deleteSkill, getDeleteSkillVersionUrl, deleteSkillVersion;
194
+ var init_fetch = __esm({
195
+ "../../packages/sdk/src/generated/fetch/index.ts"() {
196
+ init_cli_fetcher();
197
+ getMeUrl = () => {
198
+ return `/api/skills/me`;
199
+ };
200
+ me = async (options) => {
201
+ return customFetch(
202
+ getMeUrl(),
203
+ {
204
+ ...options,
205
+ method: "GET"
206
+ }
207
+ );
208
+ };
209
+ getListSkillVersionsUrl = (username, name) => {
210
+ return `/api/skills/@user/${username}/${name}/versions`;
211
+ };
212
+ listSkillVersions = async (username, name, options) => {
213
+ return customFetch(
214
+ getListSkillVersionsUrl(username, name),
215
+ {
216
+ ...options,
217
+ method: "GET"
218
+ }
219
+ );
220
+ };
221
+ getGetSkillVersionUrl = (username, name, version2) => {
222
+ return `/api/skills/@user/${username}/${name}/${version2}`;
223
+ };
224
+ getSkillVersion = async (username, name, version2, options) => {
225
+ return customFetch(
226
+ getGetSkillVersionUrl(username, name, version2),
227
+ {
228
+ ...options,
229
+ method: "GET"
230
+ }
231
+ );
232
+ };
233
+ getPublishSkillUrl = () => {
234
+ return `/api/skills/publish`;
235
+ };
236
+ publishSkill = async (publishSkillInput, options) => {
237
+ return customFetch(
238
+ getPublishSkillUrl(),
239
+ {
240
+ ...options,
241
+ method: "POST",
242
+ headers: { "Content-Type": "application/json", ...options?.headers },
243
+ body: JSON.stringify(
244
+ publishSkillInput
245
+ )
246
+ }
247
+ );
248
+ };
249
+ getDeleteSkillUrl = (name) => {
250
+ return `/api/skills/${name}`;
251
+ };
252
+ deleteSkill = async (name, options) => {
253
+ return customFetch(
254
+ getDeleteSkillUrl(name),
255
+ {
256
+ ...options,
257
+ method: "DELETE"
258
+ }
259
+ );
260
+ };
261
+ getDeleteSkillVersionUrl = (name, version2) => {
262
+ return `/api/skills/${name}/${version2}`;
263
+ };
264
+ deleteSkillVersion = async (name, version2, options) => {
265
+ return customFetch(
266
+ getDeleteSkillVersionUrl(name, version2),
267
+ {
268
+ ...options,
269
+ method: "DELETE"
270
+ }
271
+ );
272
+ };
273
+ }
274
+ });
275
+
276
+ // ../../packages/sdk/src/index.ts
277
+ var init_src2 = __esm({
278
+ "../../packages/sdk/src/index.ts"() {
279
+ init_cli_fetcher();
280
+ init_fetch();
281
+ }
282
+ });
190
283
 
191
284
  // src/api-client.ts
192
285
  function registryUrlToBaseUrl(registryUrl) {
@@ -308,22 +401,13 @@ async function changeSkillAccess(skillName, input) {
308
401
  };
309
402
  }
310
403
  }
404
+ var init_api_client = __esm({
405
+ "src/api-client.ts"() {
406
+ init_src2();
407
+ }
408
+ });
311
409
 
312
410
  // src/errors.ts
313
- var ConfigError = class extends Error {
314
- constructor(message) {
315
- super(message);
316
- this.name = "ConfigError";
317
- }
318
- };
319
- var NotLoggedInError = class extends ConfigError {
320
- constructor() {
321
- super(
322
- "Not logged in. Run 'pspm login --api-key <key>' first, or set PSPM_API_KEY env var."
323
- );
324
- this.name = "NotLoggedInError";
325
- }
326
- };
327
411
  function extractApiErrorMessage(response, fallbackMessage) {
328
412
  const errorData = response.data;
329
413
  if (process.env.PSPM_DEBUG) {
@@ -358,9 +442,25 @@ ${issueMessages}`;
358
442
  }
359
443
  return errorMessage;
360
444
  }
361
-
362
- // src/config.ts
363
- var DEFAULT_REGISTRY_URL = "https://pspm.dev";
445
+ var ConfigError, NotLoggedInError;
446
+ var init_errors = __esm({
447
+ "src/errors.ts"() {
448
+ ConfigError = class extends Error {
449
+ constructor(message) {
450
+ super(message);
451
+ this.name = "ConfigError";
452
+ }
453
+ };
454
+ NotLoggedInError = class extends ConfigError {
455
+ constructor() {
456
+ super(
457
+ "Not logged in. Run 'pspm login --api-key <key>' first, or set PSPM_API_KEY env var."
458
+ );
459
+ this.name = "NotLoggedInError";
460
+ }
461
+ };
462
+ }
463
+ });
364
464
  function getConfigPath() {
365
465
  return join(homedir(), ".pspmrc");
366
466
  }
@@ -630,88 +730,261 @@ async function getRegistryUrl() {
630
730
  const resolved = await resolveConfig();
631
731
  return resolved.registryUrl;
632
732
  }
633
-
634
- // src/commands/access.ts
635
- async function access(specifier, options) {
636
- try {
637
- const apiKey = await requireApiKey();
638
- const registryUrl = await getRegistryUrl();
639
- if (options.public && options.private) {
640
- console.error("Error: Cannot specify both --public and --private");
641
- process.exit(1);
733
+ var DEFAULT_REGISTRY_URL;
734
+ var init_config = __esm({
735
+ "src/config.ts"() {
736
+ init_errors();
737
+ DEFAULT_REGISTRY_URL = "https://pspm.dev";
738
+ }
739
+ });
740
+ function resolveAgentConfig(name, overrides) {
741
+ if (overrides?.[name]) {
742
+ return overrides[name];
743
+ }
744
+ if (name in DEFAULT_AGENT_CONFIGS) {
745
+ return DEFAULT_AGENT_CONFIGS[name];
746
+ }
747
+ return null;
748
+ }
749
+ function parseAgentArg(agentArg) {
750
+ if (!agentArg) {
751
+ return [...DEFAULT_AGENTS];
752
+ }
753
+ if (agentArg === "none") {
754
+ return ["none"];
755
+ }
756
+ return agentArg.split(",").map((a) => a.trim()).filter(Boolean);
757
+ }
758
+ function getAvailableAgents(overrides) {
759
+ const builtIn = Object.keys(DEFAULT_AGENT_CONFIGS);
760
+ const custom = overrides ? Object.keys(overrides) : [];
761
+ return [.../* @__PURE__ */ new Set([...builtIn, ...custom])];
762
+ }
763
+ async function promptForAgents() {
764
+ const choices = ALL_AGENTS.map((agent) => ({
765
+ name: `${AGENT_INFO[agent].displayName} (${AGENT_INFO[agent].skillsDir})`,
766
+ value: agent,
767
+ checked: true
768
+ // All selected by default
769
+ }));
770
+ const selected = await checkbox({
771
+ message: "Select agents to install skills to",
772
+ choices
773
+ });
774
+ if (selected.length === 0) {
775
+ return ["none"];
776
+ }
777
+ return selected;
778
+ }
779
+ var AGENT_INFO, DEFAULT_AGENT_CONFIGS, ALL_AGENTS, DEFAULT_AGENTS;
780
+ var init_agents = __esm({
781
+ "src/agents.ts"() {
782
+ AGENT_INFO = {
783
+ "claude-code": {
784
+ displayName: "Claude Code",
785
+ skillsDir: ".claude/skills"
786
+ },
787
+ codex: {
788
+ displayName: "Codex",
789
+ skillsDir: ".codex/skills"
790
+ },
791
+ cursor: {
792
+ displayName: "Cursor",
793
+ skillsDir: ".cursor/skills"
794
+ },
795
+ gemini: {
796
+ displayName: "Gemini CLI",
797
+ skillsDir: ".gemini/skills"
798
+ },
799
+ kiro: {
800
+ displayName: "Kiro CLI",
801
+ skillsDir: ".kiro/skills"
802
+ },
803
+ opencode: {
804
+ displayName: "OpenCode",
805
+ skillsDir: ".opencode/skills"
806
+ }
807
+ };
808
+ DEFAULT_AGENT_CONFIGS = {
809
+ "claude-code": { skillsDir: AGENT_INFO["claude-code"].skillsDir },
810
+ codex: { skillsDir: AGENT_INFO.codex.skillsDir },
811
+ cursor: { skillsDir: AGENT_INFO.cursor.skillsDir },
812
+ gemini: { skillsDir: AGENT_INFO.gemini.skillsDir },
813
+ kiro: { skillsDir: AGENT_INFO.kiro.skillsDir },
814
+ opencode: { skillsDir: AGENT_INFO.opencode.skillsDir }
815
+ };
816
+ ALL_AGENTS = [
817
+ "claude-code",
818
+ "codex",
819
+ "cursor",
820
+ "gemini",
821
+ "kiro",
822
+ "opencode"
823
+ ];
824
+ DEFAULT_AGENTS = ALL_AGENTS;
825
+ }
826
+ });
827
+ function getGitHubHeaders() {
828
+ const headers = {
829
+ Accept: "application/vnd.github+json",
830
+ "X-GitHub-Api-Version": "2022-11-28",
831
+ "User-Agent": "pspm-cli"
832
+ };
833
+ const token = process.env.GITHUB_TOKEN;
834
+ if (token) {
835
+ headers.Authorization = `Bearer ${token}`;
836
+ }
837
+ return headers;
838
+ }
839
+ async function resolveGitHubRef(owner, repo, ref) {
840
+ const headers = getGitHubHeaders();
841
+ if (!ref || ref === "latest") {
842
+ const repoUrl = `https://api.github.com/repos/${owner}/${repo}`;
843
+ const repoResponse = await fetch(repoUrl, { headers });
844
+ if (repoResponse.status === 404) {
845
+ throw new GitHubNotFoundError({ owner, repo });
846
+ }
847
+ if (repoResponse.status === 403) {
848
+ const remaining = repoResponse.headers.get("x-ratelimit-remaining");
849
+ if (remaining === "0") {
850
+ throw new GitHubRateLimitError();
851
+ }
642
852
  }
643
- if (!options.public && !options.private) {
644
- console.error("Error: Must specify either --public or --private");
645
- process.exit(1);
853
+ if (!repoResponse.ok) {
854
+ throw new Error(`GitHub API error: ${repoResponse.status}`);
646
855
  }
647
- const visibility = options.public ? "public" : "private";
648
- let packageName;
649
- if (specifier) {
650
- const parsed = parseSkillSpecifier(specifier);
651
- if (!parsed) {
652
- console.error(
653
- `Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}`
856
+ const repoData = await repoResponse.json();
857
+ ref = repoData.default_branch;
858
+ }
859
+ const commitUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${ref}`;
860
+ const commitResponse = await fetch(commitUrl, { headers });
861
+ if (commitResponse.status === 404) {
862
+ throw new GitHubNotFoundError({ owner, repo, ref });
863
+ }
864
+ if (commitResponse.status === 403) {
865
+ const remaining = commitResponse.headers.get("x-ratelimit-remaining");
866
+ if (remaining === "0") {
867
+ throw new GitHubRateLimitError();
868
+ }
869
+ }
870
+ if (!commitResponse.ok) {
871
+ throw new Error(`GitHub API error: ${commitResponse.status}`);
872
+ }
873
+ const commitData = await commitResponse.json();
874
+ return commitData.sha;
875
+ }
876
+ async function downloadGitHubPackage(spec) {
877
+ const headers = getGitHubHeaders();
878
+ const commit = await resolveGitHubRef(spec.owner, spec.repo, spec.ref);
879
+ const tarballUrl = `https://api.github.com/repos/${spec.owner}/${spec.repo}/tarball/${commit}`;
880
+ const response = await fetch(tarballUrl, {
881
+ headers,
882
+ redirect: "follow"
883
+ });
884
+ if (response.status === 404) {
885
+ throw new GitHubNotFoundError(spec);
886
+ }
887
+ if (response.status === 403) {
888
+ const remaining = response.headers.get("x-ratelimit-remaining");
889
+ if (remaining === "0") {
890
+ throw new GitHubRateLimitError();
891
+ }
892
+ }
893
+ if (!response.ok) {
894
+ throw new Error(`Failed to download GitHub tarball: ${response.status}`);
895
+ }
896
+ const buffer = Buffer.from(await response.arrayBuffer());
897
+ const integrity = calculateIntegrity(buffer);
898
+ return { buffer, commit, integrity };
899
+ }
900
+ async function extractGitHubPackage(spec, buffer, skillsDir) {
901
+ const destPath = spec.path ? join(skillsDir, "_github", spec.owner, spec.repo, spec.path) : join(skillsDir, "_github", spec.owner, spec.repo);
902
+ const tempDir = join(skillsDir, "_github", ".temp", `${Date.now()}`);
903
+ await mkdir(tempDir, { recursive: true });
904
+ const tempFile = join(tempDir, "archive.tgz");
905
+ try {
906
+ await writeFile(tempFile, buffer);
907
+ const { exec: exec2 } = await import('child_process');
908
+ const { promisify: promisify2 } = await import('util');
909
+ const execAsync = promisify2(exec2);
910
+ await execAsync(`tar -xzf "${tempFile}" -C "${tempDir}"`);
911
+ const entries = await readdir(tempDir);
912
+ const extractedDir = entries.find(
913
+ (e) => e !== "archive.tgz" && !e.startsWith(".")
914
+ );
915
+ if (!extractedDir) {
916
+ throw new Error("Failed to find extracted directory in tarball");
917
+ }
918
+ const sourcePath = join(tempDir, extractedDir);
919
+ const copySource = spec.path ? join(sourcePath, spec.path) : sourcePath;
920
+ if (spec.path) {
921
+ const pathExists = await lstat(copySource).catch(() => null);
922
+ if (!pathExists) {
923
+ const rootEntries = await readdir(sourcePath);
924
+ const dirs = [];
925
+ for (const entry of rootEntries) {
926
+ const stat7 = await lstat(join(sourcePath, entry)).catch(() => null);
927
+ if (stat7?.isDirectory() && !entry.startsWith(".")) {
928
+ dirs.push(entry);
929
+ }
930
+ }
931
+ throw new GitHubPathNotFoundError(spec, dirs);
932
+ }
933
+ }
934
+ await rm(destPath, { recursive: true, force: true });
935
+ await mkdir(destPath, { recursive: true });
936
+ await cp(copySource, destPath, { recursive: true });
937
+ return spec.path ? `.pspm/skills/_github/${spec.owner}/${spec.repo}/${spec.path}` : `.pspm/skills/_github/${spec.owner}/${spec.repo}`;
938
+ } finally {
939
+ await rm(tempDir, { recursive: true, force: true });
940
+ }
941
+ }
942
+ function getGitHubDisplayName(spec, commit) {
943
+ let name = `github:${spec.owner}/${spec.repo}`;
944
+ if (spec.path) {
945
+ name += `/${spec.path}`;
946
+ }
947
+ if (spec.ref || commit) {
948
+ const ref = spec.ref || "HEAD";
949
+ name += ` (${ref}${""})`;
950
+ }
951
+ return name;
952
+ }
953
+ var GitHubRateLimitError, GitHubNotFoundError, GitHubPathNotFoundError;
954
+ var init_github = __esm({
955
+ "src/github.ts"() {
956
+ init_src();
957
+ GitHubRateLimitError = class extends Error {
958
+ constructor() {
959
+ super(
960
+ "GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable for higher limits."
654
961
  );
655
- process.exit(1);
962
+ this.name = "GitHubRateLimitError";
656
963
  }
657
- packageName = parsed.name;
658
- } else {
659
- const { readFile: readFile6 } = await import('fs/promises');
660
- const { join: join10 } = await import('path');
661
- let manifest = null;
662
- try {
663
- const content = await readFile6(
664
- join10(process.cwd(), "pspm.json"),
665
- "utf-8"
964
+ };
965
+ GitHubNotFoundError = class extends Error {
966
+ constructor(spec) {
967
+ const path = spec.path ? `/${spec.path}` : "";
968
+ const ref = spec.ref ? `@${spec.ref}` : "";
969
+ super(
970
+ `GitHub repository not found: ${spec.owner}/${spec.repo}${path}${ref}`
666
971
  );
667
- manifest = JSON.parse(content);
668
- } catch {
669
- try {
670
- const content = await readFile6(
671
- join10(process.cwd(), "package.json"),
672
- "utf-8"
673
- );
674
- manifest = JSON.parse(content);
675
- } catch {
676
- console.error(
677
- "Error: No pspm.json or package.json found in current directory"
678
- );
679
- console.error(
680
- "Either run this command in a package directory or specify a package name"
681
- );
682
- process.exit(1);
683
- }
972
+ this.name = "GitHubNotFoundError";
684
973
  }
685
- if (!manifest?.name) {
686
- console.error("Error: Package manifest is missing 'name' field");
687
- process.exit(1);
974
+ };
975
+ GitHubPathNotFoundError = class extends Error {
976
+ constructor(spec, availablePaths) {
977
+ const pathInfo = availablePaths?.length ? `
978
+ Available paths in repository root:
979
+ ${availablePaths.join("\n ")}` : "";
980
+ super(
981
+ `Path "${spec.path}" not found in ${spec.owner}/${spec.repo}${pathInfo}`
982
+ );
983
+ this.name = "GitHubPathNotFoundError";
688
984
  }
689
- packageName = manifest.name;
690
- }
691
- configure2({ registryUrl, apiKey });
692
- console.log(`Setting ${packageName} to ${visibility}...`);
693
- const response = await changeSkillAccess(packageName, { visibility });
694
- if (response.status !== 200 || !response.data) {
695
- const errorMessage = response.error ?? "Failed to change visibility";
696
- console.error(`Error: ${errorMessage}`);
697
- process.exit(1);
698
- }
699
- const result = response.data;
700
- console.log(
701
- `+ @user/${result.username}/${result.name} is now ${result.visibility}`
702
- );
703
- if (visibility === "public") {
704
- console.log("");
705
- console.log(
706
- "Note: This action is irreversible. Public packages cannot be made private."
707
- );
708
- }
709
- } catch (error) {
710
- const message = error instanceof Error ? error.message : "Unknown error";
711
- console.error(`Error: ${message}`);
712
- process.exit(1);
985
+ };
713
986
  }
714
- }
987
+ });
715
988
  async function hasLegacyLockfile() {
716
989
  try {
717
990
  await stat(getLegacyLockfilePath());
@@ -783,17 +1056,20 @@ async function writeLockfile(lockfile) {
783
1056
  const lockfilePath = getLockfilePath();
784
1057
  await mkdir(dirname(lockfilePath), { recursive: true });
785
1058
  const normalized = {
786
- lockfileVersion: 2,
1059
+ lockfileVersion: 3,
787
1060
  registryUrl: lockfile.registryUrl,
788
1061
  packages: lockfile.packages ?? lockfile.skills ?? {}
789
1062
  };
1063
+ if (lockfile.githubPackages && Object.keys(lockfile.githubPackages).length > 0) {
1064
+ normalized.githubPackages = lockfile.githubPackages;
1065
+ }
790
1066
  await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}
791
1067
  `);
792
1068
  }
793
1069
  async function createEmptyLockfile() {
794
1070
  const registryUrl = await getRegistryUrl();
795
1071
  return {
796
- lockfileVersion: 2,
1072
+ lockfileVersion: 3,
797
1073
  registryUrl,
798
1074
  packages: {}
799
1075
  };
@@ -836,125 +1112,596 @@ async function listLockfileSkills() {
836
1112
  entry
837
1113
  }));
838
1114
  }
839
-
840
- // src/commands/add.ts
841
- async function add(specifier, _options) {
1115
+ async function addGitHubToLockfile(specifier, entry) {
1116
+ let lockfile = await readLockfile();
1117
+ if (!lockfile) {
1118
+ lockfile = await createEmptyLockfile();
1119
+ }
1120
+ if (!lockfile.githubPackages) {
1121
+ lockfile.githubPackages = {};
1122
+ }
1123
+ lockfile.githubPackages[specifier] = entry;
1124
+ await writeLockfile(lockfile);
1125
+ }
1126
+ async function removeGitHubFromLockfile(specifier) {
1127
+ const lockfile = await readLockfile();
1128
+ if (!lockfile?.githubPackages?.[specifier]) {
1129
+ return false;
1130
+ }
1131
+ delete lockfile.githubPackages[specifier];
1132
+ await writeLockfile(lockfile);
1133
+ return true;
1134
+ }
1135
+ async function listLockfileGitHubPackages() {
1136
+ const lockfile = await readLockfile();
1137
+ if (!lockfile?.githubPackages) {
1138
+ return [];
1139
+ }
1140
+ return Object.entries(lockfile.githubPackages).map(([specifier, entry]) => ({
1141
+ specifier,
1142
+ entry
1143
+ }));
1144
+ }
1145
+ var init_lockfile = __esm({
1146
+ "src/lockfile.ts"() {
1147
+ init_config();
1148
+ }
1149
+ });
1150
+ function getManifestPath() {
1151
+ return join(process.cwd(), "pspm.json");
1152
+ }
1153
+ async function readManifest() {
842
1154
  try {
843
- const config2 = await resolveConfig();
844
- const registryUrl = config2.registryUrl;
845
- const apiKey = getTokenForRegistry(config2, registryUrl);
846
- const parsed = parseSkillSpecifier(specifier);
847
- if (!parsed) {
848
- console.error(
849
- `Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}[@{version}]`
850
- );
851
- process.exit(1);
1155
+ const content = await readFile(getManifestPath(), "utf-8");
1156
+ return JSON.parse(content);
1157
+ } catch {
1158
+ return null;
1159
+ }
1160
+ }
1161
+ async function writeManifest(manifest) {
1162
+ const content = JSON.stringify(manifest, null, 2);
1163
+ await writeFile(getManifestPath(), `${content}
1164
+ `);
1165
+ }
1166
+ async function createMinimalManifest() {
1167
+ return {
1168
+ dependencies: {}
1169
+ };
1170
+ }
1171
+ async function ensureManifest() {
1172
+ let manifest = await readManifest();
1173
+ if (!manifest) {
1174
+ manifest = await createMinimalManifest();
1175
+ await writeManifest(manifest);
1176
+ }
1177
+ return manifest;
1178
+ }
1179
+ async function addDependency(skillName, versionRange) {
1180
+ const manifest = await ensureManifest();
1181
+ if (!manifest.dependencies) {
1182
+ manifest.dependencies = {};
1183
+ }
1184
+ manifest.dependencies[skillName] = versionRange;
1185
+ await writeManifest(manifest);
1186
+ }
1187
+ async function removeDependency(skillName) {
1188
+ const manifest = await readManifest();
1189
+ if (!manifest?.dependencies?.[skillName]) {
1190
+ return false;
1191
+ }
1192
+ delete manifest.dependencies[skillName];
1193
+ await writeManifest(manifest);
1194
+ return true;
1195
+ }
1196
+ async function getDependencies() {
1197
+ const manifest = await readManifest();
1198
+ return manifest?.dependencies ?? {};
1199
+ }
1200
+ async function getGitHubDependencies() {
1201
+ const manifest = await readManifest();
1202
+ return manifest?.githubDependencies ?? {};
1203
+ }
1204
+ async function addGitHubDependency(specifier, ref) {
1205
+ const manifest = await ensureManifest();
1206
+ if (!manifest.githubDependencies) {
1207
+ manifest.githubDependencies = {};
1208
+ }
1209
+ manifest.githubDependencies[specifier] = ref;
1210
+ await writeManifest(manifest);
1211
+ }
1212
+ async function removeGitHubDependency(specifier) {
1213
+ const manifest = await readManifest();
1214
+ if (!manifest?.githubDependencies?.[specifier]) {
1215
+ return false;
1216
+ }
1217
+ delete manifest.githubDependencies[specifier];
1218
+ await writeManifest(manifest);
1219
+ return true;
1220
+ }
1221
+ var init_manifest2 = __esm({
1222
+ "src/manifest.ts"() {
1223
+ }
1224
+ });
1225
+ async function createAgentSymlinks(skills, options) {
1226
+ const { agents, projectRoot, agentConfigs } = options;
1227
+ if (agents.length === 1 && agents[0] === "none") {
1228
+ return;
1229
+ }
1230
+ for (const agentName of agents) {
1231
+ const config2 = resolveAgentConfig(agentName, agentConfigs);
1232
+ if (!config2) {
1233
+ console.warn(`Warning: Unknown agent "${agentName}", skipping symlinks`);
1234
+ continue;
852
1235
  }
853
- const { username, name, versionRange } = parsed;
854
- configure2({ registryUrl, apiKey: apiKey ?? "" });
855
- console.log(`Resolving ${specifier}...`);
856
- const versionsResponse = await listSkillVersions(username, name);
857
- if (versionsResponse.status !== 200) {
858
- if (versionsResponse.status === 401) {
859
- if (!apiKey) {
860
- console.error(
861
- `Error: Package @user/${username}/${name} requires authentication`
862
- );
863
- console.error("Please run 'pspm login' to authenticate");
864
- } else {
865
- console.error(
866
- `Error: Access denied to @user/${username}/${name}. You may not have permission to access this private package.`
867
- );
1236
+ const agentSkillsDir = join(projectRoot, config2.skillsDir);
1237
+ await mkdir(agentSkillsDir, { recursive: true });
1238
+ for (const skill of skills) {
1239
+ const symlinkPath = join(agentSkillsDir, skill.name);
1240
+ const targetPath = join(projectRoot, skill.sourcePath);
1241
+ const relativeTarget = relative(dirname(symlinkPath), targetPath);
1242
+ await createSymlink(symlinkPath, relativeTarget, skill.name);
1243
+ }
1244
+ }
1245
+ }
1246
+ async function createSymlink(symlinkPath, target, skillName) {
1247
+ try {
1248
+ const stats = await lstat(symlinkPath).catch(() => null);
1249
+ if (stats) {
1250
+ if (stats.isSymbolicLink()) {
1251
+ const existingTarget = await readlink(symlinkPath);
1252
+ if (existingTarget === target) {
1253
+ return;
868
1254
  }
869
- process.exit(1);
1255
+ await rm(symlinkPath);
1256
+ } else {
1257
+ console.warn(
1258
+ `Warning: File exists at symlink path for "${skillName}", skipping: ${symlinkPath}`
1259
+ );
1260
+ return;
870
1261
  }
871
- const errorMessage = extractApiErrorMessage(
872
- versionsResponse,
873
- `Skill @user/${username}/${name} not found`
874
- );
875
- console.error(`Error: ${errorMessage}`);
876
- process.exit(1);
877
1262
  }
878
- const versions = versionsResponse.data;
879
- if (versions.length === 0) {
880
- console.error(`Error: Skill @user/${username}/${name} not found`);
881
- process.exit(1);
1263
+ await symlink(target, symlinkPath);
1264
+ } catch (error) {
1265
+ const message = error instanceof Error ? error.message : String(error);
1266
+ console.warn(
1267
+ `Warning: Failed to create symlink for "${skillName}": ${message}`
1268
+ );
1269
+ }
1270
+ }
1271
+ async function removeAgentSymlinks(skillName, options) {
1272
+ const { agents, projectRoot, agentConfigs } = options;
1273
+ if (agents.length === 1 && agents[0] === "none") {
1274
+ return;
1275
+ }
1276
+ for (const agentName of agents) {
1277
+ const config2 = resolveAgentConfig(agentName, agentConfigs);
1278
+ if (!config2) {
1279
+ continue;
882
1280
  }
883
- const versionStrings = versions.map((v) => v.version);
884
- const resolved = resolveVersion(versionRange || "*", versionStrings);
885
- if (!resolved) {
886
- console.error(
887
- `Error: No version matching "${versionRange || "latest"}" found for @user/${username}/${name}`
888
- );
889
- console.error(`Available versions: ${versionStrings.join(", ")}`);
1281
+ const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
1282
+ try {
1283
+ const stats = await lstat(symlinkPath).catch(() => null);
1284
+ if (stats?.isSymbolicLink()) {
1285
+ await rm(symlinkPath);
1286
+ }
1287
+ } catch {
1288
+ }
1289
+ }
1290
+ }
1291
+ function getRegistrySkillPath(username, skillName) {
1292
+ return `.pspm/skills/${username}/${skillName}`;
1293
+ }
1294
+ function getGitHubSkillPath(owner, repo, path) {
1295
+ if (path) {
1296
+ return `.pspm/skills/_github/${owner}/${repo}/${path}`;
1297
+ }
1298
+ return `.pspm/skills/_github/${owner}/${repo}`;
1299
+ }
1300
+ async function getLinkedAgents(skillName, agents, projectRoot, agentConfigs) {
1301
+ const linkedAgents = [];
1302
+ for (const agentName of agents) {
1303
+ const config2 = resolveAgentConfig(agentName, agentConfigs);
1304
+ if (!config2) continue;
1305
+ const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
1306
+ try {
1307
+ const stats = await lstat(symlinkPath);
1308
+ if (stats.isSymbolicLink()) {
1309
+ linkedAgents.push(agentName);
1310
+ }
1311
+ } catch {
1312
+ }
1313
+ }
1314
+ return linkedAgents;
1315
+ }
1316
+ var init_symlinks = __esm({
1317
+ "src/symlinks.ts"() {
1318
+ init_agents();
1319
+ }
1320
+ });
1321
+
1322
+ // src/commands/add.ts
1323
+ var add_exports = {};
1324
+ __export(add_exports, {
1325
+ add: () => add
1326
+ });
1327
+ async function add(specifiers, options) {
1328
+ console.log("Resolving packages...\n");
1329
+ const resolvedPackages = [];
1330
+ const validationErrors = [];
1331
+ for (const specifier of specifiers) {
1332
+ try {
1333
+ if (isGitHubSpecifier(specifier)) {
1334
+ const resolved = await validateGitHubPackage(specifier);
1335
+ resolvedPackages.push(resolved);
1336
+ } else {
1337
+ const resolved = await validateRegistryPackage(specifier);
1338
+ resolvedPackages.push(resolved);
1339
+ }
1340
+ } catch (error) {
1341
+ const message = error instanceof Error ? error.message : "Unknown error";
1342
+ validationErrors.push({ specifier, error: message });
1343
+ console.error(`Failed to resolve ${specifier}: ${message}
1344
+ `);
1345
+ }
1346
+ }
1347
+ if (resolvedPackages.length === 0) {
1348
+ console.error("No packages could be resolved.");
1349
+ process.exit(1);
1350
+ }
1351
+ if (validationErrors.length > 0) {
1352
+ console.log(
1353
+ `Resolved ${resolvedPackages.length} of ${specifiers.length} packages.
1354
+ `
1355
+ );
1356
+ }
1357
+ let agents;
1358
+ const manifest = await readManifest();
1359
+ if (options.agent) {
1360
+ agents = parseAgentArg(options.agent);
1361
+ } else if (manifest) {
1362
+ agents = parseAgentArg(void 0);
1363
+ } else if (options.yes) {
1364
+ agents = parseAgentArg(void 0);
1365
+ } else {
1366
+ console.log("No pspm.json found. Let's set up your project.\n");
1367
+ agents = await promptForAgents();
1368
+ console.log();
1369
+ }
1370
+ const results = [];
1371
+ for (const resolved of resolvedPackages) {
1372
+ try {
1373
+ if (resolved.type === "github") {
1374
+ await installGitHubPackage(resolved, {
1375
+ ...options,
1376
+ resolvedAgents: agents
1377
+ });
1378
+ } else {
1379
+ await installRegistryPackage(resolved, {
1380
+ ...options,
1381
+ resolvedAgents: agents
1382
+ });
1383
+ }
1384
+ results.push({ specifier: resolved.specifier, success: true });
1385
+ } catch (error) {
1386
+ const message = error instanceof Error ? error.message : "Unknown error";
1387
+ results.push({
1388
+ specifier: resolved.specifier,
1389
+ success: false,
1390
+ error: message
1391
+ });
1392
+ console.error(`Failed to install ${resolved.specifier}: ${message}
1393
+ `);
1394
+ }
1395
+ }
1396
+ if (specifiers.length > 1) {
1397
+ const succeeded = results.filter((r) => r.success).length;
1398
+ const failed = results.filter((r) => !r.success).length + validationErrors.length;
1399
+ console.log(`
1400
+ Summary: ${succeeded} added, ${failed} failed`);
1401
+ if (failed > 0) {
890
1402
  process.exit(1);
891
1403
  }
892
- console.log(`Installing @user/${username}/${name}@${resolved}...`);
893
- const versionResponse = await getSkillVersion(username, name, resolved);
894
- if (versionResponse.status !== 200 || !versionResponse.data) {
895
- const errorMessage = extractApiErrorMessage(
896
- versionResponse,
897
- `Version ${resolved} not found`
1404
+ }
1405
+ }
1406
+ async function validateRegistryPackage(specifier) {
1407
+ const config2 = await resolveConfig();
1408
+ const registryUrl = config2.registryUrl;
1409
+ const apiKey = getTokenForRegistry(config2, registryUrl);
1410
+ const parsed = parseSkillSpecifier(specifier);
1411
+ if (!parsed) {
1412
+ throw new Error(
1413
+ `Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}[@{version}]`
1414
+ );
1415
+ }
1416
+ const { username, name, versionRange } = parsed;
1417
+ configure2({ registryUrl, apiKey: apiKey ?? "" });
1418
+ console.log(`Resolving ${specifier}...`);
1419
+ const versionsResponse = await listSkillVersions(username, name);
1420
+ if (versionsResponse.status !== 200) {
1421
+ if (versionsResponse.status === 401) {
1422
+ if (!apiKey) {
1423
+ throw new Error(
1424
+ `Package @user/${username}/${name} requires authentication. Please run 'pspm login' to authenticate`
1425
+ );
1426
+ }
1427
+ throw new Error(
1428
+ `Access denied to @user/${username}/${name}. You may not have permission to access this private package.`
898
1429
  );
899
- console.error(`Error: ${errorMessage}`);
900
- process.exit(1);
901
1430
  }
902
- const versionInfo = versionResponse.data;
903
- const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
904
- const downloadHeaders = {};
905
- if (!isPresignedUrl && apiKey) {
906
- downloadHeaders.Authorization = `Bearer ${apiKey}`;
1431
+ const errorMessage = extractApiErrorMessage(
1432
+ versionsResponse,
1433
+ `Skill @user/${username}/${name} not found`
1434
+ );
1435
+ throw new Error(errorMessage);
1436
+ }
1437
+ const versions = versionsResponse.data;
1438
+ if (versions.length === 0) {
1439
+ throw new Error(`Skill @user/${username}/${name} not found`);
1440
+ }
1441
+ const versionStrings = versions.map((v) => v.version);
1442
+ const resolvedVersion = resolveVersion(versionRange || "*", versionStrings);
1443
+ if (!resolvedVersion) {
1444
+ throw new Error(
1445
+ `No version matching "${versionRange || "latest"}" found for @user/${username}/${name}. Available versions: ${versionStrings.join(", ")}`
1446
+ );
1447
+ }
1448
+ const versionResponse = await getSkillVersion(
1449
+ username,
1450
+ name,
1451
+ resolvedVersion
1452
+ );
1453
+ if (versionResponse.status !== 200 || !versionResponse.data) {
1454
+ const errorMessage = extractApiErrorMessage(
1455
+ versionResponse,
1456
+ `Version ${resolvedVersion} not found`
1457
+ );
1458
+ throw new Error(errorMessage);
1459
+ }
1460
+ console.log(`Resolved @user/${username}/${name}@${resolvedVersion}`);
1461
+ return {
1462
+ type: "registry",
1463
+ specifier,
1464
+ username,
1465
+ name,
1466
+ versionRange,
1467
+ resolvedVersion,
1468
+ versionInfo: {
1469
+ downloadUrl: versionResponse.data.downloadUrl,
1470
+ checksum: versionResponse.data.checksum
907
1471
  }
908
- const tarballResponse = await fetch(versionInfo.downloadUrl, {
909
- headers: downloadHeaders,
910
- redirect: "follow"
1472
+ };
1473
+ }
1474
+ async function validateGitHubPackage(specifier) {
1475
+ const parsed = parseGitHubSpecifier(specifier);
1476
+ if (!parsed) {
1477
+ throw new Error(
1478
+ `Invalid GitHub specifier "${specifier}". Use format: github:{owner}/{repo}[/{path}][@{ref}]`
1479
+ );
1480
+ }
1481
+ const ref = parsed.ref || "HEAD";
1482
+ console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
1483
+ const result = await downloadGitHubPackage(parsed);
1484
+ console.log(`Resolved ${specifier} (${ref}@${result.commit.slice(0, 7)})`);
1485
+ return {
1486
+ type: "github",
1487
+ specifier,
1488
+ parsed,
1489
+ ref,
1490
+ downloadResult: result
1491
+ };
1492
+ }
1493
+ async function installRegistryPackage(resolved, options) {
1494
+ const { username, name, versionRange, resolvedVersion, versionInfo } = resolved;
1495
+ console.log(`Installing @user/${username}/${name}@${resolvedVersion}...`);
1496
+ const config2 = await resolveConfig();
1497
+ const apiKey = getTokenForRegistry(config2, config2.registryUrl);
1498
+ const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
1499
+ const downloadHeaders = {};
1500
+ if (!isPresignedUrl && apiKey) {
1501
+ downloadHeaders.Authorization = `Bearer ${apiKey}`;
1502
+ }
1503
+ const tarballResponse = await fetch(versionInfo.downloadUrl, {
1504
+ headers: downloadHeaders,
1505
+ redirect: "follow"
1506
+ });
1507
+ if (!tarballResponse.ok) {
1508
+ throw new Error(`Failed to download tarball (${tarballResponse.status})`);
1509
+ }
1510
+ const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
1511
+ const integrity = calculateIntegrity(tarballBuffer);
1512
+ const expectedIntegrity = `sha256-${Buffer.from(versionInfo.checksum, "hex").toString("base64")}`;
1513
+ if (integrity !== expectedIntegrity) {
1514
+ throw new Error("Checksum verification failed");
1515
+ }
1516
+ const skillsDir = getSkillsDir();
1517
+ const destDir = join(skillsDir, username, name);
1518
+ await mkdir(destDir, { recursive: true });
1519
+ const { writeFile: writeFile8 } = await import('fs/promises');
1520
+ const tempFile = join(destDir, ".temp.tgz");
1521
+ await writeFile8(tempFile, tarballBuffer);
1522
+ const { exec: exec2 } = await import('child_process');
1523
+ const { promisify: promisify2 } = await import('util');
1524
+ const execAsync = promisify2(exec2);
1525
+ try {
1526
+ await rm(destDir, { recursive: true, force: true });
1527
+ await mkdir(destDir, { recursive: true });
1528
+ await writeFile8(tempFile, tarballBuffer);
1529
+ await execAsync(
1530
+ `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
1531
+ );
1532
+ } finally {
1533
+ await rm(tempFile, { force: true });
1534
+ }
1535
+ const fullName = `@user/${username}/${name}`;
1536
+ await addToLockfile(fullName, {
1537
+ version: resolvedVersion,
1538
+ resolved: versionInfo.downloadUrl,
1539
+ integrity
1540
+ });
1541
+ const dependencyRange = versionRange || `^${resolvedVersion}`;
1542
+ await addDependency(fullName, dependencyRange);
1543
+ const agents = options.resolvedAgents;
1544
+ if (agents[0] !== "none") {
1545
+ const skillManifest = await readManifest();
1546
+ const skillInfo = {
1547
+ name,
1548
+ sourcePath: getRegistrySkillPath(username, name)
1549
+ };
1550
+ await createAgentSymlinks([skillInfo], {
1551
+ agents,
1552
+ projectRoot: process.cwd(),
1553
+ agentConfigs: skillManifest?.agents
911
1554
  });
912
- if (!tarballResponse.ok) {
913
- console.error(
914
- `Error: Failed to download tarball (${tarballResponse.status})`
915
- );
1555
+ }
1556
+ console.log(`Installed @user/${username}/${name}@${resolvedVersion}`);
1557
+ console.log(`Location: ${destDir}`);
1558
+ }
1559
+ async function installGitHubPackage(resolved, options) {
1560
+ const { specifier, parsed, ref, downloadResult } = resolved;
1561
+ console.log(
1562
+ `Installing ${specifier} (${ref}@${downloadResult.commit.slice(0, 7)})...`
1563
+ );
1564
+ const skillsDir = getSkillsDir();
1565
+ const destPath = await extractGitHubPackage(
1566
+ parsed,
1567
+ downloadResult.buffer,
1568
+ skillsDir
1569
+ );
1570
+ const lockfileSpecifier = formatGitHubSpecifier({
1571
+ owner: parsed.owner,
1572
+ repo: parsed.repo,
1573
+ path: parsed.path
1574
+ // Don't include ref in the specifier key, it's stored in gitRef
1575
+ });
1576
+ const entry = {
1577
+ version: downloadResult.commit.slice(0, 7),
1578
+ resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
1579
+ integrity: downloadResult.integrity,
1580
+ gitCommit: downloadResult.commit,
1581
+ gitRef: ref
1582
+ };
1583
+ await addGitHubToLockfile(lockfileSpecifier, entry);
1584
+ await addGitHubDependency(lockfileSpecifier, ref);
1585
+ const agents = options.resolvedAgents;
1586
+ if (agents[0] !== "none") {
1587
+ const manifest = await readManifest();
1588
+ const skillName = getGitHubSkillName(parsed);
1589
+ const skillInfo = {
1590
+ name: skillName,
1591
+ sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
1592
+ };
1593
+ await createAgentSymlinks([skillInfo], {
1594
+ agents,
1595
+ projectRoot: process.cwd(),
1596
+ agentConfigs: manifest?.agents
1597
+ });
1598
+ }
1599
+ console.log(
1600
+ `Installed ${specifier} (${ref}@${downloadResult.commit.slice(0, 7)})`
1601
+ );
1602
+ console.log(`Location: ${destPath}`);
1603
+ }
1604
+ var init_add = __esm({
1605
+ "src/commands/add.ts"() {
1606
+ init_src();
1607
+ init_agents();
1608
+ init_api_client();
1609
+ init_config();
1610
+ init_errors();
1611
+ init_github();
1612
+ init_lockfile();
1613
+ init_manifest2();
1614
+ init_symlinks();
1615
+ }
1616
+ });
1617
+
1618
+ // src/commands/access.ts
1619
+ init_src();
1620
+ init_api_client();
1621
+ init_config();
1622
+ async function access(specifier, options) {
1623
+ try {
1624
+ const apiKey = await requireApiKey();
1625
+ const registryUrl = await getRegistryUrl();
1626
+ if (options.public && options.private) {
1627
+ console.error("Error: Cannot specify both --public and --private");
916
1628
  process.exit(1);
917
1629
  }
918
- const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
919
- const integrity = calculateIntegrity(tarballBuffer);
920
- const expectedIntegrity = `sha256-${Buffer.from(versionInfo.checksum, "hex").toString("base64")}`;
921
- if (integrity !== expectedIntegrity) {
922
- console.error("Error: Checksum verification failed");
1630
+ if (!options.public && !options.private) {
1631
+ console.error("Error: Must specify either --public or --private");
923
1632
  process.exit(1);
924
1633
  }
925
- const skillsDir = getSkillsDir();
926
- const destDir = join(skillsDir, username, name);
927
- await mkdir(destDir, { recursive: true });
928
- const { writeFile: writeFile6 } = await import('fs/promises');
929
- const tempFile = join(destDir, ".temp.tgz");
930
- await writeFile6(tempFile, tarballBuffer);
931
- const { exec: exec2 } = await import('child_process');
932
- const { promisify: promisify2 } = await import('util');
933
- const execAsync = promisify2(exec2);
934
- try {
935
- await rm(destDir, { recursive: true, force: true });
936
- await mkdir(destDir, { recursive: true });
937
- await writeFile6(tempFile, tarballBuffer);
938
- await execAsync(
939
- `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
1634
+ const visibility = options.public ? "public" : "private";
1635
+ let packageName;
1636
+ if (specifier) {
1637
+ const parsed = parseSkillSpecifier(specifier);
1638
+ if (!parsed) {
1639
+ console.error(
1640
+ `Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}`
1641
+ );
1642
+ process.exit(1);
1643
+ }
1644
+ packageName = parsed.name;
1645
+ } else {
1646
+ const { readFile: readFile7 } = await import('fs/promises');
1647
+ const { join: join13 } = await import('path');
1648
+ let manifest = null;
1649
+ try {
1650
+ const content = await readFile7(
1651
+ join13(process.cwd(), "pspm.json"),
1652
+ "utf-8"
1653
+ );
1654
+ manifest = JSON.parse(content);
1655
+ } catch {
1656
+ try {
1657
+ const content = await readFile7(
1658
+ join13(process.cwd(), "package.json"),
1659
+ "utf-8"
1660
+ );
1661
+ manifest = JSON.parse(content);
1662
+ } catch {
1663
+ console.error(
1664
+ "Error: No pspm.json or package.json found in current directory"
1665
+ );
1666
+ console.error(
1667
+ "Either run this command in a package directory or specify a package name"
1668
+ );
1669
+ process.exit(1);
1670
+ }
1671
+ }
1672
+ if (!manifest?.name) {
1673
+ console.error("Error: Package manifest is missing 'name' field");
1674
+ process.exit(1);
1675
+ }
1676
+ packageName = manifest.name;
1677
+ }
1678
+ configure2({ registryUrl, apiKey });
1679
+ console.log(`Setting ${packageName} to ${visibility}...`);
1680
+ const response = await changeSkillAccess(packageName, { visibility });
1681
+ if (response.status !== 200 || !response.data) {
1682
+ const errorMessage = response.error ?? "Failed to change visibility";
1683
+ console.error(`Error: ${errorMessage}`);
1684
+ process.exit(1);
1685
+ }
1686
+ const result = response.data;
1687
+ console.log(
1688
+ `+ @user/${result.username}/${result.name} is now ${result.visibility}`
1689
+ );
1690
+ if (visibility === "public") {
1691
+ console.log("");
1692
+ console.log(
1693
+ "Note: This action is irreversible. Public packages cannot be made private."
940
1694
  );
941
- } finally {
942
- await rm(tempFile, { force: true });
943
1695
  }
944
- const fullName = `@user/${username}/${name}`;
945
- await addToLockfile(fullName, {
946
- version: resolved,
947
- resolved: versionInfo.downloadUrl,
948
- integrity
949
- });
950
- console.log(`Installed @user/${username}/${name}@${resolved}`);
951
- console.log(`Location: ${destDir}`);
952
1696
  } catch (error) {
953
1697
  const message = error instanceof Error ? error.message : "Unknown error";
954
1698
  console.error(`Error: ${message}`);
955
1699
  process.exit(1);
956
1700
  }
957
1701
  }
1702
+
1703
+ // src/commands/index.ts
1704
+ init_add();
958
1705
  async function configInit(options) {
959
1706
  try {
960
1707
  const configPath = join(process.cwd(), ".pspmrc");
@@ -987,6 +1734,7 @@ async function configInit(options) {
987
1734
  }
988
1735
 
989
1736
  // src/commands/config/show.ts
1737
+ init_config();
990
1738
  async function configShow() {
991
1739
  try {
992
1740
  const resolved = await resolveConfig();
@@ -1016,6 +1764,9 @@ async function configShow() {
1016
1764
  }
1017
1765
 
1018
1766
  // src/commands/deprecate.ts
1767
+ init_src();
1768
+ init_api_client();
1769
+ init_config();
1019
1770
  async function deprecate(specifier, message, options) {
1020
1771
  try {
1021
1772
  const apiKey = await requireApiKey();
@@ -1078,6 +1829,17 @@ async function deprecate(specifier, message, options) {
1078
1829
  process.exit(1);
1079
1830
  }
1080
1831
  }
1832
+
1833
+ // src/commands/init.ts
1834
+ init_src();
1835
+ function prompt(rl, question, defaultValue) {
1836
+ return new Promise((resolve) => {
1837
+ const displayDefault = defaultValue ? ` (${defaultValue})` : "";
1838
+ rl.question(`${question}${displayDefault} `, (answer) => {
1839
+ resolve(answer.trim() || defaultValue);
1840
+ });
1841
+ });
1842
+ }
1081
1843
  async function readExistingPackageJson() {
1082
1844
  try {
1083
1845
  const content = await readFile(
@@ -1096,52 +1858,177 @@ async function readExistingPackageJson() {
1096
1858
  return null;
1097
1859
  }
1098
1860
  }
1861
+ async function getGitAuthor() {
1862
+ try {
1863
+ const { exec: exec2 } = await import('child_process');
1864
+ const { promisify: promisify2 } = await import('util');
1865
+ const execAsync = promisify2(exec2);
1866
+ const [nameResult, emailResult] = await Promise.all([
1867
+ execAsync("git config user.name").catch(() => ({ stdout: "" })),
1868
+ execAsync("git config user.email").catch(() => ({ stdout: "" }))
1869
+ ]);
1870
+ const name = nameResult.stdout.trim();
1871
+ const email = emailResult.stdout.trim();
1872
+ if (name && email) {
1873
+ return `${name} <${email}>`;
1874
+ }
1875
+ if (name) {
1876
+ return name;
1877
+ }
1878
+ return null;
1879
+ } catch {
1880
+ return null;
1881
+ }
1882
+ }
1099
1883
  function sanitizeName(name) {
1100
1884
  const withoutScope = name.replace(/^@[^/]+\//, "");
1101
1885
  return withoutScope.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
1102
1886
  }
1887
+ function isValidName(name) {
1888
+ return /^[a-z][a-z0-9_-]*$/.test(name);
1889
+ }
1890
+ function isValidVersion(version2) {
1891
+ return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(version2);
1892
+ }
1103
1893
  async function init(options) {
1104
1894
  try {
1105
1895
  const pspmJsonPath = join(process.cwd(), "pspm.json");
1896
+ let exists = false;
1106
1897
  try {
1107
1898
  await stat(pspmJsonPath);
1899
+ exists = true;
1900
+ } catch {
1901
+ }
1902
+ if (exists && !options.force) {
1108
1903
  console.error("Error: pspm.json already exists in this directory.");
1109
- console.error("Use --force to overwrite (not yet implemented).");
1904
+ console.error("Use --force to overwrite.");
1110
1905
  process.exit(1);
1111
- } catch {
1112
1906
  }
1113
- const existingPkg = await readExistingPackageJson();
1114
- const defaultName = options.name || sanitizeName(existingPkg?.name || basename(process.cwd()));
1115
- const defaultVersion = existingPkg?.version || "0.1.0";
1116
- const defaultDescription = options.description || existingPkg?.description || "";
1117
- const defaultAuthor = options.author || existingPkg?.author || "";
1118
- const defaultLicense = existingPkg?.license || "MIT";
1119
- const manifest = {
1120
- $schema: PSPM_SCHEMA_URL,
1121
- name: defaultName,
1122
- version: defaultVersion,
1123
- description: defaultDescription || void 0,
1124
- author: defaultAuthor || void 0,
1125
- license: defaultLicense,
1126
- type: "skill",
1127
- capabilities: [],
1128
- main: "SKILL.md",
1129
- requirements: {
1130
- pspm: ">=0.1.0"
1131
- },
1132
- files: [...DEFAULT_SKILL_FILES],
1133
- dependencies: {},
1134
- private: false
1135
- };
1907
+ const existingPkg = await readExistingPackageJson();
1908
+ const gitAuthor = await getGitAuthor();
1909
+ const defaultName = sanitizeName(
1910
+ options.name || existingPkg?.name || basename(process.cwd())
1911
+ );
1912
+ const defaultVersion = existingPkg?.version || "0.1.0";
1913
+ const defaultDescription = options.description || existingPkg?.description || "";
1914
+ const defaultAuthor = options.author || existingPkg?.author || gitAuthor || "";
1915
+ const defaultLicense = existingPkg?.license || "MIT";
1916
+ const defaultMain = "SKILL.md";
1917
+ const defaultCapabilities = "";
1918
+ let manifest;
1919
+ if (options.yes) {
1920
+ manifest = {
1921
+ $schema: PSPM_SCHEMA_URL,
1922
+ name: defaultName,
1923
+ version: defaultVersion,
1924
+ description: defaultDescription || void 0,
1925
+ author: defaultAuthor || void 0,
1926
+ license: defaultLicense,
1927
+ type: "skill",
1928
+ capabilities: [],
1929
+ main: defaultMain,
1930
+ requirements: {
1931
+ pspm: ">=0.1.0"
1932
+ },
1933
+ files: [...DEFAULT_SKILL_FILES],
1934
+ dependencies: {},
1935
+ private: false
1936
+ };
1937
+ } else {
1938
+ console.log(
1939
+ "This utility will walk you through creating a pspm.json file."
1940
+ );
1941
+ console.log(
1942
+ "It only covers the most common items, and tries to guess sensible defaults."
1943
+ );
1944
+ console.log("");
1945
+ console.log(
1946
+ "See `pspm init --help` for definitive documentation on these fields"
1947
+ );
1948
+ console.log("and exactly what they do.");
1949
+ console.log("");
1950
+ console.log("Press ^C at any time to quit.");
1951
+ const rl = createInterface({
1952
+ input: process.stdin,
1953
+ output: process.stdout
1954
+ });
1955
+ try {
1956
+ let name = await prompt(rl, "skill name:", defaultName);
1957
+ while (!isValidName(name)) {
1958
+ console.log(
1959
+ " Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores."
1960
+ );
1961
+ name = await prompt(rl, "skill name:", sanitizeName(name));
1962
+ }
1963
+ let version2 = await prompt(rl, "version:", defaultVersion);
1964
+ while (!isValidVersion(version2)) {
1965
+ console.log(" Version must be valid semver (e.g., 1.0.0)");
1966
+ version2 = await prompt(rl, "version:", "0.1.0");
1967
+ }
1968
+ const description = await prompt(
1969
+ rl,
1970
+ "description:",
1971
+ defaultDescription
1972
+ );
1973
+ const main = await prompt(rl, "entry point:", defaultMain);
1974
+ const capabilitiesStr = await prompt(
1975
+ rl,
1976
+ "capabilities (comma-separated):",
1977
+ defaultCapabilities
1978
+ );
1979
+ const author = await prompt(rl, "author:", defaultAuthor);
1980
+ const license = await prompt(rl, "license:", defaultLicense);
1981
+ rl.close();
1982
+ const capabilities = capabilitiesStr ? capabilitiesStr.split(",").map((s) => s.trim()).filter(Boolean) : [];
1983
+ manifest = {
1984
+ $schema: PSPM_SCHEMA_URL,
1985
+ name,
1986
+ version: version2,
1987
+ description: description || void 0,
1988
+ author: author || void 0,
1989
+ license,
1990
+ type: "skill",
1991
+ capabilities,
1992
+ main,
1993
+ requirements: {
1994
+ pspm: ">=0.1.0"
1995
+ },
1996
+ files: [...DEFAULT_SKILL_FILES],
1997
+ dependencies: {},
1998
+ private: false
1999
+ };
2000
+ } catch (error) {
2001
+ rl.close();
2002
+ if (error instanceof Error && error.message.includes("readline was closed")) {
2003
+ console.log("\nAborted.");
2004
+ process.exit(0);
2005
+ }
2006
+ throw error;
2007
+ }
2008
+ }
1136
2009
  if (!manifest.description) delete manifest.description;
1137
2010
  if (!manifest.author) delete manifest.author;
2011
+ if (manifest.capabilities?.length === 0) delete manifest.capabilities;
1138
2012
  const content = JSON.stringify(manifest, null, 2);
1139
- await writeFile(pspmJsonPath, `${content}
1140
- `);
1141
- console.log("Created pspm.json:");
2013
+ console.log("");
2014
+ console.log(`About to write to ${pspmJsonPath}:`);
1142
2015
  console.log("");
1143
2016
  console.log(content);
1144
2017
  console.log("");
2018
+ if (!options.yes) {
2019
+ const rl = createInterface({
2020
+ input: process.stdin,
2021
+ output: process.stdout
2022
+ });
2023
+ const confirm = await prompt(rl, "Is this OK?", "yes");
2024
+ rl.close();
2025
+ if (confirm.toLowerCase() !== "yes" && confirm.toLowerCase() !== "y") {
2026
+ console.log("Aborted.");
2027
+ process.exit(0);
2028
+ }
2029
+ }
2030
+ await writeFile(pspmJsonPath, `${content}
2031
+ `);
1145
2032
  try {
1146
2033
  await stat(join(process.cwd(), "SKILL.md"));
1147
2034
  } catch {
@@ -1160,6 +2047,17 @@ async function init(options) {
1160
2047
  process.exit(1);
1161
2048
  }
1162
2049
  }
2050
+
2051
+ // src/commands/install.ts
2052
+ init_src();
2053
+ init_agents();
2054
+ init_api_client();
2055
+ init_config();
2056
+ init_errors();
2057
+ init_github();
2058
+ init_lockfile();
2059
+ init_manifest2();
2060
+ init_symlinks();
1163
2061
  function getCacheFilePath(cacheDir, integrity) {
1164
2062
  const match = integrity.match(/^sha256-(.+)$/);
1165
2063
  if (!match) {
@@ -1191,120 +2089,537 @@ async function writeToCache(cacheDir, integrity, data) {
1191
2089
  } catch {
1192
2090
  }
1193
2091
  }
1194
- async function install(options) {
2092
+ async function install(specifiers, options) {
2093
+ if (specifiers.length > 0) {
2094
+ const { add: add2 } = await Promise.resolve().then(() => (init_add(), add_exports));
2095
+ await add2(specifiers, {
2096
+ save: true,
2097
+ agent: options.agent,
2098
+ yes: options.yes
2099
+ });
2100
+ return;
2101
+ }
2102
+ await installFromLockfile(options);
2103
+ }
2104
+ async function installFromLockfile(options) {
1195
2105
  try {
1196
2106
  const config2 = await resolveConfig();
1197
- const apiKey = getTokenForRegistry(config2, config2.registryUrl);
2107
+ const registryUrl = config2.registryUrl;
2108
+ const apiKey = getTokenForRegistry(config2, registryUrl);
1198
2109
  const skillsDir = options.dir || getSkillsDir();
1199
2110
  const cacheDir = getCacheDir();
1200
2111
  await migrateLockfileIfNeeded();
1201
- const lockfile = await readLockfile();
1202
- if (!lockfile) {
2112
+ let lockfile = await readLockfile();
2113
+ const manifestDeps = await getDependencies();
2114
+ const manifestGitHubDeps = await getGitHubDependencies();
2115
+ const lockfilePackages = lockfile?.packages ?? lockfile?.skills ?? {};
2116
+ const lockfileGitHubPackages = lockfile?.githubPackages ?? {};
2117
+ const installedSkills = [];
2118
+ const missingDeps = [];
2119
+ for (const [fullName, versionRange] of Object.entries(manifestDeps)) {
2120
+ if (!lockfilePackages[fullName]) {
2121
+ missingDeps.push({ fullName, versionRange });
2122
+ }
2123
+ }
2124
+ if (missingDeps.length > 0) {
1203
2125
  if (options.frozenLockfile) {
1204
2126
  console.error(
1205
- "Error: No lockfile found. Cannot install with --frozen-lockfile"
2127
+ "Error: Dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
1206
2128
  );
2129
+ console.error("Missing dependencies:");
2130
+ for (const dep of missingDeps) {
2131
+ console.error(` - ${dep.fullName}@${dep.versionRange}`);
2132
+ }
1207
2133
  process.exit(1);
1208
2134
  }
1209
- console.log("No lockfile found. Nothing to install.");
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)...
2135
+ console.log(`Resolving ${missingDeps.length} new dependency(ies)...
1220
2136
  `);
1221
- const packages = lockfile.packages ?? lockfile.skills ?? {};
1222
- const entries = Object.entries(packages);
1223
- for (const [fullName, entry] of entries) {
1224
- const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
1225
- if (!match) {
1226
- console.warn(`Warning: Invalid skill name in lockfile: ${fullName}`);
1227
- continue;
1228
- }
1229
- const [, username, name] = match;
1230
- console.log(`Installing ${fullName}@${entry.version}...`);
1231
- let tarballBuffer;
1232
- let fromCache = false;
1233
- const cachedTarball = await readFromCache(cacheDir, entry.integrity);
1234
- if (cachedTarball) {
1235
- tarballBuffer = cachedTarball;
1236
- fromCache = true;
1237
- } else {
1238
- const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
2137
+ configure2({ registryUrl, apiKey: apiKey ?? "" });
2138
+ for (const { fullName, versionRange } of missingDeps) {
2139
+ const parsed = parseSkillSpecifier(fullName);
2140
+ if (!parsed) {
2141
+ console.error(`Error: Invalid dependency specifier: ${fullName}`);
2142
+ continue;
2143
+ }
2144
+ const { username, name } = parsed;
2145
+ console.log(`Resolving ${fullName}@${versionRange}...`);
2146
+ const versionsResponse = await listSkillVersions(username, name);
2147
+ if (versionsResponse.status !== 200) {
2148
+ const errorMessage = extractApiErrorMessage(
2149
+ versionsResponse,
2150
+ `Skill ${fullName} not found`
2151
+ );
2152
+ console.error(`Error: ${errorMessage}`);
2153
+ continue;
2154
+ }
2155
+ const versions = versionsResponse.data;
2156
+ if (versions.length === 0) {
2157
+ console.error(`Error: Skill ${fullName} not found`);
2158
+ continue;
2159
+ }
2160
+ const versionStrings = versions.map(
2161
+ (v) => v.version
2162
+ );
2163
+ const resolved = resolveVersion(versionRange || "*", versionStrings);
2164
+ if (!resolved) {
2165
+ console.error(
2166
+ `Error: No version matching "${versionRange}" for ${fullName}`
2167
+ );
2168
+ continue;
2169
+ }
2170
+ const versionResponse = await getSkillVersion(username, name, resolved);
2171
+ if (versionResponse.status !== 200 || !versionResponse.data) {
2172
+ const errorMessage = extractApiErrorMessage(
2173
+ versionResponse,
2174
+ `Version ${resolved} not found`
2175
+ );
2176
+ console.error(`Error: ${errorMessage}`);
2177
+ continue;
2178
+ }
2179
+ const versionInfo = versionResponse.data;
2180
+ const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
1239
2181
  const downloadHeaders = {};
1240
2182
  if (!isPresignedUrl && apiKey) {
1241
2183
  downloadHeaders.Authorization = `Bearer ${apiKey}`;
1242
2184
  }
1243
- const response = await fetch(entry.resolved, {
2185
+ const tarballResponse = await fetch(versionInfo.downloadUrl, {
1244
2186
  headers: downloadHeaders,
1245
2187
  redirect: "follow"
1246
2188
  });
1247
- if (!response.ok) {
1248
- if (response.status === 401) {
1249
- if (!apiKey) {
1250
- console.error(
1251
- ` Error: ${fullName} requires authentication. Run 'pspm login' first.`
1252
- );
2189
+ if (!tarballResponse.ok) {
2190
+ console.error(
2191
+ `Error: Failed to download tarball for ${fullName} (${tarballResponse.status})`
2192
+ );
2193
+ continue;
2194
+ }
2195
+ const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
2196
+ const integrity = calculateIntegrity(tarballBuffer);
2197
+ await addToLockfile(fullName, {
2198
+ version: resolved,
2199
+ resolved: versionInfo.downloadUrl,
2200
+ integrity
2201
+ });
2202
+ await writeToCache(cacheDir, integrity, tarballBuffer);
2203
+ console.log(` Resolved ${fullName}@${resolved}`);
2204
+ }
2205
+ lockfile = await readLockfile();
2206
+ }
2207
+ const missingGitHubDeps = [];
2208
+ for (const [specifier, ref] of Object.entries(manifestGitHubDeps)) {
2209
+ if (!lockfileGitHubPackages[specifier]) {
2210
+ missingGitHubDeps.push({ specifier, ref });
2211
+ }
2212
+ }
2213
+ if (missingGitHubDeps.length > 0) {
2214
+ if (options.frozenLockfile) {
2215
+ console.error(
2216
+ "Error: GitHub dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
2217
+ );
2218
+ console.error("Missing GitHub dependencies:");
2219
+ for (const dep of missingGitHubDeps) {
2220
+ console.error(` - ${dep.specifier}@${dep.ref}`);
2221
+ }
2222
+ process.exit(1);
2223
+ }
2224
+ console.log(
2225
+ `
2226
+ Resolving ${missingGitHubDeps.length} GitHub dependency(ies)...
2227
+ `
2228
+ );
2229
+ for (const { specifier, ref } of missingGitHubDeps) {
2230
+ const parsed = parseGitHubSpecifier(specifier);
2231
+ if (!parsed) {
2232
+ console.error(`Error: Invalid GitHub specifier: ${specifier}`);
2233
+ continue;
2234
+ }
2235
+ parsed.ref = parsed.ref || ref;
2236
+ console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
2237
+ try {
2238
+ const result = await downloadGitHubPackage(parsed);
2239
+ await extractGitHubPackage(parsed, result.buffer, skillsDir);
2240
+ const entry = {
2241
+ version: result.commit.slice(0, 7),
2242
+ resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
2243
+ integrity: result.integrity,
2244
+ gitCommit: result.commit,
2245
+ gitRef: ref || "HEAD"
2246
+ };
2247
+ await addGitHubToLockfile(specifier, entry);
2248
+ await writeToCache(cacheDir, result.integrity, result.buffer);
2249
+ console.log(
2250
+ ` Resolved ${specifier} (${ref}@${result.commit.slice(0, 7)})`
2251
+ );
2252
+ } catch (error) {
2253
+ if (error instanceof GitHubRateLimitError) {
2254
+ console.error(`Error: ${error.message}`);
2255
+ } else if (error instanceof GitHubPathNotFoundError) {
2256
+ console.error(`Error: ${error.message}`);
2257
+ } else if (error instanceof GitHubNotFoundError) {
2258
+ console.error(`Error: ${error.message}`);
2259
+ } else {
2260
+ const message = error instanceof Error ? error.message : String(error);
2261
+ console.error(`Error resolving ${specifier}: ${message}`);
2262
+ }
2263
+ }
2264
+ }
2265
+ lockfile = await readLockfile();
2266
+ }
2267
+ const manifest = await readManifest();
2268
+ const agentConfigs = manifest?.agents;
2269
+ let agents;
2270
+ if (options.agent) {
2271
+ agents = parseAgentArg(options.agent);
2272
+ } else if (manifest) {
2273
+ agents = parseAgentArg(void 0);
2274
+ } else if (options.yes) {
2275
+ agents = parseAgentArg(void 0);
2276
+ } else {
2277
+ console.log("\nNo pspm.json found. Let's set up your project.\n");
2278
+ agents = await promptForAgents();
2279
+ console.log();
2280
+ }
2281
+ const packages = lockfile?.packages ?? lockfile?.skills ?? {};
2282
+ const packageCount = Object.keys(packages).length;
2283
+ if (packageCount > 0) {
2284
+ console.log(`
2285
+ Installing ${packageCount} registry skill(s)...
2286
+ `);
2287
+ const entries = Object.entries(packages);
2288
+ for (const [fullName, entry] of entries) {
2289
+ const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
2290
+ if (!match) {
2291
+ console.warn(`Warning: Invalid skill name in lockfile: ${fullName}`);
2292
+ continue;
2293
+ }
2294
+ const [, username, name] = match;
2295
+ console.log(`Installing ${fullName}@${entry.version}...`);
2296
+ let tarballBuffer;
2297
+ let fromCache = false;
2298
+ const cachedTarball = await readFromCache(cacheDir, entry.integrity);
2299
+ if (cachedTarball) {
2300
+ tarballBuffer = cachedTarball;
2301
+ fromCache = true;
2302
+ } else {
2303
+ const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
2304
+ const downloadHeaders = {};
2305
+ if (!isPresignedUrl && apiKey) {
2306
+ downloadHeaders.Authorization = `Bearer ${apiKey}`;
2307
+ }
2308
+ const response = await fetch(entry.resolved, {
2309
+ headers: downloadHeaders,
2310
+ redirect: "follow"
2311
+ });
2312
+ if (!response.ok) {
2313
+ if (response.status === 401) {
2314
+ if (!apiKey) {
2315
+ console.error(
2316
+ ` Error: ${fullName} requires authentication. Run 'pspm login' first.`
2317
+ );
2318
+ } else {
2319
+ console.error(
2320
+ ` Error: Access denied to ${fullName}. You may not have permission to access this private package.`
2321
+ );
2322
+ }
1253
2323
  } else {
1254
2324
  console.error(
1255
- ` Error: Access denied to ${fullName}. You may not have permission to access this private package.`
2325
+ ` Error: Failed to download ${fullName} (${response.status})`
1256
2326
  );
1257
2327
  }
1258
- } else {
2328
+ continue;
2329
+ }
2330
+ tarballBuffer = Buffer.from(await response.arrayBuffer());
2331
+ const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
2332
+ if (actualIntegrity !== entry.integrity) {
1259
2333
  console.error(
1260
- ` Error: Failed to download ${fullName} (${response.status})`
2334
+ ` Error: Checksum verification failed for ${fullName}`
1261
2335
  );
2336
+ if (options.frozenLockfile) {
2337
+ process.exit(1);
2338
+ }
2339
+ continue;
1262
2340
  }
1263
- continue;
2341
+ await writeToCache(cacheDir, entry.integrity, tarballBuffer);
1264
2342
  }
1265
- tarballBuffer = Buffer.from(await response.arrayBuffer());
1266
- const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
1267
- if (actualIntegrity !== entry.integrity) {
1268
- console.error(
1269
- ` Error: Checksum verification failed for ${fullName}`
2343
+ const destDir = join(skillsDir, username, name);
2344
+ await rm(destDir, { recursive: true, force: true });
2345
+ await mkdir(destDir, { recursive: true });
2346
+ const tempFile = join(destDir, ".temp.tgz");
2347
+ await writeFile(tempFile, tarballBuffer);
2348
+ const { exec: exec2 } = await import('child_process');
2349
+ const { promisify: promisify2 } = await import('util');
2350
+ const execAsync = promisify2(exec2);
2351
+ try {
2352
+ await execAsync(
2353
+ `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
2354
+ );
2355
+ } finally {
2356
+ await rm(tempFile, { force: true });
2357
+ }
2358
+ console.log(
2359
+ ` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
2360
+ );
2361
+ installedSkills.push({
2362
+ name,
2363
+ sourcePath: getRegistrySkillPath(username, name)
2364
+ });
2365
+ }
2366
+ }
2367
+ const githubPackages = lockfile?.githubPackages ?? {};
2368
+ const githubCount = Object.keys(githubPackages).length;
2369
+ if (githubCount > 0) {
2370
+ console.log(`
2371
+ Installing ${githubCount} GitHub skill(s)...
2372
+ `);
2373
+ for (const [specifier, entry] of Object.entries(githubPackages)) {
2374
+ const parsed = parseGitHubSpecifier(specifier);
2375
+ if (!parsed) {
2376
+ console.warn(
2377
+ `Warning: Invalid GitHub specifier in lockfile: ${specifier}`
1270
2378
  );
1271
- if (options.frozenLockfile) {
1272
- process.exit(1);
1273
- }
1274
2379
  continue;
1275
2380
  }
1276
- await writeToCache(cacheDir, entry.integrity, tarballBuffer);
1277
- }
1278
- const destDir = join(skillsDir, username, name);
1279
- await rm(destDir, { recursive: true, force: true });
1280
- await mkdir(destDir, { recursive: true });
1281
- const tempFile = join(destDir, ".temp.tgz");
1282
- await writeFile(tempFile, tarballBuffer);
1283
- const { exec: exec2 } = await import('child_process');
1284
- const { promisify: promisify2 } = await import('util');
1285
- const execAsync = promisify2(exec2);
1286
- try {
1287
- await execAsync(
1288
- `tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
2381
+ const ghEntry = entry;
2382
+ console.log(
2383
+ `Installing ${specifier} (${ghEntry.gitRef}@${ghEntry.gitCommit.slice(0, 7)})...`
2384
+ );
2385
+ let tarballBuffer;
2386
+ let fromCache = false;
2387
+ const cachedTarball = await readFromCache(cacheDir, ghEntry.integrity);
2388
+ if (cachedTarball) {
2389
+ tarballBuffer = cachedTarball;
2390
+ fromCache = true;
2391
+ } else {
2392
+ try {
2393
+ const specWithCommit = { ...parsed, ref: ghEntry.gitCommit };
2394
+ const result = await downloadGitHubPackage(specWithCommit);
2395
+ tarballBuffer = result.buffer;
2396
+ if (result.integrity !== ghEntry.integrity) {
2397
+ console.error(
2398
+ ` Error: Checksum verification failed for ${specifier}`
2399
+ );
2400
+ if (options.frozenLockfile) {
2401
+ process.exit(1);
2402
+ }
2403
+ continue;
2404
+ }
2405
+ await writeToCache(cacheDir, ghEntry.integrity, tarballBuffer);
2406
+ } catch (error) {
2407
+ if (error instanceof GitHubRateLimitError) {
2408
+ console.error(` Error: ${error.message}`);
2409
+ } else if (error instanceof GitHubPathNotFoundError) {
2410
+ console.error(` Error: ${error.message}`);
2411
+ } else if (error instanceof GitHubNotFoundError) {
2412
+ console.error(` Error: ${error.message}`);
2413
+ } else {
2414
+ const message = error instanceof Error ? error.message : String(error);
2415
+ console.error(` Error downloading ${specifier}: ${message}`);
2416
+ }
2417
+ continue;
2418
+ }
2419
+ }
2420
+ try {
2421
+ const destPath = await extractGitHubPackage(
2422
+ parsed,
2423
+ tarballBuffer,
2424
+ skillsDir
2425
+ );
2426
+ console.log(
2427
+ ` Installed to ${destPath}${fromCache ? " (from cache)" : ""}`
2428
+ );
2429
+ const skillName = getGitHubSkillName(parsed);
2430
+ installedSkills.push({
2431
+ name: skillName,
2432
+ sourcePath: getGitHubSkillPath(
2433
+ parsed.owner,
2434
+ parsed.repo,
2435
+ parsed.path
2436
+ )
2437
+ });
2438
+ } catch (error) {
2439
+ const message = error instanceof Error ? error.message : String(error);
2440
+ console.error(` Error extracting ${specifier}: ${message}`);
2441
+ }
2442
+ }
2443
+ }
2444
+ if (installedSkills.length > 0 && agents[0] !== "none") {
2445
+ console.log(`
2446
+ Creating symlinks for agent(s): ${agents.join(", ")}...`);
2447
+ await createAgentSymlinks(installedSkills, {
2448
+ agents,
2449
+ projectRoot: process.cwd(),
2450
+ agentConfigs
2451
+ });
2452
+ console.log(" Symlinks created.");
2453
+ }
2454
+ const totalCount = packageCount + githubCount;
2455
+ if (totalCount === 0) {
2456
+ console.log("No skills to install.");
2457
+ } else {
2458
+ console.log(`
2459
+ All ${totalCount} skill(s) installed.`);
2460
+ }
2461
+ } catch (error) {
2462
+ const message = error instanceof Error ? error.message : "Unknown error";
2463
+ console.error(`Error: ${message}`);
2464
+ process.exit(1);
2465
+ }
2466
+ }
2467
+
2468
+ // src/commands/link.ts
2469
+ init_src();
2470
+ init_agents();
2471
+ init_lockfile();
2472
+ init_manifest2();
2473
+ init_symlinks();
2474
+ async function link(options) {
2475
+ try {
2476
+ const manifest = await readManifest();
2477
+ const agentConfigs = manifest?.agents;
2478
+ let agents;
2479
+ if (options.agent) {
2480
+ agents = parseAgentArg(options.agent);
2481
+ } else if (manifest) {
2482
+ agents = parseAgentArg(void 0);
2483
+ } else if (options.yes) {
2484
+ agents = parseAgentArg(void 0);
2485
+ } else {
2486
+ console.log("No pspm.json found. Let's set up your project.\n");
2487
+ agents = await promptForAgents();
2488
+ }
2489
+ if (agents.length === 1 && agents[0] === "none") {
2490
+ console.log("Skipping symlink creation (--agent none)");
2491
+ return;
2492
+ }
2493
+ const skills = [];
2494
+ const registrySkills = await listLockfileSkills();
2495
+ for (const { name } of registrySkills) {
2496
+ const parsed = parseSkillSpecifier(name);
2497
+ if (!parsed) {
2498
+ console.warn(`Warning: Invalid skill name in lockfile: ${name}`);
2499
+ continue;
2500
+ }
2501
+ skills.push({
2502
+ name: parsed.name,
2503
+ sourcePath: getRegistrySkillPath(parsed.username, parsed.name)
2504
+ });
2505
+ }
2506
+ const githubSkills = await listLockfileGitHubPackages();
2507
+ for (const { specifier } of githubSkills) {
2508
+ const parsed = parseGitHubSpecifier(specifier);
2509
+ if (!parsed) {
2510
+ console.warn(
2511
+ `Warning: Invalid GitHub specifier in lockfile: ${specifier}`
1289
2512
  );
1290
- } finally {
1291
- await rm(tempFile, { force: true });
2513
+ continue;
1292
2514
  }
1293
- console.log(
1294
- ` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
1295
- );
2515
+ const skillName = getGitHubSkillName(parsed);
2516
+ skills.push({
2517
+ name: skillName,
2518
+ sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
2519
+ });
2520
+ }
2521
+ if (skills.length === 0) {
2522
+ console.log("No skills found in lockfile. Nothing to link.");
2523
+ return;
2524
+ }
2525
+ console.log(
2526
+ `Creating symlinks for ${skills.length} skill(s) to agent(s): ${agents.join(", ")}...`
2527
+ );
2528
+ await createAgentSymlinks(skills, {
2529
+ agents,
2530
+ projectRoot: process.cwd(),
2531
+ agentConfigs
2532
+ });
2533
+ console.log("Symlinks created successfully.");
2534
+ console.log("\nLinked skills:");
2535
+ for (const skill of skills) {
2536
+ console.log(` ${skill.name} -> ${skill.sourcePath}`);
1296
2537
  }
1297
- console.log(`
1298
- All ${skillCount} skill(s) installed.`);
1299
2538
  } catch (error) {
1300
2539
  const message = error instanceof Error ? error.message : "Unknown error";
1301
2540
  console.error(`Error: ${message}`);
1302
2541
  process.exit(1);
1303
2542
  }
1304
2543
  }
2544
+
2545
+ // src/commands/list.ts
2546
+ init_src();
2547
+ init_agents();
2548
+ init_lockfile();
2549
+ init_manifest2();
2550
+ init_symlinks();
1305
2551
  async function list(options) {
1306
2552
  try {
1307
- const skills = await listLockfileSkills();
2553
+ const registrySkills = await listLockfileSkills();
2554
+ const githubSkills = await listLockfileGitHubPackages();
2555
+ const manifest = await readManifest();
2556
+ const agentConfigs = manifest?.agents;
2557
+ const availableAgents = getAvailableAgents(agentConfigs);
2558
+ const projectRoot = process.cwd();
2559
+ const skills = [];
2560
+ for (const { name: fullName, entry } of registrySkills) {
2561
+ const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
2562
+ if (!match) continue;
2563
+ const [, username, skillName] = match;
2564
+ const sourcePath = getRegistrySkillPath(username, skillName);
2565
+ const absolutePath = join(projectRoot, sourcePath);
2566
+ let status = "installed";
2567
+ try {
2568
+ await access$1(absolutePath);
2569
+ } catch {
2570
+ status = "missing";
2571
+ }
2572
+ const linkedAgents = await getLinkedAgents(
2573
+ skillName,
2574
+ availableAgents,
2575
+ projectRoot,
2576
+ agentConfigs
2577
+ );
2578
+ skills.push({
2579
+ name: skillName,
2580
+ fullName,
2581
+ version: entry.version,
2582
+ source: "registry",
2583
+ sourcePath,
2584
+ status,
2585
+ linkedAgents
2586
+ });
2587
+ }
2588
+ for (const { specifier, entry } of githubSkills) {
2589
+ const parsed = parseGitHubSpecifier(specifier);
2590
+ if (!parsed) continue;
2591
+ const ghEntry = entry;
2592
+ const skillName = getGitHubSkillName(parsed);
2593
+ const sourcePath = getGitHubSkillPath(
2594
+ parsed.owner,
2595
+ parsed.repo,
2596
+ parsed.path
2597
+ );
2598
+ const absolutePath = join(projectRoot, sourcePath);
2599
+ let status = "installed";
2600
+ try {
2601
+ await access$1(absolutePath);
2602
+ } catch {
2603
+ status = "missing";
2604
+ }
2605
+ const linkedAgents = await getLinkedAgents(
2606
+ skillName,
2607
+ availableAgents,
2608
+ projectRoot,
2609
+ agentConfigs
2610
+ );
2611
+ skills.push({
2612
+ name: skillName,
2613
+ fullName: specifier,
2614
+ version: ghEntry.gitCommit.slice(0, 7),
2615
+ source: "github",
2616
+ sourcePath,
2617
+ status,
2618
+ linkedAgents,
2619
+ gitRef: ghEntry.gitRef,
2620
+ gitCommit: ghEntry.gitCommit
2621
+ });
2622
+ }
1308
2623
  if (skills.length === 0) {
1309
2624
  console.log("No skills installed.");
1310
2625
  return;
@@ -1313,32 +2628,43 @@ async function list(options) {
1313
2628
  console.log(JSON.stringify(skills, null, 2));
1314
2629
  return;
1315
2630
  }
1316
- const skillsDir = getSkillsDir();
1317
2631
  console.log("Installed skills:\n");
1318
- for (const { name, entry } of skills) {
1319
- const match = name.match(/^@user\/([^/]+)\/([^/]+)$/);
1320
- if (!match) continue;
1321
- const [, username, skillName] = match;
1322
- const skillPath = join(skillsDir, username, skillName);
1323
- let status = "installed";
1324
- try {
1325
- await access$1(skillPath);
1326
- } catch {
1327
- status = "missing";
2632
+ for (const skill of skills) {
2633
+ if (skill.source === "registry") {
2634
+ console.log(` ${skill.fullName}@${skill.version} (registry)`);
2635
+ } else {
2636
+ const refInfo = skill.gitRef ? `${skill.gitRef}@${skill.gitCommit?.slice(0, 7)}` : skill.version;
2637
+ console.log(` ${skill.fullName} (${refInfo})`);
1328
2638
  }
1329
- console.log(` ${name}@${entry.version}`);
1330
- if (status === "missing") {
2639
+ if (skill.status === "missing") {
1331
2640
  console.log(` Status: MISSING (run 'pspm install' to restore)`);
1332
2641
  }
2642
+ if (skill.linkedAgents.length > 0) {
2643
+ for (const agent of skill.linkedAgents) {
2644
+ const config2 = resolveAgentConfig(agent, agentConfigs);
2645
+ if (config2) {
2646
+ console.log(` -> ${config2.skillsDir}/${skill.name}`);
2647
+ }
2648
+ }
2649
+ }
1333
2650
  }
2651
+ const registryCount = skills.filter((s) => s.source === "registry").length;
2652
+ const githubCount = skills.filter((s) => s.source === "github").length;
2653
+ const parts = [];
2654
+ if (registryCount > 0) parts.push(`${registryCount} registry`);
2655
+ if (githubCount > 0) parts.push(`${githubCount} github`);
1334
2656
  console.log(`
1335
- Total: ${skills.length} skill(s)`);
2657
+ Total: ${skills.length} skill(s) (${parts.join(", ")})`);
1336
2658
  } catch (error) {
1337
2659
  const message = error instanceof Error ? error.message : "Unknown error";
1338
2660
  console.error(`Error: ${message}`);
1339
2661
  process.exit(1);
1340
2662
  }
1341
2663
  }
2664
+
2665
+ // src/commands/login.ts
2666
+ init_api_client();
2667
+ init_config();
1342
2668
  var DEFAULT_WEB_APP_URL = "https://pspm.dev";
1343
2669
  function getWebAppUrl(registryUrl) {
1344
2670
  if (process.env.PSPM_WEB_URL) {
@@ -1516,6 +2842,7 @@ async function login(options) {
1516
2842
  }
1517
2843
 
1518
2844
  // src/commands/logout.ts
2845
+ init_config();
1519
2846
  async function logout() {
1520
2847
  try {
1521
2848
  const loggedIn = await isLoggedIn();
@@ -1531,6 +2858,10 @@ async function logout() {
1531
2858
  process.exit(1);
1532
2859
  }
1533
2860
  }
2861
+
2862
+ // src/commands/migrate.ts
2863
+ init_config();
2864
+ init_lockfile();
1534
2865
  async function migrate(options) {
1535
2866
  try {
1536
2867
  const legacySkillsDir = getLegacySkillsDir();
@@ -1630,6 +2961,12 @@ async function migrate(options) {
1630
2961
  process.exit(1);
1631
2962
  }
1632
2963
  }
2964
+
2965
+ // src/commands/publish.ts
2966
+ init_src();
2967
+ init_api_client();
2968
+ init_config();
2969
+ init_errors();
1633
2970
  var exec = promisify(exec$1);
1634
2971
  async function detectManifest() {
1635
2972
  const cwd = process.cwd();
@@ -1835,63 +3172,119 @@ Setting visibility to ${options.access}...`);
1835
3172
  process.exit(1);
1836
3173
  }
1837
3174
  }
3175
+
3176
+ // src/commands/remove.ts
3177
+ init_src();
3178
+ init_agents();
3179
+ init_config();
3180
+ init_lockfile();
3181
+ init_manifest2();
3182
+ init_symlinks();
1838
3183
  async function remove(nameOrSpecifier) {
1839
3184
  try {
1840
- await requireApiKey();
1841
- let fullName;
1842
- let username;
1843
- let name;
1844
- if (nameOrSpecifier.startsWith("@user/")) {
1845
- const match = nameOrSpecifier.match(/^@user\/([^/]+)\/([^@/]+)/);
1846
- if (!match) {
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];
3185
+ const manifest = await readManifest();
3186
+ const agentConfigs = manifest?.agents;
3187
+ const agents = getAvailableAgents(agentConfigs);
3188
+ if (isGitHubSpecifier(nameOrSpecifier)) {
3189
+ await removeGitHub(nameOrSpecifier, agents, agentConfigs);
3190
+ } else if (nameOrSpecifier.startsWith("@user/")) {
3191
+ await removeRegistry(nameOrSpecifier, agents, agentConfigs);
1853
3192
  } else {
1854
- const skills = await listLockfileSkills();
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 {
3193
+ await removeByShortName(nameOrSpecifier, agents, agentConfigs);
1885
3194
  }
1886
- console.log(`Removed ${fullName}`);
1887
3195
  } catch (error) {
1888
3196
  const message = error instanceof Error ? error.message : "Unknown error";
1889
3197
  console.error(`Error: ${message}`);
1890
3198
  process.exit(1);
1891
3199
  }
1892
3200
  }
3201
+ async function removeRegistry(specifier, agents, agentConfigs) {
3202
+ const match = specifier.match(/^@user\/([^/]+)\/([^@/]+)/);
3203
+ if (!match) {
3204
+ console.error(`Error: Invalid skill specifier: ${specifier}`);
3205
+ process.exit(1);
3206
+ }
3207
+ const fullName = `@user/${match[1]}/${match[2]}`;
3208
+ const username = match[1];
3209
+ const name = match[2];
3210
+ console.log(`Removing ${fullName}...`);
3211
+ const removedFromLockfile = await removeFromLockfile(fullName);
3212
+ const removedFromManifest = await removeDependency(fullName);
3213
+ if (!removedFromLockfile && !removedFromManifest) {
3214
+ console.error(`Error: ${fullName} not found in lockfile or pspm.json`);
3215
+ process.exit(1);
3216
+ }
3217
+ await removeAgentSymlinks(name, {
3218
+ agents,
3219
+ projectRoot: process.cwd(),
3220
+ agentConfigs
3221
+ });
3222
+ const skillsDir = getSkillsDir();
3223
+ const destDir = join(skillsDir, username, name);
3224
+ try {
3225
+ await rm(destDir, { recursive: true, force: true });
3226
+ } catch {
3227
+ }
3228
+ console.log(`Removed ${fullName}`);
3229
+ }
3230
+ async function removeGitHub(specifier, agents, agentConfigs) {
3231
+ const parsed = parseGitHubSpecifier(specifier);
3232
+ if (!parsed) {
3233
+ console.error(`Error: Invalid GitHub specifier: ${specifier}`);
3234
+ process.exit(1);
3235
+ }
3236
+ const lockfileKey = parsed.path ? `github:${parsed.owner}/${parsed.repo}/${parsed.path}` : `github:${parsed.owner}/${parsed.repo}`;
3237
+ console.log(`Removing ${lockfileKey}...`);
3238
+ const removedFromLockfile = await removeGitHubFromLockfile(lockfileKey);
3239
+ const removedFromManifest = await removeGitHubDependency(lockfileKey);
3240
+ if (!removedFromLockfile && !removedFromManifest) {
3241
+ console.error(`Error: ${lockfileKey} not found in lockfile or pspm.json`);
3242
+ process.exit(1);
3243
+ }
3244
+ const skillName = getGitHubSkillName(parsed);
3245
+ await removeAgentSymlinks(skillName, {
3246
+ agents,
3247
+ projectRoot: process.cwd(),
3248
+ agentConfigs
3249
+ });
3250
+ const skillsDir = getSkillsDir();
3251
+ const destPath = getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path);
3252
+ const destDir = join(skillsDir, "..", destPath);
3253
+ try {
3254
+ await rm(destDir, { recursive: true, force: true });
3255
+ } catch {
3256
+ }
3257
+ console.log(`Removed ${lockfileKey}`);
3258
+ }
3259
+ async function removeByShortName(shortName, agents, agentConfigs) {
3260
+ const registrySkills = await listLockfileSkills();
3261
+ const foundRegistry = registrySkills.find((s) => {
3262
+ const match = s.name.match(/^@user\/([^/]+)\/([^/]+)$/);
3263
+ return match && match[2] === shortName;
3264
+ });
3265
+ if (foundRegistry) {
3266
+ await removeRegistry(foundRegistry.name, agents, agentConfigs);
3267
+ return;
3268
+ }
3269
+ const githubSkills = await listLockfileGitHubPackages();
3270
+ const foundGitHub = githubSkills.find((s) => {
3271
+ const parsed = parseGitHubSpecifier(s.specifier);
3272
+ if (!parsed) return false;
3273
+ return getGitHubSkillName(parsed) === shortName;
3274
+ });
3275
+ if (foundGitHub) {
3276
+ await removeGitHub(foundGitHub.specifier, agents, agentConfigs);
3277
+ return;
3278
+ }
3279
+ console.error(`Error: Skill "${shortName}" not found in lockfile`);
3280
+ process.exit(1);
3281
+ }
1893
3282
 
1894
3283
  // src/commands/unpublish.ts
3284
+ init_src();
3285
+ init_api_client();
3286
+ init_config();
3287
+ init_errors();
1895
3288
  async function unpublish(specifier, options) {
1896
3289
  try {
1897
3290
  const apiKey = await requireApiKey();
@@ -1950,6 +3343,12 @@ async function unpublish(specifier, options) {
1950
3343
  }
1951
3344
 
1952
3345
  // src/commands/update.ts
3346
+ init_src();
3347
+ init_api_client();
3348
+ init_config();
3349
+ init_errors();
3350
+ init_lockfile();
3351
+ init_add();
1953
3352
  async function update(options) {
1954
3353
  try {
1955
3354
  const apiKey = await requireApiKey();
@@ -2011,7 +3410,7 @@ async function update(options) {
2011
3410
  if (!match) continue;
2012
3411
  const [, username, skillName] = match;
2013
3412
  const specifier = `@user/${username}/${skillName}@${latest}`;
2014
- await add(specifier, {});
3413
+ await add([specifier], {});
2015
3414
  }
2016
3415
  console.log("\nAll skills updated.");
2017
3416
  } catch (error) {
@@ -2022,6 +3421,8 @@ async function update(options) {
2022
3421
  }
2023
3422
 
2024
3423
  // src/commands/whoami.ts
3424
+ init_api_client();
3425
+ init_config();
2025
3426
  async function whoami() {
2026
3427
  try {
2027
3428
  const resolved = await resolveConfig();
@@ -2075,12 +3476,13 @@ program.command("logout").description("Log out and clear stored credentials").ac
2075
3476
  program.command("whoami").description("Show current user information").action(async () => {
2076
3477
  await whoami();
2077
3478
  });
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) => {
3479
+ 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
3480
  await init({
2080
3481
  name: options.name,
2081
3482
  description: options.description,
2082
3483
  author: options.author,
2083
- yes: options.yes
3484
+ yes: options.yes,
3485
+ force: options.force
2084
3486
  });
2085
3487
  });
2086
3488
  program.command("migrate").description(
@@ -2088,8 +3490,17 @@ program.command("migrate").description(
2088
3490
  ).option("--dry-run", "Show what would be migrated without making changes").action(async (options) => {
2089
3491
  await migrate({ dryRun: options.dryRun });
2090
3492
  });
2091
- program.command("add <specifier>").description("Add a skill (e.g., @user/bsheng/vite_slides@^2.0.0)").option("--save", "Save to lockfile (default)").action(async (specifier, options) => {
2092
- await add(specifier, { save: options.save ?? true });
3493
+ program.command("add <specifiers...>").description(
3494
+ "Add one or more skills (e.g., @user/bsheng/vite_slides@^2.0.0 or github:owner/repo/path@ref)"
3495
+ ).option("--save", "Save to lockfile (default)").option(
3496
+ "--agent <agents>",
3497
+ 'Comma-separated agents for symlinks (default: all agents, use "none" to skip)'
3498
+ ).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (specifiers, options) => {
3499
+ await add(specifiers, {
3500
+ save: options.save ?? true,
3501
+ agent: options.agent,
3502
+ yes: options.yes
3503
+ });
2093
3504
  });
2094
3505
  program.command("remove <name>").alias("rm").description("Remove an installed skill").action(async (name) => {
2095
3506
  await remove(name);
@@ -2097,12 +3508,25 @@ program.command("remove <name>").alias("rm").description("Remove an installed sk
2097
3508
  program.command("list").alias("ls").description("List installed skills").option("--json", "Output as JSON").action(async (options) => {
2098
3509
  await list({ json: options.json });
2099
3510
  });
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").action(async (options) => {
2101
- await install({
3511
+ program.command("install [specifiers...]").alias("i").description(
3512
+ "Install skills from lockfile, or add and install specific packages"
3513
+ ).option("--frozen-lockfile", "Fail if lockfile is missing or outdated").option("--dir <path>", "Install skills to a specific directory").option(
3514
+ "--agent <agents>",
3515
+ 'Comma-separated agents for symlinks (default: all agents, use "none" to skip)'
3516
+ ).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (specifiers, options) => {
3517
+ await install(specifiers, {
2102
3518
  frozenLockfile: options.frozenLockfile,
2103
- dir: options.dir
3519
+ dir: options.dir,
3520
+ agent: options.agent,
3521
+ yes: options.yes
2104
3522
  });
2105
3523
  });
3524
+ program.command("link").description("Recreate agent symlinks without reinstalling").option(
3525
+ "--agent <agents>",
3526
+ 'Comma-separated agents for symlinks (default: all agents, use "none" to skip)'
3527
+ ).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (options) => {
3528
+ await link({ agent: options.agent, yes: options.yes });
3529
+ });
2106
3530
  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
3531
  await update({ dryRun: options.dryRun });
2108
3532
  });