@anytio/pspm 0.0.4 → 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 +833 -86
- 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) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
const packages = getPackages(lockfile);
|
|
820
|
+
if (!packages[fullName]) {
|
|
479
821
|
return false;
|
|
480
822
|
}
|
|
481
|
-
delete
|
|
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`
|
|
@@ -542,11 +900,13 @@ async function add(specifier, _options) {
|
|
|
542
900
|
process.exit(1);
|
|
543
901
|
}
|
|
544
902
|
const versionInfo = versionResponse.data;
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
+
}
|
|
908
|
+
const tarballResponse = await fetch(versionInfo.downloadUrl, {
|
|
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,34 +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
|
-
}
|
|
701
|
-
const tarballBuffer = Buffer.from(await response.arrayBuffer());
|
|
702
|
-
const { createHash: createHash3 } = await import('crypto');
|
|
703
|
-
const actualIntegrity = `sha256-${createHash3("sha256").update(tarballBuffer).digest("base64")}`;
|
|
704
|
-
if (actualIntegrity !== entry.integrity) {
|
|
705
|
-
console.error(` Error: Checksum verification failed for ${fullName}`);
|
|
706
|
-
if (options.frozenLockfile) {
|
|
707
|
-
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}`;
|
|
708
1242
|
}
|
|
709
|
-
|
|
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);
|
|
710
1277
|
}
|
|
711
1278
|
const destDir = join(skillsDir, username, name);
|
|
712
1279
|
await rm(destDir, { recursive: true, force: true });
|
|
713
1280
|
await mkdir(destDir, { recursive: true });
|
|
714
1281
|
const tempFile = join(destDir, ".temp.tgz");
|
|
715
|
-
|
|
716
|
-
await writeFile4(tempFile, tarballBuffer);
|
|
1282
|
+
await writeFile(tempFile, tarballBuffer);
|
|
717
1283
|
const { exec: exec2 } = await import('child_process');
|
|
718
1284
|
const { promisify: promisify2 } = await import('util');
|
|
719
1285
|
const execAsync = promisify2(exec2);
|
|
@@ -724,7 +1290,9 @@ async function install(options) {
|
|
|
724
1290
|
} finally {
|
|
725
1291
|
await rm(tempFile, { force: true });
|
|
726
1292
|
}
|
|
727
|
-
console.log(
|
|
1293
|
+
console.log(
|
|
1294
|
+
` Installed to ${destDir}${fromCache ? " (from cache)" : ""}`
|
|
1295
|
+
);
|
|
728
1296
|
}
|
|
729
1297
|
console.log(`
|
|
730
1298
|
All ${skillCount} skill(s) installed.`);
|
|
@@ -754,7 +1322,7 @@ async function list(options) {
|
|
|
754
1322
|
const skillPath = join(skillsDir, username, skillName);
|
|
755
1323
|
let status = "installed";
|
|
756
1324
|
try {
|
|
757
|
-
await access(skillPath);
|
|
1325
|
+
await access$1(skillPath);
|
|
758
1326
|
} catch {
|
|
759
1327
|
status = "missing";
|
|
760
1328
|
}
|
|
@@ -777,7 +1345,7 @@ function getWebAppUrl(registryUrl) {
|
|
|
777
1345
|
return process.env.PSPM_WEB_URL.replace(/\/$/, "");
|
|
778
1346
|
}
|
|
779
1347
|
try {
|
|
780
|
-
const url = new URL(registryUrl);
|
|
1348
|
+
const url = new URL$1(registryUrl);
|
|
781
1349
|
return `${url.protocol}//${url.host}`;
|
|
782
1350
|
} catch {
|
|
783
1351
|
return DEFAULT_WEB_APP_URL;
|
|
@@ -785,7 +1353,7 @@ function getWebAppUrl(registryUrl) {
|
|
|
785
1353
|
}
|
|
786
1354
|
function getServerUrl(registryUrl) {
|
|
787
1355
|
try {
|
|
788
|
-
const url = new URL(registryUrl);
|
|
1356
|
+
const url = new URL$1(registryUrl);
|
|
789
1357
|
return `${url.protocol}//${url.host}`;
|
|
790
1358
|
} catch {
|
|
791
1359
|
return DEFAULT_WEB_APP_URL;
|
|
@@ -817,7 +1385,7 @@ function startCallbackServer(expectedState) {
|
|
|
817
1385
|
rejectToken = reject;
|
|
818
1386
|
});
|
|
819
1387
|
const server = http.createServer((req, res) => {
|
|
820
|
-
const url = new URL(req.url || "/", `http://localhost`);
|
|
1388
|
+
const url = new URL$1(req.url || "/", `http://localhost`);
|
|
821
1389
|
if (url.pathname === "/callback") {
|
|
822
1390
|
const token = url.searchParams.get("token");
|
|
823
1391
|
const state = url.searchParams.get("state");
|
|
@@ -963,7 +1531,132 @@ async function logout() {
|
|
|
963
1531
|
process.exit(1);
|
|
964
1532
|
}
|
|
965
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
|
+
}
|
|
966
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
|
+
}
|
|
967
1660
|
function formatBytes(bytes) {
|
|
968
1661
|
if (bytes < 1024) return `${bytes}B`;
|
|
969
1662
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}kB`;
|
|
@@ -995,23 +1688,26 @@ async function publishCommand(options) {
|
|
|
995
1688
|
try {
|
|
996
1689
|
const apiKey = await requireApiKey();
|
|
997
1690
|
const registryUrl = await getRegistryUrl();
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
if (!packageJson2.name) {
|
|
1008
|
-
console.error("Error: package.json must have a 'name' field");
|
|
1009
|
-
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("");
|
|
1010
1699
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1700
|
+
const validation = validateManifest(manifest);
|
|
1701
|
+
if (!validation.valid) {
|
|
1702
|
+
console.error(`Error: ${validation.error}`);
|
|
1013
1703
|
process.exit(1);
|
|
1014
1704
|
}
|
|
1705
|
+
const packageJson2 = {
|
|
1706
|
+
name: manifest.name,
|
|
1707
|
+
version: manifest.version,
|
|
1708
|
+
description: manifest.description,
|
|
1709
|
+
files: manifest.files
|
|
1710
|
+
};
|
|
1015
1711
|
if (options.bump) {
|
|
1016
1712
|
const semver2 = await import('semver');
|
|
1017
1713
|
const newVersion = semver2.default.inc(packageJson2.version, options.bump);
|
|
@@ -1029,13 +1725,7 @@ async function publishCommand(options) {
|
|
|
1029
1725
|
const tempDir = join(process.cwd(), ".pspm-publish");
|
|
1030
1726
|
try {
|
|
1031
1727
|
await exec(`rm -rf "${tempDir}" && mkdir -p "${tempDir}"`);
|
|
1032
|
-
const files = packageJson2.files || [
|
|
1033
|
-
"package.json",
|
|
1034
|
-
"SKILL.md",
|
|
1035
|
-
"runtime",
|
|
1036
|
-
"scripts",
|
|
1037
|
-
"data"
|
|
1038
|
-
];
|
|
1728
|
+
const files = packageJson2.files || [...DEFAULT_SKILL_FILES];
|
|
1039
1729
|
await exec(`mkdir -p "${tempDir}/package"`);
|
|
1040
1730
|
for (const file of files) {
|
|
1041
1731
|
try {
|
|
@@ -1045,7 +1735,18 @@ async function publishCommand(options) {
|
|
|
1045
1735
|
} catch {
|
|
1046
1736
|
}
|
|
1047
1737
|
}
|
|
1048
|
-
|
|
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
|
+
}
|
|
1049
1750
|
const packageDir = join(tempDir, "package");
|
|
1050
1751
|
const tarballContents = await getFilesWithSizes(packageDir, packageDir);
|
|
1051
1752
|
const unpackedSize = tarballContents.reduce((acc, f) => acc + f.size, 0);
|
|
@@ -1102,6 +1803,25 @@ async function publishCommand(options) {
|
|
|
1102
1803
|
`+ @user/${result.skill.username}/${result.skill.name}@${result.version.version}`
|
|
1103
1804
|
);
|
|
1104
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
|
+
}
|
|
1105
1825
|
} finally {
|
|
1106
1826
|
await exec(`rm -rf "${tempDir}"`).catch(() => {
|
|
1107
1827
|
});
|
|
@@ -1355,6 +2075,19 @@ program.command("logout").description("Log out and clear stored credentials").ac
|
|
|
1355
2075
|
program.command("whoami").description("Show current user information").action(async () => {
|
|
1356
2076
|
await whoami();
|
|
1357
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
|
+
});
|
|
1358
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) => {
|
|
1359
2092
|
await add(specifier, { save: options.save ?? true });
|
|
1360
2093
|
});
|
|
@@ -1373,15 +2106,29 @@ program.command("install").alias("i").description("Install all skills from lockf
|
|
|
1373
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) => {
|
|
1374
2107
|
await update({ dryRun: options.dryRun });
|
|
1375
2108
|
});
|
|
1376
|
-
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) => {
|
|
1377
2110
|
await publishCommand({
|
|
1378
2111
|
bump: options.bump,
|
|
1379
|
-
tag: options.tag
|
|
2112
|
+
tag: options.tag,
|
|
2113
|
+
access: options.access
|
|
1380
2114
|
});
|
|
1381
2115
|
});
|
|
1382
|
-
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) => {
|
|
1383
2119
|
await unpublish(specifier, { force: options.force });
|
|
1384
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
|
+
});
|
|
1385
2132
|
program.parse();
|
|
1386
2133
|
//# sourceMappingURL=index.js.map
|
|
1387
2134
|
//# sourceMappingURL=index.js.map
|