@anytio/pspm 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +149 -0
- package/README.md +72 -13
- package/dist/index.js +2164 -252
- package/dist/index.js.map +1 -1
- package/package.json +67 -67
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from 'fs';
|
|
3
|
-
import { dirname, join, relative } from 'path';
|
|
4
|
-
import { fileURLToPath, URL } from 'url';
|
|
3
|
+
import { dirname, join, basename, relative } from 'path';
|
|
4
|
+
import { fileURLToPath, URL as URL$1 } from 'url';
|
|
5
5
|
import { Command } from 'commander';
|
|
6
|
-
import { stat, writeFile, mkdir, rm, access, readFile, readdir, unlink } from 'fs/promises';
|
|
7
6
|
import { createHash, randomBytes } from 'crypto';
|
|
8
7
|
import * as semver from 'semver';
|
|
8
|
+
import { stat, writeFile, readdir, mkdir, rm, rename, access as access$1, readFile, lstat, cp, unlink, readlink, symlink } from 'fs/promises';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import * as ini from 'ini';
|
|
11
|
+
import { checkbox } from '@inquirer/prompts';
|
|
12
|
+
import { createInterface } from 'readline';
|
|
11
13
|
import http from 'http';
|
|
12
14
|
import open from 'open';
|
|
13
15
|
import { exec as exec$1 } from 'child_process';
|
|
@@ -18,6 +20,36 @@ function calculateIntegrity(data) {
|
|
|
18
20
|
return `sha256-${hash}`;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
// ../../packages/shared/pspm-types/src/manifest.ts
|
|
24
|
+
var DEFAULT_SKILL_FILES = [
|
|
25
|
+
"SKILL.md",
|
|
26
|
+
"runtime",
|
|
27
|
+
"scripts",
|
|
28
|
+
"data"
|
|
29
|
+
];
|
|
30
|
+
var PSPM_SCHEMA_URL = "https://pspm.dev/schema/v1/pspm.json";
|
|
31
|
+
function validateManifest(manifest) {
|
|
32
|
+
if (!manifest.name) {
|
|
33
|
+
return { valid: false, error: "Manifest must have a 'name' field" };
|
|
34
|
+
}
|
|
35
|
+
if (!manifest.version) {
|
|
36
|
+
return { valid: false, error: "Manifest must have a 'version' field" };
|
|
37
|
+
}
|
|
38
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(manifest.name)) {
|
|
39
|
+
return {
|
|
40
|
+
valid: false,
|
|
41
|
+
error: "Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (!/^\d+\.\d+\.\d+/.test(manifest.version)) {
|
|
45
|
+
return {
|
|
46
|
+
valid: false,
|
|
47
|
+
error: "Version must be a valid semantic version (e.g., 1.0.0)"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { valid: true };
|
|
51
|
+
}
|
|
52
|
+
|
|
21
53
|
// ../../packages/shared/pspm-types/src/specifier.ts
|
|
22
54
|
var SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
|
|
23
55
|
function parseSkillSpecifier(specifier) {
|
|
@@ -31,6 +63,41 @@ function parseSkillSpecifier(specifier) {
|
|
|
31
63
|
versionRange: match[3]
|
|
32
64
|
};
|
|
33
65
|
}
|
|
66
|
+
var GITHUB_SPECIFIER_PATTERN = /^github:([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)(\/[^@]+)?(?:@(.+))?$/;
|
|
67
|
+
function parseGitHubSpecifier(specifier) {
|
|
68
|
+
const match = specifier.match(GITHUB_SPECIFIER_PATTERN);
|
|
69
|
+
if (!match) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const [, owner, repo, pathWithSlash, ref] = match;
|
|
73
|
+
return {
|
|
74
|
+
owner,
|
|
75
|
+
repo,
|
|
76
|
+
// Remove leading slash from path
|
|
77
|
+
path: pathWithSlash ? pathWithSlash.slice(1) : void 0,
|
|
78
|
+
ref: ref || void 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function formatGitHubSpecifier(spec) {
|
|
82
|
+
let result = `github:${spec.owner}/${spec.repo}`;
|
|
83
|
+
if (spec.path) {
|
|
84
|
+
result += `/${spec.path}`;
|
|
85
|
+
}
|
|
86
|
+
if (spec.ref) {
|
|
87
|
+
result += `@${spec.ref}`;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function getGitHubSkillName(spec) {
|
|
92
|
+
if (spec.path) {
|
|
93
|
+
const segments = spec.path.split("/").filter(Boolean);
|
|
94
|
+
return segments[segments.length - 1];
|
|
95
|
+
}
|
|
96
|
+
return spec.repo;
|
|
97
|
+
}
|
|
98
|
+
function isGitHubSpecifier(specifier) {
|
|
99
|
+
return specifier.startsWith("github:");
|
|
100
|
+
}
|
|
34
101
|
function resolveVersion(range, availableVersions) {
|
|
35
102
|
const sorted = availableVersions.filter((v) => semver.valid(v)).sort((a, b) => semver.rcompare(a, b));
|
|
36
103
|
if (!range || range === "latest" || range === "*") {
|
|
@@ -53,13 +120,16 @@ function getConfig() {
|
|
|
53
120
|
async function customFetch(url, options) {
|
|
54
121
|
const { baseUrl, apiKey } = getConfig();
|
|
55
122
|
const fullUrl = `${baseUrl}${url}`;
|
|
123
|
+
const headers = {
|
|
124
|
+
...options.headers ?? {},
|
|
125
|
+
"Content-Type": "application/json"
|
|
126
|
+
};
|
|
127
|
+
if (apiKey) {
|
|
128
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
129
|
+
}
|
|
56
130
|
const response = await fetch(fullUrl, {
|
|
57
131
|
...options,
|
|
58
|
-
headers
|
|
59
|
-
...options.headers,
|
|
60
|
-
Authorization: `Bearer ${apiKey}`,
|
|
61
|
-
"Content-Type": "application/json"
|
|
62
|
-
}
|
|
132
|
+
headers
|
|
63
133
|
});
|
|
64
134
|
const text = await response.text();
|
|
65
135
|
let data = null;
|
|
@@ -179,6 +249,102 @@ async function whoamiRequest(registryUrl, apiKey) {
|
|
|
179
249
|
return null;
|
|
180
250
|
}
|
|
181
251
|
}
|
|
252
|
+
async function deprecateSkillVersion(skillName, version2, message) {
|
|
253
|
+
const config2 = getConfig();
|
|
254
|
+
if (!config2) {
|
|
255
|
+
return { status: 401, error: "SDK not configured" };
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
const response = await fetch(
|
|
259
|
+
`${config2.baseUrl}/api/skills/${skillName}/${version2}/deprecate`,
|
|
260
|
+
{
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
265
|
+
},
|
|
266
|
+
body: JSON.stringify({ message })
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
const error = await response.text();
|
|
271
|
+
return { status: response.status, error };
|
|
272
|
+
}
|
|
273
|
+
const data = await response.json();
|
|
274
|
+
return { status: response.status, data };
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return {
|
|
277
|
+
status: 500,
|
|
278
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function undeprecateSkillVersion(skillName, version2) {
|
|
283
|
+
const config2 = getConfig();
|
|
284
|
+
if (!config2) {
|
|
285
|
+
return { status: 401, error: "SDK not configured" };
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch(
|
|
289
|
+
`${config2.baseUrl}/api/skills/${skillName}/${version2}/deprecate`,
|
|
290
|
+
{
|
|
291
|
+
method: "DELETE",
|
|
292
|
+
headers: {
|
|
293
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
const error = await response.text();
|
|
299
|
+
return { status: response.status, error };
|
|
300
|
+
}
|
|
301
|
+
const data = await response.json();
|
|
302
|
+
return { status: response.status, data };
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return {
|
|
305
|
+
status: 500,
|
|
306
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function changeSkillAccess(skillName, input) {
|
|
311
|
+
const config2 = getConfig();
|
|
312
|
+
if (!config2) {
|
|
313
|
+
return { status: 401, error: "SDK not configured" };
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const response = await fetch(
|
|
317
|
+
`${config2.baseUrl}/api/skills/${skillName}/access`,
|
|
318
|
+
{
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: {
|
|
321
|
+
"Content-Type": "application/json",
|
|
322
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify(input)
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
const error = await response.text();
|
|
329
|
+
try {
|
|
330
|
+
const errorJson = JSON.parse(error);
|
|
331
|
+
return {
|
|
332
|
+
status: response.status,
|
|
333
|
+
error: errorJson.message || errorJson.error || error
|
|
334
|
+
};
|
|
335
|
+
} catch {
|
|
336
|
+
return { status: response.status, error };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const data = await response.json();
|
|
340
|
+
return { status: response.status, data };
|
|
341
|
+
} catch (error) {
|
|
342
|
+
return {
|
|
343
|
+
status: 500,
|
|
344
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
182
348
|
|
|
183
349
|
// src/errors.ts
|
|
184
350
|
var ConfigError = class extends Error {
|
|
@@ -238,12 +404,24 @@ function getConfigPath() {
|
|
|
238
404
|
function getLegacyConfigPath() {
|
|
239
405
|
return join(homedir(), ".pspm", "config.json");
|
|
240
406
|
}
|
|
407
|
+
function getPspmDir() {
|
|
408
|
+
return join(process.cwd(), ".pspm");
|
|
409
|
+
}
|
|
241
410
|
function getSkillsDir() {
|
|
242
|
-
return join(process.cwd(), ".skills");
|
|
411
|
+
return join(process.cwd(), ".pspm", "skills");
|
|
412
|
+
}
|
|
413
|
+
function getCacheDir() {
|
|
414
|
+
return join(process.cwd(), ".pspm", "cache");
|
|
243
415
|
}
|
|
244
416
|
function getLockfilePath() {
|
|
417
|
+
return join(process.cwd(), "pspm-lock.json");
|
|
418
|
+
}
|
|
419
|
+
function getLegacyLockfilePath() {
|
|
245
420
|
return join(process.cwd(), "skill-lock.json");
|
|
246
421
|
}
|
|
422
|
+
function getLegacySkillsDir() {
|
|
423
|
+
return join(process.cwd(), ".skills");
|
|
424
|
+
}
|
|
247
425
|
async function readUserConfig() {
|
|
248
426
|
const configPath = getConfigPath();
|
|
249
427
|
if (process.env.PSPM_DEBUG) {
|
|
@@ -255,10 +433,35 @@ async function readUserConfig() {
|
|
|
255
433
|
if (process.env.PSPM_DEBUG) {
|
|
256
434
|
console.log(`[config] Parsed config:`, JSON.stringify(parsed, null, 2));
|
|
257
435
|
}
|
|
436
|
+
const scopedRegistries = {};
|
|
437
|
+
for (const key of Object.keys(parsed)) {
|
|
438
|
+
const scopeMatch = key.match(/^(@[^:]+):registry$/);
|
|
439
|
+
if (scopeMatch) {
|
|
440
|
+
const scope = scopeMatch[1];
|
|
441
|
+
scopedRegistries[scope] = parsed[key];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const registryTokens = {};
|
|
445
|
+
for (const key of Object.keys(parsed)) {
|
|
446
|
+
const tokenMatch = key.match(/^\/\/([^:]+):authToken$/);
|
|
447
|
+
if (tokenMatch) {
|
|
448
|
+
const host = tokenMatch[1];
|
|
449
|
+
registryTokens[host] = parsed[key];
|
|
450
|
+
}
|
|
451
|
+
if (key.startsWith("//") && typeof parsed[key] === "object") {
|
|
452
|
+
const host = key.slice(2);
|
|
453
|
+
const section = parsed[key];
|
|
454
|
+
if (section.authToken) {
|
|
455
|
+
registryTokens[host] = section.authToken;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
258
459
|
return {
|
|
259
460
|
registry: parsed.registry,
|
|
260
461
|
authToken: parsed.authToken,
|
|
261
|
-
username: parsed.username
|
|
462
|
+
username: parsed.username,
|
|
463
|
+
scopedRegistries: Object.keys(scopedRegistries).length > 0 ? scopedRegistries : void 0,
|
|
464
|
+
registryTokens: Object.keys(registryTokens).length > 0 ? registryTokens : void 0
|
|
262
465
|
};
|
|
263
466
|
} catch (error) {
|
|
264
467
|
if (process.env.PSPM_DEBUG) {
|
|
@@ -374,6 +577,8 @@ async function resolveConfig() {
|
|
|
374
577
|
let registryUrl = DEFAULT_REGISTRY_URL;
|
|
375
578
|
let apiKey = userConfig.authToken;
|
|
376
579
|
const username = userConfig.username;
|
|
580
|
+
const scopedRegistries = userConfig.scopedRegistries ?? {};
|
|
581
|
+
const registryTokens = userConfig.registryTokens ?? {};
|
|
377
582
|
if (userConfig.registry) {
|
|
378
583
|
registryUrl = userConfig.registry;
|
|
379
584
|
}
|
|
@@ -391,13 +596,33 @@ async function resolveConfig() {
|
|
|
391
596
|
console.log(`[config] registryUrl: ${registryUrl}`);
|
|
392
597
|
console.log(`[config] apiKey: ${apiKey ? "***" : "(not set)"}`);
|
|
393
598
|
console.log(`[config] username: ${username || "(not set)"}`);
|
|
599
|
+
console.log(
|
|
600
|
+
`[config] scopedRegistries: ${JSON.stringify(scopedRegistries)}`
|
|
601
|
+
);
|
|
602
|
+
console.log(
|
|
603
|
+
`[config] registryTokens: ${Object.keys(registryTokens).length} configured`
|
|
604
|
+
);
|
|
394
605
|
}
|
|
395
606
|
return {
|
|
396
607
|
registryUrl,
|
|
397
608
|
apiKey,
|
|
398
|
-
username
|
|
609
|
+
username,
|
|
610
|
+
scopedRegistries,
|
|
611
|
+
registryTokens
|
|
399
612
|
};
|
|
400
613
|
}
|
|
614
|
+
function getTokenForRegistry(config2, registryUrl) {
|
|
615
|
+
try {
|
|
616
|
+
const url = new URL(registryUrl);
|
|
617
|
+
const host = url.host;
|
|
618
|
+
if (config2.registryTokens[host]) {
|
|
619
|
+
return config2.registryTokens[host];
|
|
620
|
+
}
|
|
621
|
+
return config2.apiKey;
|
|
622
|
+
} catch {
|
|
623
|
+
return config2.apiKey;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
401
626
|
async function setCredentials(authToken, username, registry) {
|
|
402
627
|
const config2 = await readUserConfig();
|
|
403
628
|
config2.authToken = authToken;
|
|
@@ -442,43 +667,438 @@ async function getRegistryUrl() {
|
|
|
442
667
|
const resolved = await resolveConfig();
|
|
443
668
|
return resolved.registryUrl;
|
|
444
669
|
}
|
|
670
|
+
|
|
671
|
+
// src/commands/access.ts
|
|
672
|
+
async function access(specifier, options) {
|
|
673
|
+
try {
|
|
674
|
+
const apiKey = await requireApiKey();
|
|
675
|
+
const registryUrl = await getRegistryUrl();
|
|
676
|
+
if (options.public && options.private) {
|
|
677
|
+
console.error("Error: Cannot specify both --public and --private");
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
if (!options.public && !options.private) {
|
|
681
|
+
console.error("Error: Must specify either --public or --private");
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
const visibility = options.public ? "public" : "private";
|
|
685
|
+
let packageName;
|
|
686
|
+
if (specifier) {
|
|
687
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
688
|
+
if (!parsed) {
|
|
689
|
+
console.error(
|
|
690
|
+
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}`
|
|
691
|
+
);
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
packageName = parsed.name;
|
|
695
|
+
} else {
|
|
696
|
+
const { readFile: readFile7 } = await import('fs/promises');
|
|
697
|
+
const { join: join13 } = await import('path');
|
|
698
|
+
let manifest = null;
|
|
699
|
+
try {
|
|
700
|
+
const content = await readFile7(
|
|
701
|
+
join13(process.cwd(), "pspm.json"),
|
|
702
|
+
"utf-8"
|
|
703
|
+
);
|
|
704
|
+
manifest = JSON.parse(content);
|
|
705
|
+
} catch {
|
|
706
|
+
try {
|
|
707
|
+
const content = await readFile7(
|
|
708
|
+
join13(process.cwd(), "package.json"),
|
|
709
|
+
"utf-8"
|
|
710
|
+
);
|
|
711
|
+
manifest = JSON.parse(content);
|
|
712
|
+
} catch {
|
|
713
|
+
console.error(
|
|
714
|
+
"Error: No pspm.json or package.json found in current directory"
|
|
715
|
+
);
|
|
716
|
+
console.error(
|
|
717
|
+
"Either run this command in a package directory or specify a package name"
|
|
718
|
+
);
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (!manifest?.name) {
|
|
723
|
+
console.error("Error: Package manifest is missing 'name' field");
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
packageName = manifest.name;
|
|
727
|
+
}
|
|
728
|
+
configure2({ registryUrl, apiKey });
|
|
729
|
+
console.log(`Setting ${packageName} to ${visibility}...`);
|
|
730
|
+
const response = await changeSkillAccess(packageName, { visibility });
|
|
731
|
+
if (response.status !== 200 || !response.data) {
|
|
732
|
+
const errorMessage = response.error ?? "Failed to change visibility";
|
|
733
|
+
console.error(`Error: ${errorMessage}`);
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
const result = response.data;
|
|
737
|
+
console.log(
|
|
738
|
+
`+ @user/${result.username}/${result.name} is now ${result.visibility}`
|
|
739
|
+
);
|
|
740
|
+
if (visibility === "public") {
|
|
741
|
+
console.log("");
|
|
742
|
+
console.log(
|
|
743
|
+
"Note: This action is irreversible. Public packages cannot be made private."
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
} catch (error) {
|
|
747
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
748
|
+
console.error(`Error: ${message}`);
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
var AGENT_INFO = {
|
|
753
|
+
"claude-code": {
|
|
754
|
+
displayName: "Claude Code",
|
|
755
|
+
skillsDir: ".claude/skills"
|
|
756
|
+
},
|
|
757
|
+
codex: {
|
|
758
|
+
displayName: "Codex",
|
|
759
|
+
skillsDir: ".codex/skills"
|
|
760
|
+
},
|
|
761
|
+
cursor: {
|
|
762
|
+
displayName: "Cursor",
|
|
763
|
+
skillsDir: ".cursor/skills"
|
|
764
|
+
},
|
|
765
|
+
gemini: {
|
|
766
|
+
displayName: "Gemini CLI",
|
|
767
|
+
skillsDir: ".gemini/skills"
|
|
768
|
+
},
|
|
769
|
+
kiro: {
|
|
770
|
+
displayName: "Kiro CLI",
|
|
771
|
+
skillsDir: ".kiro/skills"
|
|
772
|
+
},
|
|
773
|
+
opencode: {
|
|
774
|
+
displayName: "OpenCode",
|
|
775
|
+
skillsDir: ".opencode/skills"
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
var DEFAULT_AGENT_CONFIGS = {
|
|
779
|
+
"claude-code": { skillsDir: AGENT_INFO["claude-code"].skillsDir },
|
|
780
|
+
codex: { skillsDir: AGENT_INFO.codex.skillsDir },
|
|
781
|
+
cursor: { skillsDir: AGENT_INFO.cursor.skillsDir },
|
|
782
|
+
gemini: { skillsDir: AGENT_INFO.gemini.skillsDir },
|
|
783
|
+
kiro: { skillsDir: AGENT_INFO.kiro.skillsDir },
|
|
784
|
+
opencode: { skillsDir: AGENT_INFO.opencode.skillsDir }
|
|
785
|
+
};
|
|
786
|
+
var DEFAULT_AGENT = "claude-code";
|
|
787
|
+
var ALL_AGENTS = [
|
|
788
|
+
"claude-code",
|
|
789
|
+
"codex",
|
|
790
|
+
"cursor",
|
|
791
|
+
"gemini",
|
|
792
|
+
"kiro",
|
|
793
|
+
"opencode"
|
|
794
|
+
];
|
|
795
|
+
function resolveAgentConfig(name, overrides) {
|
|
796
|
+
if (overrides?.[name]) {
|
|
797
|
+
return overrides[name];
|
|
798
|
+
}
|
|
799
|
+
if (name in DEFAULT_AGENT_CONFIGS) {
|
|
800
|
+
return DEFAULT_AGENT_CONFIGS[name];
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function parseAgentArg(agentArg) {
|
|
805
|
+
if (!agentArg) {
|
|
806
|
+
return [DEFAULT_AGENT];
|
|
807
|
+
}
|
|
808
|
+
if (agentArg === "none") {
|
|
809
|
+
return ["none"];
|
|
810
|
+
}
|
|
811
|
+
return agentArg.split(",").map((a) => a.trim()).filter(Boolean);
|
|
812
|
+
}
|
|
813
|
+
function getAvailableAgents(overrides) {
|
|
814
|
+
const builtIn = Object.keys(DEFAULT_AGENT_CONFIGS);
|
|
815
|
+
const custom = overrides ? Object.keys(overrides) : [];
|
|
816
|
+
return [.../* @__PURE__ */ new Set([...builtIn, ...custom])];
|
|
817
|
+
}
|
|
818
|
+
async function promptForAgents() {
|
|
819
|
+
const choices = ALL_AGENTS.map((agent) => ({
|
|
820
|
+
name: `${AGENT_INFO[agent].displayName} (${AGENT_INFO[agent].skillsDir})`,
|
|
821
|
+
value: agent,
|
|
822
|
+
checked: true
|
|
823
|
+
// All selected by default
|
|
824
|
+
}));
|
|
825
|
+
const selected = await checkbox({
|
|
826
|
+
message: "Select agents to install skills to",
|
|
827
|
+
choices
|
|
828
|
+
});
|
|
829
|
+
if (selected.length === 0) {
|
|
830
|
+
return ["none"];
|
|
831
|
+
}
|
|
832
|
+
return selected;
|
|
833
|
+
}
|
|
834
|
+
var GitHubRateLimitError = class extends Error {
|
|
835
|
+
constructor() {
|
|
836
|
+
super(
|
|
837
|
+
"GitHub API rate limit exceeded. Set GITHUB_TOKEN environment variable for higher limits."
|
|
838
|
+
);
|
|
839
|
+
this.name = "GitHubRateLimitError";
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var GitHubNotFoundError = class extends Error {
|
|
843
|
+
constructor(spec) {
|
|
844
|
+
const path = spec.path ? `/${spec.path}` : "";
|
|
845
|
+
const ref = spec.ref ? `@${spec.ref}` : "";
|
|
846
|
+
super(
|
|
847
|
+
`GitHub repository not found: ${spec.owner}/${spec.repo}${path}${ref}`
|
|
848
|
+
);
|
|
849
|
+
this.name = "GitHubNotFoundError";
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
var GitHubPathNotFoundError = class extends Error {
|
|
853
|
+
constructor(spec, availablePaths) {
|
|
854
|
+
const pathInfo = availablePaths?.length ? `
|
|
855
|
+
Available paths in repository root:
|
|
856
|
+
${availablePaths.join("\n ")}` : "";
|
|
857
|
+
super(
|
|
858
|
+
`Path "${spec.path}" not found in ${spec.owner}/${spec.repo}${pathInfo}`
|
|
859
|
+
);
|
|
860
|
+
this.name = "GitHubPathNotFoundError";
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
function getGitHubHeaders() {
|
|
864
|
+
const headers = {
|
|
865
|
+
Accept: "application/vnd.github+json",
|
|
866
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
867
|
+
"User-Agent": "pspm-cli"
|
|
868
|
+
};
|
|
869
|
+
const token = process.env.GITHUB_TOKEN;
|
|
870
|
+
if (token) {
|
|
871
|
+
headers.Authorization = `Bearer ${token}`;
|
|
872
|
+
}
|
|
873
|
+
return headers;
|
|
874
|
+
}
|
|
875
|
+
async function resolveGitHubRef(owner, repo, ref) {
|
|
876
|
+
const headers = getGitHubHeaders();
|
|
877
|
+
if (!ref || ref === "latest") {
|
|
878
|
+
const repoUrl = `https://api.github.com/repos/${owner}/${repo}`;
|
|
879
|
+
const repoResponse = await fetch(repoUrl, { headers });
|
|
880
|
+
if (repoResponse.status === 404) {
|
|
881
|
+
throw new GitHubNotFoundError({ owner, repo });
|
|
882
|
+
}
|
|
883
|
+
if (repoResponse.status === 403) {
|
|
884
|
+
const remaining = repoResponse.headers.get("x-ratelimit-remaining");
|
|
885
|
+
if (remaining === "0") {
|
|
886
|
+
throw new GitHubRateLimitError();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (!repoResponse.ok) {
|
|
890
|
+
throw new Error(`GitHub API error: ${repoResponse.status}`);
|
|
891
|
+
}
|
|
892
|
+
const repoData = await repoResponse.json();
|
|
893
|
+
ref = repoData.default_branch;
|
|
894
|
+
}
|
|
895
|
+
const commitUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${ref}`;
|
|
896
|
+
const commitResponse = await fetch(commitUrl, { headers });
|
|
897
|
+
if (commitResponse.status === 404) {
|
|
898
|
+
throw new GitHubNotFoundError({ owner, repo, ref });
|
|
899
|
+
}
|
|
900
|
+
if (commitResponse.status === 403) {
|
|
901
|
+
const remaining = commitResponse.headers.get("x-ratelimit-remaining");
|
|
902
|
+
if (remaining === "0") {
|
|
903
|
+
throw new GitHubRateLimitError();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (!commitResponse.ok) {
|
|
907
|
+
throw new Error(`GitHub API error: ${commitResponse.status}`);
|
|
908
|
+
}
|
|
909
|
+
const commitData = await commitResponse.json();
|
|
910
|
+
return commitData.sha;
|
|
911
|
+
}
|
|
912
|
+
async function downloadGitHubPackage(spec) {
|
|
913
|
+
const headers = getGitHubHeaders();
|
|
914
|
+
const commit = await resolveGitHubRef(spec.owner, spec.repo, spec.ref);
|
|
915
|
+
const tarballUrl = `https://api.github.com/repos/${spec.owner}/${spec.repo}/tarball/${commit}`;
|
|
916
|
+
const response = await fetch(tarballUrl, {
|
|
917
|
+
headers,
|
|
918
|
+
redirect: "follow"
|
|
919
|
+
});
|
|
920
|
+
if (response.status === 404) {
|
|
921
|
+
throw new GitHubNotFoundError(spec);
|
|
922
|
+
}
|
|
923
|
+
if (response.status === 403) {
|
|
924
|
+
const remaining = response.headers.get("x-ratelimit-remaining");
|
|
925
|
+
if (remaining === "0") {
|
|
926
|
+
throw new GitHubRateLimitError();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (!response.ok) {
|
|
930
|
+
throw new Error(`Failed to download GitHub tarball: ${response.status}`);
|
|
931
|
+
}
|
|
932
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
933
|
+
const integrity = calculateIntegrity(buffer);
|
|
934
|
+
return { buffer, commit, integrity };
|
|
935
|
+
}
|
|
936
|
+
async function extractGitHubPackage(spec, buffer, skillsDir) {
|
|
937
|
+
const destPath = spec.path ? join(skillsDir, "_github", spec.owner, spec.repo, spec.path) : join(skillsDir, "_github", spec.owner, spec.repo);
|
|
938
|
+
const tempDir = join(skillsDir, "_github", ".temp", `${Date.now()}`);
|
|
939
|
+
await mkdir(tempDir, { recursive: true });
|
|
940
|
+
const tempFile = join(tempDir, "archive.tgz");
|
|
941
|
+
try {
|
|
942
|
+
await writeFile(tempFile, buffer);
|
|
943
|
+
const { exec: exec2 } = await import('child_process');
|
|
944
|
+
const { promisify: promisify2 } = await import('util');
|
|
945
|
+
const execAsync = promisify2(exec2);
|
|
946
|
+
await execAsync(`tar -xzf "${tempFile}" -C "${tempDir}"`);
|
|
947
|
+
const entries = await readdir(tempDir);
|
|
948
|
+
const extractedDir = entries.find(
|
|
949
|
+
(e) => e !== "archive.tgz" && !e.startsWith(".")
|
|
950
|
+
);
|
|
951
|
+
if (!extractedDir) {
|
|
952
|
+
throw new Error("Failed to find extracted directory in tarball");
|
|
953
|
+
}
|
|
954
|
+
const sourcePath = join(tempDir, extractedDir);
|
|
955
|
+
const copySource = spec.path ? join(sourcePath, spec.path) : sourcePath;
|
|
956
|
+
if (spec.path) {
|
|
957
|
+
const pathExists = await lstat(copySource).catch(() => null);
|
|
958
|
+
if (!pathExists) {
|
|
959
|
+
const rootEntries = await readdir(sourcePath);
|
|
960
|
+
const dirs = [];
|
|
961
|
+
for (const entry of rootEntries) {
|
|
962
|
+
const stat7 = await lstat(join(sourcePath, entry)).catch(() => null);
|
|
963
|
+
if (stat7?.isDirectory() && !entry.startsWith(".")) {
|
|
964
|
+
dirs.push(entry);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
throw new GitHubPathNotFoundError(spec, dirs);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
await rm(destPath, { recursive: true, force: true });
|
|
971
|
+
await mkdir(destPath, { recursive: true });
|
|
972
|
+
await cp(copySource, destPath, { recursive: true });
|
|
973
|
+
return spec.path ? `.pspm/skills/_github/${spec.owner}/${spec.repo}/${spec.path}` : `.pspm/skills/_github/${spec.owner}/${spec.repo}`;
|
|
974
|
+
} finally {
|
|
975
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function getGitHubDisplayName(spec, commit) {
|
|
979
|
+
let name = `github:${spec.owner}/${spec.repo}`;
|
|
980
|
+
if (spec.path) {
|
|
981
|
+
name += `/${spec.path}`;
|
|
982
|
+
}
|
|
983
|
+
if (spec.ref || commit) {
|
|
984
|
+
const ref = spec.ref || "HEAD";
|
|
985
|
+
name += ` (${ref}${""})`;
|
|
986
|
+
}
|
|
987
|
+
return name;
|
|
988
|
+
}
|
|
989
|
+
async function hasLegacyLockfile() {
|
|
990
|
+
try {
|
|
991
|
+
await stat(getLegacyLockfilePath());
|
|
992
|
+
return true;
|
|
993
|
+
} catch {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
async function migrateLockfileIfNeeded() {
|
|
998
|
+
const legacyPath = getLegacyLockfilePath();
|
|
999
|
+
const newPath = getLockfilePath();
|
|
1000
|
+
try {
|
|
1001
|
+
await stat(legacyPath);
|
|
1002
|
+
} catch {
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
await stat(newPath);
|
|
1007
|
+
return false;
|
|
1008
|
+
} catch {
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
const content = await readFile(legacyPath, "utf-8");
|
|
1012
|
+
const oldLockfile = JSON.parse(content);
|
|
1013
|
+
const newLockfile = {
|
|
1014
|
+
lockfileVersion: 2,
|
|
1015
|
+
registryUrl: oldLockfile.registryUrl,
|
|
1016
|
+
packages: oldLockfile.skills ?? {}
|
|
1017
|
+
};
|
|
1018
|
+
await writeFile(newPath, `${JSON.stringify(newLockfile, null, 2)}
|
|
1019
|
+
`);
|
|
1020
|
+
console.log(`Migrated lockfile: skill-lock.json \u2192 pspm-lock.json`);
|
|
1021
|
+
return true;
|
|
1022
|
+
} catch {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
445
1026
|
async function readLockfile() {
|
|
446
1027
|
const lockfilePath = getLockfilePath();
|
|
447
1028
|
try {
|
|
448
1029
|
const content = await readFile(lockfilePath, "utf-8");
|
|
449
|
-
|
|
1030
|
+
const lockfile = JSON.parse(content);
|
|
1031
|
+
if (lockfile.lockfileVersion === 1 && lockfile.skills && !lockfile.packages) {
|
|
1032
|
+
return {
|
|
1033
|
+
...lockfile,
|
|
1034
|
+
lockfileVersion: 2,
|
|
1035
|
+
packages: lockfile.skills
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
return lockfile;
|
|
450
1039
|
} catch {
|
|
1040
|
+
if (await hasLegacyLockfile()) {
|
|
1041
|
+
try {
|
|
1042
|
+
const content = await readFile(getLegacyLockfilePath(), "utf-8");
|
|
1043
|
+
const legacyLockfile = JSON.parse(content);
|
|
1044
|
+
return {
|
|
1045
|
+
lockfileVersion: 2,
|
|
1046
|
+
registryUrl: legacyLockfile.registryUrl,
|
|
1047
|
+
packages: legacyLockfile.skills ?? {}
|
|
1048
|
+
};
|
|
1049
|
+
} catch {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
451
1053
|
return null;
|
|
452
1054
|
}
|
|
453
1055
|
}
|
|
454
1056
|
async function writeLockfile(lockfile) {
|
|
455
1057
|
const lockfilePath = getLockfilePath();
|
|
456
1058
|
await mkdir(dirname(lockfilePath), { recursive: true });
|
|
457
|
-
|
|
1059
|
+
const normalized = {
|
|
1060
|
+
lockfileVersion: 3,
|
|
1061
|
+
registryUrl: lockfile.registryUrl,
|
|
1062
|
+
packages: lockfile.packages ?? lockfile.skills ?? {}
|
|
1063
|
+
};
|
|
1064
|
+
if (lockfile.githubPackages && Object.keys(lockfile.githubPackages).length > 0) {
|
|
1065
|
+
normalized.githubPackages = lockfile.githubPackages;
|
|
1066
|
+
}
|
|
1067
|
+
await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}
|
|
458
1068
|
`);
|
|
459
1069
|
}
|
|
460
1070
|
async function createEmptyLockfile() {
|
|
461
1071
|
const registryUrl = await getRegistryUrl();
|
|
462
1072
|
return {
|
|
463
|
-
lockfileVersion:
|
|
1073
|
+
lockfileVersion: 3,
|
|
464
1074
|
registryUrl,
|
|
465
|
-
|
|
1075
|
+
packages: {}
|
|
466
1076
|
};
|
|
467
1077
|
}
|
|
1078
|
+
function getPackages(lockfile) {
|
|
1079
|
+
return lockfile.packages ?? lockfile.skills ?? {};
|
|
1080
|
+
}
|
|
468
1081
|
async function addToLockfile(fullName, entry) {
|
|
469
1082
|
let lockfile = await readLockfile();
|
|
470
1083
|
if (!lockfile) {
|
|
471
1084
|
lockfile = await createEmptyLockfile();
|
|
472
1085
|
}
|
|
473
|
-
|
|
1086
|
+
const packages = getPackages(lockfile);
|
|
1087
|
+
packages[fullName] = entry;
|
|
1088
|
+
lockfile.packages = packages;
|
|
474
1089
|
await writeLockfile(lockfile);
|
|
475
1090
|
}
|
|
476
1091
|
async function removeFromLockfile(fullName) {
|
|
477
1092
|
const lockfile = await readLockfile();
|
|
478
|
-
if (!lockfile
|
|
1093
|
+
if (!lockfile) {
|
|
479
1094
|
return false;
|
|
480
1095
|
}
|
|
481
|
-
|
|
1096
|
+
const packages = getPackages(lockfile);
|
|
1097
|
+
if (!packages[fullName]) {
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
delete packages[fullName];
|
|
1101
|
+
lockfile.packages = packages;
|
|
482
1102
|
await writeLockfile(lockfile);
|
|
483
1103
|
return true;
|
|
484
1104
|
}
|
|
@@ -487,29 +1107,255 @@ async function listLockfileSkills() {
|
|
|
487
1107
|
if (!lockfile) {
|
|
488
1108
|
return [];
|
|
489
1109
|
}
|
|
490
|
-
|
|
1110
|
+
const packages = getPackages(lockfile);
|
|
1111
|
+
return Object.entries(packages).map(([name, entry]) => ({
|
|
491
1112
|
name,
|
|
492
1113
|
entry
|
|
493
1114
|
}));
|
|
494
1115
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1116
|
+
async function addGitHubToLockfile(specifier, entry) {
|
|
1117
|
+
let lockfile = await readLockfile();
|
|
1118
|
+
if (!lockfile) {
|
|
1119
|
+
lockfile = await createEmptyLockfile();
|
|
1120
|
+
}
|
|
1121
|
+
if (!lockfile.githubPackages) {
|
|
1122
|
+
lockfile.githubPackages = {};
|
|
1123
|
+
}
|
|
1124
|
+
lockfile.githubPackages[specifier] = entry;
|
|
1125
|
+
await writeLockfile(lockfile);
|
|
1126
|
+
}
|
|
1127
|
+
async function removeGitHubFromLockfile(specifier) {
|
|
1128
|
+
const lockfile = await readLockfile();
|
|
1129
|
+
if (!lockfile?.githubPackages?.[specifier]) {
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
delete lockfile.githubPackages[specifier];
|
|
1133
|
+
await writeLockfile(lockfile);
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1136
|
+
async function listLockfileGitHubPackages() {
|
|
1137
|
+
const lockfile = await readLockfile();
|
|
1138
|
+
if (!lockfile?.githubPackages) {
|
|
1139
|
+
return [];
|
|
1140
|
+
}
|
|
1141
|
+
return Object.entries(lockfile.githubPackages).map(([specifier, entry]) => ({
|
|
1142
|
+
specifier,
|
|
1143
|
+
entry
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1146
|
+
function getManifestPath() {
|
|
1147
|
+
return join(process.cwd(), "pspm.json");
|
|
1148
|
+
}
|
|
1149
|
+
async function readManifest() {
|
|
498
1150
|
try {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
1151
|
+
const content = await readFile(getManifestPath(), "utf-8");
|
|
1152
|
+
return JSON.parse(content);
|
|
1153
|
+
} catch {
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function writeManifest(manifest) {
|
|
1158
|
+
const content = JSON.stringify(manifest, null, 2);
|
|
1159
|
+
await writeFile(getManifestPath(), `${content}
|
|
1160
|
+
`);
|
|
1161
|
+
}
|
|
1162
|
+
async function createMinimalManifest() {
|
|
1163
|
+
return {
|
|
1164
|
+
dependencies: {}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
async function ensureManifest() {
|
|
1168
|
+
let manifest = await readManifest();
|
|
1169
|
+
if (!manifest) {
|
|
1170
|
+
manifest = await createMinimalManifest();
|
|
1171
|
+
await writeManifest(manifest);
|
|
1172
|
+
}
|
|
1173
|
+
return manifest;
|
|
1174
|
+
}
|
|
1175
|
+
async function addDependency(skillName, versionRange) {
|
|
1176
|
+
const manifest = await ensureManifest();
|
|
1177
|
+
if (!manifest.dependencies) {
|
|
1178
|
+
manifest.dependencies = {};
|
|
1179
|
+
}
|
|
1180
|
+
manifest.dependencies[skillName] = versionRange;
|
|
1181
|
+
await writeManifest(manifest);
|
|
1182
|
+
}
|
|
1183
|
+
async function removeDependency(skillName) {
|
|
1184
|
+
const manifest = await readManifest();
|
|
1185
|
+
if (!manifest?.dependencies?.[skillName]) {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
delete manifest.dependencies[skillName];
|
|
1189
|
+
await writeManifest(manifest);
|
|
1190
|
+
return true;
|
|
1191
|
+
}
|
|
1192
|
+
async function getDependencies() {
|
|
1193
|
+
const manifest = await readManifest();
|
|
1194
|
+
return manifest?.dependencies ?? {};
|
|
1195
|
+
}
|
|
1196
|
+
async function getGitHubDependencies() {
|
|
1197
|
+
const manifest = await readManifest();
|
|
1198
|
+
return manifest?.githubDependencies ?? {};
|
|
1199
|
+
}
|
|
1200
|
+
async function addGitHubDependency(specifier, ref) {
|
|
1201
|
+
const manifest = await ensureManifest();
|
|
1202
|
+
if (!manifest.githubDependencies) {
|
|
1203
|
+
manifest.githubDependencies = {};
|
|
1204
|
+
}
|
|
1205
|
+
manifest.githubDependencies[specifier] = ref;
|
|
1206
|
+
await writeManifest(manifest);
|
|
1207
|
+
}
|
|
1208
|
+
async function removeGitHubDependency(specifier) {
|
|
1209
|
+
const manifest = await readManifest();
|
|
1210
|
+
if (!manifest?.githubDependencies?.[specifier]) {
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
delete manifest.githubDependencies[specifier];
|
|
1214
|
+
await writeManifest(manifest);
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1217
|
+
async function createAgentSymlinks(skills, options) {
|
|
1218
|
+
const { agents, projectRoot, agentConfigs } = options;
|
|
1219
|
+
if (agents.length === 1 && agents[0] === "none") {
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
for (const agentName of agents) {
|
|
1223
|
+
const config2 = resolveAgentConfig(agentName, agentConfigs);
|
|
1224
|
+
if (!config2) {
|
|
1225
|
+
console.warn(`Warning: Unknown agent "${agentName}", skipping symlinks`);
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const agentSkillsDir = join(projectRoot, config2.skillsDir);
|
|
1229
|
+
await mkdir(agentSkillsDir, { recursive: true });
|
|
1230
|
+
for (const skill of skills) {
|
|
1231
|
+
const symlinkPath = join(agentSkillsDir, skill.name);
|
|
1232
|
+
const targetPath = join(projectRoot, skill.sourcePath);
|
|
1233
|
+
const relativeTarget = relative(dirname(symlinkPath), targetPath);
|
|
1234
|
+
await createSymlink(symlinkPath, relativeTarget, skill.name);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
async function createSymlink(symlinkPath, target, skillName) {
|
|
1239
|
+
try {
|
|
1240
|
+
const stats = await lstat(symlinkPath).catch(() => null);
|
|
1241
|
+
if (stats) {
|
|
1242
|
+
if (stats.isSymbolicLink()) {
|
|
1243
|
+
const existingTarget = await readlink(symlinkPath);
|
|
1244
|
+
if (existingTarget === target) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
await rm(symlinkPath);
|
|
1248
|
+
} else {
|
|
1249
|
+
console.warn(
|
|
1250
|
+
`Warning: File exists at symlink path for "${skillName}", skipping: ${symlinkPath}`
|
|
1251
|
+
);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
await symlink(target, symlinkPath);
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1258
|
+
console.warn(
|
|
1259
|
+
`Warning: Failed to create symlink for "${skillName}": ${message}`
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
async function removeAgentSymlinks(skillName, options) {
|
|
1264
|
+
const { agents, projectRoot, agentConfigs } = options;
|
|
1265
|
+
if (agents.length === 1 && agents[0] === "none") {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
for (const agentName of agents) {
|
|
1269
|
+
const config2 = resolveAgentConfig(agentName, agentConfigs);
|
|
1270
|
+
if (!config2) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
|
|
1274
|
+
try {
|
|
1275
|
+
const stats = await lstat(symlinkPath).catch(() => null);
|
|
1276
|
+
if (stats?.isSymbolicLink()) {
|
|
1277
|
+
await rm(symlinkPath);
|
|
1278
|
+
}
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function getRegistrySkillPath(username, skillName) {
|
|
1284
|
+
return `.pspm/skills/${username}/${skillName}`;
|
|
1285
|
+
}
|
|
1286
|
+
function getGitHubSkillPath(owner, repo, path) {
|
|
1287
|
+
if (path) {
|
|
1288
|
+
return `.pspm/skills/_github/${owner}/${repo}/${path}`;
|
|
1289
|
+
}
|
|
1290
|
+
return `.pspm/skills/_github/${owner}/${repo}`;
|
|
1291
|
+
}
|
|
1292
|
+
async function getLinkedAgents(skillName, agents, projectRoot, agentConfigs) {
|
|
1293
|
+
const linkedAgents = [];
|
|
1294
|
+
for (const agentName of agents) {
|
|
1295
|
+
const config2 = resolveAgentConfig(agentName, agentConfigs);
|
|
1296
|
+
if (!config2) continue;
|
|
1297
|
+
const symlinkPath = join(projectRoot, config2.skillsDir, skillName);
|
|
1298
|
+
try {
|
|
1299
|
+
const stats = await lstat(symlinkPath);
|
|
1300
|
+
if (stats.isSymbolicLink()) {
|
|
1301
|
+
linkedAgents.push(agentName);
|
|
1302
|
+
}
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return linkedAgents;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// src/commands/add.ts
|
|
1310
|
+
async function add(specifier, options) {
|
|
1311
|
+
let agents;
|
|
1312
|
+
const manifest = await readManifest();
|
|
1313
|
+
if (options.agent) {
|
|
1314
|
+
agents = parseAgentArg(options.agent);
|
|
1315
|
+
} else if (manifest) {
|
|
1316
|
+
agents = parseAgentArg(void 0);
|
|
1317
|
+
} else if (options.yes) {
|
|
1318
|
+
agents = parseAgentArg(void 0);
|
|
1319
|
+
} else {
|
|
1320
|
+
console.log("No pspm.json found. Let's set up your project.\n");
|
|
1321
|
+
agents = await promptForAgents();
|
|
1322
|
+
}
|
|
1323
|
+
if (isGitHubSpecifier(specifier)) {
|
|
1324
|
+
await addGitHub(specifier, { ...options, resolvedAgents: agents });
|
|
1325
|
+
} else {
|
|
1326
|
+
await addRegistry(specifier, { ...options, resolvedAgents: agents });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
async function addRegistry(specifier, options) {
|
|
1330
|
+
try {
|
|
1331
|
+
const config2 = await resolveConfig();
|
|
1332
|
+
const registryUrl = config2.registryUrl;
|
|
1333
|
+
const apiKey = getTokenForRegistry(config2, registryUrl);
|
|
1334
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
1335
|
+
if (!parsed) {
|
|
503
1336
|
console.error(
|
|
504
1337
|
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}[@{version}]`
|
|
505
1338
|
);
|
|
506
1339
|
process.exit(1);
|
|
507
1340
|
}
|
|
508
1341
|
const { username, name, versionRange } = parsed;
|
|
509
|
-
configure2({ registryUrl, apiKey });
|
|
1342
|
+
configure2({ registryUrl, apiKey: apiKey ?? "" });
|
|
510
1343
|
console.log(`Resolving ${specifier}...`);
|
|
511
1344
|
const versionsResponse = await listSkillVersions(username, name);
|
|
512
1345
|
if (versionsResponse.status !== 200) {
|
|
1346
|
+
if (versionsResponse.status === 401) {
|
|
1347
|
+
if (!apiKey) {
|
|
1348
|
+
console.error(
|
|
1349
|
+
`Error: Package @user/${username}/${name} requires authentication`
|
|
1350
|
+
);
|
|
1351
|
+
console.error("Please run 'pspm login' to authenticate");
|
|
1352
|
+
} else {
|
|
1353
|
+
console.error(
|
|
1354
|
+
`Error: Access denied to @user/${username}/${name}. You may not have permission to access this private package.`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
process.exit(1);
|
|
1358
|
+
}
|
|
513
1359
|
const errorMessage = extractApiErrorMessage(
|
|
514
1360
|
versionsResponse,
|
|
515
1361
|
`Skill @user/${username}/${name} not found`
|
|
@@ -543,10 +1389,12 @@ async function add(specifier, _options) {
|
|
|
543
1389
|
}
|
|
544
1390
|
const versionInfo = versionResponse.data;
|
|
545
1391
|
const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
|
|
1392
|
+
const downloadHeaders = {};
|
|
1393
|
+
if (!isPresignedUrl && apiKey) {
|
|
1394
|
+
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
1395
|
+
}
|
|
546
1396
|
const tarballResponse = await fetch(versionInfo.downloadUrl, {
|
|
547
|
-
headers:
|
|
548
|
-
Authorization: `Bearer ${apiKey}`
|
|
549
|
-
},
|
|
1397
|
+
headers: downloadHeaders,
|
|
550
1398
|
redirect: "follow"
|
|
551
1399
|
});
|
|
552
1400
|
if (!tarballResponse.ok) {
|
|
@@ -565,60 +1413,842 @@ async function add(specifier, _options) {
|
|
|
565
1413
|
const skillsDir = getSkillsDir();
|
|
566
1414
|
const destDir = join(skillsDir, username, name);
|
|
567
1415
|
await mkdir(destDir, { recursive: true });
|
|
568
|
-
const { writeFile:
|
|
1416
|
+
const { writeFile: writeFile8 } = await import('fs/promises');
|
|
569
1417
|
const tempFile = join(destDir, ".temp.tgz");
|
|
570
|
-
await
|
|
1418
|
+
await writeFile8(tempFile, tarballBuffer);
|
|
571
1419
|
const { exec: exec2 } = await import('child_process');
|
|
572
1420
|
const { promisify: promisify2 } = await import('util');
|
|
573
1421
|
const execAsync = promisify2(exec2);
|
|
574
1422
|
try {
|
|
575
1423
|
await rm(destDir, { recursive: true, force: true });
|
|
576
1424
|
await mkdir(destDir, { recursive: true });
|
|
577
|
-
await
|
|
1425
|
+
await writeFile8(tempFile, tarballBuffer);
|
|
578
1426
|
await execAsync(
|
|
579
1427
|
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
580
1428
|
);
|
|
581
1429
|
} finally {
|
|
582
1430
|
await rm(tempFile, { force: true });
|
|
583
1431
|
}
|
|
584
|
-
const fullName = `@user/${username}/${name}`;
|
|
585
|
-
await addToLockfile(fullName, {
|
|
586
|
-
version: resolved,
|
|
587
|
-
resolved: versionInfo.downloadUrl,
|
|
588
|
-
integrity
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
1432
|
+
const fullName = `@user/${username}/${name}`;
|
|
1433
|
+
await addToLockfile(fullName, {
|
|
1434
|
+
version: resolved,
|
|
1435
|
+
resolved: versionInfo.downloadUrl,
|
|
1436
|
+
integrity
|
|
1437
|
+
});
|
|
1438
|
+
const dependencyRange = versionRange || `^${resolved}`;
|
|
1439
|
+
await addDependency(fullName, dependencyRange);
|
|
1440
|
+
const agents = options.resolvedAgents;
|
|
1441
|
+
if (agents[0] !== "none") {
|
|
1442
|
+
const skillManifest = await readManifest();
|
|
1443
|
+
const skillInfo = {
|
|
1444
|
+
name,
|
|
1445
|
+
sourcePath: getRegistrySkillPath(username, name)
|
|
1446
|
+
};
|
|
1447
|
+
await createAgentSymlinks([skillInfo], {
|
|
1448
|
+
agents,
|
|
1449
|
+
projectRoot: process.cwd(),
|
|
1450
|
+
agentConfigs: skillManifest?.agents
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
console.log(`Installed @user/${username}/${name}@${resolved}`);
|
|
1454
|
+
console.log(`Location: ${destDir}`);
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1457
|
+
console.error(`Error: ${message}`);
|
|
1458
|
+
process.exit(1);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
async function addGitHub(specifier, options) {
|
|
1462
|
+
try {
|
|
1463
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
1464
|
+
if (!parsed) {
|
|
1465
|
+
console.error(
|
|
1466
|
+
`Error: Invalid GitHub specifier "${specifier}". Use format: github:{owner}/{repo}[/{path}][@{ref}]`
|
|
1467
|
+
);
|
|
1468
|
+
process.exit(1);
|
|
1469
|
+
}
|
|
1470
|
+
const ref = parsed.ref || "HEAD";
|
|
1471
|
+
console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
|
|
1472
|
+
const result = await downloadGitHubPackage(parsed);
|
|
1473
|
+
console.log(
|
|
1474
|
+
`Installing ${specifier} (${ref}@${result.commit.slice(0, 7)})...`
|
|
1475
|
+
);
|
|
1476
|
+
const skillsDir = getSkillsDir();
|
|
1477
|
+
const destPath = await extractGitHubPackage(
|
|
1478
|
+
parsed,
|
|
1479
|
+
result.buffer,
|
|
1480
|
+
skillsDir
|
|
1481
|
+
);
|
|
1482
|
+
const lockfileSpecifier = formatGitHubSpecifier({
|
|
1483
|
+
owner: parsed.owner,
|
|
1484
|
+
repo: parsed.repo,
|
|
1485
|
+
path: parsed.path
|
|
1486
|
+
// Don't include ref in the specifier key, it's stored in gitRef
|
|
1487
|
+
});
|
|
1488
|
+
const entry = {
|
|
1489
|
+
version: result.commit.slice(0, 7),
|
|
1490
|
+
resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
|
|
1491
|
+
integrity: result.integrity,
|
|
1492
|
+
gitCommit: result.commit,
|
|
1493
|
+
gitRef: ref
|
|
1494
|
+
};
|
|
1495
|
+
await addGitHubToLockfile(lockfileSpecifier, entry);
|
|
1496
|
+
await addGitHubDependency(lockfileSpecifier, ref);
|
|
1497
|
+
const agents = options.resolvedAgents;
|
|
1498
|
+
if (agents[0] !== "none") {
|
|
1499
|
+
const manifest = await readManifest();
|
|
1500
|
+
const skillName = getGitHubSkillName(parsed);
|
|
1501
|
+
const skillInfo = {
|
|
1502
|
+
name: skillName,
|
|
1503
|
+
sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
|
|
1504
|
+
};
|
|
1505
|
+
await createAgentSymlinks([skillInfo], {
|
|
1506
|
+
agents,
|
|
1507
|
+
projectRoot: process.cwd(),
|
|
1508
|
+
agentConfigs: manifest?.agents
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
console.log(`Installed ${specifier} (${ref}@${result.commit.slice(0, 7)})`);
|
|
1512
|
+
console.log(`Location: ${destPath}`);
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
if (error instanceof GitHubRateLimitError) {
|
|
1515
|
+
console.error(`Error: ${error.message}`);
|
|
1516
|
+
} else if (error instanceof GitHubPathNotFoundError) {
|
|
1517
|
+
console.error(`Error: ${error.message}`);
|
|
1518
|
+
} else if (error instanceof GitHubNotFoundError) {
|
|
1519
|
+
console.error(`Error: ${error.message}`);
|
|
1520
|
+
} else {
|
|
1521
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1522
|
+
console.error(`Error: ${message}`);
|
|
1523
|
+
}
|
|
1524
|
+
process.exit(1);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
async function configInit(options) {
|
|
1528
|
+
try {
|
|
1529
|
+
const configPath = join(process.cwd(), ".pspmrc");
|
|
1530
|
+
try {
|
|
1531
|
+
await stat(configPath);
|
|
1532
|
+
console.error("Error: .pspmrc already exists in this directory.");
|
|
1533
|
+
process.exit(1);
|
|
1534
|
+
} catch {
|
|
1535
|
+
}
|
|
1536
|
+
const lines = ["; Project-specific PSPM configuration", ""];
|
|
1537
|
+
if (options.registry) {
|
|
1538
|
+
lines.push(`registry = ${options.registry}`);
|
|
1539
|
+
} else {
|
|
1540
|
+
lines.push("; Uncomment to use a custom registry:");
|
|
1541
|
+
lines.push("; registry = https://custom-registry.example.com");
|
|
1542
|
+
}
|
|
1543
|
+
lines.push("");
|
|
1544
|
+
await writeFile(configPath, lines.join("\n"));
|
|
1545
|
+
console.log("Created .pspmrc");
|
|
1546
|
+
console.log("");
|
|
1547
|
+
console.log("Contents:");
|
|
1548
|
+
console.log(lines.join("\n"));
|
|
1549
|
+
console.log("Note: .pspmrc should be committed to version control.");
|
|
1550
|
+
console.log("API keys should NOT be stored here - use pspm login instead.");
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1553
|
+
console.error(`Error: ${message}`);
|
|
1554
|
+
process.exit(1);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// src/commands/config/show.ts
|
|
1559
|
+
async function configShow() {
|
|
1560
|
+
try {
|
|
1561
|
+
const resolved = await resolveConfig();
|
|
1562
|
+
const projectConfig = await findProjectConfig();
|
|
1563
|
+
const configPath = getConfigPath();
|
|
1564
|
+
console.log("Resolved Configuration:\n");
|
|
1565
|
+
console.log(` Registry URL: ${resolved.registryUrl}`);
|
|
1566
|
+
console.log(` API Key: ${resolved.apiKey ? "***" : "(not set)"}`);
|
|
1567
|
+
console.log(` Username: ${resolved.username || "(not set)"}`);
|
|
1568
|
+
console.log("");
|
|
1569
|
+
console.log("Config Locations:");
|
|
1570
|
+
console.log(` User config: ${configPath}`);
|
|
1571
|
+
console.log(` Project config: ${projectConfig ? ".pspmrc" : "(none)"}`);
|
|
1572
|
+
console.log("");
|
|
1573
|
+
console.log("Environment Variables:");
|
|
1574
|
+
console.log(
|
|
1575
|
+
` PSPM_REGISTRY_URL: ${process.env.PSPM_REGISTRY_URL || "(not set)"}`
|
|
1576
|
+
);
|
|
1577
|
+
console.log(
|
|
1578
|
+
` PSPM_API_KEY: ${process.env.PSPM_API_KEY ? "***" : "(not set)"}`
|
|
1579
|
+
);
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1582
|
+
console.error(`Error: ${message}`);
|
|
1583
|
+
process.exit(1);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// src/commands/deprecate.ts
|
|
1588
|
+
async function deprecate(specifier, message, options) {
|
|
1589
|
+
try {
|
|
1590
|
+
const apiKey = await requireApiKey();
|
|
1591
|
+
const registryUrl = await getRegistryUrl();
|
|
1592
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
1593
|
+
if (!parsed) {
|
|
1594
|
+
console.error(
|
|
1595
|
+
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}@{version}`
|
|
1596
|
+
);
|
|
1597
|
+
process.exit(1);
|
|
1598
|
+
}
|
|
1599
|
+
const { username, name, versionRange } = parsed;
|
|
1600
|
+
if (!versionRange) {
|
|
1601
|
+
console.error(
|
|
1602
|
+
"Error: Version is required for deprecation. Use format: @user/{username}/{name}@{version}"
|
|
1603
|
+
);
|
|
1604
|
+
process.exit(1);
|
|
1605
|
+
}
|
|
1606
|
+
configure2({ registryUrl, apiKey });
|
|
1607
|
+
if (options.undo) {
|
|
1608
|
+
console.log(
|
|
1609
|
+
`Removing deprecation from @user/${username}/${name}@${versionRange}...`
|
|
1610
|
+
);
|
|
1611
|
+
const response = await undeprecateSkillVersion(name, versionRange);
|
|
1612
|
+
if (response.status !== 200) {
|
|
1613
|
+
console.error(
|
|
1614
|
+
`Error: ${response.error || "Failed to remove deprecation"}`
|
|
1615
|
+
);
|
|
1616
|
+
process.exit(1);
|
|
1617
|
+
}
|
|
1618
|
+
console.log(
|
|
1619
|
+
`Removed deprecation from @user/${username}/${name}@${versionRange}`
|
|
1620
|
+
);
|
|
1621
|
+
} else {
|
|
1622
|
+
if (!message) {
|
|
1623
|
+
console.error(
|
|
1624
|
+
"Error: Deprecation message is required. Usage: pspm deprecate <specifier> <message>"
|
|
1625
|
+
);
|
|
1626
|
+
process.exit(1);
|
|
1627
|
+
}
|
|
1628
|
+
console.log(`Deprecating @user/${username}/${name}@${versionRange}...`);
|
|
1629
|
+
const response = await deprecateSkillVersion(name, versionRange, message);
|
|
1630
|
+
if (response.status !== 200) {
|
|
1631
|
+
console.error(
|
|
1632
|
+
`Error: ${response.error || "Failed to deprecate version"}`
|
|
1633
|
+
);
|
|
1634
|
+
process.exit(1);
|
|
1635
|
+
}
|
|
1636
|
+
console.log(`Deprecated @user/${username}/${name}@${versionRange}`);
|
|
1637
|
+
console.log(`Message: ${message}`);
|
|
1638
|
+
console.log("");
|
|
1639
|
+
console.log(
|
|
1640
|
+
"Users installing this version will see a deprecation warning."
|
|
1641
|
+
);
|
|
1642
|
+
console.log("The package is still available for download.");
|
|
1643
|
+
}
|
|
1644
|
+
} catch (error) {
|
|
1645
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1646
|
+
console.error(`Error: ${errorMessage}`);
|
|
1647
|
+
process.exit(1);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
function prompt(rl, question, defaultValue) {
|
|
1651
|
+
return new Promise((resolve) => {
|
|
1652
|
+
const displayDefault = defaultValue ? ` (${defaultValue})` : "";
|
|
1653
|
+
rl.question(`${question}${displayDefault} `, (answer) => {
|
|
1654
|
+
resolve(answer.trim() || defaultValue);
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
async function readExistingPackageJson() {
|
|
1659
|
+
try {
|
|
1660
|
+
const content = await readFile(
|
|
1661
|
+
join(process.cwd(), "package.json"),
|
|
1662
|
+
"utf-8"
|
|
1663
|
+
);
|
|
1664
|
+
const pkg = JSON.parse(content);
|
|
1665
|
+
return {
|
|
1666
|
+
name: pkg.name,
|
|
1667
|
+
version: pkg.version,
|
|
1668
|
+
description: pkg.description,
|
|
1669
|
+
author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name,
|
|
1670
|
+
license: pkg.license
|
|
1671
|
+
};
|
|
1672
|
+
} catch {
|
|
1673
|
+
return null;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
async function getGitAuthor() {
|
|
1677
|
+
try {
|
|
1678
|
+
const { exec: exec2 } = await import('child_process');
|
|
1679
|
+
const { promisify: promisify2 } = await import('util');
|
|
1680
|
+
const execAsync = promisify2(exec2);
|
|
1681
|
+
const [nameResult, emailResult] = await Promise.all([
|
|
1682
|
+
execAsync("git config user.name").catch(() => ({ stdout: "" })),
|
|
1683
|
+
execAsync("git config user.email").catch(() => ({ stdout: "" }))
|
|
1684
|
+
]);
|
|
1685
|
+
const name = nameResult.stdout.trim();
|
|
1686
|
+
const email = emailResult.stdout.trim();
|
|
1687
|
+
if (name && email) {
|
|
1688
|
+
return `${name} <${email}>`;
|
|
1689
|
+
}
|
|
1690
|
+
if (name) {
|
|
1691
|
+
return name;
|
|
1692
|
+
}
|
|
1693
|
+
return null;
|
|
1694
|
+
} catch {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
function sanitizeName(name) {
|
|
1699
|
+
const withoutScope = name.replace(/^@[^/]+\//, "");
|
|
1700
|
+
return withoutScope.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
1701
|
+
}
|
|
1702
|
+
function isValidName(name) {
|
|
1703
|
+
return /^[a-z][a-z0-9_-]*$/.test(name);
|
|
1704
|
+
}
|
|
1705
|
+
function isValidVersion(version2) {
|
|
1706
|
+
return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(version2);
|
|
1707
|
+
}
|
|
1708
|
+
async function init(options) {
|
|
1709
|
+
try {
|
|
1710
|
+
const pspmJsonPath = join(process.cwd(), "pspm.json");
|
|
1711
|
+
let exists = false;
|
|
1712
|
+
try {
|
|
1713
|
+
await stat(pspmJsonPath);
|
|
1714
|
+
exists = true;
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
if (exists && !options.force) {
|
|
1718
|
+
console.error("Error: pspm.json already exists in this directory.");
|
|
1719
|
+
console.error("Use --force to overwrite.");
|
|
1720
|
+
process.exit(1);
|
|
1721
|
+
}
|
|
1722
|
+
const existingPkg = await readExistingPackageJson();
|
|
1723
|
+
const gitAuthor = await getGitAuthor();
|
|
1724
|
+
const defaultName = sanitizeName(
|
|
1725
|
+
options.name || existingPkg?.name || basename(process.cwd())
|
|
1726
|
+
);
|
|
1727
|
+
const defaultVersion = existingPkg?.version || "0.1.0";
|
|
1728
|
+
const defaultDescription = options.description || existingPkg?.description || "";
|
|
1729
|
+
const defaultAuthor = options.author || existingPkg?.author || gitAuthor || "";
|
|
1730
|
+
const defaultLicense = existingPkg?.license || "MIT";
|
|
1731
|
+
const defaultMain = "SKILL.md";
|
|
1732
|
+
const defaultCapabilities = "";
|
|
1733
|
+
let manifest;
|
|
1734
|
+
if (options.yes) {
|
|
1735
|
+
manifest = {
|
|
1736
|
+
$schema: PSPM_SCHEMA_URL,
|
|
1737
|
+
name: defaultName,
|
|
1738
|
+
version: defaultVersion,
|
|
1739
|
+
description: defaultDescription || void 0,
|
|
1740
|
+
author: defaultAuthor || void 0,
|
|
1741
|
+
license: defaultLicense,
|
|
1742
|
+
type: "skill",
|
|
1743
|
+
capabilities: [],
|
|
1744
|
+
main: defaultMain,
|
|
1745
|
+
requirements: {
|
|
1746
|
+
pspm: ">=0.1.0"
|
|
1747
|
+
},
|
|
1748
|
+
files: [...DEFAULT_SKILL_FILES],
|
|
1749
|
+
dependencies: {},
|
|
1750
|
+
private: false
|
|
1751
|
+
};
|
|
1752
|
+
} else {
|
|
1753
|
+
console.log(
|
|
1754
|
+
"This utility will walk you through creating a pspm.json file."
|
|
1755
|
+
);
|
|
1756
|
+
console.log(
|
|
1757
|
+
"It only covers the most common items, and tries to guess sensible defaults."
|
|
1758
|
+
);
|
|
1759
|
+
console.log("");
|
|
1760
|
+
console.log(
|
|
1761
|
+
"See `pspm init --help` for definitive documentation on these fields"
|
|
1762
|
+
);
|
|
1763
|
+
console.log("and exactly what they do.");
|
|
1764
|
+
console.log("");
|
|
1765
|
+
console.log("Press ^C at any time to quit.");
|
|
1766
|
+
const rl = createInterface({
|
|
1767
|
+
input: process.stdin,
|
|
1768
|
+
output: process.stdout
|
|
1769
|
+
});
|
|
1770
|
+
try {
|
|
1771
|
+
let name = await prompt(rl, "skill name:", defaultName);
|
|
1772
|
+
while (!isValidName(name)) {
|
|
1773
|
+
console.log(
|
|
1774
|
+
" Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores."
|
|
1775
|
+
);
|
|
1776
|
+
name = await prompt(rl, "skill name:", sanitizeName(name));
|
|
1777
|
+
}
|
|
1778
|
+
let version2 = await prompt(rl, "version:", defaultVersion);
|
|
1779
|
+
while (!isValidVersion(version2)) {
|
|
1780
|
+
console.log(" Version must be valid semver (e.g., 1.0.0)");
|
|
1781
|
+
version2 = await prompt(rl, "version:", "0.1.0");
|
|
1782
|
+
}
|
|
1783
|
+
const description = await prompt(
|
|
1784
|
+
rl,
|
|
1785
|
+
"description:",
|
|
1786
|
+
defaultDescription
|
|
1787
|
+
);
|
|
1788
|
+
const main = await prompt(rl, "entry point:", defaultMain);
|
|
1789
|
+
const capabilitiesStr = await prompt(
|
|
1790
|
+
rl,
|
|
1791
|
+
"capabilities (comma-separated):",
|
|
1792
|
+
defaultCapabilities
|
|
1793
|
+
);
|
|
1794
|
+
const author = await prompt(rl, "author:", defaultAuthor);
|
|
1795
|
+
const license = await prompt(rl, "license:", defaultLicense);
|
|
1796
|
+
rl.close();
|
|
1797
|
+
const capabilities = capabilitiesStr ? capabilitiesStr.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
1798
|
+
manifest = {
|
|
1799
|
+
$schema: PSPM_SCHEMA_URL,
|
|
1800
|
+
name,
|
|
1801
|
+
version: version2,
|
|
1802
|
+
description: description || void 0,
|
|
1803
|
+
author: author || void 0,
|
|
1804
|
+
license,
|
|
1805
|
+
type: "skill",
|
|
1806
|
+
capabilities,
|
|
1807
|
+
main,
|
|
1808
|
+
requirements: {
|
|
1809
|
+
pspm: ">=0.1.0"
|
|
1810
|
+
},
|
|
1811
|
+
files: [...DEFAULT_SKILL_FILES],
|
|
1812
|
+
dependencies: {},
|
|
1813
|
+
private: false
|
|
1814
|
+
};
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
rl.close();
|
|
1817
|
+
if (error instanceof Error && error.message.includes("readline was closed")) {
|
|
1818
|
+
console.log("\nAborted.");
|
|
1819
|
+
process.exit(0);
|
|
1820
|
+
}
|
|
1821
|
+
throw error;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
if (!manifest.description) delete manifest.description;
|
|
1825
|
+
if (!manifest.author) delete manifest.author;
|
|
1826
|
+
if (manifest.capabilities?.length === 0) delete manifest.capabilities;
|
|
1827
|
+
const content = JSON.stringify(manifest, null, 2);
|
|
1828
|
+
console.log("");
|
|
1829
|
+
console.log(`About to write to ${pspmJsonPath}:`);
|
|
1830
|
+
console.log("");
|
|
1831
|
+
console.log(content);
|
|
1832
|
+
console.log("");
|
|
1833
|
+
if (!options.yes) {
|
|
1834
|
+
const rl = createInterface({
|
|
1835
|
+
input: process.stdin,
|
|
1836
|
+
output: process.stdout
|
|
1837
|
+
});
|
|
1838
|
+
const confirm = await prompt(rl, "Is this OK?", "yes");
|
|
1839
|
+
rl.close();
|
|
1840
|
+
if (confirm.toLowerCase() !== "yes" && confirm.toLowerCase() !== "y") {
|
|
1841
|
+
console.log("Aborted.");
|
|
1842
|
+
process.exit(0);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
await writeFile(pspmJsonPath, `${content}
|
|
1846
|
+
`);
|
|
1847
|
+
try {
|
|
1848
|
+
await stat(join(process.cwd(), "SKILL.md"));
|
|
1849
|
+
} catch {
|
|
1850
|
+
console.log(
|
|
1851
|
+
"Note: Create a SKILL.md file with your skill's prompt content."
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
if (existingPkg) {
|
|
1855
|
+
console.log("Note: Values were derived from existing package.json.");
|
|
1856
|
+
console.log(" pspm.json is for publishing to PSPM registry.");
|
|
1857
|
+
console.log(" package.json can still be used for npm dependencies.");
|
|
1858
|
+
}
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1861
|
+
console.error(`Error: ${message}`);
|
|
1862
|
+
process.exit(1);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
function getCacheFilePath(cacheDir, integrity) {
|
|
1866
|
+
const match = integrity.match(/^sha256-(.+)$/);
|
|
1867
|
+
if (!match) {
|
|
1868
|
+
throw new Error(`Invalid integrity format: ${integrity}`);
|
|
1869
|
+
}
|
|
1870
|
+
const base64Hash = match[1];
|
|
1871
|
+
const hexHash = Buffer.from(base64Hash, "base64").toString("hex");
|
|
1872
|
+
return join(cacheDir, `sha256-${hexHash}.tgz`);
|
|
1873
|
+
}
|
|
1874
|
+
async function readFromCache(cacheDir, integrity) {
|
|
1875
|
+
try {
|
|
1876
|
+
const cachePath = getCacheFilePath(cacheDir, integrity);
|
|
1877
|
+
const data = await readFile(cachePath);
|
|
1878
|
+
const actualIntegrity = `sha256-${createHash("sha256").update(data).digest("base64")}`;
|
|
1879
|
+
if (actualIntegrity !== integrity) {
|
|
1880
|
+
await rm(cachePath, { force: true });
|
|
1881
|
+
return null;
|
|
1882
|
+
}
|
|
1883
|
+
return data;
|
|
1884
|
+
} catch {
|
|
1885
|
+
return null;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
async function writeToCache(cacheDir, integrity, data) {
|
|
1889
|
+
try {
|
|
1890
|
+
await mkdir(cacheDir, { recursive: true });
|
|
1891
|
+
const cachePath = getCacheFilePath(cacheDir, integrity);
|
|
1892
|
+
await writeFile(cachePath, data);
|
|
1893
|
+
} catch {
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
async function install(options) {
|
|
1897
|
+
try {
|
|
1898
|
+
const config2 = await resolveConfig();
|
|
1899
|
+
const registryUrl = config2.registryUrl;
|
|
1900
|
+
const apiKey = getTokenForRegistry(config2, registryUrl);
|
|
1901
|
+
const skillsDir = options.dir || getSkillsDir();
|
|
1902
|
+
const cacheDir = getCacheDir();
|
|
1903
|
+
const manifest = await readManifest();
|
|
1904
|
+
const agentConfigs = manifest?.agents;
|
|
1905
|
+
let agents;
|
|
1906
|
+
if (options.agent) {
|
|
1907
|
+
agents = parseAgentArg(options.agent);
|
|
1908
|
+
} else if (manifest) {
|
|
1909
|
+
agents = parseAgentArg(void 0);
|
|
1910
|
+
} else if (options.yes) {
|
|
1911
|
+
agents = parseAgentArg(void 0);
|
|
1912
|
+
} else {
|
|
1913
|
+
console.log("No pspm.json found. Let's set up your project.\n");
|
|
1914
|
+
agents = await promptForAgents();
|
|
1915
|
+
}
|
|
1916
|
+
await migrateLockfileIfNeeded();
|
|
1917
|
+
let lockfile = await readLockfile();
|
|
1918
|
+
const manifestDeps = await getDependencies();
|
|
1919
|
+
const manifestGitHubDeps = await getGitHubDependencies();
|
|
1920
|
+
const lockfilePackages = lockfile?.packages ?? lockfile?.skills ?? {};
|
|
1921
|
+
const lockfileGitHubPackages = lockfile?.githubPackages ?? {};
|
|
1922
|
+
const installedSkills = [];
|
|
1923
|
+
const missingDeps = [];
|
|
1924
|
+
for (const [fullName, versionRange] of Object.entries(manifestDeps)) {
|
|
1925
|
+
if (!lockfilePackages[fullName]) {
|
|
1926
|
+
missingDeps.push({ fullName, versionRange });
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if (missingDeps.length > 0) {
|
|
1930
|
+
if (options.frozenLockfile) {
|
|
1931
|
+
console.error(
|
|
1932
|
+
"Error: Dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
|
|
1933
|
+
);
|
|
1934
|
+
console.error("Missing dependencies:");
|
|
1935
|
+
for (const dep of missingDeps) {
|
|
1936
|
+
console.error(` - ${dep.fullName}@${dep.versionRange}`);
|
|
1937
|
+
}
|
|
1938
|
+
process.exit(1);
|
|
1939
|
+
}
|
|
1940
|
+
console.log(`Resolving ${missingDeps.length} new dependency(ies)...
|
|
1941
|
+
`);
|
|
1942
|
+
configure2({ registryUrl, apiKey: apiKey ?? "" });
|
|
1943
|
+
for (const { fullName, versionRange } of missingDeps) {
|
|
1944
|
+
const parsed = parseSkillSpecifier(fullName);
|
|
1945
|
+
if (!parsed) {
|
|
1946
|
+
console.error(`Error: Invalid dependency specifier: ${fullName}`);
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
const { username, name } = parsed;
|
|
1950
|
+
console.log(`Resolving ${fullName}@${versionRange}...`);
|
|
1951
|
+
const versionsResponse = await listSkillVersions(username, name);
|
|
1952
|
+
if (versionsResponse.status !== 200) {
|
|
1953
|
+
const errorMessage = extractApiErrorMessage(
|
|
1954
|
+
versionsResponse,
|
|
1955
|
+
`Skill ${fullName} not found`
|
|
1956
|
+
);
|
|
1957
|
+
console.error(`Error: ${errorMessage}`);
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
const versions = versionsResponse.data;
|
|
1961
|
+
if (versions.length === 0) {
|
|
1962
|
+
console.error(`Error: Skill ${fullName} not found`);
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
const versionStrings = versions.map(
|
|
1966
|
+
(v) => v.version
|
|
1967
|
+
);
|
|
1968
|
+
const resolved = resolveVersion(versionRange || "*", versionStrings);
|
|
1969
|
+
if (!resolved) {
|
|
1970
|
+
console.error(
|
|
1971
|
+
`Error: No version matching "${versionRange}" for ${fullName}`
|
|
1972
|
+
);
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
const versionResponse = await getSkillVersion(username, name, resolved);
|
|
1976
|
+
if (versionResponse.status !== 200 || !versionResponse.data) {
|
|
1977
|
+
const errorMessage = extractApiErrorMessage(
|
|
1978
|
+
versionResponse,
|
|
1979
|
+
`Version ${resolved} not found`
|
|
1980
|
+
);
|
|
1981
|
+
console.error(`Error: ${errorMessage}`);
|
|
1982
|
+
continue;
|
|
1983
|
+
}
|
|
1984
|
+
const versionInfo = versionResponse.data;
|
|
1985
|
+
const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
|
|
1986
|
+
const downloadHeaders = {};
|
|
1987
|
+
if (!isPresignedUrl && apiKey) {
|
|
1988
|
+
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
1989
|
+
}
|
|
1990
|
+
const tarballResponse = await fetch(versionInfo.downloadUrl, {
|
|
1991
|
+
headers: downloadHeaders,
|
|
1992
|
+
redirect: "follow"
|
|
1993
|
+
});
|
|
1994
|
+
if (!tarballResponse.ok) {
|
|
1995
|
+
console.error(
|
|
1996
|
+
`Error: Failed to download tarball for ${fullName} (${tarballResponse.status})`
|
|
1997
|
+
);
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
|
|
2001
|
+
const integrity = calculateIntegrity(tarballBuffer);
|
|
2002
|
+
await addToLockfile(fullName, {
|
|
2003
|
+
version: resolved,
|
|
2004
|
+
resolved: versionInfo.downloadUrl,
|
|
2005
|
+
integrity
|
|
2006
|
+
});
|
|
2007
|
+
await writeToCache(cacheDir, integrity, tarballBuffer);
|
|
2008
|
+
console.log(` Resolved ${fullName}@${resolved}`);
|
|
2009
|
+
}
|
|
2010
|
+
lockfile = await readLockfile();
|
|
2011
|
+
}
|
|
2012
|
+
const missingGitHubDeps = [];
|
|
2013
|
+
for (const [specifier, ref] of Object.entries(manifestGitHubDeps)) {
|
|
2014
|
+
if (!lockfileGitHubPackages[specifier]) {
|
|
2015
|
+
missingGitHubDeps.push({ specifier, ref });
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
if (missingGitHubDeps.length > 0) {
|
|
2019
|
+
if (options.frozenLockfile) {
|
|
2020
|
+
console.error(
|
|
2021
|
+
"Error: GitHub dependencies in pspm.json are not in lockfile. Cannot install with --frozen-lockfile"
|
|
2022
|
+
);
|
|
2023
|
+
console.error("Missing GitHub dependencies:");
|
|
2024
|
+
for (const dep of missingGitHubDeps) {
|
|
2025
|
+
console.error(` - ${dep.specifier}@${dep.ref}`);
|
|
2026
|
+
}
|
|
2027
|
+
process.exit(1);
|
|
2028
|
+
}
|
|
2029
|
+
console.log(
|
|
2030
|
+
`
|
|
2031
|
+
Resolving ${missingGitHubDeps.length} GitHub dependency(ies)...
|
|
2032
|
+
`
|
|
2033
|
+
);
|
|
2034
|
+
for (const { specifier, ref } of missingGitHubDeps) {
|
|
2035
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2036
|
+
if (!parsed) {
|
|
2037
|
+
console.error(`Error: Invalid GitHub specifier: ${specifier}`);
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
parsed.ref = parsed.ref || ref;
|
|
2041
|
+
console.log(`Resolving ${getGitHubDisplayName(parsed)}...`);
|
|
2042
|
+
try {
|
|
2043
|
+
const result = await downloadGitHubPackage(parsed);
|
|
2044
|
+
await extractGitHubPackage(parsed, result.buffer, skillsDir);
|
|
2045
|
+
const entry = {
|
|
2046
|
+
version: result.commit.slice(0, 7),
|
|
2047
|
+
resolved: `https://github.com/${parsed.owner}/${parsed.repo}`,
|
|
2048
|
+
integrity: result.integrity,
|
|
2049
|
+
gitCommit: result.commit,
|
|
2050
|
+
gitRef: ref || "HEAD"
|
|
2051
|
+
};
|
|
2052
|
+
await addGitHubToLockfile(specifier, entry);
|
|
2053
|
+
await writeToCache(cacheDir, result.integrity, result.buffer);
|
|
2054
|
+
console.log(
|
|
2055
|
+
` Resolved ${specifier} (${ref}@${result.commit.slice(0, 7)})`
|
|
2056
|
+
);
|
|
2057
|
+
} catch (error) {
|
|
2058
|
+
if (error instanceof GitHubRateLimitError) {
|
|
2059
|
+
console.error(`Error: ${error.message}`);
|
|
2060
|
+
} else if (error instanceof GitHubPathNotFoundError) {
|
|
2061
|
+
console.error(`Error: ${error.message}`);
|
|
2062
|
+
} else if (error instanceof GitHubNotFoundError) {
|
|
2063
|
+
console.error(`Error: ${error.message}`);
|
|
2064
|
+
} else {
|
|
2065
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2066
|
+
console.error(`Error resolving ${specifier}: ${message}`);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
lockfile = await readLockfile();
|
|
2071
|
+
}
|
|
2072
|
+
const packages = lockfile?.packages ?? lockfile?.skills ?? {};
|
|
2073
|
+
const packageCount = Object.keys(packages).length;
|
|
2074
|
+
if (packageCount > 0) {
|
|
2075
|
+
console.log(`
|
|
2076
|
+
Installing ${packageCount} registry skill(s)...
|
|
2077
|
+
`);
|
|
2078
|
+
const entries = Object.entries(packages);
|
|
2079
|
+
for (const [fullName, entry] of entries) {
|
|
2080
|
+
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
2081
|
+
if (!match) {
|
|
2082
|
+
console.warn(`Warning: Invalid skill name in lockfile: ${fullName}`);
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
const [, username, name] = match;
|
|
2086
|
+
console.log(`Installing ${fullName}@${entry.version}...`);
|
|
2087
|
+
let tarballBuffer;
|
|
2088
|
+
let fromCache = false;
|
|
2089
|
+
const cachedTarball = await readFromCache(cacheDir, entry.integrity);
|
|
2090
|
+
if (cachedTarball) {
|
|
2091
|
+
tarballBuffer = cachedTarball;
|
|
2092
|
+
fromCache = true;
|
|
2093
|
+
} else {
|
|
2094
|
+
const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
|
|
2095
|
+
const downloadHeaders = {};
|
|
2096
|
+
if (!isPresignedUrl && apiKey) {
|
|
2097
|
+
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
2098
|
+
}
|
|
2099
|
+
const response = await fetch(entry.resolved, {
|
|
2100
|
+
headers: downloadHeaders,
|
|
2101
|
+
redirect: "follow"
|
|
2102
|
+
});
|
|
2103
|
+
if (!response.ok) {
|
|
2104
|
+
if (response.status === 401) {
|
|
2105
|
+
if (!apiKey) {
|
|
2106
|
+
console.error(
|
|
2107
|
+
` Error: ${fullName} requires authentication. Run 'pspm login' first.`
|
|
2108
|
+
);
|
|
2109
|
+
} else {
|
|
2110
|
+
console.error(
|
|
2111
|
+
` Error: Access denied to ${fullName}. You may not have permission to access this private package.`
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
} else {
|
|
2115
|
+
console.error(
|
|
2116
|
+
` Error: Failed to download ${fullName} (${response.status})`
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
tarballBuffer = Buffer.from(await response.arrayBuffer());
|
|
2122
|
+
const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
|
|
2123
|
+
if (actualIntegrity !== entry.integrity) {
|
|
2124
|
+
console.error(
|
|
2125
|
+
` Error: Checksum verification failed for ${fullName}`
|
|
2126
|
+
);
|
|
2127
|
+
if (options.frozenLockfile) {
|
|
2128
|
+
process.exit(1);
|
|
2129
|
+
}
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
await writeToCache(cacheDir, entry.integrity, tarballBuffer);
|
|
2133
|
+
}
|
|
2134
|
+
const destDir = join(skillsDir, username, name);
|
|
2135
|
+
await rm(destDir, { recursive: true, force: true });
|
|
2136
|
+
await mkdir(destDir, { recursive: true });
|
|
2137
|
+
const tempFile = join(destDir, ".temp.tgz");
|
|
2138
|
+
await writeFile(tempFile, tarballBuffer);
|
|
2139
|
+
const { exec: exec2 } = await import('child_process');
|
|
2140
|
+
const { promisify: promisify2 } = await import('util');
|
|
2141
|
+
const execAsync = promisify2(exec2);
|
|
2142
|
+
try {
|
|
2143
|
+
await execAsync(
|
|
2144
|
+
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
2145
|
+
);
|
|
2146
|
+
} finally {
|
|
2147
|
+
await rm(tempFile, { force: true });
|
|
2148
|
+
}
|
|
2149
|
+
console.log(
|
|
2150
|
+
` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
|
|
2151
|
+
);
|
|
2152
|
+
installedSkills.push({
|
|
2153
|
+
name,
|
|
2154
|
+
sourcePath: getRegistrySkillPath(username, name)
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
const githubPackages = lockfile?.githubPackages ?? {};
|
|
2159
|
+
const githubCount = Object.keys(githubPackages).length;
|
|
2160
|
+
if (githubCount > 0) {
|
|
2161
|
+
console.log(`
|
|
2162
|
+
Installing ${githubCount} GitHub skill(s)...
|
|
2163
|
+
`);
|
|
2164
|
+
for (const [specifier, entry] of Object.entries(githubPackages)) {
|
|
2165
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2166
|
+
if (!parsed) {
|
|
2167
|
+
console.warn(
|
|
2168
|
+
`Warning: Invalid GitHub specifier in lockfile: ${specifier}`
|
|
2169
|
+
);
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
const ghEntry = entry;
|
|
2173
|
+
console.log(
|
|
2174
|
+
`Installing ${specifier} (${ghEntry.gitRef}@${ghEntry.gitCommit.slice(0, 7)})...`
|
|
2175
|
+
);
|
|
2176
|
+
let tarballBuffer;
|
|
2177
|
+
let fromCache = false;
|
|
2178
|
+
const cachedTarball = await readFromCache(cacheDir, ghEntry.integrity);
|
|
2179
|
+
if (cachedTarball) {
|
|
2180
|
+
tarballBuffer = cachedTarball;
|
|
2181
|
+
fromCache = true;
|
|
2182
|
+
} else {
|
|
2183
|
+
try {
|
|
2184
|
+
const specWithCommit = { ...parsed, ref: ghEntry.gitCommit };
|
|
2185
|
+
const result = await downloadGitHubPackage(specWithCommit);
|
|
2186
|
+
tarballBuffer = result.buffer;
|
|
2187
|
+
if (result.integrity !== ghEntry.integrity) {
|
|
2188
|
+
console.error(
|
|
2189
|
+
` Error: Checksum verification failed for ${specifier}`
|
|
2190
|
+
);
|
|
2191
|
+
if (options.frozenLockfile) {
|
|
2192
|
+
process.exit(1);
|
|
2193
|
+
}
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
await writeToCache(cacheDir, ghEntry.integrity, tarballBuffer);
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
if (error instanceof GitHubRateLimitError) {
|
|
2199
|
+
console.error(` Error: ${error.message}`);
|
|
2200
|
+
} else if (error instanceof GitHubPathNotFoundError) {
|
|
2201
|
+
console.error(` Error: ${error.message}`);
|
|
2202
|
+
} else if (error instanceof GitHubNotFoundError) {
|
|
2203
|
+
console.error(` Error: ${error.message}`);
|
|
2204
|
+
} else {
|
|
2205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2206
|
+
console.error(` Error downloading ${specifier}: ${message}`);
|
|
2207
|
+
}
|
|
2208
|
+
continue;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
try {
|
|
2212
|
+
const destPath = await extractGitHubPackage(
|
|
2213
|
+
parsed,
|
|
2214
|
+
tarballBuffer,
|
|
2215
|
+
skillsDir
|
|
2216
|
+
);
|
|
2217
|
+
console.log(
|
|
2218
|
+
` Installed to ${destPath}${fromCache ? " (from cache)" : ""}`
|
|
2219
|
+
);
|
|
2220
|
+
const skillName = getGitHubSkillName(parsed);
|
|
2221
|
+
installedSkills.push({
|
|
2222
|
+
name: skillName,
|
|
2223
|
+
sourcePath: getGitHubSkillPath(
|
|
2224
|
+
parsed.owner,
|
|
2225
|
+
parsed.repo,
|
|
2226
|
+
parsed.path
|
|
2227
|
+
)
|
|
2228
|
+
});
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2231
|
+
console.error(` Error extracting ${specifier}: ${message}`);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
if (installedSkills.length > 0 && agents[0] !== "none") {
|
|
2236
|
+
console.log(`
|
|
2237
|
+
Creating symlinks for agent(s): ${agents.join(", ")}...`);
|
|
2238
|
+
await createAgentSymlinks(installedSkills, {
|
|
2239
|
+
agents,
|
|
2240
|
+
projectRoot: process.cwd(),
|
|
2241
|
+
agentConfigs
|
|
2242
|
+
});
|
|
2243
|
+
console.log(" Symlinks created.");
|
|
606
2244
|
}
|
|
607
|
-
const
|
|
608
|
-
if (
|
|
609
|
-
|
|
2245
|
+
const totalCount = packageCount + githubCount;
|
|
2246
|
+
if (totalCount === 0) {
|
|
2247
|
+
console.log("No skills to install.");
|
|
610
2248
|
} else {
|
|
611
|
-
|
|
612
|
-
|
|
2249
|
+
console.log(`
|
|
2250
|
+
All ${totalCount} skill(s) installed.`);
|
|
613
2251
|
}
|
|
614
|
-
lines.push("");
|
|
615
|
-
await writeFile(configPath, lines.join("\n"));
|
|
616
|
-
console.log("Created .pspmrc");
|
|
617
|
-
console.log("");
|
|
618
|
-
console.log("Contents:");
|
|
619
|
-
console.log(lines.join("\n"));
|
|
620
|
-
console.log("Note: .pspmrc should be committed to version control.");
|
|
621
|
-
console.log("API keys should NOT be stored here - use pspm login instead.");
|
|
622
2252
|
} catch (error) {
|
|
623
2253
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
624
2254
|
console.error(`Error: ${message}`);
|
|
@@ -626,109 +2256,71 @@ async function configInit(options) {
|
|
|
626
2256
|
}
|
|
627
2257
|
}
|
|
628
2258
|
|
|
629
|
-
// src/commands/
|
|
630
|
-
async function
|
|
631
|
-
try {
|
|
632
|
-
const resolved = await resolveConfig();
|
|
633
|
-
const projectConfig = await findProjectConfig();
|
|
634
|
-
const configPath = getConfigPath();
|
|
635
|
-
console.log("Resolved Configuration:\n");
|
|
636
|
-
console.log(` Registry URL: ${resolved.registryUrl}`);
|
|
637
|
-
console.log(` API Key: ${resolved.apiKey ? "***" : "(not set)"}`);
|
|
638
|
-
console.log(` Username: ${resolved.username || "(not set)"}`);
|
|
639
|
-
console.log("");
|
|
640
|
-
console.log("Config Locations:");
|
|
641
|
-
console.log(` User config: ${configPath}`);
|
|
642
|
-
console.log(` Project config: ${projectConfig ? ".pspmrc" : "(none)"}`);
|
|
643
|
-
console.log("");
|
|
644
|
-
console.log("Environment Variables:");
|
|
645
|
-
console.log(
|
|
646
|
-
` PSPM_REGISTRY_URL: ${process.env.PSPM_REGISTRY_URL || "(not set)"}`
|
|
647
|
-
);
|
|
648
|
-
console.log(
|
|
649
|
-
` PSPM_API_KEY: ${process.env.PSPM_API_KEY ? "***" : "(not set)"}`
|
|
650
|
-
);
|
|
651
|
-
} catch (error) {
|
|
652
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
653
|
-
console.error(`Error: ${message}`);
|
|
654
|
-
process.exit(1);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
async function install(options) {
|
|
2259
|
+
// src/commands/link.ts
|
|
2260
|
+
async function link(options) {
|
|
658
2261
|
try {
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
return;
|
|
2262
|
+
const manifest = await readManifest();
|
|
2263
|
+
const agentConfigs = manifest?.agents;
|
|
2264
|
+
let agents;
|
|
2265
|
+
if (options.agent) {
|
|
2266
|
+
agents = parseAgentArg(options.agent);
|
|
2267
|
+
} else if (manifest) {
|
|
2268
|
+
agents = parseAgentArg(void 0);
|
|
2269
|
+
} else if (options.yes) {
|
|
2270
|
+
agents = parseAgentArg(void 0);
|
|
2271
|
+
} else {
|
|
2272
|
+
console.log("No pspm.json found. Let's set up your project.\n");
|
|
2273
|
+
agents = await promptForAgents();
|
|
672
2274
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
console.log("No skills in lockfile. Nothing to install.");
|
|
2275
|
+
if (agents.length === 1 && agents[0] === "none") {
|
|
2276
|
+
console.log("Skipping symlink creation (--agent none)");
|
|
676
2277
|
return;
|
|
677
2278
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
console.warn(`Warning: Invalid skill name in lockfile: ${fullName}`);
|
|
2279
|
+
const skills = [];
|
|
2280
|
+
const registrySkills = await listLockfileSkills();
|
|
2281
|
+
for (const { name } of registrySkills) {
|
|
2282
|
+
const parsed = parseSkillSpecifier(name);
|
|
2283
|
+
if (!parsed) {
|
|
2284
|
+
console.warn(`Warning: Invalid skill name in lockfile: ${name}`);
|
|
685
2285
|
continue;
|
|
686
2286
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const response = await fetch(entry.resolved, {
|
|
691
|
-
headers: isPresignedUrl ? {} : {
|
|
692
|
-
Authorization: `Bearer ${apiKey}`
|
|
693
|
-
},
|
|
694
|
-
redirect: "follow"
|
|
2287
|
+
skills.push({
|
|
2288
|
+
name: parsed.name,
|
|
2289
|
+
sourcePath: getRegistrySkillPath(parsed.username, parsed.name)
|
|
695
2290
|
});
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
2291
|
+
}
|
|
2292
|
+
const githubSkills = await listLockfileGitHubPackages();
|
|
2293
|
+
for (const { specifier } of githubSkills) {
|
|
2294
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2295
|
+
if (!parsed) {
|
|
2296
|
+
console.warn(
|
|
2297
|
+
`Warning: Invalid GitHub specifier in lockfile: ${specifier}`
|
|
699
2298
|
);
|
|
700
2299
|
continue;
|
|
701
2300
|
}
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
);
|
|
725
|
-
} finally {
|
|
726
|
-
await rm(tempFile, { force: true });
|
|
727
|
-
}
|
|
728
|
-
console.log(` Installed to ${destDir}`);
|
|
2301
|
+
const skillName = getGitHubSkillName(parsed);
|
|
2302
|
+
skills.push({
|
|
2303
|
+
name: skillName,
|
|
2304
|
+
sourcePath: getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path)
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
if (skills.length === 0) {
|
|
2308
|
+
console.log("No skills found in lockfile. Nothing to link.");
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
console.log(
|
|
2312
|
+
`Creating symlinks for ${skills.length} skill(s) to agent(s): ${agents.join(", ")}...`
|
|
2313
|
+
);
|
|
2314
|
+
await createAgentSymlinks(skills, {
|
|
2315
|
+
agents,
|
|
2316
|
+
projectRoot: process.cwd(),
|
|
2317
|
+
agentConfigs
|
|
2318
|
+
});
|
|
2319
|
+
console.log("Symlinks created successfully.");
|
|
2320
|
+
console.log("\nLinked skills:");
|
|
2321
|
+
for (const skill of skills) {
|
|
2322
|
+
console.log(` ${skill.name} -> ${skill.sourcePath}`);
|
|
729
2323
|
}
|
|
730
|
-
console.log(`
|
|
731
|
-
All ${skillCount} skill(s) installed.`);
|
|
732
2324
|
} catch (error) {
|
|
733
2325
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
734
2326
|
console.error(`Error: ${message}`);
|
|
@@ -737,7 +2329,76 @@ All ${skillCount} skill(s) installed.`);
|
|
|
737
2329
|
}
|
|
738
2330
|
async function list(options) {
|
|
739
2331
|
try {
|
|
740
|
-
const
|
|
2332
|
+
const registrySkills = await listLockfileSkills();
|
|
2333
|
+
const githubSkills = await listLockfileGitHubPackages();
|
|
2334
|
+
const manifest = await readManifest();
|
|
2335
|
+
const agentConfigs = manifest?.agents;
|
|
2336
|
+
const availableAgents = getAvailableAgents(agentConfigs);
|
|
2337
|
+
const projectRoot = process.cwd();
|
|
2338
|
+
const skills = [];
|
|
2339
|
+
for (const { name: fullName, entry } of registrySkills) {
|
|
2340
|
+
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
2341
|
+
if (!match) continue;
|
|
2342
|
+
const [, username, skillName] = match;
|
|
2343
|
+
const sourcePath = getRegistrySkillPath(username, skillName);
|
|
2344
|
+
const absolutePath = join(projectRoot, sourcePath);
|
|
2345
|
+
let status = "installed";
|
|
2346
|
+
try {
|
|
2347
|
+
await access$1(absolutePath);
|
|
2348
|
+
} catch {
|
|
2349
|
+
status = "missing";
|
|
2350
|
+
}
|
|
2351
|
+
const linkedAgents = await getLinkedAgents(
|
|
2352
|
+
skillName,
|
|
2353
|
+
availableAgents,
|
|
2354
|
+
projectRoot,
|
|
2355
|
+
agentConfigs
|
|
2356
|
+
);
|
|
2357
|
+
skills.push({
|
|
2358
|
+
name: skillName,
|
|
2359
|
+
fullName,
|
|
2360
|
+
version: entry.version,
|
|
2361
|
+
source: "registry",
|
|
2362
|
+
sourcePath,
|
|
2363
|
+
status,
|
|
2364
|
+
linkedAgents
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
for (const { specifier, entry } of githubSkills) {
|
|
2368
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2369
|
+
if (!parsed) continue;
|
|
2370
|
+
const ghEntry = entry;
|
|
2371
|
+
const skillName = getGitHubSkillName(parsed);
|
|
2372
|
+
const sourcePath = getGitHubSkillPath(
|
|
2373
|
+
parsed.owner,
|
|
2374
|
+
parsed.repo,
|
|
2375
|
+
parsed.path
|
|
2376
|
+
);
|
|
2377
|
+
const absolutePath = join(projectRoot, sourcePath);
|
|
2378
|
+
let status = "installed";
|
|
2379
|
+
try {
|
|
2380
|
+
await access$1(absolutePath);
|
|
2381
|
+
} catch {
|
|
2382
|
+
status = "missing";
|
|
2383
|
+
}
|
|
2384
|
+
const linkedAgents = await getLinkedAgents(
|
|
2385
|
+
skillName,
|
|
2386
|
+
availableAgents,
|
|
2387
|
+
projectRoot,
|
|
2388
|
+
agentConfigs
|
|
2389
|
+
);
|
|
2390
|
+
skills.push({
|
|
2391
|
+
name: skillName,
|
|
2392
|
+
fullName: specifier,
|
|
2393
|
+
version: ghEntry.gitCommit.slice(0, 7),
|
|
2394
|
+
source: "github",
|
|
2395
|
+
sourcePath,
|
|
2396
|
+
status,
|
|
2397
|
+
linkedAgents,
|
|
2398
|
+
gitRef: ghEntry.gitRef,
|
|
2399
|
+
gitCommit: ghEntry.gitCommit
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
741
2402
|
if (skills.length === 0) {
|
|
742
2403
|
console.log("No skills installed.");
|
|
743
2404
|
return;
|
|
@@ -746,26 +2407,33 @@ async function list(options) {
|
|
|
746
2407
|
console.log(JSON.stringify(skills, null, 2));
|
|
747
2408
|
return;
|
|
748
2409
|
}
|
|
749
|
-
const skillsDir = getSkillsDir();
|
|
750
2410
|
console.log("Installed skills:\n");
|
|
751
|
-
for (const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
try {
|
|
758
|
-
await access(skillPath);
|
|
759
|
-
} catch {
|
|
760
|
-
status = "missing";
|
|
2411
|
+
for (const skill of skills) {
|
|
2412
|
+
if (skill.source === "registry") {
|
|
2413
|
+
console.log(` ${skill.fullName}@${skill.version} (registry)`);
|
|
2414
|
+
} else {
|
|
2415
|
+
const refInfo = skill.gitRef ? `${skill.gitRef}@${skill.gitCommit?.slice(0, 7)}` : skill.version;
|
|
2416
|
+
console.log(` ${skill.fullName} (${refInfo})`);
|
|
761
2417
|
}
|
|
762
|
-
|
|
763
|
-
if (status === "missing") {
|
|
2418
|
+
if (skill.status === "missing") {
|
|
764
2419
|
console.log(` Status: MISSING (run 'pspm install' to restore)`);
|
|
765
2420
|
}
|
|
2421
|
+
if (skill.linkedAgents.length > 0) {
|
|
2422
|
+
for (const agent of skill.linkedAgents) {
|
|
2423
|
+
const config2 = resolveAgentConfig(agent, agentConfigs);
|
|
2424
|
+
if (config2) {
|
|
2425
|
+
console.log(` -> ${config2.skillsDir}/${skill.name}`);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
766
2429
|
}
|
|
2430
|
+
const registryCount = skills.filter((s) => s.source === "registry").length;
|
|
2431
|
+
const githubCount = skills.filter((s) => s.source === "github").length;
|
|
2432
|
+
const parts = [];
|
|
2433
|
+
if (registryCount > 0) parts.push(`${registryCount} registry`);
|
|
2434
|
+
if (githubCount > 0) parts.push(`${githubCount} github`);
|
|
767
2435
|
console.log(`
|
|
768
|
-
Total: ${skills.length} skill(s)`);
|
|
2436
|
+
Total: ${skills.length} skill(s) (${parts.join(", ")})`);
|
|
769
2437
|
} catch (error) {
|
|
770
2438
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
771
2439
|
console.error(`Error: ${message}`);
|
|
@@ -778,7 +2446,7 @@ function getWebAppUrl(registryUrl) {
|
|
|
778
2446
|
return process.env.PSPM_WEB_URL.replace(/\/$/, "");
|
|
779
2447
|
}
|
|
780
2448
|
try {
|
|
781
|
-
const url = new URL(registryUrl);
|
|
2449
|
+
const url = new URL$1(registryUrl);
|
|
782
2450
|
return `${url.protocol}//${url.host}`;
|
|
783
2451
|
} catch {
|
|
784
2452
|
return DEFAULT_WEB_APP_URL;
|
|
@@ -786,7 +2454,7 @@ function getWebAppUrl(registryUrl) {
|
|
|
786
2454
|
}
|
|
787
2455
|
function getServerUrl(registryUrl) {
|
|
788
2456
|
try {
|
|
789
|
-
const url = new URL(registryUrl);
|
|
2457
|
+
const url = new URL$1(registryUrl);
|
|
790
2458
|
return `${url.protocol}//${url.host}`;
|
|
791
2459
|
} catch {
|
|
792
2460
|
return DEFAULT_WEB_APP_URL;
|
|
@@ -818,7 +2486,7 @@ function startCallbackServer(expectedState) {
|
|
|
818
2486
|
rejectToken = reject;
|
|
819
2487
|
});
|
|
820
2488
|
const server = http.createServer((req, res) => {
|
|
821
|
-
const url = new URL(req.url || "/", `http://localhost`);
|
|
2489
|
+
const url = new URL$1(req.url || "/", `http://localhost`);
|
|
822
2490
|
if (url.pathname === "/callback") {
|
|
823
2491
|
const token = url.searchParams.get("token");
|
|
824
2492
|
const state = url.searchParams.get("state");
|
|
@@ -964,7 +2632,132 @@ async function logout() {
|
|
|
964
2632
|
process.exit(1);
|
|
965
2633
|
}
|
|
966
2634
|
}
|
|
2635
|
+
async function migrate(options) {
|
|
2636
|
+
try {
|
|
2637
|
+
const legacySkillsDir = getLegacySkillsDir();
|
|
2638
|
+
const newSkillsDir = getSkillsDir();
|
|
2639
|
+
const legacyLockfilePath = getLegacyLockfilePath();
|
|
2640
|
+
const newLockfilePath = getLockfilePath();
|
|
2641
|
+
const pspmDir = getPspmDir();
|
|
2642
|
+
let migrationNeeded = false;
|
|
2643
|
+
const actions = [];
|
|
2644
|
+
try {
|
|
2645
|
+
const legacyStats = await stat(legacySkillsDir);
|
|
2646
|
+
if (legacyStats.isDirectory()) {
|
|
2647
|
+
const contents = await readdir(legacySkillsDir);
|
|
2648
|
+
if (contents.length > 0) {
|
|
2649
|
+
migrationNeeded = true;
|
|
2650
|
+
actions.push(`Move .skills/ \u2192 .pspm/skills/`);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
} catch {
|
|
2654
|
+
}
|
|
2655
|
+
try {
|
|
2656
|
+
await stat(legacyLockfilePath);
|
|
2657
|
+
try {
|
|
2658
|
+
await stat(newLockfilePath);
|
|
2659
|
+
actions.push(
|
|
2660
|
+
`Note: Both skill-lock.json and pspm-lock.json exist. Manual merge may be needed.`
|
|
2661
|
+
);
|
|
2662
|
+
} catch {
|
|
2663
|
+
migrationNeeded = true;
|
|
2664
|
+
actions.push(`Migrate skill-lock.json \u2192 pspm-lock.json`);
|
|
2665
|
+
}
|
|
2666
|
+
} catch {
|
|
2667
|
+
}
|
|
2668
|
+
if (!migrationNeeded && actions.length === 0) {
|
|
2669
|
+
console.log(
|
|
2670
|
+
"No migration needed. Project is already using the new structure."
|
|
2671
|
+
);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
if (options.dryRun) {
|
|
2675
|
+
console.log("Migration plan (dry run):");
|
|
2676
|
+
console.log("");
|
|
2677
|
+
for (const action of actions) {
|
|
2678
|
+
console.log(` - ${action}`);
|
|
2679
|
+
}
|
|
2680
|
+
console.log("");
|
|
2681
|
+
console.log("Run without --dry-run to perform migration.");
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
console.log("Migrating project structure...\n");
|
|
2685
|
+
const lockfileMigrated = await migrateLockfileIfNeeded();
|
|
2686
|
+
if (lockfileMigrated) {
|
|
2687
|
+
console.log(" \u2713 Migrated skill-lock.json \u2192 pspm-lock.json");
|
|
2688
|
+
}
|
|
2689
|
+
try {
|
|
2690
|
+
const legacyStats = await stat(legacySkillsDir);
|
|
2691
|
+
if (legacyStats.isDirectory()) {
|
|
2692
|
+
const contents = await readdir(legacySkillsDir);
|
|
2693
|
+
if (contents.length > 0) {
|
|
2694
|
+
await mkdir(pspmDir, { recursive: true });
|
|
2695
|
+
try {
|
|
2696
|
+
const newStats = await stat(newSkillsDir);
|
|
2697
|
+
if (newStats.isDirectory()) {
|
|
2698
|
+
const newContents = await readdir(newSkillsDir);
|
|
2699
|
+
if (newContents.length > 0) {
|
|
2700
|
+
console.log(
|
|
2701
|
+
" ! Both .skills/ and .pspm/skills/ have content. Manual merge required."
|
|
2702
|
+
);
|
|
2703
|
+
} else {
|
|
2704
|
+
await rm(newSkillsDir, { recursive: true, force: true });
|
|
2705
|
+
await rename(legacySkillsDir, newSkillsDir);
|
|
2706
|
+
console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
} catch {
|
|
2710
|
+
await rename(legacySkillsDir, newSkillsDir);
|
|
2711
|
+
console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
} catch {
|
|
2716
|
+
}
|
|
2717
|
+
console.log("");
|
|
2718
|
+
console.log("Migration complete!");
|
|
2719
|
+
console.log("");
|
|
2720
|
+
console.log(
|
|
2721
|
+
"You can safely delete these legacy files if they still exist:"
|
|
2722
|
+
);
|
|
2723
|
+
console.log(" - skill-lock.json (replaced by pspm-lock.json)");
|
|
2724
|
+
console.log(" - .skills/ (replaced by .pspm/skills/)");
|
|
2725
|
+
console.log("");
|
|
2726
|
+
console.log("Update your .gitignore to include:");
|
|
2727
|
+
console.log(" .pspm/cache/");
|
|
2728
|
+
} catch (error) {
|
|
2729
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2730
|
+
console.error(`Error: ${message}`);
|
|
2731
|
+
process.exit(1);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
967
2734
|
var exec = promisify(exec$1);
|
|
2735
|
+
async function detectManifest() {
|
|
2736
|
+
const cwd = process.cwd();
|
|
2737
|
+
const pspmJsonPath = join(cwd, "pspm.json");
|
|
2738
|
+
try {
|
|
2739
|
+
const content = await readFile(pspmJsonPath, "utf-8");
|
|
2740
|
+
const manifest = JSON.parse(content);
|
|
2741
|
+
return { type: "pspm.json", manifest, path: pspmJsonPath };
|
|
2742
|
+
} catch {
|
|
2743
|
+
}
|
|
2744
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
2745
|
+
try {
|
|
2746
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
2747
|
+
const packageJson2 = JSON.parse(content);
|
|
2748
|
+
const manifest = {
|
|
2749
|
+
name: packageJson2.name,
|
|
2750
|
+
version: packageJson2.version,
|
|
2751
|
+
description: packageJson2.description,
|
|
2752
|
+
author: typeof packageJson2.author === "string" ? packageJson2.author : packageJson2.author?.name,
|
|
2753
|
+
license: packageJson2.license,
|
|
2754
|
+
files: packageJson2.files
|
|
2755
|
+
};
|
|
2756
|
+
return { type: "package.json", manifest, path: packageJsonPath };
|
|
2757
|
+
} catch {
|
|
2758
|
+
throw new Error("No pspm.json or package.json found in current directory");
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
968
2761
|
function formatBytes(bytes) {
|
|
969
2762
|
if (bytes < 1024) return `${bytes}B`;
|
|
970
2763
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kB`;
|
|
@@ -996,23 +2789,26 @@ async function publishCommand(options) {
|
|
|
996
2789
|
try {
|
|
997
2790
|
const apiKey = await requireApiKey();
|
|
998
2791
|
const registryUrl = await getRegistryUrl();
|
|
999
|
-
const
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
if (!packageJson2.name) {
|
|
1009
|
-
console.error("Error: package.json must have a 'name' field");
|
|
1010
|
-
process.exit(1);
|
|
2792
|
+
const detection = await detectManifest();
|
|
2793
|
+
const manifest = detection.manifest;
|
|
2794
|
+
if (detection.type === "package.json") {
|
|
2795
|
+
console.log("pspm warn Using package.json instead of pspm.json");
|
|
2796
|
+
console.log(
|
|
2797
|
+
"pspm warn Run 'pspm init' to create a dedicated pspm.json manifest"
|
|
2798
|
+
);
|
|
2799
|
+
console.log("");
|
|
1011
2800
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
2801
|
+
const validation = validateManifest(manifest);
|
|
2802
|
+
if (!validation.valid) {
|
|
2803
|
+
console.error(`Error: ${validation.error}`);
|
|
1014
2804
|
process.exit(1);
|
|
1015
2805
|
}
|
|
2806
|
+
const packageJson2 = {
|
|
2807
|
+
name: manifest.name,
|
|
2808
|
+
version: manifest.version,
|
|
2809
|
+
description: manifest.description,
|
|
2810
|
+
files: manifest.files
|
|
2811
|
+
};
|
|
1016
2812
|
if (options.bump) {
|
|
1017
2813
|
const semver2 = await import('semver');
|
|
1018
2814
|
const newVersion = semver2.default.inc(packageJson2.version, options.bump);
|
|
@@ -1030,13 +2826,7 @@ async function publishCommand(options) {
|
|
|
1030
2826
|
const tempDir = join(process.cwd(), ".pspm-publish");
|
|
1031
2827
|
try {
|
|
1032
2828
|
await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
|
|
1033
|
-
const files = packageJson2.files || [
|
|
1034
|
-
"package.json",
|
|
1035
|
-
"SKILL.md",
|
|
1036
|
-
"runtime",
|
|
1037
|
-
"scripts",
|
|
1038
|
-
"data"
|
|
1039
|
-
];
|
|
2829
|
+
const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
|
|
1040
2830
|
await exec(`mkdir -p "${tempDir}/package"`);
|
|
1041
2831
|
for (const file of files) {
|
|
1042
2832
|
try {
|
|
@@ -1046,7 +2836,18 @@ async function publishCommand(options) {
|
|
|
1046
2836
|
} catch {
|
|
1047
2837
|
}
|
|
1048
2838
|
}
|
|
1049
|
-
|
|
2839
|
+
if (detection.type === "pspm.json") {
|
|
2840
|
+
await exec(`cp pspm.json "${tempDir}/package/"`);
|
|
2841
|
+
try {
|
|
2842
|
+
await stat(join(process.cwd(), "package.json"));
|
|
2843
|
+
await exec(
|
|
2844
|
+
`cp package.json "${tempDir}/package/" 2>/dev/null || true`
|
|
2845
|
+
);
|
|
2846
|
+
} catch {
|
|
2847
|
+
}
|
|
2848
|
+
} else {
|
|
2849
|
+
await exec(`cp package.json "${tempDir}/package/"`);
|
|
2850
|
+
}
|
|
1050
2851
|
const packageDir = join(tempDir, "package");
|
|
1051
2852
|
const tarballContents = await getFilesWithSizes(packageDir, packageDir);
|
|
1052
2853
|
const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
|
|
@@ -1103,6 +2904,25 @@ async function publishCommand(options) {
|
|
|
1103
2904
|
`+ @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
|
|
1104
2905
|
);
|
|
1105
2906
|
console.log(`Checksum: ${result.version.checksum}`);
|
|
2907
|
+
if (options.access) {
|
|
2908
|
+
console.log(`
|
|
2909
|
+
Setting visibility to ${options.access}...`);
|
|
2910
|
+
const accessResponse = await changeSkillAccess(packageJson2.name, {
|
|
2911
|
+
visibility: options.access
|
|
2912
|
+
});
|
|
2913
|
+
if (accessResponse.status !== 200 || !accessResponse.data) {
|
|
2914
|
+
console.warn(
|
|
2915
|
+
`Warning: Failed to set visibility: ${accessResponse.error ?? "Unknown error"}`
|
|
2916
|
+
);
|
|
2917
|
+
} else {
|
|
2918
|
+
console.log(`Package is now ${accessResponse.data.visibility}`);
|
|
2919
|
+
if (options.access === "public") {
|
|
2920
|
+
console.log(
|
|
2921
|
+
"Note: This action is irreversible. Public packages cannot be made private."
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
1106
2926
|
} finally {
|
|
1107
2927
|
await exec(`rm -rf "${tempDir}"`).catch(() => {
|
|
1108
2928
|
});
|
|
@@ -1118,59 +2938,103 @@ async function publishCommand(options) {
|
|
|
1118
2938
|
}
|
|
1119
2939
|
async function remove(nameOrSpecifier) {
|
|
1120
2940
|
try {
|
|
1121
|
-
await
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
console.error(`Error: Invalid skill specifier: ${nameOrSpecifier}`);
|
|
1129
|
-
process.exit(1);
|
|
1130
|
-
}
|
|
1131
|
-
fullName = `@user/${match[1]}/${match[2]}`;
|
|
1132
|
-
username = match[1];
|
|
1133
|
-
name = match[2];
|
|
2941
|
+
const manifest = await readManifest();
|
|
2942
|
+
const agentConfigs = manifest?.agents;
|
|
2943
|
+
const agents = getAvailableAgents(agentConfigs);
|
|
2944
|
+
if (isGitHubSpecifier(nameOrSpecifier)) {
|
|
2945
|
+
await removeGitHub(nameOrSpecifier, agents, agentConfigs);
|
|
2946
|
+
} else if (nameOrSpecifier.startsWith("@user/")) {
|
|
2947
|
+
await removeRegistry(nameOrSpecifier, agents, agentConfigs);
|
|
1134
2948
|
} else {
|
|
1135
|
-
|
|
1136
|
-
const found = skills.find((s) => {
|
|
1137
|
-
const match2 = s.name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1138
|
-
return match2 && match2[2] === nameOrSpecifier;
|
|
1139
|
-
});
|
|
1140
|
-
if (!found) {
|
|
1141
|
-
console.error(
|
|
1142
|
-
`Error: Skill "${nameOrSpecifier}" not found in lockfile`
|
|
1143
|
-
);
|
|
1144
|
-
process.exit(1);
|
|
1145
|
-
}
|
|
1146
|
-
fullName = found.name;
|
|
1147
|
-
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
1148
|
-
if (!match) {
|
|
1149
|
-
console.error(`Error: Invalid skill name in lockfile: ${fullName}`);
|
|
1150
|
-
process.exit(1);
|
|
1151
|
-
}
|
|
1152
|
-
username = match[1];
|
|
1153
|
-
name = match[2];
|
|
1154
|
-
}
|
|
1155
|
-
console.log(`Removing ${fullName}...`);
|
|
1156
|
-
const removed = await removeFromLockfile(fullName);
|
|
1157
|
-
if (!removed) {
|
|
1158
|
-
console.error(`Error: ${fullName} not found in lockfile`);
|
|
1159
|
-
process.exit(1);
|
|
1160
|
-
}
|
|
1161
|
-
const skillsDir = getSkillsDir();
|
|
1162
|
-
const destDir = join(skillsDir, username, name);
|
|
1163
|
-
try {
|
|
1164
|
-
await rm(destDir, { recursive: true, force: true });
|
|
1165
|
-
} catch {
|
|
2949
|
+
await removeByShortName(nameOrSpecifier, agents, agentConfigs);
|
|
1166
2950
|
}
|
|
1167
|
-
console.log(`Removed ${fullName}`);
|
|
1168
2951
|
} catch (error) {
|
|
1169
2952
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1170
2953
|
console.error(`Error: ${message}`);
|
|
1171
2954
|
process.exit(1);
|
|
1172
2955
|
}
|
|
1173
2956
|
}
|
|
2957
|
+
async function removeRegistry(specifier, agents, agentConfigs) {
|
|
2958
|
+
const match = specifier.match(/^@user\/([^/]+)\/([^@/]+)/);
|
|
2959
|
+
if (!match) {
|
|
2960
|
+
console.error(`Error: Invalid skill specifier: ${specifier}`);
|
|
2961
|
+
process.exit(1);
|
|
2962
|
+
}
|
|
2963
|
+
const fullName = `@user/${match[1]}/${match[2]}`;
|
|
2964
|
+
const username = match[1];
|
|
2965
|
+
const name = match[2];
|
|
2966
|
+
console.log(`Removing ${fullName}...`);
|
|
2967
|
+
const removedFromLockfile = await removeFromLockfile(fullName);
|
|
2968
|
+
const removedFromManifest = await removeDependency(fullName);
|
|
2969
|
+
if (!removedFromLockfile && !removedFromManifest) {
|
|
2970
|
+
console.error(`Error: ${fullName} not found in lockfile or pspm.json`);
|
|
2971
|
+
process.exit(1);
|
|
2972
|
+
}
|
|
2973
|
+
await removeAgentSymlinks(name, {
|
|
2974
|
+
agents,
|
|
2975
|
+
projectRoot: process.cwd(),
|
|
2976
|
+
agentConfigs
|
|
2977
|
+
});
|
|
2978
|
+
const skillsDir = getSkillsDir();
|
|
2979
|
+
const destDir = join(skillsDir, username, name);
|
|
2980
|
+
try {
|
|
2981
|
+
await rm(destDir, { recursive: true, force: true });
|
|
2982
|
+
} catch {
|
|
2983
|
+
}
|
|
2984
|
+
console.log(`Removed ${fullName}`);
|
|
2985
|
+
}
|
|
2986
|
+
async function removeGitHub(specifier, agents, agentConfigs) {
|
|
2987
|
+
const parsed = parseGitHubSpecifier(specifier);
|
|
2988
|
+
if (!parsed) {
|
|
2989
|
+
console.error(`Error: Invalid GitHub specifier: ${specifier}`);
|
|
2990
|
+
process.exit(1);
|
|
2991
|
+
}
|
|
2992
|
+
const lockfileKey = parsed.path ? `github:${parsed.owner}/${parsed.repo}/${parsed.path}` : `github:${parsed.owner}/${parsed.repo}`;
|
|
2993
|
+
console.log(`Removing ${lockfileKey}...`);
|
|
2994
|
+
const removedFromLockfile = await removeGitHubFromLockfile(lockfileKey);
|
|
2995
|
+
const removedFromManifest = await removeGitHubDependency(lockfileKey);
|
|
2996
|
+
if (!removedFromLockfile && !removedFromManifest) {
|
|
2997
|
+
console.error(`Error: ${lockfileKey} not found in lockfile or pspm.json`);
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
const skillName = getGitHubSkillName(parsed);
|
|
3001
|
+
await removeAgentSymlinks(skillName, {
|
|
3002
|
+
agents,
|
|
3003
|
+
projectRoot: process.cwd(),
|
|
3004
|
+
agentConfigs
|
|
3005
|
+
});
|
|
3006
|
+
const skillsDir = getSkillsDir();
|
|
3007
|
+
const destPath = getGitHubSkillPath(parsed.owner, parsed.repo, parsed.path);
|
|
3008
|
+
const destDir = join(skillsDir, "..", destPath);
|
|
3009
|
+
try {
|
|
3010
|
+
await rm(destDir, { recursive: true, force: true });
|
|
3011
|
+
} catch {
|
|
3012
|
+
}
|
|
3013
|
+
console.log(`Removed ${lockfileKey}`);
|
|
3014
|
+
}
|
|
3015
|
+
async function removeByShortName(shortName, agents, agentConfigs) {
|
|
3016
|
+
const registrySkills = await listLockfileSkills();
|
|
3017
|
+
const foundRegistry = registrySkills.find((s) => {
|
|
3018
|
+
const match = s.name.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
3019
|
+
return match && match[2] === shortName;
|
|
3020
|
+
});
|
|
3021
|
+
if (foundRegistry) {
|
|
3022
|
+
await removeRegistry(foundRegistry.name, agents, agentConfigs);
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
const githubSkills = await listLockfileGitHubPackages();
|
|
3026
|
+
const foundGitHub = githubSkills.find((s) => {
|
|
3027
|
+
const parsed = parseGitHubSpecifier(s.specifier);
|
|
3028
|
+
if (!parsed) return false;
|
|
3029
|
+
return getGitHubSkillName(parsed) === shortName;
|
|
3030
|
+
});
|
|
3031
|
+
if (foundGitHub) {
|
|
3032
|
+
await removeGitHub(foundGitHub.specifier, agents, agentConfigs);
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
console.error(`Error: Skill "${shortName}" not found in lockfile`);
|
|
3036
|
+
process.exit(1);
|
|
3037
|
+
}
|
|
1174
3038
|
|
|
1175
3039
|
// src/commands/unpublish.ts
|
|
1176
3040
|
async function unpublish(specifier, options) {
|
|
@@ -1356,8 +3220,31 @@ program.command("logout").description("Log out and clear stored credentials").ac
|
|
|
1356
3220
|
program.command("whoami").description("Show current user information").action(async () => {
|
|
1357
3221
|
await whoami();
|
|
1358
3222
|
});
|
|
1359
|
-
program.command("
|
|
1360
|
-
await
|
|
3223
|
+
program.command("init").description("Create a new pspm.json manifest in the current directory").option("-n, --name <name>", "Skill name").option("-d, --description <desc>", "Skill description").option("-a, --author <author>", "Author name").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Overwrite existing pspm.json").action(async (options) => {
|
|
3224
|
+
await init({
|
|
3225
|
+
name: options.name,
|
|
3226
|
+
description: options.description,
|
|
3227
|
+
author: options.author,
|
|
3228
|
+
yes: options.yes,
|
|
3229
|
+
force: options.force
|
|
3230
|
+
});
|
|
3231
|
+
});
|
|
3232
|
+
program.command("migrate").description(
|
|
3233
|
+
"Migrate from old directory structure (.skills/, skill-lock.json)"
|
|
3234
|
+
).option("--dry-run", "Show what would be migrated without making changes").action(async (options) => {
|
|
3235
|
+
await migrate({ dryRun: options.dryRun });
|
|
3236
|
+
});
|
|
3237
|
+
program.command("add <specifier>").description(
|
|
3238
|
+
"Add a skill (e.g., @user/bsheng/vite_slides@^2.0.0 or github:owner/repo/path@ref)"
|
|
3239
|
+
).option("--save", "Save to lockfile (default)").option(
|
|
3240
|
+
"--agent <agents>",
|
|
3241
|
+
'Comma-separated agents for symlinks (default: "claude-code", use "none" to skip)'
|
|
3242
|
+
).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (specifier, options) => {
|
|
3243
|
+
await add(specifier, {
|
|
3244
|
+
save: options.save ?? true,
|
|
3245
|
+
agent: options.agent,
|
|
3246
|
+
yes: options.yes
|
|
3247
|
+
});
|
|
1361
3248
|
});
|
|
1362
3249
|
program.command("remove <name>").alias("rm").description("Remove an installed skill").action(async (name) => {
|
|
1363
3250
|
await remove(name);
|
|
@@ -1365,24 +3252,49 @@ program.command("remove <name>").alias("rm").description("Remove an installed sk
|
|
|
1365
3252
|
program.command("list").alias("ls").description("List installed skills").option("--json", "Output as JSON").action(async (options) => {
|
|
1366
3253
|
await list({ json: options.json });
|
|
1367
3254
|
});
|
|
1368
|
-
program.command("install").alias("i").description("Install all skills from lockfile").option("--frozen-lockfile", "Fail if lockfile is missing or outdated").option("--dir <path>", "Install skills to a specific directory").
|
|
3255
|
+
program.command("install").alias("i").description("Install all skills from lockfile").option("--frozen-lockfile", "Fail if lockfile is missing or outdated").option("--dir <path>", "Install skills to a specific directory").option(
|
|
3256
|
+
"--agent <agents>",
|
|
3257
|
+
'Comma-separated agents for symlinks (default: "claude-code", use "none" to skip)'
|
|
3258
|
+
).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (options) => {
|
|
1369
3259
|
await install({
|
|
1370
3260
|
frozenLockfile: options.frozenLockfile,
|
|
1371
|
-
dir: options.dir
|
|
3261
|
+
dir: options.dir,
|
|
3262
|
+
agent: options.agent,
|
|
3263
|
+
yes: options.yes
|
|
1372
3264
|
});
|
|
1373
3265
|
});
|
|
3266
|
+
program.command("link").description("Recreate agent symlinks without reinstalling").option(
|
|
3267
|
+
"--agent <agents>",
|
|
3268
|
+
'Comma-separated agents for symlinks (default: "claude-code", use "none" to skip)'
|
|
3269
|
+
).option("-y, --yes", "Skip agent selection prompt and use defaults").action(async (options) => {
|
|
3270
|
+
await link({ agent: options.agent, yes: options.yes });
|
|
3271
|
+
});
|
|
1374
3272
|
program.command("update").description("Update all skills to latest compatible versions").option("--dry-run", "Show what would be updated without making changes").action(async (options) => {
|
|
1375
3273
|
await update({ dryRun: options.dryRun });
|
|
1376
3274
|
});
|
|
1377
|
-
program.command("publish").description("Publish current directory as a skill").option("--bump <level>", "Bump version (major, minor, patch)").option("--tag <tag>", "Tag for the release").action(async (options) => {
|
|
3275
|
+
program.command("publish").description("Publish current directory as a skill").option("--bump <level>", "Bump version (major, minor, patch)").option("--tag <tag>", "Tag for the release").option("--access <level>", "Set package visibility (public or private)").action(async (options) => {
|
|
1378
3276
|
await publishCommand({
|
|
1379
3277
|
bump: options.bump,
|
|
1380
|
-
tag: options.tag
|
|
3278
|
+
tag: options.tag,
|
|
3279
|
+
access: options.access
|
|
1381
3280
|
});
|
|
1382
3281
|
});
|
|
1383
|
-
program.command("unpublish <specifier>").description(
|
|
3282
|
+
program.command("unpublish <specifier>").description(
|
|
3283
|
+
"Remove a published skill version (only within 72 hours of publishing)"
|
|
3284
|
+
).option("--force", "Confirm destructive action").action(async (specifier, options) => {
|
|
1384
3285
|
await unpublish(specifier, { force: options.force });
|
|
1385
3286
|
});
|
|
3287
|
+
program.command("access [specifier]").description("Change package visibility (public/private)").option("--public", "Make the package public (irreversible)").option("--private", "Make the package private (only for private packages)").action(async (specifier, options) => {
|
|
3288
|
+
await access(specifier, {
|
|
3289
|
+
public: options.public,
|
|
3290
|
+
private: options.private
|
|
3291
|
+
});
|
|
3292
|
+
});
|
|
3293
|
+
program.command("deprecate <specifier> [message]").description(
|
|
3294
|
+
"Mark a skill version as deprecated (alternative to unpublish after 72 hours)"
|
|
3295
|
+
).option("--undo", "Remove deprecation status").action(async (specifier, message, options) => {
|
|
3296
|
+
await deprecate(specifier, message, { undo: options.undo });
|
|
3297
|
+
});
|
|
1386
3298
|
program.parse();
|
|
1387
3299
|
//# sourceMappingURL=index.js.map
|
|
1388
3300
|
//# sourceMappingURL=index.js.map
|