@anytio/pspm 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,755 @@
1
+ import { a as getGithubSkillVersion, g as listSkillVersions, m as getSkillVersion, n as configure, o as listGithubSkillVersions } from "./api-client-DBXUpGoX.js";
2
+ import { T as extractApiErrorMessage, f as resolveConfig, l as getSkillsDir, p as setGlobalMode, u as isGlobalMode, y as getTokenForRegistry } from "./config-BQy_Rjip.js";
3
+ import { C as downloadGitHubPackage, D as addGitHubDependency, E as addDependency, G as isGitHubSpecifier, H as generateRegistryIdentifier, J as parseGitHubShorthand, K as isGitHubUrl, N as readManifest, O as addLocalDependency, Q as resolveRecursive, R as parseAgentArg, T as getGitHubDisplayName, U as getGitHubSkillName, V as formatGitHubSpecifier, W as isGitHubShorthand, X as parseGitHubUrl, Y as parseGitHubSpecifier, Z as parseRegistrySpecifier, a as getRegistrySkillPath, c as addGitHubToLockfile, d as addToLockfileWithDeps, et as resolveVersion, f as addWellKnownToLockfile, i as getLocalSkillPath, k as addWellKnownDependency, l as addLocalToLockfile, n as getGitHubSkillPath, nt as printResolutionErrors, o as getWellKnownSkillPath, ot as calculateIntegrity, q as isRegistrySpecifier, t as createAgentSymlinks, w as extractGitHubPackage, z as promptForAgents } from "./symlinks-BTw8X0GG.js";
4
+ import { basename, dirname, join, relative, resolve } from "node:path";
5
+ import { mkdir, rm, stat, symlink, writeFile } from "node:fs/promises";
6
+ import { homedir } from "node:os";
7
+ //#region src/wellknown.ts
8
+ /**
9
+ * Well-Known Skills Discovery (RFC 8615)
10
+ *
11
+ * Fetches skills from any HTTPS domain that serves a
12
+ * /.well-known/skills/index.json endpoint.
13
+ */
14
+ const WELL_KNOWN_PATH = ".well-known/skills";
15
+ const INDEX_FILE = "index.json";
16
+ const SKILL_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$/;
17
+ /** Hosts that should NOT be treated as well-known (they have dedicated providers) */
18
+ const EXCLUDED_HOSTS = [
19
+ "github.com",
20
+ "gitlab.com",
21
+ "raw.githubusercontent.com"
22
+ ];
23
+ /**
24
+ * Check if a string looks like a well-known skills URL.
25
+ * Must be HTTP(S) and not a known git host.
26
+ */
27
+ function isWellKnownSpecifier(input) {
28
+ if (!input.startsWith("http://") && !input.startsWith("https://")) return false;
29
+ try {
30
+ const parsed = new URL(input);
31
+ if (EXCLUDED_HOSTS.includes(parsed.hostname)) return false;
32
+ if (input.endsWith(".git")) return false;
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Extract hostname from a well-known URL (strips www. prefix).
40
+ */
41
+ function getWellKnownHostname(url) {
42
+ try {
43
+ return new URL(url).hostname.replace(/^www\./, "");
44
+ } catch {
45
+ return url;
46
+ }
47
+ }
48
+ /**
49
+ * Validate a single skill entry from the index.
50
+ */
51
+ function isValidSkillEntry(entry) {
52
+ if (!entry || typeof entry !== "object") return false;
53
+ const e = entry;
54
+ if (typeof e.name !== "string" || e.name.length === 0) return false;
55
+ if (typeof e.description !== "string" || e.description.length === 0) return false;
56
+ if (!SKILL_NAME_PATTERN.test(e.name)) return false;
57
+ if (!Array.isArray(e.files) || e.files.length === 0) return false;
58
+ let hasSkillMd = false;
59
+ for (const file of e.files) {
60
+ if (typeof file !== "string") return false;
61
+ if (file.startsWith("/") || file.startsWith("\\")) return false;
62
+ if (file.includes("..")) return false;
63
+ if (file.toLowerCase() === "skill.md") hasSkillMd = true;
64
+ }
65
+ if (!hasSkillMd) return false;
66
+ return true;
67
+ }
68
+ /**
69
+ * Validate a well-known index structure.
70
+ */
71
+ function isValidIndex(data) {
72
+ if (!data || typeof data !== "object") return false;
73
+ const d = data;
74
+ if (!Array.isArray(d.skills)) return false;
75
+ return d.skills.every(isValidSkillEntry);
76
+ }
77
+ /**
78
+ * Fetch the well-known index from a URL.
79
+ *
80
+ * Tries path-relative first, then root:
81
+ * 1. {baseUrl}/.well-known/skills/index.json
82
+ * 2. {protocol}://{host}/.well-known/skills/index.json
83
+ */
84
+ async function fetchWellKnownIndex(baseUrl) {
85
+ const parsed = new URL(baseUrl);
86
+ const pathRelativeUrl = `${baseUrl.replace(/\/$/, "")}/${WELL_KNOWN_PATH}/${INDEX_FILE}`;
87
+ const pathRelativeBase = `${baseUrl.replace(/\/$/, "")}/${WELL_KNOWN_PATH}`;
88
+ try {
89
+ const response = await fetch(pathRelativeUrl, { signal: AbortSignal.timeout(1e4) });
90
+ if (response.ok) {
91
+ const data = await response.json();
92
+ if (isValidIndex(data)) return {
93
+ index: data,
94
+ resolvedBaseUrl: pathRelativeBase
95
+ };
96
+ }
97
+ } catch {}
98
+ const rootUrl = `${parsed.protocol}//${parsed.host}/${WELL_KNOWN_PATH}/${INDEX_FILE}`;
99
+ const rootBase = `${parsed.protocol}//${parsed.host}/${WELL_KNOWN_PATH}`;
100
+ if (rootUrl !== pathRelativeUrl) try {
101
+ const response = await fetch(rootUrl, { signal: AbortSignal.timeout(1e4) });
102
+ if (response.ok) {
103
+ const data = await response.json();
104
+ if (isValidIndex(data)) return {
105
+ index: data,
106
+ resolvedBaseUrl: rootBase
107
+ };
108
+ }
109
+ } catch {}
110
+ return null;
111
+ }
112
+ /**
113
+ * Fetch a single skill's files from a well-known endpoint.
114
+ */
115
+ async function fetchSkillFiles(baseUrl, entry) {
116
+ const skillBaseUrl = `${baseUrl}/${entry.name}`;
117
+ const files = /* @__PURE__ */ new Map();
118
+ let skillMdContent = "";
119
+ const results = await Promise.allSettled(entry.files.map(async (filePath) => {
120
+ const fileUrl = `${skillBaseUrl}/${filePath}`;
121
+ const response = await fetch(fileUrl, { signal: AbortSignal.timeout(1e4) });
122
+ if (!response.ok) throw new Error(`Failed to fetch ${fileUrl}: ${response.status}`);
123
+ return {
124
+ filePath,
125
+ content: await response.text()
126
+ };
127
+ }));
128
+ for (const result of results) if (result.status === "fulfilled") {
129
+ files.set(result.value.filePath, result.value.content);
130
+ if (result.value.filePath.toLowerCase() === "skill.md") skillMdContent = result.value.content;
131
+ }
132
+ if (!skillMdContent) return null;
133
+ return {
134
+ name: entry.name,
135
+ description: entry.description,
136
+ content: skillMdContent,
137
+ files,
138
+ sourceUrl: `${skillBaseUrl}/SKILL.md`,
139
+ indexEntry: entry
140
+ };
141
+ }
142
+ /**
143
+ * Fetch all skills from a well-known endpoint.
144
+ */
145
+ async function fetchWellKnownSkills(url) {
146
+ const result = await fetchWellKnownIndex(url);
147
+ if (!result) return null;
148
+ const { index, resolvedBaseUrl } = result;
149
+ const hostname = getWellKnownHostname(url);
150
+ const skillResults = await Promise.allSettled(index.skills.map((entry) => fetchSkillFiles(resolvedBaseUrl, entry)));
151
+ const skills = [];
152
+ for (const r of skillResults) if (r.status === "fulfilled" && r.value) skills.push(r.value);
153
+ if (skills.length === 0) return null;
154
+ return {
155
+ skills,
156
+ resolvedBaseUrl,
157
+ hostname
158
+ };
159
+ }
160
+ /**
161
+ * Extract a well-known skill to the .pspm/skills/_wellknown/ directory.
162
+ *
163
+ * @param skill - The fetched skill
164
+ * @param hostname - Source hostname (for directory namespacing)
165
+ * @param skillsDir - Base skills directory (.pspm/skills)
166
+ * @returns Path to extracted skill (relative to project root)
167
+ */
168
+ async function extractWellKnownSkill(skill, hostname, skillsDir) {
169
+ const destPath = join(skillsDir, "_wellknown", hostname, skill.name);
170
+ await rm(destPath, {
171
+ recursive: true,
172
+ force: true
173
+ });
174
+ await mkdir(destPath, { recursive: true });
175
+ for (const [filePath, content] of skill.files) {
176
+ const fullPath = join(destPath, filePath);
177
+ if (!fullPath.startsWith(destPath)) continue;
178
+ const { dirname } = await import("node:path");
179
+ await mkdir(dirname(fullPath), { recursive: true });
180
+ await writeFile(fullPath, content, "utf-8");
181
+ }
182
+ return `.pspm/skills/_wellknown/${hostname}/${skill.name}`;
183
+ }
184
+ /**
185
+ * Calculate integrity hash for a well-known skill (hash of all file contents).
186
+ */
187
+ function calculateWellKnownIntegrity(skill) {
188
+ const combined = [...skill.files.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([path, content]) => `${path}:${content}`).join("\n");
189
+ return calculateIntegrity(Buffer.from(combined, "utf-8"));
190
+ }
191
+ /**
192
+ * Get display name for a well-known skill.
193
+ */
194
+ function getWellKnownDisplayName(hostname, skillName) {
195
+ return `${hostname}/${skillName} (well-known)`;
196
+ }
197
+ //#endregion
198
+ //#region src/commands/add-helpers.ts
199
+ /**
200
+ * Check if a specifier is a local file reference.
201
+ */
202
+ function isLocalSpecifier(specifier) {
203
+ return specifier.startsWith("file:") || specifier.startsWith("./") || specifier.startsWith("../");
204
+ }
205
+ /**
206
+ * Parse a local specifier and return the path.
207
+ */
208
+ function parseLocalPath(specifier) {
209
+ if (specifier.startsWith("file:")) return specifier.slice(5);
210
+ return specifier;
211
+ }
212
+ /**
213
+ * Normalize a path to a file: specifier.
214
+ */
215
+ function normalizeToFileSpecifier(path) {
216
+ if (path.startsWith("file:")) return path;
217
+ return `file:${path}`;
218
+ }
219
+ /**
220
+ * Get the root directory for symlink creation.
221
+ * Global: home directory. Project: current working directory.
222
+ */
223
+ function getSymlinkRoot() {
224
+ return isGlobalMode() ? homedir() : process.cwd();
225
+ }
226
+ /**
227
+ * Parse any GitHub input format into a GitHubSpecifier.
228
+ *
229
+ * Accepts:
230
+ * - github:owner/repo[/path][@ref] (canonical prefix format)
231
+ * - https://github.com/owner/repo (full URL)
232
+ * - https://github.com/owner/repo/tree/branch/path (tree URL)
233
+ * - owner/repo[/path] (shorthand)
234
+ */
235
+ function parseAnyGitHubFormat(specifier) {
236
+ if (isGitHubSpecifier(specifier)) return parseGitHubSpecifier(specifier);
237
+ if (isGitHubUrl(specifier)) return parseGitHubUrl(specifier);
238
+ if (isGitHubShorthand(specifier)) return parseGitHubShorthand(specifier);
239
+ return null;
240
+ }
241
+ //#endregion
242
+ //#region src/commands/add-github.ts
243
+ /**
244
+ * Validate and download a GitHub package.
245
+ */
246
+ async function validateGitHubPackage(specifier) {
247
+ const parsed = parseAnyGitHubFormat(specifier);
248
+ if (!parsed) throw new Error(`Invalid GitHub specifier "${specifier}". Supported formats:\n github:owner/repo[/path][@ref]\n https://github.com/owner/repo[/tree/branch/path]\n owner/repo[/path]`);
249
+ const ref = parsed.ref || "HEAD";
250
+ console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
251
+ const result = await downloadGitHubPackage(parsed);
252
+ console.log(`Resolved ${specifier} (${ref}@${result.commit.slice(0, 7)})`);
253
+ return {
254
+ type: "github",
255
+ specifier,
256
+ parsed,
257
+ ref,
258
+ downloadResult: result
259
+ };
260
+ }
261
+ /**
262
+ * Install a pre-validated GitHub package.
263
+ */
264
+ async function installGitHubPackage(resolved, options) {
265
+ const { specifier, parsed, ref, downloadResult } = resolved;
266
+ console.log(`Installing ${specifier} (${ref}@${downloadResult.commit.slice(0, 7)})...`);
267
+ const skillsDir = getSkillsDir();
268
+ const destPath = await extractGitHubPackage(parsed, downloadResult.buffer, skillsDir);
269
+ const lockfileSpecifier = formatGitHubSpecifier({
270
+ owner: parsed.owner,
271
+ repo: parsed.repo,
272
+ path: parsed.path
273
+ });
274
+ await addGitHubToLockfile(lockfileSpecifier, {
275
+ version: downloadResult.commit.slice(0, 7),
276
+ resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
277
+ integrity: downloadResult.integrity,
278
+ gitCommit: downloadResult.commit,
279
+ gitRef: ref
280
+ });
281
+ await addGitHubDependency(lockfileSpecifier, ref);
282
+ const agents = options.resolvedAgents;
283
+ if (agents[0] !== "none") {
284
+ const manifest = await readManifest();
285
+ await createAgentSymlinks([{
286
+ name: getGitHubSkillName(parsed),
287
+ sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
288
+ }], {
289
+ agents,
290
+ projectRoot: getSymlinkRoot(),
291
+ agentConfigs: manifest?.agents,
292
+ global: isGlobalMode()
293
+ });
294
+ }
295
+ console.log(`Installed ${specifier} (${ref}@${downloadResult.commit.slice(0, 7)})`);
296
+ console.log(`Location: ${destPath}`);
297
+ }
298
+ //#endregion
299
+ //#region src/commands/add-local.ts
300
+ /**
301
+ * Validate a local package path exists and contains a valid skill.
302
+ */
303
+ async function validateLocalPackage(specifier) {
304
+ const path = parseLocalPath(specifier);
305
+ const resolvedPath = resolve(process.cwd(), path);
306
+ const normalizedSpecifier = normalizeToFileSpecifier(path);
307
+ console.log(`Resolving ${specifier}...`);
308
+ try {
309
+ if (!(await stat(resolvedPath)).isDirectory()) throw new Error(`Path is not a directory: ${resolvedPath}`);
310
+ } catch (error) {
311
+ if (error.code === "ENOENT") throw new Error(`Directory not found: ${resolvedPath}\n Check that the path exists and is accessible.`);
312
+ throw error;
313
+ }
314
+ let hasSkillMd = false;
315
+ let hasPspmJson = false;
316
+ try {
317
+ await stat(join(resolvedPath, "SKILL.md"));
318
+ hasSkillMd = true;
319
+ } catch {}
320
+ try {
321
+ await stat(join(resolvedPath, "pspm.json"));
322
+ hasPspmJson = true;
323
+ } catch {}
324
+ if (!hasSkillMd && !hasPspmJson) throw new Error(`Not a valid skill directory: ${resolvedPath}\n Missing both SKILL.md and pspm.json. At least one is required.`);
325
+ const name = basename(resolvedPath);
326
+ console.log(`Resolved ${specifier} -> ${resolvedPath}`);
327
+ return {
328
+ type: "local",
329
+ specifier,
330
+ normalizedSpecifier,
331
+ path,
332
+ resolvedPath,
333
+ name
334
+ };
335
+ }
336
+ /**
337
+ * Install a local package by creating a symlink.
338
+ */
339
+ async function installLocalPackage(resolved, options) {
340
+ const { specifier, normalizedSpecifier, path, resolvedPath, name } = resolved;
341
+ console.log(`Installing ${specifier}...`);
342
+ const localSkillsDir = join(getSkillsDir(), "_local");
343
+ await mkdir(localSkillsDir, { recursive: true });
344
+ const symlinkPath = join(localSkillsDir, name);
345
+ const relativeTarget = relative(dirname(symlinkPath), resolvedPath);
346
+ try {
347
+ await rm(symlinkPath, { force: true });
348
+ } catch {}
349
+ await symlink(relativeTarget, symlinkPath);
350
+ await addLocalToLockfile(normalizedSpecifier, {
351
+ version: "local",
352
+ path,
353
+ resolvedPath,
354
+ name
355
+ });
356
+ await addLocalDependency(normalizedSpecifier);
357
+ const agents = options.resolvedAgents;
358
+ if (agents[0] !== "none") {
359
+ const manifest = await readManifest();
360
+ await createAgentSymlinks([{
361
+ name,
362
+ sourcePath: getLocalSkillPath(name)
363
+ }], {
364
+ agents,
365
+ projectRoot: getSymlinkRoot(),
366
+ agentConfigs: manifest?.agents,
367
+ global: isGlobalMode()
368
+ });
369
+ }
370
+ console.log(`Installed ${specifier} (local)`);
371
+ console.log(`Location: ${symlinkPath} -> ${resolvedPath}`);
372
+ }
373
+ //#endregion
374
+ //#region src/commands/add-registry.ts
375
+ /**
376
+ * Validate and resolve a registry package (without downloading).
377
+ */
378
+ async function validateRegistryPackage(specifier) {
379
+ const config = await resolveConfig();
380
+ const registryUrl = config.registryUrl;
381
+ const apiKey = getTokenForRegistry(config, registryUrl);
382
+ const parsed = parseRegistrySpecifier(specifier);
383
+ if (!parsed) throw new Error(`Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}[@{version}] or @org/{orgname}/{name}[@{version}]`);
384
+ const { namespace, owner, name, subname, versionRange } = parsed;
385
+ const fullName = generateRegistryIdentifier({
386
+ namespace,
387
+ owner,
388
+ name,
389
+ subname
390
+ });
391
+ configure({
392
+ registryUrl,
393
+ apiKey
394
+ });
395
+ console.log(`Resolving ${specifier}...`);
396
+ const versionStrings = (await fetchRegistryVersions(namespace, owner, name, subname, fullName, apiKey)).map((v) => v.version);
397
+ const resolvedVersion = resolveVersion(versionRange || "*", versionStrings);
398
+ if (!resolvedVersion) throw new Error(`No version matching "${versionRange || "latest"}" found for ${fullName}. Available versions: ${versionStrings.join(", ")}`);
399
+ const { downloadUrl, checksum } = await fetchRegistryVersionInfo(namespace, owner, name, subname, resolvedVersion, fullName);
400
+ console.log(`Resolved ${fullName}@${resolvedVersion}`);
401
+ return {
402
+ type: "registry",
403
+ specifier,
404
+ namespace,
405
+ owner,
406
+ name,
407
+ subname,
408
+ versionRange,
409
+ resolvedVersion,
410
+ versionInfo: {
411
+ downloadUrl,
412
+ checksum
413
+ }
414
+ };
415
+ }
416
+ async function fetchRegistryVersions(namespace, owner, name, subname, fullName, apiKey) {
417
+ if (namespace === "github" && subname) {
418
+ const versionsResponse = await listGithubSkillVersions(owner, name, subname);
419
+ if (versionsResponse.status !== 200 || !versionsResponse.data) {
420
+ if (versionsResponse.status === 401) throw new Error(apiKey ? `Access denied to ${fullName}. You may not have permission to access this private package.` : `Package ${fullName} requires authentication. Please run 'pspm login' to authenticate`);
421
+ throw new Error(versionsResponse.error || `Skill ${fullName} not found`);
422
+ }
423
+ return versionsResponse.data;
424
+ }
425
+ const versionsResponse = await listSkillVersions(owner, name);
426
+ if (versionsResponse.status !== 200) {
427
+ if (versionsResponse.status === 401) {
428
+ if (!apiKey) throw new Error(`Package ${fullName} requires authentication. Please run 'pspm login' to authenticate`);
429
+ throw new Error(`Access denied to ${fullName}. You may not have permission to access this private package.`);
430
+ }
431
+ const errorMessage = extractApiErrorMessage(versionsResponse, `Skill ${fullName} not found`);
432
+ throw new Error(errorMessage);
433
+ }
434
+ if (versionsResponse.data.length === 0) throw new Error(`Skill ${fullName} not found`);
435
+ return versionsResponse.data;
436
+ }
437
+ async function fetchRegistryVersionInfo(namespace, owner, name, subname, resolvedVersion, fullName) {
438
+ if (namespace === "github" && subname) {
439
+ const versionResponse = await getGithubSkillVersion(owner, name, subname, resolvedVersion);
440
+ if (versionResponse.status !== 200 || !versionResponse.data) throw new Error(`Version ${resolvedVersion} not found for ${fullName}`);
441
+ return {
442
+ downloadUrl: versionResponse.data.downloadUrl,
443
+ checksum: versionResponse.data.checksum
444
+ };
445
+ }
446
+ const versionResponse = await getSkillVersion(owner, name, resolvedVersion);
447
+ if (versionResponse.status !== 200 || !versionResponse.data) {
448
+ const errorMessage = extractApiErrorMessage(versionResponse, `Version ${resolvedVersion} not found`);
449
+ throw new Error(errorMessage);
450
+ }
451
+ return {
452
+ downloadUrl: versionResponse.data.downloadUrl,
453
+ checksum: versionResponse.data.checksum
454
+ };
455
+ }
456
+ /**
457
+ * Install a package from a DependencyNode (resolved from resolver).
458
+ */
459
+ async function installFromNode(node, options) {
460
+ const parsed = parseRegistrySpecifier(node.name);
461
+ if (!parsed) throw new Error(`Invalid package name: ${node.name}`);
462
+ const { namespace, owner, name, subname } = parsed;
463
+ console.log(`Installing ${node.name}@${node.version}...`);
464
+ const config = await resolveConfig();
465
+ const tarballBuffer = await downloadNodeTarball(node, getTokenForRegistry(config, config.registryUrl));
466
+ const integrity = calculateIntegrity(tarballBuffer);
467
+ if (integrity !== node.integrity) throw new Error("Checksum verification failed");
468
+ const skillsDir = getSkillsDir();
469
+ const effectiveSkillName = subname ?? name;
470
+ const destDir = resolveRegistryDestDir(namespace, owner, name, subname, effectiveSkillName, skillsDir);
471
+ await extractRegistryTarball(destDir, tarballBuffer);
472
+ const resolvedDeps = {};
473
+ for (const [depName, range] of Object.entries(node.dependencies)) resolvedDeps[depName] = range;
474
+ await addToLockfileWithDeps(node.name, {
475
+ version: node.version,
476
+ resolved: node.downloadUrl,
477
+ integrity,
478
+ deprecated: node.deprecated
479
+ }, Object.keys(resolvedDeps).length > 0 ? resolvedDeps : void 0);
480
+ if (options.isDirect) {
481
+ const dependencyRange = node.versionRange || `^${node.version}`;
482
+ await addDependency(node.name, dependencyRange);
483
+ }
484
+ const agents = options.resolvedAgents;
485
+ if (agents[0] !== "none") {
486
+ const skillManifest = await readManifest();
487
+ await createAgentSymlinks([{
488
+ name: effectiveSkillName,
489
+ sourcePath: getRegistrySkillPath(namespace, owner, namespace === "github" && subname ? `${name}/${subname}` : name)
490
+ }], {
491
+ agents,
492
+ projectRoot: getSymlinkRoot(),
493
+ agentConfigs: skillManifest?.agents,
494
+ global: isGlobalMode()
495
+ });
496
+ }
497
+ console.log(`Installed ${node.name}@${node.version}`);
498
+ console.log(`Location: ${destDir}`);
499
+ }
500
+ async function downloadNodeTarball(node, apiKey) {
501
+ const isPresignedUrl = node.downloadUrl.includes(".r2.cloudflarestorage.com") || node.downloadUrl.includes("X-Amz-Signature");
502
+ const downloadHeaders = {};
503
+ if (!isPresignedUrl && apiKey) downloadHeaders.Authorization = `Bearer ${apiKey}`;
504
+ const tarballResponse = await fetch(node.downloadUrl, {
505
+ headers: downloadHeaders,
506
+ redirect: "follow"
507
+ });
508
+ if (!tarballResponse.ok) throw new Error(`Failed to download tarball (${tarballResponse.status})`);
509
+ return Buffer.from(await tarballResponse.arrayBuffer());
510
+ }
511
+ function resolveRegistryDestDir(namespace, owner, name, subname, effectiveSkillName, skillsDir) {
512
+ if (namespace === "org") return join(skillsDir, "_org", owner, effectiveSkillName);
513
+ if (namespace === "github" && subname) return join(skillsDir, "_github-registry", owner, name, subname);
514
+ return join(skillsDir, owner, effectiveSkillName);
515
+ }
516
+ async function extractRegistryTarball(destDir, tarballBuffer) {
517
+ await rm(destDir, {
518
+ recursive: true,
519
+ force: true
520
+ });
521
+ await mkdir(destDir, { recursive: true });
522
+ const { writeFile } = await import("node:fs/promises");
523
+ const tempFile = join(destDir, ".temp.tgz");
524
+ await writeFile(tempFile, tarballBuffer);
525
+ const { exec } = await import("node:child_process");
526
+ const { promisify } = await import("node:util");
527
+ const execAsync = promisify(exec);
528
+ try {
529
+ await execAsync(`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`);
530
+ } finally {
531
+ await rm(tempFile, { force: true });
532
+ }
533
+ }
534
+ //#endregion
535
+ //#region src/commands/add-wellknown.ts
536
+ /**
537
+ * Validate and fetch skills from a well-known endpoint.
538
+ */
539
+ async function validateWellKnownPackage(specifier) {
540
+ const hostname = getWellKnownHostname(specifier);
541
+ console.log(`Discovering skills from ${hostname}...`);
542
+ const result = await fetchWellKnownSkills(specifier);
543
+ if (!result) throw new Error(`No well-known skills found at ${specifier}\n Expected: ${specifier}/.well-known/skills/index.json`);
544
+ console.log(`Found ${result.skills.length} skill(s) from ${hostname}: ${result.skills.map((s) => s.name).join(", ")}`);
545
+ return {
546
+ type: "wellknown",
547
+ specifier,
548
+ hostname: result.hostname,
549
+ skills: result.skills,
550
+ resolvedBaseUrl: result.resolvedBaseUrl
551
+ };
552
+ }
553
+ /**
554
+ * Install pre-validated well-known skills.
555
+ */
556
+ async function installWellKnownPackage(resolved, options) {
557
+ const { specifier, hostname, skills } = resolved;
558
+ const skillsDir = getSkillsDir();
559
+ for (const skill of skills) {
560
+ console.log(`Installing ${getWellKnownDisplayName(hostname, skill.name)}...`);
561
+ const destPath = await extractWellKnownSkill(skill, hostname, skillsDir);
562
+ const integrity = calculateWellKnownIntegrity(skill);
563
+ await addWellKnownToLockfile(`${specifier}#${skill.name}`, {
564
+ version: "well-known",
565
+ resolved: skill.sourceUrl,
566
+ integrity,
567
+ hostname,
568
+ name: skill.name,
569
+ files: [...skill.files.keys()]
570
+ });
571
+ await addWellKnownDependency(specifier, [skill.name]);
572
+ const agents = options.resolvedAgents;
573
+ if (agents[0] !== "none") {
574
+ const manifest = await readManifest();
575
+ await createAgentSymlinks([{
576
+ name: skill.name,
577
+ sourcePath: getWellKnownSkillPath(hostname, skill.name)
578
+ }], {
579
+ agents,
580
+ projectRoot: process.cwd(),
581
+ agentConfigs: manifest?.agents
582
+ });
583
+ }
584
+ console.log(`Installed ${getWellKnownDisplayName(hostname, skill.name)}`);
585
+ console.log(`Location: ${destPath}`);
586
+ }
587
+ }
588
+ //#endregion
589
+ //#region src/commands/add.ts
590
+ async function add(specifiers, options) {
591
+ if (options.global) {
592
+ setGlobalMode(true);
593
+ console.log("Installing globally to ~/.pspm/\n");
594
+ }
595
+ console.log("Resolving packages...\n");
596
+ const { resolvedPackages, validationErrors } = await validateAllPackages(specifiers);
597
+ if (resolvedPackages.length === 0) {
598
+ console.error("No packages could be resolved.");
599
+ process.exit(1);
600
+ }
601
+ if (validationErrors.length > 0) console.log(`Resolved ${resolvedPackages.length} of ${specifiers.length} packages.\n`);
602
+ const config = await resolveConfig();
603
+ const apiKey = getTokenForRegistry(config, config.registryUrl);
604
+ const registryPackages = resolvedPackages.filter((p) => p.type === "registry");
605
+ const githubPackages = resolvedPackages.filter((p) => p.type === "github");
606
+ const localPackages = resolvedPackages.filter((p) => p.type === "local");
607
+ const wellKnownPackages = resolvedPackages.filter((p) => p.type === "wellknown");
608
+ let resolutionResult = null;
609
+ if (registryPackages.length > 0) {
610
+ const rootDeps = {};
611
+ for (const pkg of registryPackages) {
612
+ const fullName = generateRegistryIdentifier({
613
+ namespace: pkg.namespace,
614
+ owner: pkg.owner,
615
+ name: pkg.name,
616
+ subname: pkg.subname
617
+ });
618
+ rootDeps[fullName] = pkg.versionRange || `^${pkg.resolvedVersion}`;
619
+ }
620
+ console.log("Resolving dependencies...");
621
+ resolutionResult = await resolveRecursive(rootDeps, {
622
+ maxDepth: 5,
623
+ registryUrl: config.registryUrl,
624
+ apiKey
625
+ });
626
+ if (!resolutionResult.success) {
627
+ printResolutionErrors(resolutionResult.graph.errors, resolutionResult.graph.conflicts);
628
+ process.exit(1);
629
+ }
630
+ const transitiveDeps = resolutionResult.installOrder.filter((name) => !rootDeps[name]);
631
+ if (transitiveDeps.length > 0) console.log(`Resolved ${transitiveDeps.length} transitive dependencies.\n`);
632
+ else console.log();
633
+ }
634
+ const agents = await resolveAgents(options);
635
+ const results = [];
636
+ if (resolutionResult) for (const name of resolutionResult.installOrder) {
637
+ const node = resolutionResult.graph.nodes.get(name);
638
+ if (!node) continue;
639
+ try {
640
+ await installFromNode(node, {
641
+ ...options,
642
+ resolvedAgents: agents,
643
+ isDirect: node.isDirect
644
+ });
645
+ results.push({
646
+ specifier: name,
647
+ success: true
648
+ });
649
+ } catch (error) {
650
+ const message = error instanceof Error ? error.message : "Unknown error";
651
+ results.push({
652
+ specifier: name,
653
+ success: false,
654
+ error: message
655
+ });
656
+ console.error(`Failed to install ${name}: ${message}\n`);
657
+ }
658
+ }
659
+ for (const resolved of githubPackages) try {
660
+ await installGitHubPackage(resolved, {
661
+ ...options,
662
+ resolvedAgents: agents
663
+ });
664
+ results.push({
665
+ specifier: resolved.specifier,
666
+ success: true
667
+ });
668
+ } catch (error) {
669
+ const message = error instanceof Error ? error.message : "Unknown error";
670
+ results.push({
671
+ specifier: resolved.specifier,
672
+ success: false,
673
+ error: message
674
+ });
675
+ console.error(`Failed to install ${resolved.specifier}: ${message}\n`);
676
+ }
677
+ for (const resolved of localPackages) try {
678
+ await installLocalPackage(resolved, {
679
+ ...options,
680
+ resolvedAgents: agents
681
+ });
682
+ results.push({
683
+ specifier: resolved.specifier,
684
+ success: true
685
+ });
686
+ } catch (error) {
687
+ const message = error instanceof Error ? error.message : "Unknown error";
688
+ results.push({
689
+ specifier: resolved.specifier,
690
+ success: false,
691
+ error: message
692
+ });
693
+ console.error(`Failed to install ${resolved.specifier}: ${message}\n`);
694
+ }
695
+ for (const resolved of wellKnownPackages) try {
696
+ await installWellKnownPackage(resolved, {
697
+ ...options,
698
+ resolvedAgents: agents
699
+ });
700
+ results.push({
701
+ specifier: resolved.specifier,
702
+ success: true
703
+ });
704
+ } catch (error) {
705
+ const message = error instanceof Error ? error.message : "Unknown error";
706
+ results.push({
707
+ specifier: resolved.specifier,
708
+ success: false,
709
+ error: message
710
+ });
711
+ console.error(`Failed to install ${resolved.specifier}: ${message}\n`);
712
+ }
713
+ if (specifiers.length > 1) {
714
+ const succeeded = results.filter((r) => r.success).length;
715
+ const failed = results.filter((r) => !r.success).length + validationErrors.length;
716
+ console.log(`\nSummary: ${succeeded} added, ${failed} failed`);
717
+ if (failed > 0) process.exit(1);
718
+ }
719
+ }
720
+ async function validateAllPackages(specifiers) {
721
+ const resolvedPackages = [];
722
+ const validationErrors = [];
723
+ for (const specifier of specifiers) try {
724
+ if (isLocalSpecifier(specifier)) resolvedPackages.push(await validateLocalPackage(specifier));
725
+ else if (isGitHubSpecifier(specifier) || isGitHubUrl(specifier) || isGitHubShorthand(specifier)) resolvedPackages.push(await validateGitHubPackage(specifier));
726
+ else if (isWellKnownSpecifier(specifier)) resolvedPackages.push(await validateWellKnownPackage(specifier));
727
+ else if (isRegistrySpecifier(specifier)) resolvedPackages.push(await validateRegistryPackage(specifier));
728
+ else throw new Error(`Unknown specifier format "${specifier}". Supported formats:\n @user/{username}/{name}[@version] (registry)\n @org/{orgname}/{name}[@version] (organization)\n github:owner/repo[/path][@ref] (github)\n file:./path/to/skill (local)\n owner/repo (github shorthand)`);
729
+ } catch (error) {
730
+ const message = error instanceof Error ? error.message : "Unknown error";
731
+ validationErrors.push({
732
+ specifier,
733
+ error: message
734
+ });
735
+ console.error(`Failed to resolve ${specifier}: ${message}\n`);
736
+ }
737
+ return {
738
+ resolvedPackages,
739
+ validationErrors
740
+ };
741
+ }
742
+ async function resolveAgents(options) {
743
+ const manifest = await readManifest();
744
+ if (options.agent) return parseAgentArg(options.agent);
745
+ if (manifest) return parseAgentArg(void 0);
746
+ if (options.yes) return parseAgentArg(void 0);
747
+ console.log("No pspm.json found. Let's set up your project.\n");
748
+ const agents = await promptForAgents();
749
+ console.log();
750
+ return agents;
751
+ }
752
+ //#endregion
753
+ export { add as t };
754
+
755
+ //# sourceMappingURL=add-CcgUlOLa.js.map