@anytio/pspm 0.0.5 → 0.0.7
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/README.md +26 -7
- package/dist/index.js +831 -85
- package/dist/index.js.map +1 -1
- package/package.json +67 -67
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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, unlink } from 'fs/promises';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import * as ini from 'ini';
|
|
11
11
|
import http from 'http';
|
|
@@ -18,6 +18,36 @@ function calculateIntegrity(data) {
|
|
|
18
18
|
return `sha256-${hash}`;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// ../../packages/shared/pspm-types/src/manifest.ts
|
|
22
|
+
var DEFAULT_SKILL_FILES = [
|
|
23
|
+
"SKILL.md",
|
|
24
|
+
"runtime",
|
|
25
|
+
"scripts",
|
|
26
|
+
"data"
|
|
27
|
+
];
|
|
28
|
+
var PSPM_SCHEMA_URL = "https://pspm.dev/schema/pspm.json";
|
|
29
|
+
function validateManifest(manifest) {
|
|
30
|
+
if (!manifest.name) {
|
|
31
|
+
return { valid: false, error: "Manifest must have a 'name' field" };
|
|
32
|
+
}
|
|
33
|
+
if (!manifest.version) {
|
|
34
|
+
return { valid: false, error: "Manifest must have a 'version' field" };
|
|
35
|
+
}
|
|
36
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(manifest.name)) {
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
error: "Name must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!/^\d+\.\d+\.\d+/.test(manifest.version)) {
|
|
43
|
+
return {
|
|
44
|
+
valid: false,
|
|
45
|
+
error: "Version must be a valid semantic version (e.g., 1.0.0)"
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { valid: true };
|
|
49
|
+
}
|
|
50
|
+
|
|
21
51
|
// ../../packages/shared/pspm-types/src/specifier.ts
|
|
22
52
|
var SPECIFIER_PATTERN = /^@user\/([a-zA-Z0-9_-]+)\/([a-z][a-z0-9_-]*)(?:@(.+))?$/;
|
|
23
53
|
function parseSkillSpecifier(specifier) {
|
|
@@ -53,13 +83,16 @@ function getConfig() {
|
|
|
53
83
|
async function customFetch(url, options) {
|
|
54
84
|
const { baseUrl, apiKey } = getConfig();
|
|
55
85
|
const fullUrl = `${baseUrl}${url}`;
|
|
86
|
+
const headers = {
|
|
87
|
+
...options.headers ?? {},
|
|
88
|
+
"Content-Type": "application/json"
|
|
89
|
+
};
|
|
90
|
+
if (apiKey) {
|
|
91
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
92
|
+
}
|
|
56
93
|
const response = await fetch(fullUrl, {
|
|
57
94
|
...options,
|
|
58
|
-
headers
|
|
59
|
-
...options.headers,
|
|
60
|
-
Authorization: `Bearer ${apiKey}`,
|
|
61
|
-
"Content-Type": "application/json"
|
|
62
|
-
}
|
|
95
|
+
headers
|
|
63
96
|
});
|
|
64
97
|
const text = await response.text();
|
|
65
98
|
let data = null;
|
|
@@ -179,6 +212,102 @@ async function whoamiRequest(registryUrl, apiKey) {
|
|
|
179
212
|
return null;
|
|
180
213
|
}
|
|
181
214
|
}
|
|
215
|
+
async function deprecateSkillVersion(skillName, version2, message) {
|
|
216
|
+
const config2 = getConfig();
|
|
217
|
+
if (!config2) {
|
|
218
|
+
return { status: 401, error: "SDK not configured" };
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const response = await fetch(
|
|
222
|
+
`${config2.baseUrl}/api/skills/${skillName}/${version2}/deprecate`,
|
|
223
|
+
{
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
"Content-Type": "application/json",
|
|
227
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
228
|
+
},
|
|
229
|
+
body: JSON.stringify({ message })
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
const error = await response.text();
|
|
234
|
+
return { status: response.status, error };
|
|
235
|
+
}
|
|
236
|
+
const data = await response.json();
|
|
237
|
+
return { status: response.status, data };
|
|
238
|
+
} catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
status: 500,
|
|
241
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function undeprecateSkillVersion(skillName, version2) {
|
|
246
|
+
const config2 = getConfig();
|
|
247
|
+
if (!config2) {
|
|
248
|
+
return { status: 401, error: "SDK not configured" };
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch(
|
|
252
|
+
`${config2.baseUrl}/api/skills/${skillName}/${version2}/deprecate`,
|
|
253
|
+
{
|
|
254
|
+
method: "DELETE",
|
|
255
|
+
headers: {
|
|
256
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
const error = await response.text();
|
|
262
|
+
return { status: response.status, error };
|
|
263
|
+
}
|
|
264
|
+
const data = await response.json();
|
|
265
|
+
return { status: response.status, data };
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
status: 500,
|
|
269
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function changeSkillAccess(skillName, input) {
|
|
274
|
+
const config2 = getConfig();
|
|
275
|
+
if (!config2) {
|
|
276
|
+
return { status: 401, error: "SDK not configured" };
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetch(
|
|
280
|
+
`${config2.baseUrl}/api/skills/${skillName}/access`,
|
|
281
|
+
{
|
|
282
|
+
method: "POST",
|
|
283
|
+
headers: {
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
Authorization: `Bearer ${config2.apiKey}`
|
|
286
|
+
},
|
|
287
|
+
body: JSON.stringify(input)
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
const error = await response.text();
|
|
292
|
+
try {
|
|
293
|
+
const errorJson = JSON.parse(error);
|
|
294
|
+
return {
|
|
295
|
+
status: response.status,
|
|
296
|
+
error: errorJson.message || errorJson.error || error
|
|
297
|
+
};
|
|
298
|
+
} catch {
|
|
299
|
+
return { status: response.status, error };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const data = await response.json();
|
|
303
|
+
return { status: response.status, data };
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
status: 500,
|
|
307
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
182
311
|
|
|
183
312
|
// src/errors.ts
|
|
184
313
|
var ConfigError = class extends Error {
|
|
@@ -238,12 +367,24 @@ function getConfigPath() {
|
|
|
238
367
|
function getLegacyConfigPath() {
|
|
239
368
|
return join(homedir(), ".pspm", "config.json");
|
|
240
369
|
}
|
|
370
|
+
function getPspmDir() {
|
|
371
|
+
return join(process.cwd(), ".pspm");
|
|
372
|
+
}
|
|
241
373
|
function getSkillsDir() {
|
|
242
|
-
return join(process.cwd(), ".skills");
|
|
374
|
+
return join(process.cwd(), ".pspm", "skills");
|
|
375
|
+
}
|
|
376
|
+
function getCacheDir() {
|
|
377
|
+
return join(process.cwd(), ".pspm", "cache");
|
|
243
378
|
}
|
|
244
379
|
function getLockfilePath() {
|
|
380
|
+
return join(process.cwd(), "pspm-lock.json");
|
|
381
|
+
}
|
|
382
|
+
function getLegacyLockfilePath() {
|
|
245
383
|
return join(process.cwd(), "skill-lock.json");
|
|
246
384
|
}
|
|
385
|
+
function getLegacySkillsDir() {
|
|
386
|
+
return join(process.cwd(), ".skills");
|
|
387
|
+
}
|
|
247
388
|
async function readUserConfig() {
|
|
248
389
|
const configPath = getConfigPath();
|
|
249
390
|
if (process.env.PSPM_DEBUG) {
|
|
@@ -255,10 +396,35 @@ async function readUserConfig() {
|
|
|
255
396
|
if (process.env.PSPM_DEBUG) {
|
|
256
397
|
console.log(`[config] Parsed config:`, JSON.stringify(parsed, null, 2));
|
|
257
398
|
}
|
|
399
|
+
const scopedRegistries = {};
|
|
400
|
+
for (const key of Object.keys(parsed)) {
|
|
401
|
+
const scopeMatch = key.match(/^(@[^:]+):registry$/);
|
|
402
|
+
if (scopeMatch) {
|
|
403
|
+
const scope = scopeMatch[1];
|
|
404
|
+
scopedRegistries[scope] = parsed[key];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
const registryTokens = {};
|
|
408
|
+
for (const key of Object.keys(parsed)) {
|
|
409
|
+
const tokenMatch = key.match(/^\/\/([^:]+):authToken$/);
|
|
410
|
+
if (tokenMatch) {
|
|
411
|
+
const host = tokenMatch[1];
|
|
412
|
+
registryTokens[host] = parsed[key];
|
|
413
|
+
}
|
|
414
|
+
if (key.startsWith("//") && typeof parsed[key] === "object") {
|
|
415
|
+
const host = key.slice(2);
|
|
416
|
+
const section = parsed[key];
|
|
417
|
+
if (section.authToken) {
|
|
418
|
+
registryTokens[host] = section.authToken;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
258
422
|
return {
|
|
259
423
|
registry: parsed.registry,
|
|
260
424
|
authToken: parsed.authToken,
|
|
261
|
-
username: parsed.username
|
|
425
|
+
username: parsed.username,
|
|
426
|
+
scopedRegistries: Object.keys(scopedRegistries).length > 0 ? scopedRegistries : void 0,
|
|
427
|
+
registryTokens: Object.keys(registryTokens).length > 0 ? registryTokens : void 0
|
|
262
428
|
};
|
|
263
429
|
} catch (error) {
|
|
264
430
|
if (process.env.PSPM_DEBUG) {
|
|
@@ -374,6 +540,8 @@ async function resolveConfig() {
|
|
|
374
540
|
let registryUrl = DEFAULT_REGISTRY_URL;
|
|
375
541
|
let apiKey = userConfig.authToken;
|
|
376
542
|
const username = userConfig.username;
|
|
543
|
+
const scopedRegistries = userConfig.scopedRegistries ?? {};
|
|
544
|
+
const registryTokens = userConfig.registryTokens ?? {};
|
|
377
545
|
if (userConfig.registry) {
|
|
378
546
|
registryUrl = userConfig.registry;
|
|
379
547
|
}
|
|
@@ -391,13 +559,33 @@ async function resolveConfig() {
|
|
|
391
559
|
console.log(`[config] registryUrl: ${registryUrl}`);
|
|
392
560
|
console.log(`[config] apiKey: ${apiKey ? "***" : "(not set)"}`);
|
|
393
561
|
console.log(`[config] username: ${username || "(not set)"}`);
|
|
562
|
+
console.log(
|
|
563
|
+
`[config] scopedRegistries: ${JSON.stringify(scopedRegistries)}`
|
|
564
|
+
);
|
|
565
|
+
console.log(
|
|
566
|
+
`[config] registryTokens: ${Object.keys(registryTokens).length} configured`
|
|
567
|
+
);
|
|
394
568
|
}
|
|
395
569
|
return {
|
|
396
570
|
registryUrl,
|
|
397
571
|
apiKey,
|
|
398
|
-
username
|
|
572
|
+
username,
|
|
573
|
+
scopedRegistries,
|
|
574
|
+
registryTokens
|
|
399
575
|
};
|
|
400
576
|
}
|
|
577
|
+
function getTokenForRegistry(config2, registryUrl) {
|
|
578
|
+
try {
|
|
579
|
+
const url = new URL(registryUrl);
|
|
580
|
+
const host = url.host;
|
|
581
|
+
if (config2.registryTokens[host]) {
|
|
582
|
+
return config2.registryTokens[host];
|
|
583
|
+
}
|
|
584
|
+
return config2.apiKey;
|
|
585
|
+
} catch {
|
|
586
|
+
return config2.apiKey;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
401
589
|
async function setCredentials(authToken, username, registry) {
|
|
402
590
|
const config2 = await readUserConfig();
|
|
403
591
|
config2.authToken = authToken;
|
|
@@ -442,43 +630,198 @@ async function getRegistryUrl() {
|
|
|
442
630
|
const resolved = await resolveConfig();
|
|
443
631
|
return resolved.registryUrl;
|
|
444
632
|
}
|
|
633
|
+
|
|
634
|
+
// src/commands/access.ts
|
|
635
|
+
async function access(specifier, options) {
|
|
636
|
+
try {
|
|
637
|
+
const apiKey = await requireApiKey();
|
|
638
|
+
const registryUrl = await getRegistryUrl();
|
|
639
|
+
if (options.public && options.private) {
|
|
640
|
+
console.error("Error: Cannot specify both --public and --private");
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
if (!options.public && !options.private) {
|
|
644
|
+
console.error("Error: Must specify either --public or --private");
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
const visibility = options.public ? "public" : "private";
|
|
648
|
+
let packageName;
|
|
649
|
+
if (specifier) {
|
|
650
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
651
|
+
if (!parsed) {
|
|
652
|
+
console.error(
|
|
653
|
+
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}`
|
|
654
|
+
);
|
|
655
|
+
process.exit(1);
|
|
656
|
+
}
|
|
657
|
+
packageName = parsed.name;
|
|
658
|
+
} else {
|
|
659
|
+
const { readFile: readFile6 } = await import('fs/promises');
|
|
660
|
+
const { join: join10 } = await import('path');
|
|
661
|
+
let manifest = null;
|
|
662
|
+
try {
|
|
663
|
+
const content = await readFile6(
|
|
664
|
+
join10(process.cwd(), "pspm.json"),
|
|
665
|
+
"utf-8"
|
|
666
|
+
);
|
|
667
|
+
manifest = JSON.parse(content);
|
|
668
|
+
} catch {
|
|
669
|
+
try {
|
|
670
|
+
const content = await readFile6(
|
|
671
|
+
join10(process.cwd(), "package.json"),
|
|
672
|
+
"utf-8"
|
|
673
|
+
);
|
|
674
|
+
manifest = JSON.parse(content);
|
|
675
|
+
} catch {
|
|
676
|
+
console.error(
|
|
677
|
+
"Error: No pspm.json or package.json found in current directory"
|
|
678
|
+
);
|
|
679
|
+
console.error(
|
|
680
|
+
"Either run this command in a package directory or specify a package name"
|
|
681
|
+
);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (!manifest?.name) {
|
|
686
|
+
console.error("Error: Package manifest is missing 'name' field");
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
packageName = manifest.name;
|
|
690
|
+
}
|
|
691
|
+
configure2({ registryUrl, apiKey });
|
|
692
|
+
console.log(`Setting ${packageName} to ${visibility}...`);
|
|
693
|
+
const response = await changeSkillAccess(packageName, { visibility });
|
|
694
|
+
if (response.status !== 200 || !response.data) {
|
|
695
|
+
const errorMessage = response.error ?? "Failed to change visibility";
|
|
696
|
+
console.error(`Error: ${errorMessage}`);
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
const result = response.data;
|
|
700
|
+
console.log(
|
|
701
|
+
`+ @user/${result.username}/${result.name} is now ${result.visibility}`
|
|
702
|
+
);
|
|
703
|
+
if (visibility === "public") {
|
|
704
|
+
console.log("");
|
|
705
|
+
console.log(
|
|
706
|
+
"Note: This action is irreversible. Public packages cannot be made private."
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
} catch (error) {
|
|
710
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
711
|
+
console.error(`Error: ${message}`);
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
async function hasLegacyLockfile() {
|
|
716
|
+
try {
|
|
717
|
+
await stat(getLegacyLockfilePath());
|
|
718
|
+
return true;
|
|
719
|
+
} catch {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async function migrateLockfileIfNeeded() {
|
|
724
|
+
const legacyPath = getLegacyLockfilePath();
|
|
725
|
+
const newPath = getLockfilePath();
|
|
726
|
+
try {
|
|
727
|
+
await stat(legacyPath);
|
|
728
|
+
} catch {
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
await stat(newPath);
|
|
733
|
+
return false;
|
|
734
|
+
} catch {
|
|
735
|
+
}
|
|
736
|
+
try {
|
|
737
|
+
const content = await readFile(legacyPath, "utf-8");
|
|
738
|
+
const oldLockfile = JSON.parse(content);
|
|
739
|
+
const newLockfile = {
|
|
740
|
+
lockfileVersion: 2,
|
|
741
|
+
registryUrl: oldLockfile.registryUrl,
|
|
742
|
+
packages: oldLockfile.skills ?? {}
|
|
743
|
+
};
|
|
744
|
+
await writeFile(newPath, `${JSON.stringify(newLockfile, null, 2)}
|
|
745
|
+
`);
|
|
746
|
+
console.log(`Migrated lockfile: skill-lock.json \u2192 pspm-lock.json`);
|
|
747
|
+
return true;
|
|
748
|
+
} catch {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
445
752
|
async function readLockfile() {
|
|
446
753
|
const lockfilePath = getLockfilePath();
|
|
447
754
|
try {
|
|
448
755
|
const content = await readFile(lockfilePath, "utf-8");
|
|
449
|
-
|
|
756
|
+
const lockfile = JSON.parse(content);
|
|
757
|
+
if (lockfile.lockfileVersion === 1 && lockfile.skills && !lockfile.packages) {
|
|
758
|
+
return {
|
|
759
|
+
...lockfile,
|
|
760
|
+
lockfileVersion: 2,
|
|
761
|
+
packages: lockfile.skills
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
return lockfile;
|
|
450
765
|
} catch {
|
|
766
|
+
if (await hasLegacyLockfile()) {
|
|
767
|
+
try {
|
|
768
|
+
const content = await readFile(getLegacyLockfilePath(), "utf-8");
|
|
769
|
+
const legacyLockfile = JSON.parse(content);
|
|
770
|
+
return {
|
|
771
|
+
lockfileVersion: 2,
|
|
772
|
+
registryUrl: legacyLockfile.registryUrl,
|
|
773
|
+
packages: legacyLockfile.skills ?? {}
|
|
774
|
+
};
|
|
775
|
+
} catch {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
451
779
|
return null;
|
|
452
780
|
}
|
|
453
781
|
}
|
|
454
782
|
async function writeLockfile(lockfile) {
|
|
455
783
|
const lockfilePath = getLockfilePath();
|
|
456
784
|
await mkdir(dirname(lockfilePath), { recursive: true });
|
|
457
|
-
|
|
785
|
+
const normalized = {
|
|
786
|
+
lockfileVersion: 2,
|
|
787
|
+
registryUrl: lockfile.registryUrl,
|
|
788
|
+
packages: lockfile.packages ?? lockfile.skills ?? {}
|
|
789
|
+
};
|
|
790
|
+
await writeFile(lockfilePath, `${JSON.stringify(normalized, null, 2)}
|
|
458
791
|
`);
|
|
459
792
|
}
|
|
460
793
|
async function createEmptyLockfile() {
|
|
461
794
|
const registryUrl = await getRegistryUrl();
|
|
462
795
|
return {
|
|
463
|
-
lockfileVersion:
|
|
796
|
+
lockfileVersion: 2,
|
|
464
797
|
registryUrl,
|
|
465
|
-
|
|
798
|
+
packages: {}
|
|
466
799
|
};
|
|
467
800
|
}
|
|
801
|
+
function getPackages(lockfile) {
|
|
802
|
+
return lockfile.packages ?? lockfile.skills ?? {};
|
|
803
|
+
}
|
|
468
804
|
async function addToLockfile(fullName, entry) {
|
|
469
805
|
let lockfile = await readLockfile();
|
|
470
806
|
if (!lockfile) {
|
|
471
807
|
lockfile = await createEmptyLockfile();
|
|
472
808
|
}
|
|
473
|
-
|
|
809
|
+
const packages = getPackages(lockfile);
|
|
810
|
+
packages[fullName] = entry;
|
|
811
|
+
lockfile.packages = packages;
|
|
474
812
|
await writeLockfile(lockfile);
|
|
475
813
|
}
|
|
476
814
|
async function removeFromLockfile(fullName) {
|
|
477
815
|
const lockfile = await readLockfile();
|
|
478
|
-
if (!lockfile
|
|
816
|
+
if (!lockfile) {
|
|
479
817
|
return false;
|
|
480
818
|
}
|
|
481
|
-
|
|
819
|
+
const packages = getPackages(lockfile);
|
|
820
|
+
if (!packages[fullName]) {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
delete packages[fullName];
|
|
824
|
+
lockfile.packages = packages;
|
|
482
825
|
await writeLockfile(lockfile);
|
|
483
826
|
return true;
|
|
484
827
|
}
|
|
@@ -487,7 +830,8 @@ async function listLockfileSkills() {
|
|
|
487
830
|
if (!lockfile) {
|
|
488
831
|
return [];
|
|
489
832
|
}
|
|
490
|
-
|
|
833
|
+
const packages = getPackages(lockfile);
|
|
834
|
+
return Object.entries(packages).map(([name, entry]) => ({
|
|
491
835
|
name,
|
|
492
836
|
entry
|
|
493
837
|
}));
|
|
@@ -496,8 +840,9 @@ async function listLockfileSkills() {
|
|
|
496
840
|
// src/commands/add.ts
|
|
497
841
|
async function add(specifier, _options) {
|
|
498
842
|
try {
|
|
499
|
-
const
|
|
500
|
-
const registryUrl =
|
|
843
|
+
const config2 = await resolveConfig();
|
|
844
|
+
const registryUrl = config2.registryUrl;
|
|
845
|
+
const apiKey = getTokenForRegistry(config2, registryUrl);
|
|
501
846
|
const parsed = parseSkillSpecifier(specifier);
|
|
502
847
|
if (!parsed) {
|
|
503
848
|
console.error(
|
|
@@ -506,10 +851,23 @@ async function add(specifier, _options) {
|
|
|
506
851
|
process.exit(1);
|
|
507
852
|
}
|
|
508
853
|
const { username, name, versionRange } = parsed;
|
|
509
|
-
configure2({ registryUrl, apiKey });
|
|
854
|
+
configure2({ registryUrl, apiKey: apiKey ?? "" });
|
|
510
855
|
console.log(`Resolving ${specifier}...`);
|
|
511
856
|
const versionsResponse = await listSkillVersions(username, name);
|
|
512
857
|
if (versionsResponse.status !== 200) {
|
|
858
|
+
if (versionsResponse.status === 401) {
|
|
859
|
+
if (!apiKey) {
|
|
860
|
+
console.error(
|
|
861
|
+
`Error: Package @user/${username}/${name} requires authentication`
|
|
862
|
+
);
|
|
863
|
+
console.error("Please run 'pspm login' to authenticate");
|
|
864
|
+
} else {
|
|
865
|
+
console.error(
|
|
866
|
+
`Error: Access denied to @user/${username}/${name}. You may not have permission to access this private package.`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
513
871
|
const errorMessage = extractApiErrorMessage(
|
|
514
872
|
versionsResponse,
|
|
515
873
|
`Skill @user/${username}/${name} not found`
|
|
@@ -543,10 +901,12 @@ async function add(specifier, _options) {
|
|
|
543
901
|
}
|
|
544
902
|
const versionInfo = versionResponse.data;
|
|
545
903
|
const isPresignedUrl = versionInfo.downloadUrl.includes(".r2.cloudflarestorage.com") || versionInfo.downloadUrl.includes("X-Amz-Signature");
|
|
904
|
+
const downloadHeaders = {};
|
|
905
|
+
if (!isPresignedUrl && apiKey) {
|
|
906
|
+
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
907
|
+
}
|
|
546
908
|
const tarballResponse = await fetch(versionInfo.downloadUrl, {
|
|
547
|
-
headers:
|
|
548
|
-
Authorization: `Bearer ${apiKey}`
|
|
549
|
-
},
|
|
909
|
+
headers: downloadHeaders,
|
|
550
910
|
redirect: "follow"
|
|
551
911
|
});
|
|
552
912
|
if (!tarballResponse.ok) {
|
|
@@ -565,16 +925,16 @@ async function add(specifier, _options) {
|
|
|
565
925
|
const skillsDir = getSkillsDir();
|
|
566
926
|
const destDir = join(skillsDir, username, name);
|
|
567
927
|
await mkdir(destDir, { recursive: true });
|
|
568
|
-
const { writeFile:
|
|
928
|
+
const { writeFile: writeFile6 } = await import('fs/promises');
|
|
569
929
|
const tempFile = join(destDir, ".temp.tgz");
|
|
570
|
-
await
|
|
930
|
+
await writeFile6(tempFile, tarballBuffer);
|
|
571
931
|
const { exec: exec2 } = await import('child_process');
|
|
572
932
|
const { promisify: promisify2 } = await import('util');
|
|
573
933
|
const execAsync = promisify2(exec2);
|
|
574
934
|
try {
|
|
575
935
|
await rm(destDir, { recursive: true, force: true });
|
|
576
936
|
await mkdir(destDir, { recursive: true });
|
|
577
|
-
await
|
|
937
|
+
await writeFile6(tempFile, tarballBuffer);
|
|
578
938
|
await execAsync(
|
|
579
939
|
`tar -xzf "${tempFile}" -C "${destDir}" --strip-components=1`
|
|
580
940
|
);
|
|
@@ -654,11 +1014,190 @@ async function configShow() {
|
|
|
654
1014
|
process.exit(1);
|
|
655
1015
|
}
|
|
656
1016
|
}
|
|
657
|
-
|
|
1017
|
+
|
|
1018
|
+
// src/commands/deprecate.ts
|
|
1019
|
+
async function deprecate(specifier, message, options) {
|
|
658
1020
|
try {
|
|
659
1021
|
const apiKey = await requireApiKey();
|
|
660
|
-
await getRegistryUrl();
|
|
1022
|
+
const registryUrl = await getRegistryUrl();
|
|
1023
|
+
const parsed = parseSkillSpecifier(specifier);
|
|
1024
|
+
if (!parsed) {
|
|
1025
|
+
console.error(
|
|
1026
|
+
`Error: Invalid skill specifier "${specifier}". Use format: @user/{username}/{name}@{version}`
|
|
1027
|
+
);
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
const { username, name, versionRange } = parsed;
|
|
1031
|
+
if (!versionRange) {
|
|
1032
|
+
console.error(
|
|
1033
|
+
"Error: Version is required for deprecation. Use format: @user/{username}/{name}@{version}"
|
|
1034
|
+
);
|
|
1035
|
+
process.exit(1);
|
|
1036
|
+
}
|
|
1037
|
+
configure2({ registryUrl, apiKey });
|
|
1038
|
+
if (options.undo) {
|
|
1039
|
+
console.log(
|
|
1040
|
+
`Removing deprecation from @user/${username}/${name}@${versionRange}...`
|
|
1041
|
+
);
|
|
1042
|
+
const response = await undeprecateSkillVersion(name, versionRange);
|
|
1043
|
+
if (response.status !== 200) {
|
|
1044
|
+
console.error(
|
|
1045
|
+
`Error: ${response.error || "Failed to remove deprecation"}`
|
|
1046
|
+
);
|
|
1047
|
+
process.exit(1);
|
|
1048
|
+
}
|
|
1049
|
+
console.log(
|
|
1050
|
+
`Removed deprecation from @user/${username}/${name}@${versionRange}`
|
|
1051
|
+
);
|
|
1052
|
+
} else {
|
|
1053
|
+
if (!message) {
|
|
1054
|
+
console.error(
|
|
1055
|
+
"Error: Deprecation message is required. Usage: pspm deprecate <specifier> <message>"
|
|
1056
|
+
);
|
|
1057
|
+
process.exit(1);
|
|
1058
|
+
}
|
|
1059
|
+
console.log(`Deprecating @user/${username}/${name}@${versionRange}...`);
|
|
1060
|
+
const response = await deprecateSkillVersion(name, versionRange, message);
|
|
1061
|
+
if (response.status !== 200) {
|
|
1062
|
+
console.error(
|
|
1063
|
+
`Error: ${response.error || "Failed to deprecate version"}`
|
|
1064
|
+
);
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
}
|
|
1067
|
+
console.log(`Deprecated @user/${username}/${name}@${versionRange}`);
|
|
1068
|
+
console.log(`Message: ${message}`);
|
|
1069
|
+
console.log("");
|
|
1070
|
+
console.log(
|
|
1071
|
+
"Users installing this version will see a deprecation warning."
|
|
1072
|
+
);
|
|
1073
|
+
console.log("The package is still available for download.");
|
|
1074
|
+
}
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1077
|
+
console.error(`Error: ${errorMessage}`);
|
|
1078
|
+
process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async function readExistingPackageJson() {
|
|
1082
|
+
try {
|
|
1083
|
+
const content = await readFile(
|
|
1084
|
+
join(process.cwd(), "package.json"),
|
|
1085
|
+
"utf-8"
|
|
1086
|
+
);
|
|
1087
|
+
const pkg = JSON.parse(content);
|
|
1088
|
+
return {
|
|
1089
|
+
name: pkg.name,
|
|
1090
|
+
version: pkg.version,
|
|
1091
|
+
description: pkg.description,
|
|
1092
|
+
author: typeof pkg.author === "string" ? pkg.author : pkg.author?.name,
|
|
1093
|
+
license: pkg.license
|
|
1094
|
+
};
|
|
1095
|
+
} catch {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function sanitizeName(name) {
|
|
1100
|
+
const withoutScope = name.replace(/^@[^/]+\//, "");
|
|
1101
|
+
return withoutScope.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
1102
|
+
}
|
|
1103
|
+
async function init(options) {
|
|
1104
|
+
try {
|
|
1105
|
+
const pspmJsonPath = join(process.cwd(), "pspm.json");
|
|
1106
|
+
try {
|
|
1107
|
+
await stat(pspmJsonPath);
|
|
1108
|
+
console.error("Error: pspm.json already exists in this directory.");
|
|
1109
|
+
console.error("Use --force to overwrite (not yet implemented).");
|
|
1110
|
+
process.exit(1);
|
|
1111
|
+
} catch {
|
|
1112
|
+
}
|
|
1113
|
+
const existingPkg = await readExistingPackageJson();
|
|
1114
|
+
const defaultName = options.name || sanitizeName(existingPkg?.name || basename(process.cwd()));
|
|
1115
|
+
const defaultVersion = existingPkg?.version || "0.1.0";
|
|
1116
|
+
const defaultDescription = options.description || existingPkg?.description || "";
|
|
1117
|
+
const defaultAuthor = options.author || existingPkg?.author || "";
|
|
1118
|
+
const defaultLicense = existingPkg?.license || "MIT";
|
|
1119
|
+
const manifest = {
|
|
1120
|
+
$schema: PSPM_SCHEMA_URL,
|
|
1121
|
+
name: defaultName,
|
|
1122
|
+
version: defaultVersion,
|
|
1123
|
+
description: defaultDescription || void 0,
|
|
1124
|
+
author: defaultAuthor || void 0,
|
|
1125
|
+
license: defaultLicense,
|
|
1126
|
+
type: "skill",
|
|
1127
|
+
capabilities: [],
|
|
1128
|
+
main: "SKILL.md",
|
|
1129
|
+
requirements: {
|
|
1130
|
+
pspm: ">=0.1.0"
|
|
1131
|
+
},
|
|
1132
|
+
files: [...DEFAULT_SKILL_FILES],
|
|
1133
|
+
dependencies: {},
|
|
1134
|
+
private: false
|
|
1135
|
+
};
|
|
1136
|
+
if (!manifest.description) delete manifest.description;
|
|
1137
|
+
if (!manifest.author) delete manifest.author;
|
|
1138
|
+
const content = JSON.stringify(manifest, null, 2);
|
|
1139
|
+
await writeFile(pspmJsonPath, `${content}
|
|
1140
|
+
`);
|
|
1141
|
+
console.log("Created pspm.json:");
|
|
1142
|
+
console.log("");
|
|
1143
|
+
console.log(content);
|
|
1144
|
+
console.log("");
|
|
1145
|
+
try {
|
|
1146
|
+
await stat(join(process.cwd(), "SKILL.md"));
|
|
1147
|
+
} catch {
|
|
1148
|
+
console.log(
|
|
1149
|
+
"Note: Create a SKILL.md file with your skill's prompt content."
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
if (existingPkg) {
|
|
1153
|
+
console.log("Note: Values were derived from existing package.json.");
|
|
1154
|
+
console.log(" pspm.json is for publishing to PSPM registry.");
|
|
1155
|
+
console.log(" package.json can still be used for npm dependencies.");
|
|
1156
|
+
}
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1159
|
+
console.error(`Error: ${message}`);
|
|
1160
|
+
process.exit(1);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function getCacheFilePath(cacheDir, integrity) {
|
|
1164
|
+
const match = integrity.match(/^sha256-(.+)$/);
|
|
1165
|
+
if (!match) {
|
|
1166
|
+
throw new Error(`Invalid integrity format: ${integrity}`);
|
|
1167
|
+
}
|
|
1168
|
+
const base64Hash = match[1];
|
|
1169
|
+
const hexHash = Buffer.from(base64Hash, "base64").toString("hex");
|
|
1170
|
+
return join(cacheDir, `sha256-${hexHash}.tgz`);
|
|
1171
|
+
}
|
|
1172
|
+
async function readFromCache(cacheDir, integrity) {
|
|
1173
|
+
try {
|
|
1174
|
+
const cachePath = getCacheFilePath(cacheDir, integrity);
|
|
1175
|
+
const data = await readFile(cachePath);
|
|
1176
|
+
const actualIntegrity = `sha256-${createHash("sha256").update(data).digest("base64")}`;
|
|
1177
|
+
if (actualIntegrity !== integrity) {
|
|
1178
|
+
await rm(cachePath, { force: true });
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
return data;
|
|
1182
|
+
} catch {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
async function writeToCache(cacheDir, integrity, data) {
|
|
1187
|
+
try {
|
|
1188
|
+
await mkdir(cacheDir, { recursive: true });
|
|
1189
|
+
const cachePath = getCacheFilePath(cacheDir, integrity);
|
|
1190
|
+
await writeFile(cachePath, data);
|
|
1191
|
+
} catch {
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
async function install(options) {
|
|
1195
|
+
try {
|
|
1196
|
+
const config2 = await resolveConfig();
|
|
1197
|
+
const apiKey = getTokenForRegistry(config2, config2.registryUrl);
|
|
661
1198
|
const skillsDir = options.dir || getSkillsDir();
|
|
1199
|
+
const cacheDir = getCacheDir();
|
|
1200
|
+
await migrateLockfileIfNeeded();
|
|
662
1201
|
const lockfile = await readLockfile();
|
|
663
1202
|
if (!lockfile) {
|
|
664
1203
|
if (options.frozenLockfile) {
|
|
@@ -670,14 +1209,17 @@ async function install(options) {
|
|
|
670
1209
|
console.log("No lockfile found. Nothing to install.");
|
|
671
1210
|
return;
|
|
672
1211
|
}
|
|
673
|
-
const skillCount = Object.keys(
|
|
1212
|
+
const skillCount = Object.keys(
|
|
1213
|
+
lockfile.packages ?? lockfile.skills ?? {}
|
|
1214
|
+
).length;
|
|
674
1215
|
if (skillCount === 0) {
|
|
675
1216
|
console.log("No skills in lockfile. Nothing to install.");
|
|
676
1217
|
return;
|
|
677
1218
|
}
|
|
678
1219
|
console.log(`Installing ${skillCount} skill(s)...
|
|
679
1220
|
`);
|
|
680
|
-
const
|
|
1221
|
+
const packages = lockfile.packages ?? lockfile.skills ?? {};
|
|
1222
|
+
const entries = Object.entries(packages);
|
|
681
1223
|
for (const [fullName, entry] of entries) {
|
|
682
1224
|
const match = fullName.match(/^@user\/([^/]+)\/([^/]+)$/);
|
|
683
1225
|
if (!match) {
|
|
@@ -686,35 +1228,58 @@ async function install(options) {
|
|
|
686
1228
|
}
|
|
687
1229
|
const [, username, name] = match;
|
|
688
1230
|
console.log(`Installing ${fullName}@${entry.version}...`);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
const tarballBuffer = Buffer.from(await response.arrayBuffer());
|
|
703
|
-
const { createHash: createHash3 } = await import('crypto');
|
|
704
|
-
const actualIntegrity = `sha256-${createHash3("sha256").update(tarballBuffer).digest("base64")}`;
|
|
705
|
-
if (actualIntegrity !== entry.integrity) {
|
|
706
|
-
console.error(` Error: Checksum verification failed for ${fullName}`);
|
|
707
|
-
if (options.frozenLockfile) {
|
|
708
|
-
process.exit(1);
|
|
1231
|
+
let tarballBuffer;
|
|
1232
|
+
let fromCache = false;
|
|
1233
|
+
const cachedTarball = await readFromCache(cacheDir, entry.integrity);
|
|
1234
|
+
if (cachedTarball) {
|
|
1235
|
+
tarballBuffer = cachedTarball;
|
|
1236
|
+
fromCache = true;
|
|
1237
|
+
} else {
|
|
1238
|
+
const isPresignedUrl = entry.resolved.includes(".r2.cloudflarestorage.com") || entry.resolved.includes("X-Amz-Signature");
|
|
1239
|
+
const downloadHeaders = {};
|
|
1240
|
+
if (!isPresignedUrl && apiKey) {
|
|
1241
|
+
downloadHeaders.Authorization = `Bearer ${apiKey}`;
|
|
709
1242
|
}
|
|
710
|
-
|
|
1243
|
+
const response = await fetch(entry.resolved, {
|
|
1244
|
+
headers: downloadHeaders,
|
|
1245
|
+
redirect: "follow"
|
|
1246
|
+
});
|
|
1247
|
+
if (!response.ok) {
|
|
1248
|
+
if (response.status === 401) {
|
|
1249
|
+
if (!apiKey) {
|
|
1250
|
+
console.error(
|
|
1251
|
+
` Error: ${fullName} requires authentication. Run 'pspm login' first.`
|
|
1252
|
+
);
|
|
1253
|
+
} else {
|
|
1254
|
+
console.error(
|
|
1255
|
+
` Error: Access denied to ${fullName}. You may not have permission to access this private package.`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
} else {
|
|
1259
|
+
console.error(
|
|
1260
|
+
` Error: Failed to download ${fullName} (${response.status})`
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
tarballBuffer = Buffer.from(await response.arrayBuffer());
|
|
1266
|
+
const actualIntegrity = `sha256-${createHash("sha256").update(tarballBuffer).digest("base64")}`;
|
|
1267
|
+
if (actualIntegrity !== entry.integrity) {
|
|
1268
|
+
console.error(
|
|
1269
|
+
` Error: Checksum verification failed for ${fullName}`
|
|
1270
|
+
);
|
|
1271
|
+
if (options.frozenLockfile) {
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
await writeToCache(cacheDir, entry.integrity, tarballBuffer);
|
|
711
1277
|
}
|
|
712
1278
|
const destDir = join(skillsDir, username, name);
|
|
713
1279
|
await rm(destDir, { recursive: true, force: true });
|
|
714
1280
|
await mkdir(destDir, { recursive: true });
|
|
715
1281
|
const tempFile = join(destDir, ".temp.tgz");
|
|
716
|
-
|
|
717
|
-
await writeFile4(tempFile, tarballBuffer);
|
|
1282
|
+
await writeFile(tempFile, tarballBuffer);
|
|
718
1283
|
const { exec: exec2 } = await import('child_process');
|
|
719
1284
|
const { promisify: promisify2 } = await import('util');
|
|
720
1285
|
const execAsync = promisify2(exec2);
|
|
@@ -725,7 +1290,9 @@ async function install(options) {
|
|
|
725
1290
|
} finally {
|
|
726
1291
|
await rm(tempFile, { force: true });
|
|
727
1292
|
}
|
|
728
|
-
console.log(
|
|
1293
|
+
console.log(
|
|
1294
|
+
` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
|
|
1295
|
+
);
|
|
729
1296
|
}
|
|
730
1297
|
console.log(`
|
|
731
1298
|
All ${skillCount} skill(s) installed.`);
|
|
@@ -755,7 +1322,7 @@ async function list(options) {
|
|
|
755
1322
|
const skillPath = join(skillsDir, username, skillName);
|
|
756
1323
|
let status = "installed";
|
|
757
1324
|
try {
|
|
758
|
-
await access(skillPath);
|
|
1325
|
+
await access$1(skillPath);
|
|
759
1326
|
} catch {
|
|
760
1327
|
status = "missing";
|
|
761
1328
|
}
|
|
@@ -778,7 +1345,7 @@ function getWebAppUrl(registryUrl) {
|
|
|
778
1345
|
return process.env.PSPM_WEB_URL.replace(/\/$/, "");
|
|
779
1346
|
}
|
|
780
1347
|
try {
|
|
781
|
-
const url = new URL(registryUrl);
|
|
1348
|
+
const url = new URL$1(registryUrl);
|
|
782
1349
|
return `${url.protocol}//${url.host}`;
|
|
783
1350
|
} catch {
|
|
784
1351
|
return DEFAULT_WEB_APP_URL;
|
|
@@ -786,7 +1353,7 @@ function getWebAppUrl(registryUrl) {
|
|
|
786
1353
|
}
|
|
787
1354
|
function getServerUrl(registryUrl) {
|
|
788
1355
|
try {
|
|
789
|
-
const url = new URL(registryUrl);
|
|
1356
|
+
const url = new URL$1(registryUrl);
|
|
790
1357
|
return `${url.protocol}//${url.host}`;
|
|
791
1358
|
} catch {
|
|
792
1359
|
return DEFAULT_WEB_APP_URL;
|
|
@@ -818,7 +1385,7 @@ function startCallbackServer(expectedState) {
|
|
|
818
1385
|
rejectToken = reject;
|
|
819
1386
|
});
|
|
820
1387
|
const server = http.createServer((req, res) => {
|
|
821
|
-
const url = new URL(req.url || "/", `http://localhost`);
|
|
1388
|
+
const url = new URL$1(req.url || "/", `http://localhost`);
|
|
822
1389
|
if (url.pathname === "/callback") {
|
|
823
1390
|
const token = url.searchParams.get("token");
|
|
824
1391
|
const state = url.searchParams.get("state");
|
|
@@ -964,7 +1531,132 @@ async function logout() {
|
|
|
964
1531
|
process.exit(1);
|
|
965
1532
|
}
|
|
966
1533
|
}
|
|
1534
|
+
async function migrate(options) {
|
|
1535
|
+
try {
|
|
1536
|
+
const legacySkillsDir = getLegacySkillsDir();
|
|
1537
|
+
const newSkillsDir = getSkillsDir();
|
|
1538
|
+
const legacyLockfilePath = getLegacyLockfilePath();
|
|
1539
|
+
const newLockfilePath = getLockfilePath();
|
|
1540
|
+
const pspmDir = getPspmDir();
|
|
1541
|
+
let migrationNeeded = false;
|
|
1542
|
+
const actions = [];
|
|
1543
|
+
try {
|
|
1544
|
+
const legacyStats = await stat(legacySkillsDir);
|
|
1545
|
+
if (legacyStats.isDirectory()) {
|
|
1546
|
+
const contents = await readdir(legacySkillsDir);
|
|
1547
|
+
if (contents.length > 0) {
|
|
1548
|
+
migrationNeeded = true;
|
|
1549
|
+
actions.push(`Move .skills/ \u2192 .pspm/skills/`);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
} catch {
|
|
1553
|
+
}
|
|
1554
|
+
try {
|
|
1555
|
+
await stat(legacyLockfilePath);
|
|
1556
|
+
try {
|
|
1557
|
+
await stat(newLockfilePath);
|
|
1558
|
+
actions.push(
|
|
1559
|
+
`Note: Both skill-lock.json and pspm-lock.json exist. Manual merge may be needed.`
|
|
1560
|
+
);
|
|
1561
|
+
} catch {
|
|
1562
|
+
migrationNeeded = true;
|
|
1563
|
+
actions.push(`Migrate skill-lock.json \u2192 pspm-lock.json`);
|
|
1564
|
+
}
|
|
1565
|
+
} catch {
|
|
1566
|
+
}
|
|
1567
|
+
if (!migrationNeeded && actions.length === 0) {
|
|
1568
|
+
console.log(
|
|
1569
|
+
"No migration needed. Project is already using the new structure."
|
|
1570
|
+
);
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
if (options.dryRun) {
|
|
1574
|
+
console.log("Migration plan (dry run):");
|
|
1575
|
+
console.log("");
|
|
1576
|
+
for (const action of actions) {
|
|
1577
|
+
console.log(` - ${action}`);
|
|
1578
|
+
}
|
|
1579
|
+
console.log("");
|
|
1580
|
+
console.log("Run without --dry-run to perform migration.");
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
console.log("Migrating project structure...\n");
|
|
1584
|
+
const lockfileMigrated = await migrateLockfileIfNeeded();
|
|
1585
|
+
if (lockfileMigrated) {
|
|
1586
|
+
console.log(" \u2713 Migrated skill-lock.json \u2192 pspm-lock.json");
|
|
1587
|
+
}
|
|
1588
|
+
try {
|
|
1589
|
+
const legacyStats = await stat(legacySkillsDir);
|
|
1590
|
+
if (legacyStats.isDirectory()) {
|
|
1591
|
+
const contents = await readdir(legacySkillsDir);
|
|
1592
|
+
if (contents.length > 0) {
|
|
1593
|
+
await mkdir(pspmDir, { recursive: true });
|
|
1594
|
+
try {
|
|
1595
|
+
const newStats = await stat(newSkillsDir);
|
|
1596
|
+
if (newStats.isDirectory()) {
|
|
1597
|
+
const newContents = await readdir(newSkillsDir);
|
|
1598
|
+
if (newContents.length > 0) {
|
|
1599
|
+
console.log(
|
|
1600
|
+
" ! Both .skills/ and .pspm/skills/ have content. Manual merge required."
|
|
1601
|
+
);
|
|
1602
|
+
} else {
|
|
1603
|
+
await rm(newSkillsDir, { recursive: true, force: true });
|
|
1604
|
+
await rename(legacySkillsDir, newSkillsDir);
|
|
1605
|
+
console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
} catch {
|
|
1609
|
+
await rename(legacySkillsDir, newSkillsDir);
|
|
1610
|
+
console.log(" \u2713 Moved .skills/ \u2192 .pspm/skills/");
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
} catch {
|
|
1615
|
+
}
|
|
1616
|
+
console.log("");
|
|
1617
|
+
console.log("Migration complete!");
|
|
1618
|
+
console.log("");
|
|
1619
|
+
console.log(
|
|
1620
|
+
"You can safely delete these legacy files if they still exist:"
|
|
1621
|
+
);
|
|
1622
|
+
console.log(" - skill-lock.json (replaced by pspm-lock.json)");
|
|
1623
|
+
console.log(" - .skills/ (replaced by .pspm/skills/)");
|
|
1624
|
+
console.log("");
|
|
1625
|
+
console.log("Update your .gitignore to include:");
|
|
1626
|
+
console.log(" .pspm/cache/");
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1629
|
+
console.error(`Error: ${message}`);
|
|
1630
|
+
process.exit(1);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
967
1633
|
var exec = promisify(exec$1);
|
|
1634
|
+
async function detectManifest() {
|
|
1635
|
+
const cwd = process.cwd();
|
|
1636
|
+
const pspmJsonPath = join(cwd, "pspm.json");
|
|
1637
|
+
try {
|
|
1638
|
+
const content = await readFile(pspmJsonPath, "utf-8");
|
|
1639
|
+
const manifest = JSON.parse(content);
|
|
1640
|
+
return { type: "pspm.json", manifest, path: pspmJsonPath };
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
1644
|
+
try {
|
|
1645
|
+
const content = await readFile(packageJsonPath, "utf-8");
|
|
1646
|
+
const packageJson2 = JSON.parse(content);
|
|
1647
|
+
const manifest = {
|
|
1648
|
+
name: packageJson2.name,
|
|
1649
|
+
version: packageJson2.version,
|
|
1650
|
+
description: packageJson2.description,
|
|
1651
|
+
author: typeof packageJson2.author === "string" ? packageJson2.author : packageJson2.author?.name,
|
|
1652
|
+
license: packageJson2.license,
|
|
1653
|
+
files: packageJson2.files
|
|
1654
|
+
};
|
|
1655
|
+
return { type: "package.json", manifest, path: packageJsonPath };
|
|
1656
|
+
} catch {
|
|
1657
|
+
throw new Error("No pspm.json or package.json found in current directory");
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
968
1660
|
function formatBytes(bytes) {
|
|
969
1661
|
if (bytes < 1024) return `${bytes}B`;
|
|
970
1662
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kB`;
|
|
@@ -996,23 +1688,26 @@ async function publishCommand(options) {
|
|
|
996
1688
|
try {
|
|
997
1689
|
const apiKey = await requireApiKey();
|
|
998
1690
|
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);
|
|
1691
|
+
const detection = await detectManifest();
|
|
1692
|
+
const manifest = detection.manifest;
|
|
1693
|
+
if (detection.type === "package.json") {
|
|
1694
|
+
console.log("pspm warn Using package.json instead of pspm.json");
|
|
1695
|
+
console.log(
|
|
1696
|
+
"pspm warn Run 'pspm init' to create a dedicated pspm.json manifest"
|
|
1697
|
+
);
|
|
1698
|
+
console.log("");
|
|
1011
1699
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1700
|
+
const validation = validateManifest(manifest);
|
|
1701
|
+
if (!validation.valid) {
|
|
1702
|
+
console.error(`Error: ${validation.error}`);
|
|
1014
1703
|
process.exit(1);
|
|
1015
1704
|
}
|
|
1705
|
+
const packageJson2 = {
|
|
1706
|
+
name: manifest.name,
|
|
1707
|
+
version: manifest.version,
|
|
1708
|
+
description: manifest.description,
|
|
1709
|
+
files: manifest.files
|
|
1710
|
+
};
|
|
1016
1711
|
if (options.bump) {
|
|
1017
1712
|
const semver2 = await import('semver');
|
|
1018
1713
|
const newVersion = semver2.default.inc(packageJson2.version, options.bump);
|
|
@@ -1030,13 +1725,7 @@ async function publishCommand(options) {
|
|
|
1030
1725
|
const tempDir = join(process.cwd(), ".pspm-publish");
|
|
1031
1726
|
try {
|
|
1032
1727
|
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
|
-
];
|
|
1728
|
+
const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
|
|
1040
1729
|
await exec(`mkdir -p "${tempDir}/package"`);
|
|
1041
1730
|
for (const file of files) {
|
|
1042
1731
|
try {
|
|
@@ -1046,7 +1735,18 @@ async function publishCommand(options) {
|
|
|
1046
1735
|
} catch {
|
|
1047
1736
|
}
|
|
1048
1737
|
}
|
|
1049
|
-
|
|
1738
|
+
if (detection.type === "pspm.json") {
|
|
1739
|
+
await exec(`cp pspm.json "${tempDir}/package/"`);
|
|
1740
|
+
try {
|
|
1741
|
+
await stat(join(process.cwd(), "package.json"));
|
|
1742
|
+
await exec(
|
|
1743
|
+
`cp package.json "${tempDir}/package/" 2>/dev/null || true`
|
|
1744
|
+
);
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1747
|
+
} else {
|
|
1748
|
+
await exec(`cp package.json "${tempDir}/package/"`);
|
|
1749
|
+
}
|
|
1050
1750
|
const packageDir = join(tempDir, "package");
|
|
1051
1751
|
const tarballContents = await getFilesWithSizes(packageDir, packageDir);
|
|
1052
1752
|
const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
|
|
@@ -1103,6 +1803,25 @@ async function publishCommand(options) {
|
|
|
1103
1803
|
`+ @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
|
|
1104
1804
|
);
|
|
1105
1805
|
console.log(`Checksum: ${result.version.checksum}`);
|
|
1806
|
+
if (options.access) {
|
|
1807
|
+
console.log(`
|
|
1808
|
+
Setting visibility to ${options.access}...`);
|
|
1809
|
+
const accessResponse = await changeSkillAccess(packageJson2.name, {
|
|
1810
|
+
visibility: options.access
|
|
1811
|
+
});
|
|
1812
|
+
if (accessResponse.status !== 200 || !accessResponse.data) {
|
|
1813
|
+
console.warn(
|
|
1814
|
+
`Warning: Failed to set visibility: ${accessResponse.error ?? "Unknown error"}`
|
|
1815
|
+
);
|
|
1816
|
+
} else {
|
|
1817
|
+
console.log(`Package is now ${accessResponse.data.visibility}`);
|
|
1818
|
+
if (options.access === "public") {
|
|
1819
|
+
console.log(
|
|
1820
|
+
"Note: This action is irreversible. Public packages cannot be made private."
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1106
1825
|
} finally {
|
|
1107
1826
|
await exec(`rm -rf "${tempDir}"`).catch(() => {
|
|
1108
1827
|
});
|
|
@@ -1356,6 +2075,19 @@ program.command("logout").description("Log out and clear stored credentials").ac
|
|
|
1356
2075
|
program.command("whoami").description("Show current user information").action(async () => {
|
|
1357
2076
|
await whoami();
|
|
1358
2077
|
});
|
|
2078
|
+
program.command("init").description("Create a new pspm.json manifest in the current directory").option("-n, --name <name>", "Skill name").option("-d, --description <desc>", "Skill description").option("-a, --author <author>", "Author name").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
|
|
2079
|
+
await init({
|
|
2080
|
+
name: options.name,
|
|
2081
|
+
description: options.description,
|
|
2082
|
+
author: options.author,
|
|
2083
|
+
yes: options.yes
|
|
2084
|
+
});
|
|
2085
|
+
});
|
|
2086
|
+
program.command("migrate").description(
|
|
2087
|
+
"Migrate from old directory structure (.skills/, skill-lock.json)"
|
|
2088
|
+
).option("--dry-run", "Show what would be migrated without making changes").action(async (options) => {
|
|
2089
|
+
await migrate({ dryRun: options.dryRun });
|
|
2090
|
+
});
|
|
1359
2091
|
program.command("add <specifier>").description("Add a skill (e.g., @user/bsheng/vite_slides@^2.0.0)").option("--save", "Save to lockfile (default)").action(async (specifier, options) => {
|
|
1360
2092
|
await add(specifier, { save: options.save ?? true });
|
|
1361
2093
|
});
|
|
@@ -1374,15 +2106,29 @@ program.command("install").alias("i").description("Install all skills from lockf
|
|
|
1374
2106
|
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
2107
|
await update({ dryRun: options.dryRun });
|
|
1376
2108
|
});
|
|
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) => {
|
|
2109
|
+
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
2110
|
await publishCommand({
|
|
1379
2111
|
bump: options.bump,
|
|
1380
|
-
tag: options.tag
|
|
2112
|
+
tag: options.tag,
|
|
2113
|
+
access: options.access
|
|
1381
2114
|
});
|
|
1382
2115
|
});
|
|
1383
|
-
program.command("unpublish <specifier>").description(
|
|
2116
|
+
program.command("unpublish <specifier>").description(
|
|
2117
|
+
"Remove a published skill version (only within 72 hours of publishing)"
|
|
2118
|
+
).option("--force", "Confirm destructive action").action(async (specifier, options) => {
|
|
1384
2119
|
await unpublish(specifier, { force: options.force });
|
|
1385
2120
|
});
|
|
2121
|
+
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) => {
|
|
2122
|
+
await access(specifier, {
|
|
2123
|
+
public: options.public,
|
|
2124
|
+
private: options.private
|
|
2125
|
+
});
|
|
2126
|
+
});
|
|
2127
|
+
program.command("deprecate <specifier> [message]").description(
|
|
2128
|
+
"Mark a skill version as deprecated (alternative to unpublish after 72 hours)"
|
|
2129
|
+
).option("--undo", "Remove deprecation status").action(async (specifier, message, options) => {
|
|
2130
|
+
await deprecate(specifier, message, { undo: options.undo });
|
|
2131
|
+
});
|
|
1386
2132
|
program.parse();
|
|
1387
2133
|
//# sourceMappingURL=index.js.map
|
|
1388
2134
|
//# sourceMappingURL=index.js.map
|