@pushrec/skills 0.2.0 → 0.3.4
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 +21 -10
- package/bundled-skills/README.md +9 -0
- package/bundled-skills/pushrec-skills/SKILL.md +381 -0
- package/bundled-skills/pushrec-skills/hooks/session_start.py +105 -0
- package/bundled-skills/pushrec-skills/references/prerequisites.yaml +412 -0
- package/bundled-skills/pushrec-skills/scripts/diagnostic.py +261 -0
- package/dist/index.js +2465 -205
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
|
-
var
|
|
27
|
+
var import_commander13 = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/commands/auth.ts
|
|
30
30
|
var import_commander = require("commander");
|
|
@@ -114,6 +114,17 @@ async function storeInKeychain(key) {
|
|
|
114
114
|
}
|
|
115
115
|
} catch {
|
|
116
116
|
}
|
|
117
|
+
if (os === "win32") {
|
|
118
|
+
try {
|
|
119
|
+
(0, import_child_process.execFileSync)(
|
|
120
|
+
"cmdkey",
|
|
121
|
+
[`/generic:${KEYCHAIN_SERVICE}`, `/user:${KEYCHAIN_ACCOUNT}`, `/pass:${key}`],
|
|
122
|
+
{ stdio: "pipe", timeout: 5e3 }
|
|
123
|
+
);
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
117
128
|
return false;
|
|
118
129
|
}
|
|
119
130
|
async function retrieveFromKeychain() {
|
|
@@ -181,6 +192,16 @@ async function deleteFromKeychain() {
|
|
|
181
192
|
{ stdio: "pipe", timeout: 5e3 }
|
|
182
193
|
);
|
|
183
194
|
}
|
|
195
|
+
if (os === "win32") {
|
|
196
|
+
try {
|
|
197
|
+
(0, import_child_process.execFileSync)(
|
|
198
|
+
"cmdkey",
|
|
199
|
+
[`/delete:${KEYCHAIN_SERVICE}`],
|
|
200
|
+
{ stdio: "pipe", timeout: 5e3 }
|
|
201
|
+
);
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
184
205
|
} catch {
|
|
185
206
|
}
|
|
186
207
|
}
|
|
@@ -190,7 +211,18 @@ function storeInFile(key) {
|
|
|
190
211
|
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
191
212
|
}
|
|
192
213
|
(0, import_fs.writeFileSync)(CREDENTIALS_FILE, key, "utf-8");
|
|
193
|
-
(0,
|
|
214
|
+
if ((0, import_os2.platform)() !== "win32") {
|
|
215
|
+
(0, import_fs.chmodSync)(CREDENTIALS_FILE, 384);
|
|
216
|
+
} else {
|
|
217
|
+
try {
|
|
218
|
+
(0, import_child_process.execFileSync)(
|
|
219
|
+
"icacls",
|
|
220
|
+
[CREDENTIALS_FILE, "/inheritance:r", "/grant:r", `${process.env.USERNAME}:F`],
|
|
221
|
+
{ stdio: "pipe", timeout: 5e3 }
|
|
222
|
+
);
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
}
|
|
194
226
|
}
|
|
195
227
|
function retrieveFromFile() {
|
|
196
228
|
if (!(0, import_fs.existsSync)(CREDENTIALS_FILE)) return null;
|
|
@@ -294,6 +326,8 @@ function getHostname() {
|
|
|
294
326
|
// src/lib/license.ts
|
|
295
327
|
var ed = __toESM(require("@noble/ed25519"));
|
|
296
328
|
var import_fs3 = require("fs");
|
|
329
|
+
var import_child_process3 = require("child_process");
|
|
330
|
+
var import_os4 = require("os");
|
|
297
331
|
var import_path3 = require("path");
|
|
298
332
|
function fromBase64Url(str) {
|
|
299
333
|
const padded = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -359,7 +393,18 @@ function saveLicenseCache(cache) {
|
|
|
359
393
|
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
360
394
|
}
|
|
361
395
|
(0, import_fs3.writeFileSync)(LICENSE_CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
362
|
-
(0,
|
|
396
|
+
if ((0, import_os4.platform)() !== "win32") {
|
|
397
|
+
(0, import_fs3.chmodSync)(LICENSE_CACHE_FILE, 384);
|
|
398
|
+
} else {
|
|
399
|
+
try {
|
|
400
|
+
(0, import_child_process3.execFileSync)(
|
|
401
|
+
"icacls",
|
|
402
|
+
[LICENSE_CACHE_FILE, "/inheritance:r", "/grant:r", `${process.env.USERNAME}:F`],
|
|
403
|
+
{ stdio: "pipe", timeout: 5e3 }
|
|
404
|
+
);
|
|
405
|
+
} catch {
|
|
406
|
+
}
|
|
407
|
+
}
|
|
363
408
|
}
|
|
364
409
|
function deleteLicenseCache() {
|
|
365
410
|
try {
|
|
@@ -399,35 +444,341 @@ function checkOfflineStatus(cache) {
|
|
|
399
444
|
|
|
400
445
|
// src/lib/config.ts
|
|
401
446
|
var import_fs4 = require("fs");
|
|
447
|
+
var import_path5 = require("path");
|
|
448
|
+
|
|
449
|
+
// src/lib/client-roots.ts
|
|
450
|
+
var import_os5 = require("os");
|
|
402
451
|
var import_path4 = require("path");
|
|
403
|
-
|
|
452
|
+
var SUPPORTED_CLIENTS = [
|
|
453
|
+
"claude",
|
|
454
|
+
"codex",
|
|
455
|
+
"gemini",
|
|
456
|
+
"antigravity"
|
|
457
|
+
];
|
|
458
|
+
function ctxPlatform(ctx) {
|
|
459
|
+
return ctx?.platform ?? (0, import_os5.platform)();
|
|
460
|
+
}
|
|
461
|
+
function ctxHome(ctx) {
|
|
462
|
+
return ctx?.homeDir ?? (0, import_os5.homedir)();
|
|
463
|
+
}
|
|
464
|
+
function ctxEnv(ctx) {
|
|
465
|
+
return ctx?.env ?? process.env;
|
|
466
|
+
}
|
|
467
|
+
function resolveClaudeHome(ctx) {
|
|
468
|
+
const env = ctxEnv(ctx);
|
|
469
|
+
return env.PUSHREC_CLAUDE_HOME || (0, import_path4.join)(ctxHome(ctx), ".claude");
|
|
470
|
+
}
|
|
471
|
+
function resolveCodexHome(ctx) {
|
|
472
|
+
const env = ctxEnv(ctx);
|
|
473
|
+
return env.CODEX_HOME || (0, import_path4.join)(ctxHome(ctx), ".codex");
|
|
474
|
+
}
|
|
475
|
+
function resolveGeminiHome(ctx) {
|
|
476
|
+
const env = ctxEnv(ctx);
|
|
477
|
+
return env.GEMINI_HOME || (0, import_path4.join)(ctxHome(ctx), ".gemini");
|
|
478
|
+
}
|
|
479
|
+
function resolveAntigravityUserDir(ctx) {
|
|
480
|
+
const env = ctxEnv(ctx);
|
|
481
|
+
if (env.PUSHREC_ANTIGRAVITY_USER_DIR) {
|
|
482
|
+
return env.PUSHREC_ANTIGRAVITY_USER_DIR;
|
|
483
|
+
}
|
|
484
|
+
const platform5 = ctxPlatform(ctx);
|
|
485
|
+
const home = ctxHome(ctx);
|
|
486
|
+
if (platform5 === "darwin") {
|
|
487
|
+
return (0, import_path4.join)(home, "Library", "Application Support", "Antigravity", "User");
|
|
488
|
+
}
|
|
489
|
+
if (platform5 === "win32") {
|
|
490
|
+
const appData = env.APPDATA || (0, import_path4.join)(home, "AppData", "Roaming");
|
|
491
|
+
return (0, import_path4.join)(appData, "Antigravity", "User");
|
|
492
|
+
}
|
|
493
|
+
return (0, import_path4.join)(home, ".config", "Antigravity", "User");
|
|
494
|
+
}
|
|
495
|
+
function resolveClientInstallRoots(client, ctx) {
|
|
496
|
+
const platform5 = ctxPlatform(ctx);
|
|
497
|
+
switch (client) {
|
|
498
|
+
case "claude": {
|
|
499
|
+
const profileHome = resolveClaudeHome(ctx);
|
|
500
|
+
return {
|
|
501
|
+
client,
|
|
502
|
+
platform: platform5,
|
|
503
|
+
profileHome,
|
|
504
|
+
skillsRoot: (0, import_path4.join)(profileHome, "skills"),
|
|
505
|
+
nativeSkillRootVerified: true,
|
|
506
|
+
previewProfile: false,
|
|
507
|
+
notes: ["Claude Code native skills root is verified: <CLAUDE_HOME>/skills."]
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
case "codex": {
|
|
511
|
+
const profileHome = resolveCodexHome(ctx);
|
|
512
|
+
return {
|
|
513
|
+
client,
|
|
514
|
+
platform: platform5,
|
|
515
|
+
profileHome,
|
|
516
|
+
skillsRoot: (0, import_path4.join)(profileHome, "skills"),
|
|
517
|
+
nativeSkillRootVerified: true,
|
|
518
|
+
previewProfile: true,
|
|
519
|
+
notes: ["Codex native skills root is verified: <CODEX_HOME>/skills."]
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
case "gemini": {
|
|
523
|
+
const profileHome = resolveGeminiHome(ctx);
|
|
524
|
+
return {
|
|
525
|
+
client,
|
|
526
|
+
platform: platform5,
|
|
527
|
+
profileHome,
|
|
528
|
+
skillsRoot: null,
|
|
529
|
+
nativeSkillRootVerified: false,
|
|
530
|
+
previewProfile: true,
|
|
531
|
+
notes: [
|
|
532
|
+
"Gemini preview profile detected, but native skills root is not yet standardized in CLI foundation.",
|
|
533
|
+
"Use compatibility profile artifacts (e.g., GEMINI.md / commands/extensions) until native root is locked."
|
|
534
|
+
]
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
case "antigravity": {
|
|
538
|
+
const profileHome = resolveAntigravityUserDir(ctx);
|
|
539
|
+
return {
|
|
540
|
+
client,
|
|
541
|
+
platform: platform5,
|
|
542
|
+
profileHome,
|
|
543
|
+
skillsRoot: null,
|
|
544
|
+
nativeSkillRootVerified: false,
|
|
545
|
+
previewProfile: true,
|
|
546
|
+
notes: [
|
|
547
|
+
"Antigravity preview profile detected, but native skills root is not yet verified in CLI foundation.",
|
|
548
|
+
"Use compatibility profile artifacts (AGENTS.md / compatible assets) until native root is locked."
|
|
549
|
+
]
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
default: {
|
|
553
|
+
const exhaustive = client;
|
|
554
|
+
throw new Error(`Unsupported client: ${String(exhaustive)}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function getLegacyClaudeSkillsRoot(ctx) {
|
|
559
|
+
return resolveClientInstallRoots("claude", ctx).skillsRoot;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/lib/config.ts
|
|
563
|
+
var CLI_CONFIG_SCHEMA_VERSION = 2;
|
|
564
|
+
function cloneInstalledSkills(skills) {
|
|
565
|
+
if (!skills) return {};
|
|
566
|
+
return Object.fromEntries(
|
|
567
|
+
Object.entries(skills).map(([name, meta]) => [name, { ...meta }])
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
function defaultVaultState() {
|
|
571
|
+
return {
|
|
572
|
+
status: "not_started",
|
|
573
|
+
vaultPath: null,
|
|
574
|
+
claudeMdPath: null,
|
|
575
|
+
agentsMdPath: null,
|
|
576
|
+
lastUpdatedAt: null,
|
|
577
|
+
backups: []
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function defaultSetupState() {
|
|
581
|
+
return {
|
|
582
|
+
schemaVersion: 1,
|
|
583
|
+
currentRunId: null,
|
|
584
|
+
completedPhases: [],
|
|
585
|
+
lastRunAt: null,
|
|
586
|
+
lastError: null
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function emptyClientProfile(client) {
|
|
590
|
+
const roots = resolveClientInstallRoots(client);
|
|
591
|
+
return {
|
|
592
|
+
client,
|
|
593
|
+
detected: false,
|
|
594
|
+
selected: false,
|
|
595
|
+
profileHome: roots.profileHome,
|
|
596
|
+
skillsRoot: roots.skillsRoot,
|
|
597
|
+
nativeSkillRootVerified: roots.nativeSkillRootVerified,
|
|
598
|
+
previewProfile: roots.previewProfile,
|
|
599
|
+
lastDetectedAt: null,
|
|
600
|
+
installedSkills: {},
|
|
601
|
+
notes: [...roots.notes]
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function defaultClients() {
|
|
605
|
+
const out = {};
|
|
606
|
+
for (const client of SUPPORTED_CLIENTS) {
|
|
607
|
+
out[client] = emptyClientProfile(client);
|
|
608
|
+
}
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
function createDefaultConfig() {
|
|
404
612
|
return {
|
|
613
|
+
schemaVersion: CLI_CONFIG_SCHEMA_VERSION,
|
|
405
614
|
registryUrl: REGISTRY_URL,
|
|
406
615
|
lastVerifiedAt: null,
|
|
407
|
-
installedSkills: {}
|
|
616
|
+
installedSkills: {},
|
|
617
|
+
clients: defaultClients(),
|
|
618
|
+
skillState: {},
|
|
619
|
+
vault: defaultVaultState(),
|
|
620
|
+
setup: defaultSetupState()
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function asRecord(value) {
|
|
624
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
return value;
|
|
628
|
+
}
|
|
629
|
+
function migrateClientProfile(client, rawValue, legacyInstalledSkills) {
|
|
630
|
+
const base = emptyClientProfile(client);
|
|
631
|
+
const raw = asRecord(rawValue);
|
|
632
|
+
if (!raw) {
|
|
633
|
+
if (client === "claude") {
|
|
634
|
+
base.installedSkills = cloneInstalledSkills(legacyInstalledSkills);
|
|
635
|
+
}
|
|
636
|
+
return base;
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
...base,
|
|
640
|
+
detected: typeof raw.detected === "boolean" ? raw.detected : base.detected,
|
|
641
|
+
selected: typeof raw.selected === "boolean" ? raw.selected : base.selected,
|
|
642
|
+
profileHome: typeof raw.profileHome === "string" || raw.profileHome === null ? raw.profileHome : base.profileHome,
|
|
643
|
+
skillsRoot: typeof raw.skillsRoot === "string" || raw.skillsRoot === null ? raw.skillsRoot : base.skillsRoot,
|
|
644
|
+
nativeSkillRootVerified: typeof raw.nativeSkillRootVerified === "boolean" ? raw.nativeSkillRootVerified : base.nativeSkillRootVerified,
|
|
645
|
+
previewProfile: typeof raw.previewProfile === "boolean" ? raw.previewProfile : base.previewProfile,
|
|
646
|
+
lastDetectedAt: typeof raw.lastDetectedAt === "string" || raw.lastDetectedAt === null ? raw.lastDetectedAt : base.lastDetectedAt,
|
|
647
|
+
installedSkills: cloneInstalledSkills(
|
|
648
|
+
asRecord(raw.installedSkills)
|
|
649
|
+
),
|
|
650
|
+
notes: Array.isArray(raw.notes) ? raw.notes.filter((x) => typeof x === "string") : base.notes
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function migrateSkillState(rawValue) {
|
|
654
|
+
const raw = asRecord(rawValue);
|
|
655
|
+
if (!raw) return {};
|
|
656
|
+
const out = {};
|
|
657
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
658
|
+
const item = asRecord(value);
|
|
659
|
+
if (!item) continue;
|
|
660
|
+
out[name] = {
|
|
661
|
+
name,
|
|
662
|
+
mode: item.mode === "managed" || item.mode === "forked" || item.mode === "custom-unmanaged" ? item.mode : "managed",
|
|
663
|
+
lastKnownVersion: typeof item.lastKnownVersion === "string" || item.lastKnownVersion === null ? item.lastKnownVersion : null,
|
|
664
|
+
installedPath: typeof item.installedPath === "string" || item.installedPath === null ? item.installedPath : null,
|
|
665
|
+
targetClient: item.targetClient === "claude" || item.targetClient === "codex" || item.targetClient === "gemini" || item.targetClient === "antigravity" || item.targetClient === null ? item.targetClient : null,
|
|
666
|
+
contentHash: typeof item.contentHash === "string" || item.contentHash === null ? item.contentHash : null,
|
|
667
|
+
backups: Array.isArray(item.backups) ? item.backups.filter((x) => typeof x === "string") : [],
|
|
668
|
+
localModified: typeof item.localModified === "boolean" ? item.localModified : false,
|
|
669
|
+
lastCheckedAt: typeof item.lastCheckedAt === "string" || item.lastCheckedAt === null ? item.lastCheckedAt : null
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
function migrateVaultState(rawValue) {
|
|
675
|
+
const base = defaultVaultState();
|
|
676
|
+
const raw = asRecord(rawValue);
|
|
677
|
+
if (!raw) return base;
|
|
678
|
+
return {
|
|
679
|
+
status: raw.status === "not_started" || raw.status === "initialized" || raw.status === "adopted" || raw.status === "error" ? raw.status : base.status,
|
|
680
|
+
vaultPath: typeof raw.vaultPath === "string" || raw.vaultPath === null ? raw.vaultPath : base.vaultPath,
|
|
681
|
+
claudeMdPath: typeof raw.claudeMdPath === "string" || raw.claudeMdPath === null ? raw.claudeMdPath : base.claudeMdPath,
|
|
682
|
+
agentsMdPath: typeof raw.agentsMdPath === "string" || raw.agentsMdPath === null ? raw.agentsMdPath : base.agentsMdPath,
|
|
683
|
+
lastUpdatedAt: typeof raw.lastUpdatedAt === "string" || raw.lastUpdatedAt === null ? raw.lastUpdatedAt : base.lastUpdatedAt,
|
|
684
|
+
backups: Array.isArray(raw.backups) ? raw.backups.filter((x) => typeof x === "string") : base.backups
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function migrateSetupState(rawValue) {
|
|
688
|
+
const base = defaultSetupState();
|
|
689
|
+
const raw = asRecord(rawValue);
|
|
690
|
+
if (!raw) return base;
|
|
691
|
+
return {
|
|
692
|
+
schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : base.schemaVersion,
|
|
693
|
+
currentRunId: typeof raw.currentRunId === "string" || raw.currentRunId === null ? raw.currentRunId : base.currentRunId,
|
|
694
|
+
completedPhases: Array.isArray(raw.completedPhases) ? raw.completedPhases.filter((x) => typeof x === "string") : base.completedPhases,
|
|
695
|
+
lastRunAt: typeof raw.lastRunAt === "string" || raw.lastRunAt === null ? raw.lastRunAt : base.lastRunAt,
|
|
696
|
+
lastError: typeof raw.lastError === "string" || raw.lastError === null ? raw.lastError : base.lastError
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function migrateCliConfig(rawValue) {
|
|
700
|
+
const defaults = createDefaultConfig();
|
|
701
|
+
const raw = asRecord(rawValue);
|
|
702
|
+
if (!raw) return defaults;
|
|
703
|
+
const legacyInstalled = cloneInstalledSkills(
|
|
704
|
+
asRecord(raw.installedSkills)
|
|
705
|
+
);
|
|
706
|
+
const rawClients = asRecord(raw.clients);
|
|
707
|
+
const clients = {};
|
|
708
|
+
for (const client of SUPPORTED_CLIENTS) {
|
|
709
|
+
clients[client] = migrateClientProfile(
|
|
710
|
+
client,
|
|
711
|
+
rawClients?.[client],
|
|
712
|
+
client === "claude" ? legacyInstalled : {}
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
schemaVersion: CLI_CONFIG_SCHEMA_VERSION,
|
|
717
|
+
registryUrl: typeof raw.registryUrl === "string" ? raw.registryUrl : REGISTRY_URL,
|
|
718
|
+
lastVerifiedAt: typeof raw.lastVerifiedAt === "string" || raw.lastVerifiedAt === null ? raw.lastVerifiedAt : null,
|
|
719
|
+
installedSkills: legacyInstalled,
|
|
720
|
+
clients,
|
|
721
|
+
skillState: migrateSkillState(raw.skillState),
|
|
722
|
+
vault: migrateVaultState(raw.vault),
|
|
723
|
+
setup: migrateSetupState(raw.setup)
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function mergeClientProfiles(base, patch) {
|
|
727
|
+
if (!patch) return base;
|
|
728
|
+
const out = { ...base };
|
|
729
|
+
for (const [client, next] of Object.entries(patch)) {
|
|
730
|
+
if (!next) continue;
|
|
731
|
+
const key = client;
|
|
732
|
+
const prev = out[key];
|
|
733
|
+
out[key] = prev ? {
|
|
734
|
+
...prev,
|
|
735
|
+
...next,
|
|
736
|
+
installedSkills: {
|
|
737
|
+
...prev.installedSkills,
|
|
738
|
+
...next.installedSkills ?? {}
|
|
739
|
+
},
|
|
740
|
+
notes: next.notes ?? prev.notes
|
|
741
|
+
} : next;
|
|
742
|
+
}
|
|
743
|
+
return out;
|
|
744
|
+
}
|
|
745
|
+
function mergeCliConfig(current, patch) {
|
|
746
|
+
return {
|
|
747
|
+
...current,
|
|
748
|
+
...patch,
|
|
749
|
+
schemaVersion: CLI_CONFIG_SCHEMA_VERSION,
|
|
750
|
+
installedSkills: patch.installedSkills ?? current.installedSkills,
|
|
751
|
+
clients: mergeClientProfiles(current.clients, patch.clients),
|
|
752
|
+
skillState: { ...current.skillState, ...patch.skillState ?? {} },
|
|
753
|
+
vault: { ...current.vault, ...patch.vault ?? {} },
|
|
754
|
+
setup: { ...current.setup, ...patch.setup ?? {} }
|
|
408
755
|
};
|
|
409
756
|
}
|
|
410
757
|
function loadConfig() {
|
|
411
758
|
if (!(0, import_fs4.existsSync)(CONFIG_FILE)) {
|
|
412
|
-
return
|
|
759
|
+
return createDefaultConfig();
|
|
413
760
|
}
|
|
414
761
|
try {
|
|
415
762
|
const raw = (0, import_fs4.readFileSync)(CONFIG_FILE, "utf-8");
|
|
416
|
-
return
|
|
763
|
+
return migrateCliConfig(JSON.parse(raw));
|
|
417
764
|
} catch {
|
|
418
|
-
return
|
|
765
|
+
return createDefaultConfig();
|
|
419
766
|
}
|
|
420
767
|
}
|
|
421
768
|
function saveConfig(config) {
|
|
422
|
-
const dir = (0,
|
|
769
|
+
const dir = (0, import_path5.dirname)(CONFIG_FILE);
|
|
423
770
|
if (!(0, import_fs4.existsSync)(dir)) {
|
|
424
771
|
(0, import_fs4.mkdirSync)(dir, { recursive: true });
|
|
425
772
|
}
|
|
426
|
-
(0, import_fs4.writeFileSync)(
|
|
773
|
+
(0, import_fs4.writeFileSync)(
|
|
774
|
+
CONFIG_FILE,
|
|
775
|
+
JSON.stringify(migrateCliConfig(config), null, 2),
|
|
776
|
+
"utf-8"
|
|
777
|
+
);
|
|
427
778
|
}
|
|
428
779
|
function updateConfig(partial) {
|
|
429
780
|
const config = loadConfig();
|
|
430
|
-
const updated =
|
|
781
|
+
const updated = mergeCliConfig(config, partial);
|
|
431
782
|
saveConfig(updated);
|
|
432
783
|
return updated;
|
|
433
784
|
}
|
|
@@ -478,10 +829,11 @@ async function fetchSkillManifest(name) {
|
|
|
478
829
|
}
|
|
479
830
|
return response.json();
|
|
480
831
|
}
|
|
481
|
-
async function downloadSkill(name) {
|
|
832
|
+
async function downloadSkill(name, version) {
|
|
482
833
|
const authHeaders = await getAuthHeaders();
|
|
834
|
+
const versionQuery = version ? `?version=${encodeURIComponent(version)}` : "";
|
|
483
835
|
const response = await registryFetch(
|
|
484
|
-
`/api/skills/${encodeURIComponent(name)}/download`,
|
|
836
|
+
`/api/skills/${encodeURIComponent(name)}/download${versionQuery}`,
|
|
485
837
|
{ headers: authHeaders }
|
|
486
838
|
);
|
|
487
839
|
if (!response.ok) {
|
|
@@ -591,17 +943,17 @@ function readInput(question) {
|
|
|
591
943
|
input: process.stdin,
|
|
592
944
|
output: process.stdout
|
|
593
945
|
});
|
|
594
|
-
return new Promise((
|
|
946
|
+
return new Promise((resolve7, reject) => {
|
|
595
947
|
rl.on("error", (err) => {
|
|
596
948
|
rl.close();
|
|
597
949
|
reject(err);
|
|
598
950
|
});
|
|
599
951
|
rl.on("close", () => {
|
|
600
|
-
|
|
952
|
+
resolve7("");
|
|
601
953
|
});
|
|
602
954
|
rl.question(question, (answer) => {
|
|
603
955
|
rl.close();
|
|
604
|
-
|
|
956
|
+
resolve7(answer.trim());
|
|
605
957
|
});
|
|
606
958
|
});
|
|
607
959
|
}
|
|
@@ -860,33 +1212,37 @@ var import_ora = __toESM(require("ora"));
|
|
|
860
1212
|
|
|
861
1213
|
// src/lib/installer.ts
|
|
862
1214
|
var import_fs5 = require("fs");
|
|
863
|
-
var
|
|
864
|
-
var
|
|
865
|
-
var
|
|
1215
|
+
var import_path6 = require("path");
|
|
1216
|
+
var import_child_process4 = require("child_process");
|
|
1217
|
+
var import_os6 = require("os");
|
|
866
1218
|
var import_crypto2 = require("crypto");
|
|
867
|
-
|
|
1219
|
+
function resolveSkillsRoot(options) {
|
|
1220
|
+
return options?.skillsRoot ?? getLegacyClaudeSkillsRoot();
|
|
1221
|
+
}
|
|
1222
|
+
async function installSkill(name, archive, options) {
|
|
868
1223
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(name) || name.includes("..")) {
|
|
869
1224
|
throw new Error(`Invalid skill name: "${name}"`);
|
|
870
1225
|
}
|
|
871
|
-
const
|
|
1226
|
+
const skillsRoot = resolveSkillsRoot(options);
|
|
1227
|
+
const targetDir = (0, import_path6.join)(skillsRoot, name);
|
|
872
1228
|
const tmpId = (0, import_crypto2.randomBytes)(4).toString("hex");
|
|
873
|
-
const tmpDir = (0,
|
|
1229
|
+
const tmpDir = (0, import_path6.join)((0, import_os6.tmpdir)(), `pushrec-install-${tmpId}`);
|
|
874
1230
|
try {
|
|
875
1231
|
(0, import_fs5.mkdirSync)(tmpDir, { recursive: true });
|
|
876
|
-
const zipPath = (0,
|
|
1232
|
+
const zipPath = (0, import_path6.join)(tmpDir, `${name}.zip`);
|
|
877
1233
|
(0, import_fs5.writeFileSync)(zipPath, Buffer.from(archive));
|
|
878
|
-
const extractDir = (0,
|
|
1234
|
+
const extractDir = (0, import_path6.join)(tmpDir, "extracted");
|
|
879
1235
|
(0, import_fs5.mkdirSync)(extractDir, { recursive: true });
|
|
880
1236
|
extractZip(zipPath, extractDir);
|
|
881
1237
|
const skillRoot = findSkillRoot(extractDir, name);
|
|
882
|
-
const resolvedRoot = (0,
|
|
883
|
-
const resolvedExtract = (0,
|
|
884
|
-
const rel = (0,
|
|
885
|
-
if (rel.startsWith("..") || (0,
|
|
1238
|
+
const resolvedRoot = (0, import_path6.resolve)(skillRoot);
|
|
1239
|
+
const resolvedExtract = (0, import_path6.resolve)(extractDir);
|
|
1240
|
+
const rel = (0, import_path6.relative)(resolvedExtract, resolvedRoot);
|
|
1241
|
+
if (rel.startsWith("..") || (0, import_path6.resolve)(rel) === resolvedRoot) {
|
|
886
1242
|
throw new Error("Archive contains path traversal \u2014 aborting install.");
|
|
887
1243
|
}
|
|
888
|
-
if (!(0, import_fs5.existsSync)(
|
|
889
|
-
(0, import_fs5.mkdirSync)(
|
|
1244
|
+
if (!(0, import_fs5.existsSync)(skillsRoot)) {
|
|
1245
|
+
(0, import_fs5.mkdirSync)(skillsRoot, { recursive: true });
|
|
890
1246
|
}
|
|
891
1247
|
if ((0, import_fs5.existsSync)(targetDir)) {
|
|
892
1248
|
(0, import_fs5.rmSync)(targetDir, { recursive: true, force: true });
|
|
@@ -901,15 +1257,22 @@ async function installSkill(name, archive) {
|
|
|
901
1257
|
}
|
|
902
1258
|
}
|
|
903
1259
|
function extractZip(zipPath, destDir) {
|
|
904
|
-
const os = (0,
|
|
1260
|
+
const os = (0, import_os6.platform)();
|
|
905
1261
|
if (os === "win32") {
|
|
906
|
-
(
|
|
1262
|
+
const safeZip = zipPath.replace(/'/g, "''");
|
|
1263
|
+
const safeDest = destDir.replace(/'/g, "''");
|
|
1264
|
+
(0, import_child_process4.execFileSync)(
|
|
907
1265
|
"powershell",
|
|
908
|
-
[
|
|
1266
|
+
[
|
|
1267
|
+
"-NoProfile",
|
|
1268
|
+
"-NonInteractive",
|
|
1269
|
+
"-Command",
|
|
1270
|
+
`Expand-Archive -LiteralPath '${safeZip}' -DestinationPath '${safeDest}' -Force`
|
|
1271
|
+
],
|
|
909
1272
|
{ stdio: "pipe", timeout: 6e4 }
|
|
910
1273
|
);
|
|
911
1274
|
} else {
|
|
912
|
-
(0,
|
|
1275
|
+
(0, import_child_process4.execFileSync)("unzip", ["-o", "-q", zipPath, "-d", destDir], {
|
|
913
1276
|
stdio: "pipe",
|
|
914
1277
|
timeout: 6e4
|
|
915
1278
|
});
|
|
@@ -918,36 +1281,311 @@ function extractZip(zipPath, destDir) {
|
|
|
918
1281
|
function findSkillRoot(extractDir, name) {
|
|
919
1282
|
const entries = (0, import_fs5.readdirSync)(extractDir);
|
|
920
1283
|
const skillDir = entries.find((entry) => {
|
|
921
|
-
const fullPath = (0,
|
|
1284
|
+
const fullPath = (0, import_path6.join)(extractDir, entry);
|
|
922
1285
|
return (0, import_fs5.statSync)(fullPath).isDirectory() && (entry === name || entry.startsWith(`${name}-`));
|
|
923
1286
|
});
|
|
924
1287
|
if (skillDir) {
|
|
925
|
-
return (0,
|
|
1288
|
+
return (0, import_path6.join)(extractDir, skillDir);
|
|
926
1289
|
}
|
|
927
1290
|
if (entries.includes("SKILL.md")) {
|
|
928
1291
|
return extractDir;
|
|
929
1292
|
}
|
|
930
1293
|
const firstDir = entries.find(
|
|
931
|
-
(entry) => (0, import_fs5.statSync)((0,
|
|
1294
|
+
(entry) => (0, import_fs5.statSync)((0, import_path6.join)(extractDir, entry)).isDirectory()
|
|
932
1295
|
);
|
|
933
1296
|
if (firstDir) {
|
|
934
|
-
return (0,
|
|
1297
|
+
return (0, import_path6.join)(extractDir, firstDir);
|
|
935
1298
|
}
|
|
936
1299
|
throw new Error(
|
|
937
1300
|
`Could not find skill root in archive for "${name}". Expected a directory containing SKILL.md.`
|
|
938
1301
|
);
|
|
939
1302
|
}
|
|
940
|
-
function uninstallSkill(name) {
|
|
1303
|
+
function uninstallSkill(name, options) {
|
|
941
1304
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(name) || name.includes("..") || name.includes("/") || name.includes("\\")) {
|
|
942
1305
|
throw new Error(`Invalid skill name: "${name}". Only lowercase letters, numbers, and hyphens allowed.`);
|
|
943
1306
|
}
|
|
944
|
-
const targetDir = (0,
|
|
1307
|
+
const targetDir = (0, import_path6.join)(resolveSkillsRoot(options), name);
|
|
945
1308
|
if (!(0, import_fs5.existsSync)(targetDir)) return false;
|
|
946
1309
|
(0, import_fs5.rmSync)(targetDir, { recursive: true, force: true });
|
|
947
1310
|
return true;
|
|
948
1311
|
}
|
|
949
1312
|
function isSkillInstalled(name) {
|
|
950
|
-
return (
|
|
1313
|
+
return isSkillInstalledAtRoot(name, resolveSkillsRoot());
|
|
1314
|
+
}
|
|
1315
|
+
function getBundledSkillPath(name) {
|
|
1316
|
+
return (0, import_path6.join)(__dirname, "..", "bundled-skills", name);
|
|
1317
|
+
}
|
|
1318
|
+
function isSkillInstalledAtRoot(name, skillsRoot) {
|
|
1319
|
+
return (0, import_fs5.existsSync)((0, import_path6.join)(skillsRoot, name, "SKILL.md"));
|
|
1320
|
+
}
|
|
1321
|
+
function copyBundledSkill(name, options) {
|
|
1322
|
+
const sourcePath = options?.bundledSkillPath ?? getBundledSkillPath(name);
|
|
1323
|
+
if (!(0, import_fs5.existsSync)(sourcePath)) return;
|
|
1324
|
+
const skillsRoot = resolveSkillsRoot(options);
|
|
1325
|
+
const targetPath = (0, import_path6.join)(skillsRoot, name);
|
|
1326
|
+
if (!(0, import_fs5.existsSync)(skillsRoot)) {
|
|
1327
|
+
(0, import_fs5.mkdirSync)(skillsRoot, { recursive: true });
|
|
1328
|
+
}
|
|
1329
|
+
if ((0, import_fs5.existsSync)((0, import_path6.join)(targetPath, "SKILL.md"))) return;
|
|
1330
|
+
(0, import_fs5.cpSync)(sourcePath, targetPath, { recursive: true });
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/lib/managed-skill.ts
|
|
1334
|
+
var import_fs8 = require("fs");
|
|
1335
|
+
var import_path9 = require("path");
|
|
1336
|
+
|
|
1337
|
+
// src/lib/backups.ts
|
|
1338
|
+
var import_fs6 = require("fs");
|
|
1339
|
+
var import_path7 = require("path");
|
|
1340
|
+
var import_crypto3 = require("crypto");
|
|
1341
|
+
var DEFAULT_BACKUPS_DIR = (0, import_path7.join)(CONFIG_DIR, "backups");
|
|
1342
|
+
function resolveBackupsRoot(options = {}) {
|
|
1343
|
+
const env = options.env ?? process.env;
|
|
1344
|
+
const configured = env.PUSHREC_BACKUPS_DIR?.trim();
|
|
1345
|
+
return (0, import_path7.resolve)(configured && configured.length > 0 ? configured : DEFAULT_BACKUPS_DIR);
|
|
1346
|
+
}
|
|
1347
|
+
function ensureBackupsRoot(options = {}) {
|
|
1348
|
+
const root = resolveBackupsRoot(options);
|
|
1349
|
+
if (!(0, import_fs6.existsSync)(root)) {
|
|
1350
|
+
(0, import_fs6.mkdirSync)(root, { recursive: true });
|
|
1351
|
+
}
|
|
1352
|
+
return root;
|
|
1353
|
+
}
|
|
1354
|
+
function sanitizeBackupLabel(label) {
|
|
1355
|
+
const cleaned = label.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
1356
|
+
return cleaned.length > 0 ? cleaned : "item";
|
|
1357
|
+
}
|
|
1358
|
+
function createBackupId(prefix, now = /* @__PURE__ */ new Date()) {
|
|
1359
|
+
const ts = now.toISOString().replace(/[:.]/g, "-");
|
|
1360
|
+
const suffix = (0, import_crypto3.randomBytes)(3).toString("hex");
|
|
1361
|
+
return `${sanitizeBackupLabel(prefix)}-${ts}-${suffix}`;
|
|
1362
|
+
}
|
|
1363
|
+
function buildBackupDirectoryPath(label, options = {}) {
|
|
1364
|
+
const root = resolveBackupsRoot(options);
|
|
1365
|
+
const id = createBackupId(label, options.now);
|
|
1366
|
+
return (0, import_path7.join)(root, id);
|
|
1367
|
+
}
|
|
1368
|
+
function buildSkillBackupDirectoryPath(skillName, options = {}) {
|
|
1369
|
+
return buildBackupDirectoryPath(`skill-${skillName}`, options);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/lib/tree-hash.ts
|
|
1373
|
+
var import_crypto4 = require("crypto");
|
|
1374
|
+
var import_fs7 = require("fs");
|
|
1375
|
+
var import_path8 = require("path");
|
|
1376
|
+
function isExcludedName(name, exclude) {
|
|
1377
|
+
return exclude.has(name);
|
|
1378
|
+
}
|
|
1379
|
+
function walkFiles(root, current, exclude, out) {
|
|
1380
|
+
const entries = (0, import_fs7.readdirSync)(current).sort((a, b) => a.localeCompare(b));
|
|
1381
|
+
for (const entry of entries) {
|
|
1382
|
+
if (isExcludedName(entry, exclude)) continue;
|
|
1383
|
+
const fullPath = (0, import_path8.join)(current, entry);
|
|
1384
|
+
const stat = (0, import_fs7.lstatSync)(fullPath);
|
|
1385
|
+
if (stat.isDirectory()) {
|
|
1386
|
+
walkFiles(root, fullPath, exclude, out);
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (!stat.isFile()) continue;
|
|
1390
|
+
out.push(fullPath);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
function hashDirectoryTree(rootPath, options = {}) {
|
|
1394
|
+
const root = (0, import_path8.resolve)(rootPath);
|
|
1395
|
+
const stat = (0, import_fs7.lstatSync)(root);
|
|
1396
|
+
if (!stat.isDirectory()) {
|
|
1397
|
+
throw new Error(`Not a directory: ${root}`);
|
|
1398
|
+
}
|
|
1399
|
+
const exclude = new Set(options.excludeNames ?? []);
|
|
1400
|
+
const files = [];
|
|
1401
|
+
walkFiles(root, root, exclude, files);
|
|
1402
|
+
const digest = (0, import_crypto4.createHash)("sha256");
|
|
1403
|
+
let totalBytes = 0;
|
|
1404
|
+
for (const file of files) {
|
|
1405
|
+
const rel = (0, import_path8.relative)(root, file).replace(/\\/g, "/");
|
|
1406
|
+
const bytes = (0, import_fs7.readFileSync)(file);
|
|
1407
|
+
totalBytes += bytes.byteLength;
|
|
1408
|
+
digest.update("PATH\0");
|
|
1409
|
+
digest.update(rel);
|
|
1410
|
+
digest.update("\0SIZE\0");
|
|
1411
|
+
digest.update(String(bytes.byteLength));
|
|
1412
|
+
digest.update("\0DATA\0");
|
|
1413
|
+
digest.update(bytes);
|
|
1414
|
+
digest.update("\0END\0");
|
|
1415
|
+
}
|
|
1416
|
+
return {
|
|
1417
|
+
hash: digest.digest("hex"),
|
|
1418
|
+
fileCount: files.length,
|
|
1419
|
+
totalBytes,
|
|
1420
|
+
root
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// src/lib/managed-skill.ts
|
|
1425
|
+
var HASH_EXCLUDES = [".DS_Store"];
|
|
1426
|
+
var SKILL_BACKUP_METADATA_FILE = ".pushrec-backup.json";
|
|
1427
|
+
function resolveInstalledSkillPath(skillName, state) {
|
|
1428
|
+
return state?.installedPath ?? (0, import_path9.join)(getLegacyClaudeSkillsRoot(), skillName);
|
|
1429
|
+
}
|
|
1430
|
+
function hashInstalledSkillPath(path) {
|
|
1431
|
+
if (!(0, import_fs8.existsSync)(path)) return null;
|
|
1432
|
+
const stat = (0, import_fs8.lstatSync)(path);
|
|
1433
|
+
if (!stat.isDirectory()) return null;
|
|
1434
|
+
return hashDirectoryTree(path, { excludeNames: HASH_EXCLUDES }).hash;
|
|
1435
|
+
}
|
|
1436
|
+
function isLocalSkillModified(expectedHash, currentHash) {
|
|
1437
|
+
if (!expectedHash || !currentHash) return false;
|
|
1438
|
+
return expectedHash !== currentHash;
|
|
1439
|
+
}
|
|
1440
|
+
function createSkillBackupSnapshot(skillName, installedPath, metadata) {
|
|
1441
|
+
if (!(0, import_fs8.existsSync)(installedPath)) return null;
|
|
1442
|
+
const stat = (0, import_fs8.lstatSync)(installedPath);
|
|
1443
|
+
if (!stat.isDirectory()) return null;
|
|
1444
|
+
ensureBackupsRoot();
|
|
1445
|
+
const backupPath = buildSkillBackupDirectoryPath(skillName);
|
|
1446
|
+
(0, import_fs8.cpSync)(installedPath, backupPath, { recursive: true });
|
|
1447
|
+
const payload = {
|
|
1448
|
+
skillName,
|
|
1449
|
+
sourceVersion: metadata?.sourceVersion ?? null,
|
|
1450
|
+
sourceHash: metadata?.sourceHash ?? null,
|
|
1451
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1452
|
+
};
|
|
1453
|
+
(0, import_fs8.writeFileSync)(
|
|
1454
|
+
(0, import_path9.join)(backupPath, SKILL_BACKUP_METADATA_FILE),
|
|
1455
|
+
JSON.stringify(payload, null, 2) + "\n",
|
|
1456
|
+
"utf-8"
|
|
1457
|
+
);
|
|
1458
|
+
return backupPath;
|
|
1459
|
+
}
|
|
1460
|
+
function readSkillBackupMetadata(backupPath) {
|
|
1461
|
+
const metadataPath = (0, import_path9.join)(backupPath, SKILL_BACKUP_METADATA_FILE);
|
|
1462
|
+
if (!(0, import_fs8.existsSync)(metadataPath)) return null;
|
|
1463
|
+
try {
|
|
1464
|
+
const parsed = JSON.parse((0, import_fs8.readFileSync)(metadataPath, "utf-8"));
|
|
1465
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
1466
|
+
return {
|
|
1467
|
+
skillName: typeof parsed.skillName === "string" ? parsed.skillName : "unknown-skill",
|
|
1468
|
+
sourceVersion: typeof parsed.sourceVersion === "string" || parsed.sourceVersion === null ? parsed.sourceVersion : null,
|
|
1469
|
+
sourceHash: typeof parsed.sourceHash === "string" || parsed.sourceHash === null ? parsed.sourceHash : null,
|
|
1470
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
1471
|
+
};
|
|
1472
|
+
} catch {
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function buildNextManagedSkillState(params) {
|
|
1477
|
+
const nowIso2 = params.nowIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1478
|
+
const previousBackups = params.previous?.backups ?? [];
|
|
1479
|
+
const backups = params.backupPath && !previousBackups.includes(params.backupPath) ? [...previousBackups, params.backupPath] : previousBackups;
|
|
1480
|
+
return {
|
|
1481
|
+
name: params.name,
|
|
1482
|
+
mode: params.previous?.mode ?? "managed",
|
|
1483
|
+
lastKnownVersion: params.version,
|
|
1484
|
+
installedPath: params.installedPath,
|
|
1485
|
+
targetClient: params.targetClient ?? params.previous?.targetClient ?? "claude",
|
|
1486
|
+
contentHash: params.hash ?? params.previous?.contentHash ?? null,
|
|
1487
|
+
backups,
|
|
1488
|
+
localModified: false,
|
|
1489
|
+
lastCheckedAt: nowIso2
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/lib/compatibility.ts
|
|
1494
|
+
function runtimePlatformFromNodePlatform(platform5) {
|
|
1495
|
+
if (platform5 === "win32") return "windows";
|
|
1496
|
+
if (platform5 === "darwin") return "macos";
|
|
1497
|
+
return "linux";
|
|
1498
|
+
}
|
|
1499
|
+
function normalizeList(values) {
|
|
1500
|
+
return (values ?? []).map((x) => x.trim().toLowerCase()).filter((x) => x.length > 0);
|
|
1501
|
+
}
|
|
1502
|
+
function normalizeWindowsStatus(value) {
|
|
1503
|
+
return typeof value === "string" ? value.trim().toUpperCase() : "UNKNOWN";
|
|
1504
|
+
}
|
|
1505
|
+
function compatibilityOf(skill) {
|
|
1506
|
+
return skill.compatibility ?? null;
|
|
1507
|
+
}
|
|
1508
|
+
function evaluateSkillCompatibility(skill, options = {}) {
|
|
1509
|
+
const client = options.client ?? "claude";
|
|
1510
|
+
const platform5 = options.platform ?? runtimePlatformFromNodePlatform(process.platform);
|
|
1511
|
+
const compatibility = compatibilityOf(skill);
|
|
1512
|
+
if (!compatibility) {
|
|
1513
|
+
return {
|
|
1514
|
+
allowed: true,
|
|
1515
|
+
requiresOverride: false,
|
|
1516
|
+
status: "ALLOW_WITH_WARNING",
|
|
1517
|
+
reasonCode: "COMPATIBILITY_METADATA_MISSING",
|
|
1518
|
+
message: "Compatibility metadata missing; proceeding in permissive mode for backward compatibility."
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
const clients = normalizeList(compatibility.clients);
|
|
1522
|
+
if (clients.length > 0 && !clients.includes(client)) {
|
|
1523
|
+
return {
|
|
1524
|
+
allowed: false,
|
|
1525
|
+
requiresOverride: false,
|
|
1526
|
+
status: "BLOCK_UNSUPPORTED_CLIENT",
|
|
1527
|
+
reasonCode: "UNSUPPORTED_CLIENT",
|
|
1528
|
+
message: `Skill is not marked compatible with client '${client}'.`
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
const platforms = normalizeList(compatibility.platforms);
|
|
1532
|
+
if (platforms.length > 0 && !platforms.includes(platform5)) {
|
|
1533
|
+
return {
|
|
1534
|
+
allowed: false,
|
|
1535
|
+
requiresOverride: false,
|
|
1536
|
+
status: "BLOCK_UNSUPPORTED_PLATFORM",
|
|
1537
|
+
reasonCode: "UNSUPPORTED_PLATFORM",
|
|
1538
|
+
message: `Skill is not marked compatible with platform '${platform5}'.`
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
if (platform5 === "windows") {
|
|
1542
|
+
const labels = normalizeList(compatibility.labels);
|
|
1543
|
+
const windowsStatus = normalizeWindowsStatus(compatibility.windowsStatus);
|
|
1544
|
+
if (windowsStatus === "INCOMPATIBLE" || windowsStatus === "FAIL") {
|
|
1545
|
+
return {
|
|
1546
|
+
allowed: false,
|
|
1547
|
+
requiresOverride: false,
|
|
1548
|
+
status: "BLOCK_INCOMPATIBLE",
|
|
1549
|
+
reasonCode: "WINDOWS_STATUS_INCOMPATIBLE",
|
|
1550
|
+
message: `Skill is marked Windows-incompatible (${windowsStatus}).`
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
if (windowsStatus === "BLOCKED" || windowsStatus === "NOT_RUN") {
|
|
1554
|
+
return {
|
|
1555
|
+
allowed: false,
|
|
1556
|
+
requiresOverride: true,
|
|
1557
|
+
status: "BLOCK_WINDOWS_GATED",
|
|
1558
|
+
reasonCode: "WINDOWS_STATUS_BLOCKED",
|
|
1559
|
+
message: "Skill is Windows-gated (BLOCKED/NOT_RUN). Use explicit override to proceed."
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
if (labels.includes("windows-gated")) {
|
|
1563
|
+
return {
|
|
1564
|
+
allowed: false,
|
|
1565
|
+
requiresOverride: true,
|
|
1566
|
+
status: "BLOCK_WINDOWS_GATED",
|
|
1567
|
+
reasonCode: "WINDOWS_GATED_LABEL",
|
|
1568
|
+
message: "Skill is labeled windows-gated. Use explicit override to proceed."
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return {
|
|
1573
|
+
allowed: true,
|
|
1574
|
+
requiresOverride: false,
|
|
1575
|
+
status: "ALLOW",
|
|
1576
|
+
reasonCode: "ALLOWED",
|
|
1577
|
+
message: "Compatibility check passed."
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function compatibilitySummary(skill) {
|
|
1581
|
+
const compatibility = compatibilityOf(skill);
|
|
1582
|
+
if (!compatibility) return "unknown";
|
|
1583
|
+
const windowsStatus = compatibility.windowsStatus ? String(compatibility.windowsStatus).toLowerCase() : null;
|
|
1584
|
+
if (windowsStatus) return windowsStatus;
|
|
1585
|
+
const labels = normalizeList(compatibility.labels);
|
|
1586
|
+
if (labels.includes("windows-gated")) return "windows-gated";
|
|
1587
|
+
if (labels.includes("windows-incompatible")) return "windows-incompatible";
|
|
1588
|
+
return "listed";
|
|
951
1589
|
}
|
|
952
1590
|
|
|
953
1591
|
// src/commands/install.ts
|
|
@@ -1031,31 +1669,93 @@ async function ensureLicense(forFreeSkill) {
|
|
|
1031
1669
|
return false;
|
|
1032
1670
|
}
|
|
1033
1671
|
}
|
|
1034
|
-
async function installOne(name, prefix) {
|
|
1672
|
+
async function installOne(name, prefix, options) {
|
|
1035
1673
|
const spinner = (0, import_ora.default)(`${prefix}Fetching ${name}...`).start();
|
|
1036
1674
|
try {
|
|
1037
1675
|
const manifest = await fetchSkillManifest(name);
|
|
1676
|
+
const requestedVersion = options.requestedVersion?.trim() || void 0;
|
|
1677
|
+
if (requestedVersion) {
|
|
1678
|
+
const knownVersions = /* @__PURE__ */ new Set([
|
|
1679
|
+
...manifest.currentVersion ? [manifest.currentVersion] : [],
|
|
1680
|
+
...manifest.versions.map((v) => v.version)
|
|
1681
|
+
]);
|
|
1682
|
+
if (!knownVersions.has(requestedVersion)) {
|
|
1683
|
+
spinner.fail(`${prefix}Version ${requestedVersion} is not available for ${name}`);
|
|
1684
|
+
return {
|
|
1685
|
+
ok: false,
|
|
1686
|
+
error: `Version ${requestedVersion} is not available for ${name}`
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const installVersion = requestedVersion ?? manifest.currentVersion ?? "0.0.0";
|
|
1691
|
+
const compatibility = evaluateSkillCompatibility(manifest, { client: "claude" });
|
|
1692
|
+
if (!compatibility.allowed) {
|
|
1693
|
+
if (compatibility.requiresOverride && options.allowBlocked) {
|
|
1694
|
+
spinner.warn(
|
|
1695
|
+
`${prefix}${name}: compatibility override enabled (${compatibility.reasonCode}); continuing.`
|
|
1696
|
+
);
|
|
1697
|
+
} else {
|
|
1698
|
+
spinner.fail(`${prefix}${name}: ${compatibility.message}`);
|
|
1699
|
+
return {
|
|
1700
|
+
ok: false,
|
|
1701
|
+
error: `${compatibility.reasonCode}: ${compatibility.message}`
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1038
1705
|
spinner.text = `${prefix}Verifying license...`;
|
|
1039
1706
|
const licensed = await ensureLicense(manifest.isFree);
|
|
1040
1707
|
if (!licensed) {
|
|
1041
1708
|
spinner.fail(`${prefix}License check failed for ${name}`);
|
|
1042
1709
|
return { ok: false, error: "License check failed" };
|
|
1043
1710
|
}
|
|
1044
|
-
|
|
1045
|
-
const
|
|
1711
|
+
const configBefore = loadConfig();
|
|
1712
|
+
const previousManaged = configBefore.skillState[name];
|
|
1713
|
+
const installedPathBefore = resolveInstalledSkillPath(name, previousManaged);
|
|
1714
|
+
const currentHash = hashInstalledSkillPath(installedPathBefore);
|
|
1715
|
+
const modified = isLocalSkillModified(previousManaged?.contentHash ?? null, currentHash);
|
|
1716
|
+
if (modified && !options.forceReplace) {
|
|
1717
|
+
spinner.fail(`${prefix}Skipped ${name}: local modifications detected (use --force to replace with backup).`);
|
|
1718
|
+
return {
|
|
1719
|
+
ok: false,
|
|
1720
|
+
error: "Local modifications detected. Re-run with --force to back up and replace."
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
let backupPath = null;
|
|
1724
|
+
if (options.forceReplace && currentHash) {
|
|
1725
|
+
spinner.text = `${prefix}Creating backup snapshot for ${name}...`;
|
|
1726
|
+
const sourceVersion = configBefore.installedSkills[name]?.version ?? previousManaged?.lastKnownVersion ?? null;
|
|
1727
|
+
backupPath = createSkillBackupSnapshot(name, installedPathBefore, {
|
|
1728
|
+
sourceVersion,
|
|
1729
|
+
sourceHash: currentHash
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
spinner.text = `${prefix}Downloading ${name} v${installVersion}...`;
|
|
1733
|
+
const archive = await downloadSkill(name, requestedVersion);
|
|
1046
1734
|
spinner.text = `${prefix}Installing ${name}...`;
|
|
1047
1735
|
const { path } = await installSkill(name, archive);
|
|
1048
1736
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1049
1737
|
const config = loadConfig();
|
|
1050
1738
|
config.installedSkills[name] = {
|
|
1051
1739
|
name,
|
|
1052
|
-
version:
|
|
1740
|
+
version: installVersion,
|
|
1053
1741
|
installedAt: config.installedSkills[name]?.installedAt ?? now,
|
|
1054
1742
|
updatedAt: now
|
|
1055
1743
|
};
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1744
|
+
const contentHash = hashInstalledSkillPath(path);
|
|
1745
|
+
const nextManaged = buildNextManagedSkillState({
|
|
1746
|
+
name,
|
|
1747
|
+
version: installVersion,
|
|
1748
|
+
installedPath: path,
|
|
1749
|
+
previous: config.skillState[name],
|
|
1750
|
+
backupPath,
|
|
1751
|
+
hash: contentHash
|
|
1752
|
+
});
|
|
1753
|
+
updateConfig({
|
|
1754
|
+
installedSkills: config.installedSkills,
|
|
1755
|
+
skillState: { [name]: nextManaged }
|
|
1756
|
+
});
|
|
1757
|
+
spinner.succeed(`${prefix}${name} v${installVersion} installed to ${path}`);
|
|
1758
|
+
return { ok: true, path, version: installVersion, backupPath };
|
|
1059
1759
|
} catch (err) {
|
|
1060
1760
|
const message = err instanceof Error ? err.message : String(err);
|
|
1061
1761
|
spinner.fail(`${prefix}Failed to install ${name}: ${message}`);
|
|
@@ -1063,7 +1763,10 @@ async function installOne(name, prefix) {
|
|
|
1063
1763
|
}
|
|
1064
1764
|
}
|
|
1065
1765
|
function installCommand() {
|
|
1066
|
-
const cmd = new import_commander2.Command("install").description("Install skill(s) from the Pushrec registry").argument("[name]", "Skill name to install").option("--all", "Install all available skills").option("--force", "Reinstall even if already installed").option("--
|
|
1766
|
+
const cmd = new import_commander2.Command("install").description("Install skill(s) from the Pushrec registry").argument("[name]", "Skill name to install").option("--all", "Install all available skills").option("--force", "Reinstall even if already installed (backs up modified installs first)").option("--version <semver>", "Install a specific version (single skill only)").option(
|
|
1767
|
+
"--allow-blocked",
|
|
1768
|
+
"Allow installing compatibility-gated skills (for explicit preview/testing workflows)"
|
|
1769
|
+
).option("--json", "Output JSON").action(async (name, opts) => {
|
|
1067
1770
|
if (!name && !opts.all) {
|
|
1068
1771
|
if (opts.json) {
|
|
1069
1772
|
console.log(JSON.stringify({ ok: false, error: "Specify a skill name or use --all" }));
|
|
@@ -1081,6 +1784,14 @@ function installCommand() {
|
|
|
1081
1784
|
console.error("Cannot specify both a skill name and --all.");
|
|
1082
1785
|
process.exit(1);
|
|
1083
1786
|
}
|
|
1787
|
+
if (opts.version && opts.all) {
|
|
1788
|
+
if (opts.json) {
|
|
1789
|
+
console.log(JSON.stringify({ ok: false, error: "--version can only be used with a single skill name" }));
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
console.error("--version can only be used with a single skill name.");
|
|
1793
|
+
process.exit(1);
|
|
1794
|
+
}
|
|
1084
1795
|
if (name) {
|
|
1085
1796
|
if (!opts.force && isSkillInstalled(name)) {
|
|
1086
1797
|
if (opts.json) {
|
|
@@ -1090,7 +1801,11 @@ function installCommand() {
|
|
|
1090
1801
|
}
|
|
1091
1802
|
return;
|
|
1092
1803
|
}
|
|
1093
|
-
const result = await installOne(name, ""
|
|
1804
|
+
const result = await installOne(name, "", {
|
|
1805
|
+
requestedVersion: opts.version,
|
|
1806
|
+
forceReplace: !!opts.force,
|
|
1807
|
+
allowBlocked: !!opts.allowBlocked
|
|
1808
|
+
});
|
|
1094
1809
|
if (!result.ok) {
|
|
1095
1810
|
if (opts.json) {
|
|
1096
1811
|
console.log(JSON.stringify({ ok: false, skill: name, error: result.error }));
|
|
@@ -1098,7 +1813,7 @@ function installCommand() {
|
|
|
1098
1813
|
process.exit(1);
|
|
1099
1814
|
}
|
|
1100
1815
|
if (opts.json) {
|
|
1101
|
-
console.log(JSON.stringify({ ok: true, skill: name, version: result.version, path: result.path }));
|
|
1816
|
+
console.log(JSON.stringify({ ok: true, skill: name, version: result.version, path: result.path, backupPath: result.backupPath ?? null }));
|
|
1102
1817
|
} else {
|
|
1103
1818
|
console.log(`
|
|
1104
1819
|
Done! Use /${name} in your next Claude Code session.`);
|
|
@@ -1136,7 +1851,10 @@ Done! Use /${name} in your next Claude Code session.`);
|
|
|
1136
1851
|
for (let i = 0; i < toInstall.length; i++) {
|
|
1137
1852
|
const skill = toInstall[i];
|
|
1138
1853
|
const prefix = opts.json ? "" : `[${i + 1}/${toInstall.length}] `;
|
|
1139
|
-
const result = await installOne(skill.name, prefix
|
|
1854
|
+
const result = await installOne(skill.name, prefix, {
|
|
1855
|
+
forceReplace: !!opts.force,
|
|
1856
|
+
allowBlocked: !!opts.allowBlocked
|
|
1857
|
+
});
|
|
1140
1858
|
results.push({ skill: skill.name, ok: result.ok, version: result.version, error: result.error });
|
|
1141
1859
|
if (result.ok) {
|
|
1142
1860
|
installed++;
|
|
@@ -1157,125 +1875,235 @@ Done! ${installed} installed, ${failed} failed.`);
|
|
|
1157
1875
|
|
|
1158
1876
|
// src/commands/update.ts
|
|
1159
1877
|
var import_commander3 = require("commander");
|
|
1160
|
-
async function updateOne(name, currentVersion, prefix, json) {
|
|
1878
|
+
async function updateOne(name, currentVersion, prefix, json, forceModified, allowBlocked) {
|
|
1161
1879
|
try {
|
|
1162
1880
|
const manifest = await fetchSkillManifest(name);
|
|
1163
1881
|
const latest = manifest.currentVersion;
|
|
1882
|
+
const compatibility = evaluateSkillCompatibility(manifest, { client: "claude" });
|
|
1883
|
+
if (!compatibility.allowed) {
|
|
1884
|
+
if (!(compatibility.requiresOverride && allowBlocked)) {
|
|
1885
|
+
if (!json) {
|
|
1886
|
+
console.log(`${prefix}Skipped ${name}: ${compatibility.message}`);
|
|
1887
|
+
}
|
|
1888
|
+
return {
|
|
1889
|
+
status: "failed",
|
|
1890
|
+
version: currentVersion,
|
|
1891
|
+
error: `${compatibility.reasonCode}: ${compatibility.message}`
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
if (!json) {
|
|
1895
|
+
console.log(
|
|
1896
|
+
`${prefix}${name}: compatibility override enabled (${compatibility.reasonCode}); continuing.`
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1164
1900
|
if (!latest || latest === currentVersion) {
|
|
1165
1901
|
if (!json) console.log(`${prefix}${name} is up to date (v${currentVersion}).`);
|
|
1166
|
-
return
|
|
1902
|
+
return { status: "up_to_date", version: latest ?? currentVersion };
|
|
1903
|
+
}
|
|
1904
|
+
const preConfig = loadConfig();
|
|
1905
|
+
const previousManaged = preConfig.skillState[name];
|
|
1906
|
+
const installedPathBefore = resolveInstalledSkillPath(name, previousManaged);
|
|
1907
|
+
const currentHash = hashInstalledSkillPath(installedPathBefore);
|
|
1908
|
+
const modified = isLocalSkillModified(previousManaged?.contentHash ?? null, currentHash);
|
|
1909
|
+
if (modified && !forceModified) {
|
|
1910
|
+
if (!json) {
|
|
1911
|
+
console.log(
|
|
1912
|
+
`${prefix}Skipped ${name}: local modifications detected. Re-run with --force-modified to back up and replace.`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
return {
|
|
1916
|
+
status: "skipped_modified",
|
|
1917
|
+
version: currentVersion,
|
|
1918
|
+
error: "Local modifications detected. Re-run with --force-modified to back up and replace."
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
let backupPath = null;
|
|
1922
|
+
if (currentHash) {
|
|
1923
|
+
backupPath = createSkillBackupSnapshot(name, installedPathBefore, {
|
|
1924
|
+
sourceVersion: preConfig.installedSkills[name]?.version ?? previousManaged?.lastKnownVersion ?? null,
|
|
1925
|
+
sourceHash: currentHash
|
|
1926
|
+
});
|
|
1927
|
+
if (!json && backupPath) {
|
|
1928
|
+
console.log(`${prefix}Backup snapshot created: ${backupPath}`);
|
|
1929
|
+
}
|
|
1167
1930
|
}
|
|
1168
1931
|
if (!json) console.log(`${prefix}Updating ${name} v${currentVersion} -> v${latest}...`);
|
|
1169
1932
|
const archive = await downloadSkill(name);
|
|
1170
|
-
await installSkill(name, archive);
|
|
1933
|
+
const { path: installedPathAfter } = await installSkill(name, archive);
|
|
1171
1934
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1172
1935
|
const config = loadConfig();
|
|
1173
1936
|
if (config.installedSkills[name]) {
|
|
1174
1937
|
config.installedSkills[name].version = latest;
|
|
1175
1938
|
config.installedSkills[name].updatedAt = now;
|
|
1176
|
-
updateConfig({ installedSkills: config.installedSkills });
|
|
1177
1939
|
}
|
|
1940
|
+
const contentHash = hashInstalledSkillPath(installedPathAfter);
|
|
1941
|
+
const nextManaged = buildNextManagedSkillState({
|
|
1942
|
+
name,
|
|
1943
|
+
version: latest,
|
|
1944
|
+
installedPath: installedPathAfter,
|
|
1945
|
+
previous: config.skillState[name],
|
|
1946
|
+
backupPath,
|
|
1947
|
+
hash: contentHash
|
|
1948
|
+
});
|
|
1949
|
+
updateConfig({
|
|
1950
|
+
installedSkills: config.installedSkills,
|
|
1951
|
+
skillState: { [name]: nextManaged }
|
|
1952
|
+
});
|
|
1178
1953
|
if (!json) console.log(`${prefix}Updated ${name} to v${latest}.`);
|
|
1179
|
-
return
|
|
1954
|
+
return { status: "updated", version: latest, backupPath };
|
|
1180
1955
|
} catch (err) {
|
|
1956
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1181
1957
|
if (!json) {
|
|
1182
|
-
console.error(
|
|
1183
|
-
`${prefix}Failed to update ${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
1184
|
-
);
|
|
1958
|
+
console.error(`${prefix}Failed to update ${name}: ${message}`);
|
|
1185
1959
|
}
|
|
1186
|
-
return
|
|
1960
|
+
return { status: "failed", error: message };
|
|
1187
1961
|
}
|
|
1188
1962
|
}
|
|
1189
1963
|
function updateCommand() {
|
|
1190
|
-
const cmd = new import_commander3.Command("update").description("Update installed skills to latest versions").argument("[name]", "Specific skill to update (updates all if omitted)").option(
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1964
|
+
const cmd = new import_commander3.Command("update").description("Update installed skills to latest versions").argument("[name]", "Specific skill to update (updates all if omitted)").option(
|
|
1965
|
+
"--force-modified",
|
|
1966
|
+
"Allow replacing locally modified skills (creates backup before overwrite)"
|
|
1967
|
+
).option(
|
|
1968
|
+
"--allow-blocked",
|
|
1969
|
+
"Allow updating compatibility-gated skills (for explicit preview/testing workflows)"
|
|
1970
|
+
).option("--json", "Output JSON").action(
|
|
1971
|
+
async (name, opts) => {
|
|
1972
|
+
const key = await retrieveLicenseKey();
|
|
1973
|
+
if (key) {
|
|
1974
|
+
const offlineResult = await verifyOffline(key);
|
|
1975
|
+
if (!offlineResult.valid) {
|
|
1976
|
+
if (opts.json) {
|
|
1977
|
+
console.log(
|
|
1978
|
+
JSON.stringify({
|
|
1979
|
+
ok: false,
|
|
1980
|
+
error: `License invalid: ${offlineResult.error}`
|
|
1981
|
+
})
|
|
1982
|
+
);
|
|
1983
|
+
} else {
|
|
1984
|
+
console.error(`License invalid: ${offlineResult.error}`);
|
|
1985
|
+
}
|
|
1986
|
+
process.exit(1);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
const cache = loadLicenseCache();
|
|
1990
|
+
if (cache && !cache.payload.hasUpdates) {
|
|
1195
1991
|
if (opts.json) {
|
|
1196
|
-
console.log(
|
|
1992
|
+
console.log(
|
|
1993
|
+
JSON.stringify({
|
|
1994
|
+
ok: false,
|
|
1995
|
+
error: "Updates not included in your license. Visit pushrec.com to upgrade."
|
|
1996
|
+
})
|
|
1997
|
+
);
|
|
1197
1998
|
} else {
|
|
1198
|
-
console.error(
|
|
1999
|
+
console.error(
|
|
2000
|
+
"Updates not included in your license. Visit pushrec.com to upgrade."
|
|
2001
|
+
);
|
|
1199
2002
|
}
|
|
1200
2003
|
process.exit(1);
|
|
1201
2004
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (opts.json) {
|
|
1206
|
-
console.log(JSON.stringify({ ok: false, error: "Updates not included in your license. Visit pushrec.com to upgrade." }));
|
|
1207
|
-
} else {
|
|
1208
|
-
console.error("Updates not included in your license. Visit pushrec.com to upgrade.");
|
|
1209
|
-
}
|
|
1210
|
-
process.exit(1);
|
|
1211
|
-
}
|
|
1212
|
-
const config = loadConfig();
|
|
1213
|
-
const installed = config.installedSkills;
|
|
1214
|
-
if (Object.keys(installed).length === 0) {
|
|
1215
|
-
if (opts.json) {
|
|
1216
|
-
console.log(JSON.stringify({ ok: true, updated: 0, upToDate: 0, failed: 0 }));
|
|
1217
|
-
} else {
|
|
1218
|
-
console.log("No skills installed. Run `pushrec-skills install <name>` first.");
|
|
1219
|
-
}
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
if (name) {
|
|
1223
|
-
const skill = installed[name];
|
|
1224
|
-
if (!skill) {
|
|
2005
|
+
const config = loadConfig();
|
|
2006
|
+
const installed = config.installedSkills;
|
|
2007
|
+
if (Object.keys(installed).length === 0) {
|
|
1225
2008
|
if (opts.json) {
|
|
1226
|
-
console.log(
|
|
2009
|
+
console.log(
|
|
2010
|
+
JSON.stringify({ ok: true, updated: 0, upToDate: 0, skippedModified: 0, failed: 0 })
|
|
2011
|
+
);
|
|
1227
2012
|
} else {
|
|
1228
|
-
console.
|
|
2013
|
+
console.log("No skills installed. Run `pushrec-skills install <name>` first.");
|
|
1229
2014
|
}
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1232
|
-
const ok = await updateOne(name, skill.version, "", !!opts.json);
|
|
1233
|
-
if (opts.json) {
|
|
1234
|
-
console.log(JSON.stringify({ ok, skill: name }));
|
|
2015
|
+
return;
|
|
1235
2016
|
}
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
for (let i = 0; i < entries.length; i++) {
|
|
1246
|
-
const [skillName, skill] = entries[i];
|
|
1247
|
-
const prefix = opts.json ? "" : `[${i + 1}/${entries.length}] `;
|
|
1248
|
-
try {
|
|
1249
|
-
const manifest = await fetchSkillManifest(skillName);
|
|
1250
|
-
const latest = manifest.currentVersion;
|
|
1251
|
-
if (!latest || latest === skill.version) {
|
|
1252
|
-
upToDate++;
|
|
1253
|
-
results.push({ skill: skillName, ok: true });
|
|
1254
|
-
continue;
|
|
2017
|
+
if (name) {
|
|
2018
|
+
const skill = installed[name];
|
|
2019
|
+
if (!skill) {
|
|
2020
|
+
if (opts.json) {
|
|
2021
|
+
console.log(JSON.stringify({ ok: false, error: `${name} is not installed` }));
|
|
2022
|
+
} else {
|
|
2023
|
+
console.error(`${name} is not installed.`);
|
|
2024
|
+
}
|
|
2025
|
+
process.exit(1);
|
|
1255
2026
|
}
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2027
|
+
const result = await updateOne(
|
|
2028
|
+
name,
|
|
2029
|
+
skill.version,
|
|
2030
|
+
"",
|
|
2031
|
+
!!opts.json,
|
|
2032
|
+
!!opts.forceModified,
|
|
2033
|
+
!!opts.allowBlocked
|
|
2034
|
+
);
|
|
2035
|
+
if (opts.json) {
|
|
2036
|
+
console.log(
|
|
2037
|
+
JSON.stringify({
|
|
2038
|
+
ok: result.status === "updated" || result.status === "up_to_date",
|
|
2039
|
+
skill: name,
|
|
2040
|
+
status: result.status,
|
|
2041
|
+
version: result.version ?? null,
|
|
2042
|
+
backupPath: result.backupPath ?? null,
|
|
2043
|
+
error: result.error
|
|
2044
|
+
})
|
|
2045
|
+
);
|
|
1262
2046
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
2047
|
+
if (result.status === "failed" || result.status === "skipped_modified") {
|
|
2048
|
+
process.exit(1);
|
|
2049
|
+
}
|
|
2050
|
+
return;
|
|
1267
2051
|
}
|
|
2052
|
+
if (!opts.json) console.log("Checking for updates...\n");
|
|
2053
|
+
const entries = Object.entries(installed);
|
|
2054
|
+
let updated = 0;
|
|
2055
|
+
let failed = 0;
|
|
2056
|
+
let upToDate = 0;
|
|
2057
|
+
let skippedModified = 0;
|
|
2058
|
+
const results = [];
|
|
2059
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2060
|
+
const [skillName, skill] = entries[i];
|
|
2061
|
+
const prefix = opts.json ? "" : `[${i + 1}/${entries.length}] `;
|
|
2062
|
+
const result = await updateOne(
|
|
2063
|
+
skillName,
|
|
2064
|
+
skill.version,
|
|
2065
|
+
prefix,
|
|
2066
|
+
!!opts.json,
|
|
2067
|
+
!!opts.forceModified,
|
|
2068
|
+
!!opts.allowBlocked
|
|
2069
|
+
);
|
|
2070
|
+
results.push({
|
|
2071
|
+
skill: skillName,
|
|
2072
|
+
status: result.status,
|
|
2073
|
+
version: result.version,
|
|
2074
|
+
backupPath: result.backupPath,
|
|
2075
|
+
error: result.error
|
|
2076
|
+
});
|
|
2077
|
+
if (result.status === "updated") updated++;
|
|
2078
|
+
if (result.status === "up_to_date") upToDate++;
|
|
2079
|
+
if (result.status === "skipped_modified") skippedModified++;
|
|
2080
|
+
if (result.status === "failed") failed++;
|
|
2081
|
+
}
|
|
2082
|
+
if (opts.json) {
|
|
2083
|
+
console.log(
|
|
2084
|
+
JSON.stringify({
|
|
2085
|
+
ok: failed === 0,
|
|
2086
|
+
updated,
|
|
2087
|
+
upToDate,
|
|
2088
|
+
skippedModified,
|
|
2089
|
+
failed,
|
|
2090
|
+
results
|
|
2091
|
+
})
|
|
2092
|
+
);
|
|
2093
|
+
} else {
|
|
2094
|
+
console.log(
|
|
2095
|
+
`
|
|
2096
|
+
Done! ${updated} updated, ${upToDate} up to date, ${skippedModified} skipped (local modifications), ${failed} failed.`
|
|
2097
|
+
);
|
|
2098
|
+
if (skippedModified > 0) {
|
|
2099
|
+
console.log(
|
|
2100
|
+
"Use --force-modified to back up and replace locally modified skills."
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
if (failed > 0) process.exit(1);
|
|
1268
2105
|
}
|
|
1269
|
-
|
|
1270
|
-
console.log(JSON.stringify({ ok: failed === 0, updated, upToDate, failed, results }));
|
|
1271
|
-
} else {
|
|
1272
|
-
console.log(
|
|
1273
|
-
`
|
|
1274
|
-
Done! ${updated} updated, ${upToDate} up to date, ${failed} failed.`
|
|
1275
|
-
);
|
|
1276
|
-
}
|
|
1277
|
-
if (failed > 0) process.exit(1);
|
|
1278
|
-
});
|
|
2106
|
+
);
|
|
1279
2107
|
return cmd;
|
|
1280
2108
|
}
|
|
1281
2109
|
|
|
@@ -1325,14 +2153,14 @@ ${entries.length} skills installed.`);
|
|
|
1325
2153
|
return;
|
|
1326
2154
|
}
|
|
1327
2155
|
console.log(
|
|
1328
|
-
`${pad("NAME", 30)} ${pad("VERSION", 12)} ${pad("STATUS", 14)} CATEGORY`
|
|
2156
|
+
`${pad("NAME", 30)} ${pad("VERSION", 12)} ${pad("STATUS", 14)} ${pad("COMPAT", 16)} CATEGORY`
|
|
1329
2157
|
);
|
|
1330
|
-
console.log("-".repeat(
|
|
2158
|
+
console.log("-".repeat(98));
|
|
1331
2159
|
for (const skill of catalog.items) {
|
|
1332
2160
|
const installed = isSkillInstalled(skill.name);
|
|
1333
2161
|
const status = skill.isFree ? installed ? "free/installed" : "free" : installed ? "installed" : "available";
|
|
1334
2162
|
console.log(
|
|
1335
|
-
`${pad(skill.name, 30)} ${pad(skill.currentVersion ?? "-", 12)} ${pad(status, 14)} ${skill.category ?? "-"}`
|
|
2163
|
+
`${pad(skill.name, 30)} ${pad(skill.currentVersion ?? "-", 12)} ${pad(status, 14)} ${pad(compatibilitySummary(skill), 16)} ${skill.category ?? "-"}`
|
|
1336
2164
|
);
|
|
1337
2165
|
}
|
|
1338
2166
|
console.log(`
|
|
@@ -1421,6 +2249,11 @@ function infoCommand() {
|
|
|
1421
2249
|
console.log(` Free: ${manifest.isFree ? "yes" : "no"}`);
|
|
1422
2250
|
console.log(` Published: ${manifest.publishedAt ? manifest.publishedAt.split("T")[0] : "-"}`);
|
|
1423
2251
|
console.log(` Installed: ${installed ? `yes (v${installedInfo?.version ?? "?"})` : "no"}`);
|
|
2252
|
+
console.log(` Compat: ${compatibilitySummary(manifest)}`);
|
|
2253
|
+
const compatEval = evaluateSkillCompatibility(manifest, { client: "claude" });
|
|
2254
|
+
if (!compatEval.allowed || compatEval.status === "ALLOW_WITH_WARNING") {
|
|
2255
|
+
console.log(` CompatNote: ${compatEval.reasonCode} \u2014 ${compatEval.message}`);
|
|
2256
|
+
}
|
|
1424
2257
|
if (manifest.versions && manifest.versions.length > 0) {
|
|
1425
2258
|
console.log("");
|
|
1426
2259
|
console.log(" Versions:");
|
|
@@ -1441,7 +2274,7 @@ function infoCommand() {
|
|
|
1441
2274
|
var import_commander7 = require("commander");
|
|
1442
2275
|
var import_chalk2 = __toESM(require("chalk"));
|
|
1443
2276
|
function createUninstallCommand() {
|
|
1444
|
-
return new import_commander7.Command("uninstall").description("Uninstall a skill from ~/.claude/skills/").argument("<skill-name>", "Name of skill to uninstall").option("--json", "Output JSON").action(async (skillName, opts) => {
|
|
2277
|
+
return new import_commander7.Command("uninstall").description("Uninstall a skill from the active Claude skills root (default ~/.claude/skills/)").argument("<skill-name>", "Name of skill to uninstall").option("--json", "Output JSON").action(async (skillName, opts) => {
|
|
1445
2278
|
try {
|
|
1446
2279
|
const removed = uninstallSkill(skillName);
|
|
1447
2280
|
if (!removed) {
|
|
@@ -1471,9 +2304,91 @@ function createUninstallCommand() {
|
|
|
1471
2304
|
// src/commands/health.ts
|
|
1472
2305
|
var import_commander8 = require("commander");
|
|
1473
2306
|
var import_chalk3 = __toESM(require("chalk"));
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
2307
|
+
|
|
2308
|
+
// src/lib/client-detect.ts
|
|
2309
|
+
var import_fs9 = require("fs");
|
|
2310
|
+
var import_path10 = require("path");
|
|
2311
|
+
function pathExists(path, ctx) {
|
|
2312
|
+
return (ctx?.existsSync ?? import_fs9.existsSync)(path);
|
|
2313
|
+
}
|
|
2314
|
+
function nowIso(ctx) {
|
|
2315
|
+
return ctx?.nowIso?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2316
|
+
}
|
|
2317
|
+
function detectionCandidates(client, roots) {
|
|
2318
|
+
const profileHome = roots.profileHome;
|
|
2319
|
+
if (!profileHome) return [];
|
|
2320
|
+
switch (client) {
|
|
2321
|
+
case "claude":
|
|
2322
|
+
return [
|
|
2323
|
+
(0, import_path10.join)(profileHome, "settings.json"),
|
|
2324
|
+
(0, import_path10.join)(profileHome, "skills"),
|
|
2325
|
+
profileHome
|
|
2326
|
+
];
|
|
2327
|
+
case "codex":
|
|
2328
|
+
return [
|
|
2329
|
+
(0, import_path10.join)(profileHome, "config.toml"),
|
|
2330
|
+
(0, import_path10.join)(profileHome, "auth.json"),
|
|
2331
|
+
(0, import_path10.join)(profileHome, "version.json"),
|
|
2332
|
+
(0, import_path10.join)(profileHome, "skills"),
|
|
2333
|
+
profileHome
|
|
2334
|
+
];
|
|
2335
|
+
case "gemini":
|
|
2336
|
+
return [
|
|
2337
|
+
(0, import_path10.join)(profileHome, "installation_id"),
|
|
2338
|
+
(0, import_path10.join)(profileHome, "projects.json"),
|
|
2339
|
+
(0, import_path10.join)(profileHome, "GEMINI.md"),
|
|
2340
|
+
profileHome
|
|
2341
|
+
];
|
|
2342
|
+
case "antigravity":
|
|
2343
|
+
return [
|
|
2344
|
+
(0, import_path10.join)(profileHome, "settings.json"),
|
|
2345
|
+
(0, import_path10.join)(profileHome, "globalStorage"),
|
|
2346
|
+
profileHome
|
|
2347
|
+
];
|
|
2348
|
+
default: {
|
|
2349
|
+
const exhaustive = client;
|
|
2350
|
+
throw new Error(`Unsupported client: ${String(exhaustive)}`);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
function detectClientProfile(client, ctx) {
|
|
2355
|
+
const roots = resolveClientInstallRoots(client, ctx);
|
|
2356
|
+
const candidates = detectionCandidates(client, roots);
|
|
2357
|
+
const hits = candidates.filter((candidate) => pathExists(candidate, ctx));
|
|
2358
|
+
const detected = hits.length > 0;
|
|
2359
|
+
return {
|
|
2360
|
+
client,
|
|
2361
|
+
detected,
|
|
2362
|
+
selected: detected,
|
|
2363
|
+
profileHome: roots.profileHome,
|
|
2364
|
+
skillsRoot: roots.skillsRoot,
|
|
2365
|
+
nativeSkillRootVerified: roots.nativeSkillRootVerified,
|
|
2366
|
+
previewProfile: roots.previewProfile,
|
|
2367
|
+
lastDetectedAt: detected ? nowIso(ctx) : null,
|
|
2368
|
+
installedSkills: {},
|
|
2369
|
+
notes: [
|
|
2370
|
+
...roots.notes,
|
|
2371
|
+
...detected ? [`Detected profile footprint via ${hits[0]}.`] : ["No profile footprint detected on this machine."]
|
|
2372
|
+
]
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function detectClientProfiles(ctx) {
|
|
2376
|
+
const profiles = {};
|
|
2377
|
+
const detectedClients = [];
|
|
2378
|
+
for (const client of SUPPORTED_CLIENTS) {
|
|
2379
|
+
const profile = detectClientProfile(client, ctx);
|
|
2380
|
+
profiles[client] = profile;
|
|
2381
|
+
if (profile.detected) {
|
|
2382
|
+
detectedClients.push(client);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
return { profiles, detectedClients };
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
// src/commands/health.ts
|
|
2389
|
+
function healthCommand() {
|
|
2390
|
+
const cmd = new import_commander8.Command("health").description("Check registry server health").option("--full", "Run full diagnostic (registry + license + device + skills)").option("--json", "Output as JSON").action(async (opts) => {
|
|
2391
|
+
if (opts.full) {
|
|
1477
2392
|
return runFullDiagnostic(opts.json ?? false);
|
|
1478
2393
|
}
|
|
1479
2394
|
let response;
|
|
@@ -1518,6 +2433,35 @@ function healthCommand() {
|
|
|
1518
2433
|
});
|
|
1519
2434
|
return cmd;
|
|
1520
2435
|
}
|
|
2436
|
+
function pushClientDetectionCheck(checks) {
|
|
2437
|
+
try {
|
|
2438
|
+
const detected = detectClientProfiles();
|
|
2439
|
+
const detectedNames = detected.detectedClients;
|
|
2440
|
+
const detectedCount = detectedNames.length;
|
|
2441
|
+
const previewDetected = detectedNames.filter(
|
|
2442
|
+
(client) => detected.profiles[client]?.previewProfile
|
|
2443
|
+
);
|
|
2444
|
+
const claudeDetected = Boolean(detected.profiles.claude?.detected);
|
|
2445
|
+
let detail = detectedCount > 0 ? `${detectedCount} detected (${detectedNames.join(", ")})` : "No supported local client profiles detected";
|
|
2446
|
+
if (!claudeDetected) {
|
|
2447
|
+
detail += " | Claude Code profile not detected";
|
|
2448
|
+
}
|
|
2449
|
+
if (previewDetected.length > 0) {
|
|
2450
|
+
detail += ` | preview profiles: ${previewDetected.join(", ")}`;
|
|
2451
|
+
}
|
|
2452
|
+
checks.push({
|
|
2453
|
+
name: "Clients",
|
|
2454
|
+
status: detectedCount > 0 ? "pass" : "warn",
|
|
2455
|
+
detail
|
|
2456
|
+
});
|
|
2457
|
+
} catch (err) {
|
|
2458
|
+
checks.push({
|
|
2459
|
+
name: "Clients",
|
|
2460
|
+
status: "warn",
|
|
2461
|
+
detail: `Detection error: ${err instanceof Error ? err.message : String(err)}`
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
1521
2465
|
async function runFullDiagnostic(json) {
|
|
1522
2466
|
const checks = [];
|
|
1523
2467
|
try {
|
|
@@ -1535,6 +2479,7 @@ async function runFullDiagnostic(json) {
|
|
|
1535
2479
|
checks.push({ name: "License", status: "fail", detail: "No license key found. Run: pushrec-skills setup" });
|
|
1536
2480
|
checks.push({ name: "Device", status: "skip", detail: "Skipped (no license)" });
|
|
1537
2481
|
checks.push({ name: "Skills", status: "skip", detail: "Skipped (no license)" });
|
|
2482
|
+
pushClientDetectionCheck(checks);
|
|
1538
2483
|
return printDiagnostic(checks, json);
|
|
1539
2484
|
}
|
|
1540
2485
|
const offlineResult = await verifyOffline(key);
|
|
@@ -1542,6 +2487,7 @@ async function runFullDiagnostic(json) {
|
|
|
1542
2487
|
checks.push({ name: "License", status: "fail", detail: `Invalid signature: ${offlineResult.error}` });
|
|
1543
2488
|
checks.push({ name: "Device", status: "skip", detail: "Skipped (invalid license)" });
|
|
1544
2489
|
checks.push({ name: "Skills", status: "skip", detail: "Skipped (invalid license)" });
|
|
2490
|
+
pushClientDetectionCheck(checks);
|
|
1545
2491
|
return printDiagnostic(checks, json);
|
|
1546
2492
|
}
|
|
1547
2493
|
const fingerprint = getMachineFingerprint();
|
|
@@ -1580,6 +2526,7 @@ async function runFullDiagnostic(json) {
|
|
|
1580
2526
|
} else {
|
|
1581
2527
|
checks.push({ name: "Skills", status: "warn", detail: "No skills installed. Run: pushrec-skills install --all" });
|
|
1582
2528
|
}
|
|
2529
|
+
pushClientDetectionCheck(checks);
|
|
1583
2530
|
printDiagnostic(checks, json);
|
|
1584
2531
|
}
|
|
1585
2532
|
function printDiagnostic(checks, json) {
|
|
@@ -1609,88 +2556,459 @@ function printDiagnostic(checks, json) {
|
|
|
1609
2556
|
}
|
|
1610
2557
|
|
|
1611
2558
|
// src/commands/setup.ts
|
|
2559
|
+
var import_fs11 = require("fs");
|
|
2560
|
+
var import_path12 = require("path");
|
|
1612
2561
|
var import_commander9 = require("commander");
|
|
1613
2562
|
var import_readline2 = require("readline");
|
|
1614
2563
|
var import_chalk4 = __toESM(require("chalk"));
|
|
1615
2564
|
var import_ora2 = __toESM(require("ora"));
|
|
2565
|
+
|
|
2566
|
+
// src/lib/setup-state.ts
|
|
2567
|
+
var import_crypto5 = require("crypto");
|
|
2568
|
+
function createSetupRunId(now = /* @__PURE__ */ new Date()) {
|
|
2569
|
+
const ts = now.toISOString().replace(/[:.]/g, "-");
|
|
2570
|
+
const suffix = (0, import_crypto5.randomBytes)(3).toString("hex");
|
|
2571
|
+
return `setup-${ts}-${suffix}`;
|
|
2572
|
+
}
|
|
2573
|
+
function beginSetupRunState(current, params) {
|
|
2574
|
+
const nowIso2 = params?.nowIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2575
|
+
return {
|
|
2576
|
+
...current,
|
|
2577
|
+
schemaVersion: current.schemaVersion || 1,
|
|
2578
|
+
currentRunId: params?.runId ?? createSetupRunId(new Date(nowIso2)),
|
|
2579
|
+
completedPhases: [],
|
|
2580
|
+
lastRunAt: nowIso2,
|
|
2581
|
+
lastError: null
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
function markSetupPhaseCompletedState(current, phase, params) {
|
|
2585
|
+
const nowIso2 = params?.nowIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2586
|
+
const completed = current.completedPhases.includes(phase) ? current.completedPhases : [...current.completedPhases, phase];
|
|
2587
|
+
return {
|
|
2588
|
+
...current,
|
|
2589
|
+
schemaVersion: current.schemaVersion || 1,
|
|
2590
|
+
completedPhases: completed,
|
|
2591
|
+
lastRunAt: nowIso2
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
function failSetupRunState(current, error, params) {
|
|
2595
|
+
const nowIso2 = params?.nowIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2596
|
+
return {
|
|
2597
|
+
...current,
|
|
2598
|
+
schemaVersion: current.schemaVersion || 1,
|
|
2599
|
+
lastRunAt: nowIso2,
|
|
2600
|
+
lastError: error
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
function finishSetupRunState(current, params) {
|
|
2604
|
+
const nowIso2 = params?.nowIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2605
|
+
const withComplete = current.completedPhases.includes("complete") ? current.completedPhases : [...current.completedPhases, "complete"];
|
|
2606
|
+
return {
|
|
2607
|
+
...current,
|
|
2608
|
+
schemaVersion: current.schemaVersion || 1,
|
|
2609
|
+
currentRunId: null,
|
|
2610
|
+
completedPhases: withComplete,
|
|
2611
|
+
lastRunAt: nowIso2,
|
|
2612
|
+
lastError: null
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
function nextConfigWithSetup(config, setup) {
|
|
2616
|
+
return { ...config, setup };
|
|
2617
|
+
}
|
|
2618
|
+
function beginSetupRunCheckpoint(params) {
|
|
2619
|
+
const config = loadConfig();
|
|
2620
|
+
const setup = beginSetupRunState(config.setup, params);
|
|
2621
|
+
return updateConfig(nextConfigWithSetup(config, setup));
|
|
2622
|
+
}
|
|
2623
|
+
function completeSetupPhaseCheckpoint(phase, params) {
|
|
2624
|
+
const config = loadConfig();
|
|
2625
|
+
const setup = markSetupPhaseCompletedState(config.setup, phase, params);
|
|
2626
|
+
return updateConfig(nextConfigWithSetup(config, setup));
|
|
2627
|
+
}
|
|
2628
|
+
function failSetupRunCheckpoint(error, params) {
|
|
2629
|
+
const config = loadConfig();
|
|
2630
|
+
const setup = failSetupRunState(config.setup, error, params);
|
|
2631
|
+
return updateConfig(nextConfigWithSetup(config, setup));
|
|
2632
|
+
}
|
|
2633
|
+
function finishSetupRunCheckpoint(params) {
|
|
2634
|
+
const config = loadConfig();
|
|
2635
|
+
const setup = finishSetupRunState(config.setup, params);
|
|
2636
|
+
return updateConfig(nextConfigWithSetup(config, setup));
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// src/lib/vault-bootstrap.ts
|
|
2640
|
+
var import_fs10 = require("fs");
|
|
2641
|
+
var import_path11 = require("path");
|
|
2642
|
+
var VAULT_ROOT_FILE_NAMES = ["CLAUDE.md", "AGENTS.md"];
|
|
2643
|
+
var DEFAULT_VAULT_DIRECTORIES = [
|
|
2644
|
+
"0-Inbox",
|
|
2645
|
+
"1-Projects",
|
|
2646
|
+
"2-Areas",
|
|
2647
|
+
"3-Resources",
|
|
2648
|
+
"4-Archive",
|
|
2649
|
+
"Daily",
|
|
2650
|
+
"Templates"
|
|
2651
|
+
];
|
|
2652
|
+
function normalizeMarkdown(content) {
|
|
2653
|
+
return content.endsWith("\n") ? content : `${content}
|
|
2654
|
+
`;
|
|
2655
|
+
}
|
|
2656
|
+
function rootFilePath(vaultPath, name) {
|
|
2657
|
+
return (0, import_path11.join)(vaultPath, name);
|
|
2658
|
+
}
|
|
2659
|
+
function scanRootFile(vaultPath, name) {
|
|
2660
|
+
const path = rootFilePath(vaultPath, name);
|
|
2661
|
+
if (!(0, import_fs10.existsSync)(path)) {
|
|
2662
|
+
return { name, path, exists: false, sizeBytes: null };
|
|
2663
|
+
}
|
|
2664
|
+
const stat = (0, import_fs10.lstatSync)(path);
|
|
2665
|
+
return {
|
|
2666
|
+
name,
|
|
2667
|
+
path,
|
|
2668
|
+
exists: stat.isFile(),
|
|
2669
|
+
sizeBytes: stat.isFile() ? stat.size : null
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
function scanVaultBootstrapState(vaultPathInput) {
|
|
2673
|
+
const vaultPath = (0, import_path11.resolve)(vaultPathInput);
|
|
2674
|
+
const exists = (0, import_fs10.existsSync)(vaultPath);
|
|
2675
|
+
let isDirectory = false;
|
|
2676
|
+
let isEmptyDirectory = null;
|
|
2677
|
+
let entryNames = [];
|
|
2678
|
+
if (exists) {
|
|
2679
|
+
const stat = (0, import_fs10.lstatSync)(vaultPath);
|
|
2680
|
+
if (!stat.isDirectory()) {
|
|
2681
|
+
throw new Error(`Vault path exists but is not a directory: ${vaultPath}`);
|
|
2682
|
+
}
|
|
2683
|
+
isDirectory = true;
|
|
2684
|
+
entryNames = (0, import_fs10.readdirSync)(vaultPath).sort((a, b) => a.localeCompare(b));
|
|
2685
|
+
isEmptyDirectory = entryNames.length === 0;
|
|
2686
|
+
}
|
|
2687
|
+
const rootFiles = {
|
|
2688
|
+
"CLAUDE.md": scanRootFile(vaultPath, "CLAUDE.md"),
|
|
2689
|
+
"AGENTS.md": scanRootFile(vaultPath, "AGENTS.md")
|
|
2690
|
+
};
|
|
2691
|
+
const requiredDirectories = DEFAULT_VAULT_DIRECTORIES.map((name) => {
|
|
2692
|
+
const path = (0, import_path11.join)(vaultPath, name);
|
|
2693
|
+
return {
|
|
2694
|
+
name,
|
|
2695
|
+
path,
|
|
2696
|
+
exists: (0, import_fs10.existsSync)(path) && (0, import_fs10.lstatSync)(path).isDirectory()
|
|
2697
|
+
};
|
|
2698
|
+
});
|
|
2699
|
+
const recommendedMode = exists && isDirectory && !isEmptyDirectory ? "adopt" : "initialize";
|
|
2700
|
+
return {
|
|
2701
|
+
vaultPath,
|
|
2702
|
+
exists,
|
|
2703
|
+
isDirectory,
|
|
2704
|
+
isEmptyDirectory,
|
|
2705
|
+
entryNames,
|
|
2706
|
+
recommendedMode,
|
|
2707
|
+
rootFiles,
|
|
2708
|
+
requiredDirectories
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function renderVaultRootFilesFromClaudeSource(claudeSourceContent) {
|
|
2712
|
+
const normalized = normalizeMarkdown(claudeSourceContent);
|
|
2713
|
+
return {
|
|
2714
|
+
claudeMd: normalized,
|
|
2715
|
+
// L3 scaffolding: AGENTS.md mirrors the same source content until client-aware generation lands.
|
|
2716
|
+
agentsMd: normalized
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
function planVaultBootstrap(scan, rendered, options = {}) {
|
|
2720
|
+
const selectedMode = options.modeOverride ?? scan.recommendedMode;
|
|
2721
|
+
if (selectedMode === "adopt" && !scan.exists) {
|
|
2722
|
+
throw new Error(
|
|
2723
|
+
`Vault adopt mode requires an existing directory: ${scan.vaultPath}`
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
const createMissingDirectories = options.createMissingDirectories ?? true;
|
|
2727
|
+
const createMissingRootFiles = options.createMissingRootFiles ?? true;
|
|
2728
|
+
const createDirectories = createMissingDirectories ? scan.requiredDirectories.filter((dir) => !dir.exists).map((dir) => dir.path) : [];
|
|
2729
|
+
const rootContentMap = {
|
|
2730
|
+
"CLAUDE.md": rendered.claudeMd,
|
|
2731
|
+
"AGENTS.md": rendered.agentsMd
|
|
2732
|
+
};
|
|
2733
|
+
const createRootFiles = [];
|
|
2734
|
+
const skippedExistingRootFiles = [];
|
|
2735
|
+
for (const name of VAULT_ROOT_FILE_NAMES) {
|
|
2736
|
+
const rootFile = scan.rootFiles[name];
|
|
2737
|
+
if (rootFile.exists) {
|
|
2738
|
+
skippedExistingRootFiles.push({ name, path: rootFile.path, reason: "exists" });
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
if (!createMissingRootFiles) continue;
|
|
2742
|
+
createRootFiles.push({
|
|
2743
|
+
name,
|
|
2744
|
+
path: rootFile.path,
|
|
2745
|
+
bytes: Buffer.byteLength(rootContentMap[name], "utf-8")
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
return {
|
|
2749
|
+
vaultPath: scan.vaultPath,
|
|
2750
|
+
mode: selectedMode,
|
|
2751
|
+
destructive: false,
|
|
2752
|
+
createDirectories,
|
|
2753
|
+
createRootFiles,
|
|
2754
|
+
skippedExistingRootFiles,
|
|
2755
|
+
requiredDirectories: scan.requiredDirectories,
|
|
2756
|
+
rootFiles: scan.rootFiles
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
function applyVaultBootstrapPlan(plan, rendered) {
|
|
2760
|
+
(0, import_fs10.mkdirSync)(plan.vaultPath, { recursive: true });
|
|
2761
|
+
const createdDirectories = [];
|
|
2762
|
+
const createdRootFiles = [];
|
|
2763
|
+
const skippedExistingRootFiles = [...plan.skippedExistingRootFiles.map((x) => x.path)];
|
|
2764
|
+
for (const dir of plan.createDirectories) {
|
|
2765
|
+
if (!(0, import_fs10.existsSync)(dir)) {
|
|
2766
|
+
(0, import_fs10.mkdirSync)(dir, { recursive: true });
|
|
2767
|
+
createdDirectories.push(dir);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
const rootContentMap = {
|
|
2771
|
+
"CLAUDE.md": rendered.claudeMd,
|
|
2772
|
+
"AGENTS.md": rendered.agentsMd
|
|
2773
|
+
};
|
|
2774
|
+
for (const action of plan.createRootFiles) {
|
|
2775
|
+
if ((0, import_fs10.existsSync)(action.path)) {
|
|
2776
|
+
skippedExistingRootFiles.push(action.path);
|
|
2777
|
+
continue;
|
|
2778
|
+
}
|
|
2779
|
+
const content = rootContentMap[action.name];
|
|
2780
|
+
(0, import_fs10.writeFileSync)(action.path, content, "utf-8");
|
|
2781
|
+
createdRootFiles.push(action.path);
|
|
2782
|
+
}
|
|
2783
|
+
return {
|
|
2784
|
+
createdDirectories,
|
|
2785
|
+
createdRootFiles,
|
|
2786
|
+
skippedExistingRootFiles
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
// src/commands/setup.ts
|
|
2791
|
+
var DEFAULT_VAULT_MODE = "adopt";
|
|
2792
|
+
var VALID_VAULT_MODES = /* @__PURE__ */ new Set(["initialize", "adopt", "skip"]);
|
|
2793
|
+
var VAULT_MARKERS = [
|
|
2794
|
+
"0-Inbox",
|
|
2795
|
+
"1-Projects",
|
|
2796
|
+
"2-Areas",
|
|
2797
|
+
"3-Resources",
|
|
2798
|
+
"4-Archive",
|
|
2799
|
+
"Daily",
|
|
2800
|
+
"Templates"
|
|
2801
|
+
];
|
|
2802
|
+
var DEFAULT_CLAUDE_ROOT_TEMPLATE = `# CLAUDE.md
|
|
2803
|
+
|
|
2804
|
+
This vault is managed by \`pushrec-skills setup\`.
|
|
2805
|
+
|
|
2806
|
+
## Supported Path
|
|
2807
|
+
|
|
2808
|
+
- Claude Code is the native launch path for pushREC.
|
|
2809
|
+
- Codex, Gemini, and Antigravity are compatibility previews.
|
|
2810
|
+
- Windows support is compatibility-gated until native proof evidence is collected.
|
|
2811
|
+
|
|
2812
|
+
## Safety Rules
|
|
2813
|
+
|
|
2814
|
+
- Do not assume private maintainer paths.
|
|
2815
|
+
- Do not overwrite existing root files silently.
|
|
2816
|
+
- Keep compatibility labels explicit for non-Claude workflows.
|
|
2817
|
+
`;
|
|
2818
|
+
function parseVaultMode(input) {
|
|
2819
|
+
const value = (input ?? DEFAULT_VAULT_MODE).toLowerCase();
|
|
2820
|
+
if (!VALID_VAULT_MODES.has(value)) {
|
|
2821
|
+
throw new Error(
|
|
2822
|
+
`Invalid --vault-mode: ${input}. Expected one of: initialize, adopt, skip.`
|
|
2823
|
+
);
|
|
2824
|
+
}
|
|
2825
|
+
return value;
|
|
2826
|
+
}
|
|
2827
|
+
function hasVaultMarkerLayout(path) {
|
|
2828
|
+
let present = 0;
|
|
2829
|
+
for (const marker of VAULT_MARKERS) {
|
|
2830
|
+
if ((0, import_fs11.existsSync)((0, import_path12.join)(path, marker))) {
|
|
2831
|
+
present++;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
return present >= 3 && (0, import_fs11.existsSync)((0, import_path12.join)(path, "1-Projects"));
|
|
2835
|
+
}
|
|
2836
|
+
function resolveVaultPath(mode, optionPath) {
|
|
2837
|
+
if (mode === "skip") {
|
|
2838
|
+
return { path: null, source: "skip" };
|
|
2839
|
+
}
|
|
2840
|
+
if (optionPath) {
|
|
2841
|
+
return { path: (0, import_path12.resolve)(optionPath), source: "--vault-path" };
|
|
2842
|
+
}
|
|
2843
|
+
const envPath = process.env.PUSHREC_VAULT_PATH;
|
|
2844
|
+
if (envPath) {
|
|
2845
|
+
return { path: (0, import_path12.resolve)(envPath), source: "PUSHREC_VAULT_PATH" };
|
|
2846
|
+
}
|
|
2847
|
+
const cwd = process.cwd();
|
|
2848
|
+
if (hasVaultMarkerLayout(cwd)) {
|
|
2849
|
+
return { path: (0, import_path12.resolve)(cwd), source: "cwd-detected" };
|
|
2850
|
+
}
|
|
2851
|
+
throw new Error(
|
|
2852
|
+
"Vault path is ambiguous. Pass --vault-path <absolute-or-relative-path> or set PUSHREC_VAULT_PATH."
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
function validateVaultPath(mode, vaultPath) {
|
|
2856
|
+
if (mode !== "adopt") return;
|
|
2857
|
+
if (!(0, import_fs11.existsSync)(vaultPath)) {
|
|
2858
|
+
throw new Error(
|
|
2859
|
+
`Vault adopt mode requires an existing directory: ${vaultPath}`
|
|
2860
|
+
);
|
|
2861
|
+
}
|
|
2862
|
+
const stat = (0, import_fs11.lstatSync)(vaultPath);
|
|
2863
|
+
if (!stat.isDirectory()) {
|
|
2864
|
+
throw new Error(`Vault path is not a directory: ${vaultPath}`);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
function resolveClaudeTemplateSource() {
|
|
2868
|
+
const override = process.env.PUSHREC_CLAUDE_TEMPLATE;
|
|
2869
|
+
if (override?.trim()) {
|
|
2870
|
+
return override;
|
|
2871
|
+
}
|
|
2872
|
+
return DEFAULT_CLAUDE_ROOT_TEMPLATE;
|
|
2873
|
+
}
|
|
1616
2874
|
function readInput2(question) {
|
|
1617
2875
|
const rl = (0, import_readline2.createInterface)({
|
|
1618
2876
|
input: process.stdin,
|
|
1619
2877
|
output: process.stdout
|
|
1620
2878
|
});
|
|
1621
|
-
return new Promise((
|
|
2879
|
+
return new Promise((resolve7, reject) => {
|
|
1622
2880
|
rl.on("error", (err) => {
|
|
1623
2881
|
rl.close();
|
|
1624
2882
|
reject(err);
|
|
1625
2883
|
});
|
|
1626
2884
|
rl.on("close", () => {
|
|
1627
|
-
|
|
2885
|
+
resolve7("");
|
|
1628
2886
|
});
|
|
1629
2887
|
rl.question(question, (answer) => {
|
|
1630
2888
|
rl.close();
|
|
1631
|
-
|
|
2889
|
+
resolve7(answer.trim());
|
|
1632
2890
|
});
|
|
1633
2891
|
});
|
|
1634
2892
|
}
|
|
1635
2893
|
function setupCommand() {
|
|
1636
|
-
const cmd = new import_commander9.Command("setup").description("One-command onboarding: verify license, activate device, install all skills").option("--key <license-key>", "License key (or enter interactively)").option(
|
|
1637
|
-
|
|
1638
|
-
|
|
2894
|
+
const cmd = new import_commander9.Command("setup").description("One-command onboarding: verify license, activate device, install all skills").option("--key <license-key>", "License key (or enter interactively)").option(
|
|
2895
|
+
"--vault-mode <mode>",
|
|
2896
|
+
"Vault bootstrap mode: initialize, adopt, or skip (default: adopt)",
|
|
2897
|
+
DEFAULT_VAULT_MODE
|
|
2898
|
+
).option("--vault-path <path>", "Vault path for bootstrap/adopt").option(
|
|
2899
|
+
"--allow-blocked",
|
|
2900
|
+
"Allow installing compatibility-gated skills during setup (preview/testing only)"
|
|
2901
|
+
).option("--json", "Output JSON").action(async (opts) => {
|
|
2902
|
+
let setupRunStarted = false;
|
|
2903
|
+
let existing = "";
|
|
2904
|
+
let detectedClients = [];
|
|
2905
|
+
let vaultSummary = {
|
|
2906
|
+
mode: DEFAULT_VAULT_MODE,
|
|
2907
|
+
status: "SKIPPED",
|
|
2908
|
+
vaultPath: null,
|
|
2909
|
+
pathSource: "skip",
|
|
2910
|
+
recommendedMode: null,
|
|
2911
|
+
createdDirectories: [],
|
|
2912
|
+
createdRootFiles: [],
|
|
2913
|
+
skippedExistingRootFiles: []
|
|
2914
|
+
};
|
|
2915
|
+
const recordSetupFailure = (error) => {
|
|
2916
|
+
if (!setupRunStarted) return;
|
|
2917
|
+
try {
|
|
2918
|
+
failSetupRunCheckpoint(error);
|
|
2919
|
+
} catch {
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
const failAndExit = (error) => {
|
|
2923
|
+
recordSetupFailure(error);
|
|
1639
2924
|
if (opts.json) {
|
|
1640
|
-
console.log(JSON.stringify({ ok:
|
|
2925
|
+
console.log(JSON.stringify({ ok: false, error }));
|
|
1641
2926
|
} else {
|
|
1642
|
-
console.
|
|
1643
|
-
console.log("To re-setup, run " + import_chalk4.default.cyan("pushrec-skills auth logout") + " first.");
|
|
2927
|
+
console.error(error);
|
|
1644
2928
|
}
|
|
1645
|
-
|
|
2929
|
+
process.exit(1);
|
|
2930
|
+
};
|
|
2931
|
+
const checkpointPhase = (phase) => {
|
|
2932
|
+
try {
|
|
2933
|
+
completeSetupPhaseCheckpoint(phase);
|
|
2934
|
+
} catch (err) {
|
|
2935
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2936
|
+
failAndExit(`Failed to persist setup checkpoint (${phase}): ${message}`);
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
let selectedVaultMode = DEFAULT_VAULT_MODE;
|
|
2940
|
+
try {
|
|
2941
|
+
selectedVaultMode = parseVaultMode(opts.vaultMode);
|
|
2942
|
+
} catch (err) {
|
|
2943
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2944
|
+
failAndExit(message);
|
|
2945
|
+
}
|
|
2946
|
+
vaultSummary.mode = selectedVaultMode;
|
|
2947
|
+
try {
|
|
2948
|
+
beginSetupRunCheckpoint();
|
|
2949
|
+
setupRunStarted = true;
|
|
2950
|
+
} catch (err) {
|
|
2951
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2952
|
+
if (opts.json) {
|
|
2953
|
+
console.log(JSON.stringify({ ok: false, error: `Failed to initialize setup state: ${message}` }));
|
|
2954
|
+
} else {
|
|
2955
|
+
console.error(`Failed to initialize setup state: ${message}`);
|
|
2956
|
+
}
|
|
2957
|
+
process.exit(1);
|
|
2958
|
+
}
|
|
2959
|
+
try {
|
|
2960
|
+
existing = await retrieveLicenseKey() ?? "";
|
|
2961
|
+
} catch (err) {
|
|
2962
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2963
|
+
failAndExit(`Failed to read existing license key: ${message}`);
|
|
1646
2964
|
}
|
|
1647
2965
|
if (!opts.json) {
|
|
1648
2966
|
console.log("");
|
|
1649
2967
|
console.log(import_chalk4.default.bold("Pushrec Skills Setup"));
|
|
1650
2968
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1651
2969
|
console.log("");
|
|
2970
|
+
if (existing) {
|
|
2971
|
+
console.log(import_chalk4.default.dim("Existing license found in keychain \u2014 re-running setup to verify, reactivate, and sync installs."));
|
|
2972
|
+
console.log("");
|
|
2973
|
+
}
|
|
1652
2974
|
}
|
|
1653
|
-
const key = opts.key ?? await readInput2("Paste your license key: ");
|
|
2975
|
+
const key = (opts.key ?? existing) || await readInput2("Paste your license key: ");
|
|
1654
2976
|
if (!key) {
|
|
1655
|
-
|
|
1656
|
-
console.log(JSON.stringify({ ok: false, error: "No license key provided" }));
|
|
1657
|
-
} else {
|
|
1658
|
-
console.error("No license key provided.");
|
|
1659
|
-
}
|
|
1660
|
-
process.exit(1);
|
|
2977
|
+
failAndExit("No license key provided");
|
|
1661
2978
|
}
|
|
2979
|
+
checkpointPhase("license_input");
|
|
1662
2980
|
const verifySpinner = opts.json ? null : (0, import_ora2.default)("Verifying license signature...").start();
|
|
1663
|
-
const offlineResult = await verifyOffline(key)
|
|
2981
|
+
const offlineResult = await verifyOffline(key).catch((err) => {
|
|
2982
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2983
|
+
verifySpinner?.fail(`Could not verify license signature: ${message}`);
|
|
2984
|
+
return failAndExit(`Could not verify license signature: ${message}`);
|
|
2985
|
+
});
|
|
1664
2986
|
if (!offlineResult.valid) {
|
|
1665
2987
|
verifySpinner?.fail(`Invalid license key: ${offlineResult.error}`);
|
|
1666
|
-
|
|
1667
|
-
console.log(JSON.stringify({ ok: false, error: `Invalid license key: ${offlineResult.error}` }));
|
|
1668
|
-
}
|
|
1669
|
-
process.exit(1);
|
|
2988
|
+
failAndExit(`Invalid license key: ${offlineResult.error}`);
|
|
1670
2989
|
}
|
|
1671
2990
|
verifySpinner?.succeed("License signature valid");
|
|
2991
|
+
checkpointPhase("offline_verify");
|
|
1672
2992
|
const onlineSpinner = opts.json ? null : (0, import_ora2.default)("Checking with server...").start();
|
|
1673
2993
|
const fingerprint = getMachineFingerprint();
|
|
1674
|
-
|
|
1675
|
-
try {
|
|
1676
|
-
onlineResult = await verifyLicenseOnline(key, fingerprint);
|
|
1677
|
-
} catch (err) {
|
|
2994
|
+
const onlineResult = await verifyLicenseOnline(key, fingerprint).catch((err) => {
|
|
1678
2995
|
const message = err instanceof Error ? err.message : String(err);
|
|
1679
2996
|
onlineSpinner?.fail(`Could not reach server: ${message}`);
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1683
|
-
process.exit(1);
|
|
1684
|
-
}
|
|
2997
|
+
return failAndExit(`Could not reach server: ${message}`);
|
|
2998
|
+
});
|
|
1685
2999
|
if (!onlineResult.valid) {
|
|
1686
3000
|
onlineSpinner?.fail(`License rejected: ${onlineResult.error}`);
|
|
1687
|
-
|
|
1688
|
-
console.log(JSON.stringify({ ok: false, error: `License rejected: ${onlineResult.error}` }));
|
|
1689
|
-
}
|
|
1690
|
-
process.exit(1);
|
|
3001
|
+
failAndExit(`License rejected: ${onlineResult.error}`);
|
|
1691
3002
|
}
|
|
1692
3003
|
onlineSpinner?.succeed("Server confirmed license active");
|
|
1693
|
-
|
|
3004
|
+
checkpointPhase("online_verify");
|
|
3005
|
+
try {
|
|
3006
|
+
await storeLicenseKey(key);
|
|
3007
|
+
} catch (err) {
|
|
3008
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3009
|
+
failAndExit(`Failed to store license key: ${message}`);
|
|
3010
|
+
}
|
|
3011
|
+
checkpointPhase("store_license");
|
|
1694
3012
|
const deviceSpinner = opts.json ? null : (0, import_ora2.default)("Activating this device...").start();
|
|
1695
3013
|
let activation;
|
|
1696
3014
|
try {
|
|
@@ -1700,15 +3018,15 @@ function setupCommand() {
|
|
|
1700
3018
|
getPlatformName(),
|
|
1701
3019
|
getHostname()
|
|
1702
3020
|
);
|
|
1703
|
-
|
|
3021
|
+
const deviceCount = activation.deviceCount ?? onlineResult.deviceCount ?? 1;
|
|
3022
|
+
const maxDevices = activation.maxDevices ?? onlineResult.maxDevices ?? 3;
|
|
3023
|
+
deviceSpinner?.succeed(`Device activated (${deviceCount}/${maxDevices} slots used)`);
|
|
1704
3024
|
} catch (err) {
|
|
1705
3025
|
const message = err instanceof Error ? err.message : String(err);
|
|
1706
3026
|
deviceSpinner?.fail(`Device activation failed: ${message}`);
|
|
1707
|
-
|
|
1708
|
-
console.log(JSON.stringify({ ok: false, error: `Device activation failed: ${message}` }));
|
|
1709
|
-
}
|
|
1710
|
-
process.exit(1);
|
|
3027
|
+
failAndExit(`Device activation failed: ${message}`);
|
|
1711
3028
|
}
|
|
3029
|
+
checkpointPhase("activate_device");
|
|
1712
3030
|
const cache = {
|
|
1713
3031
|
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1714
3032
|
expiresAt: onlineResult.updatesExpireAt ?? null,
|
|
@@ -1722,7 +3040,75 @@ function setupCommand() {
|
|
|
1722
3040
|
deviceCount: onlineResult.deviceCount ?? 1
|
|
1723
3041
|
}
|
|
1724
3042
|
};
|
|
1725
|
-
|
|
3043
|
+
try {
|
|
3044
|
+
saveLicenseCache(cache);
|
|
3045
|
+
} catch (err) {
|
|
3046
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3047
|
+
failAndExit(`Failed to save license cache: ${message}`);
|
|
3048
|
+
}
|
|
3049
|
+
checkpointPhase("cache_verification");
|
|
3050
|
+
try {
|
|
3051
|
+
const detected = detectClientProfiles();
|
|
3052
|
+
detectedClients = detected.detectedClients;
|
|
3053
|
+
updateConfig({ clients: detected.profiles });
|
|
3054
|
+
} catch {
|
|
3055
|
+
}
|
|
3056
|
+
checkpointPhase("detect_clients");
|
|
3057
|
+
if (selectedVaultMode !== "skip") {
|
|
3058
|
+
let resolvedVaultPath = null;
|
|
3059
|
+
try {
|
|
3060
|
+
const resolved = resolveVaultPath(selectedVaultMode, opts.vaultPath);
|
|
3061
|
+
resolvedVaultPath = resolved.path;
|
|
3062
|
+
vaultSummary.pathSource = resolved.source;
|
|
3063
|
+
vaultSummary.vaultPath = resolved.path;
|
|
3064
|
+
checkpointPhase("resolve_vault");
|
|
3065
|
+
if (!resolved.path) {
|
|
3066
|
+
throw new Error("Resolved vault path is empty for non-skip mode.");
|
|
3067
|
+
}
|
|
3068
|
+
validateVaultPath(selectedVaultMode, resolved.path);
|
|
3069
|
+
const scan = scanVaultBootstrapState(resolved.path);
|
|
3070
|
+
const rendered = renderVaultRootFilesFromClaudeSource(resolveClaudeTemplateSource());
|
|
3071
|
+
const vaultModeForBootstrap = selectedVaultMode === "adopt" ? "adopt" : "initialize";
|
|
3072
|
+
const plan = planVaultBootstrap(scan, rendered, {
|
|
3073
|
+
modeOverride: vaultModeForBootstrap
|
|
3074
|
+
});
|
|
3075
|
+
vaultSummary.recommendedMode = scan.recommendedMode;
|
|
3076
|
+
checkpointPhase("plan_vault_bootstrap");
|
|
3077
|
+
const applyResult = applyVaultBootstrapPlan(plan, rendered);
|
|
3078
|
+
checkpointPhase("apply_vault_bootstrap");
|
|
3079
|
+
const vaultStatus = plan.mode === "adopt" ? "adopted" : "initialized";
|
|
3080
|
+
const previousVaultState = loadConfig().vault;
|
|
3081
|
+
const updatedVaultState = updateConfig({
|
|
3082
|
+
vault: {
|
|
3083
|
+
...previousVaultState,
|
|
3084
|
+
status: vaultStatus,
|
|
3085
|
+
vaultPath: plan.vaultPath,
|
|
3086
|
+
claudeMdPath: (0, import_path12.join)(plan.vaultPath, "CLAUDE.md"),
|
|
3087
|
+
agentsMdPath: (0, import_path12.join)(plan.vaultPath, "AGENTS.md"),
|
|
3088
|
+
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3089
|
+
}
|
|
3090
|
+
}).vault;
|
|
3091
|
+
vaultSummary.status = plan.mode === "adopt" ? "ADOPTED" : "INITIALIZED";
|
|
3092
|
+
vaultSummary.createdDirectories = applyResult.createdDirectories;
|
|
3093
|
+
vaultSummary.createdRootFiles = applyResult.createdRootFiles;
|
|
3094
|
+
vaultSummary.skippedExistingRootFiles = applyResult.skippedExistingRootFiles;
|
|
3095
|
+
vaultSummary.vaultPath = updatedVaultState.vaultPath;
|
|
3096
|
+
} catch (err) {
|
|
3097
|
+
if (resolvedVaultPath) {
|
|
3098
|
+
const previousVaultState = loadConfig().vault;
|
|
3099
|
+
updateConfig({
|
|
3100
|
+
vault: {
|
|
3101
|
+
...previousVaultState,
|
|
3102
|
+
status: "error",
|
|
3103
|
+
vaultPath: resolvedVaultPath,
|
|
3104
|
+
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3105
|
+
}
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
3108
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3109
|
+
failAndExit(`Vault bootstrap failed: ${message}`);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
1726
3112
|
if (!opts.json) {
|
|
1727
3113
|
console.log("");
|
|
1728
3114
|
console.log(import_chalk4.default.bold("Installing skills..."));
|
|
@@ -1732,34 +3118,70 @@ function setupCommand() {
|
|
|
1732
3118
|
catalog = await fetchSkillCatalog();
|
|
1733
3119
|
} catch (err) {
|
|
1734
3120
|
const message = err instanceof Error ? err.message : String(err);
|
|
3121
|
+
recordSetupFailure(`Could not fetch catalog: ${message}`);
|
|
1735
3122
|
if (opts.json) {
|
|
1736
|
-
console.log(
|
|
3123
|
+
console.log(
|
|
3124
|
+
JSON.stringify({
|
|
3125
|
+
ok: true,
|
|
3126
|
+
activated: true,
|
|
3127
|
+
installed: 0,
|
|
3128
|
+
error: `Could not fetch catalog: ${message}`,
|
|
3129
|
+
detectedClients,
|
|
3130
|
+
vault: vaultSummary
|
|
3131
|
+
})
|
|
3132
|
+
);
|
|
1737
3133
|
} else {
|
|
1738
3134
|
console.error(`Could not fetch catalog: ${message}`);
|
|
1739
3135
|
console.log(import_chalk4.default.yellow("License activated but skill install failed. Run ") + import_chalk4.default.cyan("pushrec-skills install --all") + import_chalk4.default.yellow(" to retry."));
|
|
1740
3136
|
}
|
|
1741
3137
|
return;
|
|
1742
3138
|
}
|
|
3139
|
+
checkpointPhase("fetch_catalog");
|
|
1743
3140
|
const toInstall = catalog.items.filter((s) => !isSkillInstalled(s.name));
|
|
1744
3141
|
let installed = 0;
|
|
1745
3142
|
let failed = 0;
|
|
3143
|
+
let blocked = 0;
|
|
1746
3144
|
for (let i = 0; i < toInstall.length; i++) {
|
|
1747
3145
|
const skill = toInstall[i];
|
|
1748
3146
|
const prefix = opts.json ? "" : `[${i + 1}/${toInstall.length}] `;
|
|
1749
3147
|
const spinner = opts.json ? null : (0, import_ora2.default)(`${prefix}Installing ${skill.name}...`).start();
|
|
1750
3148
|
try {
|
|
1751
3149
|
const manifest = await fetchSkillManifest(skill.name);
|
|
3150
|
+
const compatibility = evaluateSkillCompatibility(manifest, { client: "claude" });
|
|
3151
|
+
if (!compatibility.allowed) {
|
|
3152
|
+
if (compatibility.requiresOverride && opts.allowBlocked) {
|
|
3153
|
+
spinner?.warn(
|
|
3154
|
+
`${prefix}${skill.name}: compatibility override enabled (${compatibility.reasonCode}); continuing.`
|
|
3155
|
+
);
|
|
3156
|
+
} else {
|
|
3157
|
+
spinner?.fail(`${prefix}Skipped ${skill.name}: ${compatibility.message}`);
|
|
3158
|
+
blocked++;
|
|
3159
|
+
continue;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
1752
3162
|
const archive = await downloadSkill(skill.name);
|
|
1753
3163
|
const { path } = await installSkill(skill.name, archive);
|
|
1754
3164
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1755
3165
|
const config = loadConfig();
|
|
3166
|
+
const installedVersion = manifest.currentVersion ?? "0.0.0";
|
|
1756
3167
|
config.installedSkills[skill.name] = {
|
|
1757
3168
|
name: skill.name,
|
|
1758
|
-
version:
|
|
3169
|
+
version: installedVersion,
|
|
1759
3170
|
installedAt: config.installedSkills[skill.name]?.installedAt ?? now,
|
|
1760
3171
|
updatedAt: now
|
|
1761
3172
|
};
|
|
1762
|
-
|
|
3173
|
+
const contentHash = hashInstalledSkillPath(path);
|
|
3174
|
+
const nextManaged = buildNextManagedSkillState({
|
|
3175
|
+
name: skill.name,
|
|
3176
|
+
version: installedVersion,
|
|
3177
|
+
installedPath: path,
|
|
3178
|
+
previous: config.skillState[skill.name],
|
|
3179
|
+
hash: contentHash
|
|
3180
|
+
});
|
|
3181
|
+
updateConfig({
|
|
3182
|
+
installedSkills: config.installedSkills,
|
|
3183
|
+
skillState: { [skill.name]: nextManaged }
|
|
3184
|
+
});
|
|
1763
3185
|
spinner?.succeed(`${prefix}${skill.name} v${manifest.currentVersion ?? "latest"} installed to ${path}`);
|
|
1764
3186
|
installed++;
|
|
1765
3187
|
} catch (err) {
|
|
@@ -1768,35 +3190,870 @@ function setupCommand() {
|
|
|
1768
3190
|
failed++;
|
|
1769
3191
|
}
|
|
1770
3192
|
}
|
|
3193
|
+
checkpointPhase("install_skills");
|
|
3194
|
+
try {
|
|
3195
|
+
copyBundledSkill("pushrec-skills");
|
|
3196
|
+
} catch {
|
|
3197
|
+
}
|
|
3198
|
+
checkpointPhase("copy_bundled_skill");
|
|
1771
3199
|
const skipped = catalog.items.length - toInstall.length;
|
|
3200
|
+
try {
|
|
3201
|
+
finishSetupRunCheckpoint();
|
|
3202
|
+
} catch (err) {
|
|
3203
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3204
|
+
if (!opts.json) {
|
|
3205
|
+
console.log(import_chalk4.default.yellow(`Warning: setup completed but failed to persist final setup checkpoint: ${message}`));
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
1772
3208
|
if (opts.json) {
|
|
1773
|
-
console.log(
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
3209
|
+
console.log(
|
|
3210
|
+
JSON.stringify({
|
|
3211
|
+
ok: true,
|
|
3212
|
+
activated: true,
|
|
3213
|
+
installed,
|
|
3214
|
+
blocked,
|
|
3215
|
+
failed,
|
|
3216
|
+
skipped,
|
|
3217
|
+
total: catalog.items.length,
|
|
3218
|
+
detectedClients,
|
|
3219
|
+
vault: vaultSummary
|
|
3220
|
+
})
|
|
3221
|
+
);
|
|
1781
3222
|
} else {
|
|
1782
3223
|
console.log("");
|
|
1783
|
-
console.log(import_chalk4.default.green
|
|
1784
|
-
console.log(`
|
|
1785
|
-
if (
|
|
1786
|
-
if (
|
|
3224
|
+
console.log(import_chalk4.default.green(" Setup complete!"));
|
|
3225
|
+
console.log(` ${import_chalk4.default.green("+")} ${installed} skills installed`);
|
|
3226
|
+
if (blocked > 0) console.log(` ${import_chalk4.default.yellow("!")} ${blocked} compatibility-gated/skipped`);
|
|
3227
|
+
if (skipped > 0) console.log(` ${import_chalk4.default.dim("-")} ${skipped} already installed`);
|
|
3228
|
+
if (failed > 0) console.log(` ${import_chalk4.default.red("x")} ${failed} failed`);
|
|
3229
|
+
if (selectedVaultMode === "skip") {
|
|
3230
|
+
console.log(` ${import_chalk4.default.dim("-")} Vault bootstrap skipped (--vault-mode skip)`);
|
|
3231
|
+
} else {
|
|
3232
|
+
const vaultStatusLabel = vaultSummary.status === "INITIALIZED" ? "initialized" : "adopted";
|
|
3233
|
+
console.log(
|
|
3234
|
+
` ${import_chalk4.default.green("+")} Vault ${vaultStatusLabel} at ${vaultSummary.vaultPath ?? "(unknown path)"}`
|
|
3235
|
+
);
|
|
3236
|
+
console.log(
|
|
3237
|
+
` created dirs: ${vaultSummary.createdDirectories.length}, root files: ${vaultSummary.createdRootFiles.length}, skipped root files: ${vaultSummary.skippedExistingRootFiles.length}`
|
|
3238
|
+
);
|
|
3239
|
+
}
|
|
3240
|
+
console.log("");
|
|
3241
|
+
console.log(` ${import_chalk4.default.bold("NEXT STEP:")} Open Claude Code and type ${import_chalk4.default.cyan("/pushrec-skills")}`);
|
|
3242
|
+
console.log("");
|
|
3243
|
+
console.log(" Quick reference:");
|
|
3244
|
+
console.log(` ${import_chalk4.default.cyan("/pushrec-skills")} \u2014 Your guided setup (start here)`);
|
|
3245
|
+
console.log(` ${import_chalk4.default.cyan("/hormozi")} \u2014 Business strategy frameworks`);
|
|
3246
|
+
console.log(` ${import_chalk4.default.cyan("/linkedin-copywriting")} \u2014 LinkedIn content creation`);
|
|
3247
|
+
console.log(` ${import_chalk4.default.cyan("/copywriting")} \u2014 Master copywriting foundations`);
|
|
1787
3248
|
console.log("");
|
|
1788
|
-
|
|
1789
|
-
|
|
3249
|
+
if (detectedClients.length > 0) {
|
|
3250
|
+
console.log(` Detected local clients: ${detectedClients.join(", ")} ${import_chalk4.default.dim("(Claude-first install is active; preview client setup comes next)")}`);
|
|
3251
|
+
console.log("");
|
|
3252
|
+
}
|
|
3253
|
+
console.log(` Community: ${import_chalk4.default.underline("skool.com/pushrec-2909")}`);
|
|
3254
|
+
console.log(` Full catalog: ${import_chalk4.default.dim("npx @pushrec/skills list")}`);
|
|
1790
3255
|
}
|
|
1791
3256
|
});
|
|
1792
3257
|
return cmd;
|
|
1793
3258
|
}
|
|
1794
3259
|
|
|
3260
|
+
// src/commands/dashboard.ts
|
|
3261
|
+
var import_commander10 = require("commander");
|
|
3262
|
+
var import_http = require("http");
|
|
3263
|
+
var import_fs12 = require("fs");
|
|
3264
|
+
var import_path13 = require("path");
|
|
3265
|
+
var import_os7 = require("os");
|
|
3266
|
+
var import_child_process5 = require("child_process");
|
|
3267
|
+
var DEFAULT_PORT = 5174;
|
|
3268
|
+
function getPushrecSkillDir() {
|
|
3269
|
+
return (0, import_path13.join)(getLegacyClaudeSkillsRoot(), "pushrec-skills");
|
|
3270
|
+
}
|
|
3271
|
+
function getDataSources() {
|
|
3272
|
+
const pushrecSkillDir = getPushrecSkillDir();
|
|
3273
|
+
return {
|
|
3274
|
+
"/api/config": CONFIG_FILE,
|
|
3275
|
+
"/api/profile": (0, import_path13.join)(pushrecSkillDir, "state", "profile.json"),
|
|
3276
|
+
"/api/progress": (0, import_path13.join)(pushrecSkillDir, "state", "progress.json"),
|
|
3277
|
+
"/api/catalog": (0, import_path13.join)(pushrecSkillDir, "state", "catalog.json"),
|
|
3278
|
+
"/api/diagnostic": (0, import_path13.join)(pushrecSkillDir, "state", "diagnostic.json"),
|
|
3279
|
+
"/api/teams": (0, import_path13.join)(pushrecSkillDir, "state", "teams.json"),
|
|
3280
|
+
"/api/tasks": (0, import_path13.join)(pushrecSkillDir, "state", "tasks.json")
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
function getYamlSources() {
|
|
3284
|
+
const pushrecSkillDir = getPushrecSkillDir();
|
|
3285
|
+
return {
|
|
3286
|
+
"/api/prerequisites": (0, import_path13.join)(
|
|
3287
|
+
pushrecSkillDir,
|
|
3288
|
+
"references",
|
|
3289
|
+
"prerequisites.yaml"
|
|
3290
|
+
)
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
function expandPath(filepath) {
|
|
3294
|
+
if (filepath.startsWith("~/")) {
|
|
3295
|
+
return (0, import_path13.join)((0, import_os7.homedir)(), filepath.slice(2));
|
|
3296
|
+
}
|
|
3297
|
+
return filepath;
|
|
3298
|
+
}
|
|
3299
|
+
function serveJson(res, data) {
|
|
3300
|
+
res.setHeader("Content-Type", "application/json");
|
|
3301
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3302
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
3303
|
+
res.end(data);
|
|
3304
|
+
}
|
|
3305
|
+
function serveError(res, status, message) {
|
|
3306
|
+
res.statusCode = status;
|
|
3307
|
+
res.setHeader("Content-Type", "application/json");
|
|
3308
|
+
res.end(JSON.stringify({ error: message }));
|
|
3309
|
+
}
|
|
3310
|
+
function parseYaml(raw) {
|
|
3311
|
+
try {
|
|
3312
|
+
return JSON.parse(raw);
|
|
3313
|
+
} catch {
|
|
3314
|
+
return { raw };
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
function handleApiRequest(req, res) {
|
|
3318
|
+
const url = req.url ?? "";
|
|
3319
|
+
const dataSources = getDataSources();
|
|
3320
|
+
const yamlSources = getYamlSources();
|
|
3321
|
+
if (url === "/api/mtime") {
|
|
3322
|
+
const mtimes = {};
|
|
3323
|
+
for (const [endpoint, filepath] of Object.entries(dataSources)) {
|
|
3324
|
+
try {
|
|
3325
|
+
mtimes[endpoint] = (0, import_fs12.statSync)(expandPath(filepath)).mtimeMs;
|
|
3326
|
+
} catch {
|
|
3327
|
+
mtimes[endpoint] = 0;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
for (const [endpoint, filepath] of Object.entries(yamlSources)) {
|
|
3331
|
+
try {
|
|
3332
|
+
mtimes[endpoint] = (0, import_fs12.statSync)(expandPath(filepath)).mtimeMs;
|
|
3333
|
+
} catch {
|
|
3334
|
+
mtimes[endpoint] = 0;
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
serveJson(res, JSON.stringify(mtimes));
|
|
3338
|
+
return true;
|
|
3339
|
+
}
|
|
3340
|
+
if (url in dataSources) {
|
|
3341
|
+
const filepath = dataSources[url];
|
|
3342
|
+
const resolved = expandPath(filepath);
|
|
3343
|
+
try {
|
|
3344
|
+
const data = (0, import_fs12.readFileSync)(resolved, "utf-8");
|
|
3345
|
+
serveJson(res, data);
|
|
3346
|
+
} catch {
|
|
3347
|
+
serveError(res, 500, `Failed to read ${filepath}`);
|
|
3348
|
+
}
|
|
3349
|
+
return true;
|
|
3350
|
+
}
|
|
3351
|
+
if (url in yamlSources) {
|
|
3352
|
+
const filepath = yamlSources[url];
|
|
3353
|
+
const resolved = expandPath(filepath);
|
|
3354
|
+
try {
|
|
3355
|
+
const raw = (0, import_fs12.readFileSync)(resolved, "utf-8");
|
|
3356
|
+
const data = parseYaml(raw);
|
|
3357
|
+
serveJson(res, JSON.stringify(data));
|
|
3358
|
+
} catch {
|
|
3359
|
+
serveError(res, 500, `Failed to read ${filepath}`);
|
|
3360
|
+
}
|
|
3361
|
+
return true;
|
|
3362
|
+
}
|
|
3363
|
+
return false;
|
|
3364
|
+
}
|
|
3365
|
+
function findDistDirectory() {
|
|
3366
|
+
const candidates = [
|
|
3367
|
+
(0, import_path13.join)(getLegacyClaudeSkillsRoot(), "generative-ui", "dist"),
|
|
3368
|
+
(0, import_path13.join)((0, import_os7.homedir)(), ".cache", "genui-dashboard", "dist")
|
|
3369
|
+
];
|
|
3370
|
+
for (const candidate of candidates) {
|
|
3371
|
+
if ((0, import_fs12.existsSync)((0, import_path13.join)(candidate, "index.html"))) {
|
|
3372
|
+
return candidate;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
return null;
|
|
3376
|
+
}
|
|
3377
|
+
function serveStatic(distDir, req, res) {
|
|
3378
|
+
let urlPath = (req.url ?? "/").split("?")[0];
|
|
3379
|
+
if (urlPath === "/") urlPath = "/index.html";
|
|
3380
|
+
const filePath = (0, import_path13.join)(distDir, urlPath);
|
|
3381
|
+
if (!filePath.startsWith(distDir)) {
|
|
3382
|
+
serveError(res, 403, "Forbidden");
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
try {
|
|
3386
|
+
const content = (0, import_fs12.readFileSync)(filePath);
|
|
3387
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
3388
|
+
const mimeTypes = {
|
|
3389
|
+
html: "text/html",
|
|
3390
|
+
js: "application/javascript",
|
|
3391
|
+
css: "text/css",
|
|
3392
|
+
json: "application/json",
|
|
3393
|
+
png: "image/png",
|
|
3394
|
+
svg: "image/svg+xml",
|
|
3395
|
+
ico: "image/x-icon",
|
|
3396
|
+
woff2: "font/woff2",
|
|
3397
|
+
woff: "font/woff"
|
|
3398
|
+
};
|
|
3399
|
+
res.setHeader("Content-Type", mimeTypes[ext] ?? "application/octet-stream");
|
|
3400
|
+
res.end(content);
|
|
3401
|
+
} catch {
|
|
3402
|
+
try {
|
|
3403
|
+
const indexContent = (0, import_fs12.readFileSync)((0, import_path13.join)(distDir, "index.html"));
|
|
3404
|
+
res.setHeader("Content-Type", "text/html");
|
|
3405
|
+
res.end(indexContent);
|
|
3406
|
+
} catch {
|
|
3407
|
+
serveError(res, 404, "Not found");
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
function openBrowser(url) {
|
|
3412
|
+
const platform5 = process.platform;
|
|
3413
|
+
const command = platform5 === "darwin" ? `open "${url}"` : platform5 === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
3414
|
+
(0, import_child_process5.exec)(command, () => {
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
function dashboardCommand() {
|
|
3418
|
+
const cmd = new import_commander10.Command("dashboard").description("Launch the Generative UI dashboard").option("--port <port>", "Server port", String(DEFAULT_PORT)).option("--dev", "Start in dev mode with Vite HMR").option("--no-open", "Skip opening browser").action(
|
|
3419
|
+
async (opts) => {
|
|
3420
|
+
const port = parseInt(opts.port, 10);
|
|
3421
|
+
if (isNaN(port) || port < 1024 || port > 65535) {
|
|
3422
|
+
console.error(
|
|
3423
|
+
`ERROR: Port must be between 1024 and 65535, got "${opts.port}".`
|
|
3424
|
+
);
|
|
3425
|
+
process.exit(1);
|
|
3426
|
+
}
|
|
3427
|
+
if (opts.dev) {
|
|
3428
|
+
console.log("Dev mode is not yet supported via CLI.");
|
|
3429
|
+
console.log(
|
|
3430
|
+
"Use the Vite dev server directly: cd dashboard && npm run dev"
|
|
3431
|
+
);
|
|
3432
|
+
process.exit(0);
|
|
3433
|
+
}
|
|
3434
|
+
const distDir = findDistDirectory();
|
|
3435
|
+
if (!distDir) {
|
|
3436
|
+
console.error("ERROR: No built dashboard found.");
|
|
3437
|
+
console.error(
|
|
3438
|
+
" Build the dashboard first, or use --dev mode."
|
|
3439
|
+
);
|
|
3440
|
+
console.error(
|
|
3441
|
+
` Expected: ${(0, import_path13.join)(getLegacyClaudeSkillsRoot(), "generative-ui", "dist", "index.html")}`
|
|
3442
|
+
);
|
|
3443
|
+
process.exit(1);
|
|
3444
|
+
}
|
|
3445
|
+
const server = (0, import_http.createServer)((req, res) => {
|
|
3446
|
+
if (req.url?.startsWith("/api/")) {
|
|
3447
|
+
const handled = handleApiRequest(req, res);
|
|
3448
|
+
if (handled) return;
|
|
3449
|
+
}
|
|
3450
|
+
serveStatic(distDir, req, res);
|
|
3451
|
+
});
|
|
3452
|
+
server.listen(port, () => {
|
|
3453
|
+
const url = `http://localhost:${port}`;
|
|
3454
|
+
console.log(`Dashboard running at ${url}`);
|
|
3455
|
+
console.log(` Serving: ${distDir}`);
|
|
3456
|
+
console.log(` Press Ctrl+C to stop`);
|
|
3457
|
+
console.log();
|
|
3458
|
+
if (opts.open !== false) {
|
|
3459
|
+
openBrowser(url);
|
|
3460
|
+
}
|
|
3461
|
+
});
|
|
3462
|
+
server.on("error", (err) => {
|
|
3463
|
+
if (err.code === "EADDRINUSE") {
|
|
3464
|
+
console.error(`ERROR: Port ${port} is already in use.`);
|
|
3465
|
+
console.error(
|
|
3466
|
+
" Stop the process currently using this port with your platform's process tools, then retry."
|
|
3467
|
+
);
|
|
3468
|
+
} else {
|
|
3469
|
+
console.error(`ERROR: ${err.message}`);
|
|
3470
|
+
}
|
|
3471
|
+
process.exit(1);
|
|
3472
|
+
});
|
|
3473
|
+
const shutdown = () => {
|
|
3474
|
+
console.log("\nShutting down dashboard...");
|
|
3475
|
+
server.close(() => {
|
|
3476
|
+
process.exit(0);
|
|
3477
|
+
});
|
|
3478
|
+
setTimeout(() => process.exit(0), 3e3);
|
|
3479
|
+
};
|
|
3480
|
+
process.on("SIGINT", shutdown);
|
|
3481
|
+
process.on("SIGTERM", shutdown);
|
|
3482
|
+
if (process.platform === "win32") {
|
|
3483
|
+
process.on("SIGBREAK", shutdown);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
);
|
|
3487
|
+
return cmd;
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// src/commands/rollback.ts
|
|
3491
|
+
var import_fs13 = require("fs");
|
|
3492
|
+
var import_path14 = require("path");
|
|
3493
|
+
var import_commander11 = require("commander");
|
|
3494
|
+
function resolveBackupSelection(backups, selector) {
|
|
3495
|
+
if (backups.length === 0) {
|
|
3496
|
+
throw new Error("No backups recorded for this skill.");
|
|
3497
|
+
}
|
|
3498
|
+
if (!selector) {
|
|
3499
|
+
return backups[backups.length - 1];
|
|
3500
|
+
}
|
|
3501
|
+
if (backups.includes(selector)) {
|
|
3502
|
+
return selector;
|
|
3503
|
+
}
|
|
3504
|
+
const matches = backups.filter((path) => {
|
|
3505
|
+
const base = (0, import_path14.basename)(path);
|
|
3506
|
+
return base.includes(selector) || path.endsWith(selector);
|
|
3507
|
+
});
|
|
3508
|
+
if (matches.length === 1) {
|
|
3509
|
+
return matches[0];
|
|
3510
|
+
}
|
|
3511
|
+
if (matches.length > 1) {
|
|
3512
|
+
throw new Error(
|
|
3513
|
+
`Backup selector '${selector}' is ambiguous. Matches: ${matches.join(", ")}`
|
|
3514
|
+
);
|
|
3515
|
+
}
|
|
3516
|
+
throw new Error(`Backup '${selector}' not found in recorded backups.`);
|
|
3517
|
+
}
|
|
3518
|
+
function restoreBackupDirectory(backupPath, installedPath) {
|
|
3519
|
+
if (!(0, import_fs13.existsSync)(backupPath) || !(0, import_fs13.lstatSync)(backupPath).isDirectory()) {
|
|
3520
|
+
throw new Error(`Backup directory does not exist: ${backupPath}`);
|
|
3521
|
+
}
|
|
3522
|
+
(0, import_fs13.mkdirSync)((0, import_path14.dirname)(installedPath), { recursive: true });
|
|
3523
|
+
(0, import_fs13.rmSync)(installedPath, { recursive: true, force: true });
|
|
3524
|
+
(0, import_fs13.cpSync)(backupPath, installedPath, { recursive: true });
|
|
3525
|
+
const metadataInTarget = (0, import_path14.join)(installedPath, SKILL_BACKUP_METADATA_FILE);
|
|
3526
|
+
if ((0, import_fs13.existsSync)(metadataInTarget)) {
|
|
3527
|
+
(0, import_fs13.rmSync)(metadataInTarget, { force: true });
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
function rollbackCommand() {
|
|
3531
|
+
const cmd = new import_commander11.Command("rollback").description("Rollback an installed skill to a backup snapshot").argument("<name>", "Skill name to rollback").option("--backup-id <id>", "Backup id/path selector (defaults to latest)").option("--json", "Output JSON").action(async (name, opts) => {
|
|
3532
|
+
const config = loadConfig();
|
|
3533
|
+
const managed = config.skillState[name];
|
|
3534
|
+
if (!managed) {
|
|
3535
|
+
const error = `${name} has no managed state; rollback unavailable.`;
|
|
3536
|
+
if (opts.json) {
|
|
3537
|
+
console.log(JSON.stringify({ ok: false, error }));
|
|
3538
|
+
} else {
|
|
3539
|
+
console.error(error);
|
|
3540
|
+
}
|
|
3541
|
+
process.exit(1);
|
|
3542
|
+
}
|
|
3543
|
+
let selectedBackup;
|
|
3544
|
+
try {
|
|
3545
|
+
selectedBackup = resolveBackupSelection(managed.backups, opts.backupId);
|
|
3546
|
+
} catch (err) {
|
|
3547
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
3548
|
+
if (opts.json) {
|
|
3549
|
+
console.log(JSON.stringify({ ok: false, error }));
|
|
3550
|
+
} else {
|
|
3551
|
+
console.error(error);
|
|
3552
|
+
}
|
|
3553
|
+
process.exit(1);
|
|
3554
|
+
}
|
|
3555
|
+
const installedPath = resolveInstalledSkillPath(name, managed);
|
|
3556
|
+
const existingHash = hashInstalledSkillPath(installedPath);
|
|
3557
|
+
let preRollbackBackup = null;
|
|
3558
|
+
if (existingHash) {
|
|
3559
|
+
preRollbackBackup = createSkillBackupSnapshot(name, installedPath, {
|
|
3560
|
+
sourceVersion: config.installedSkills[name]?.version ?? managed.lastKnownVersion ?? null,
|
|
3561
|
+
sourceHash: existingHash
|
|
3562
|
+
});
|
|
3563
|
+
}
|
|
3564
|
+
try {
|
|
3565
|
+
restoreBackupDirectory(selectedBackup, installedPath);
|
|
3566
|
+
} catch (err) {
|
|
3567
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
3568
|
+
if (opts.json) {
|
|
3569
|
+
console.log(JSON.stringify({ ok: false, error }));
|
|
3570
|
+
} else {
|
|
3571
|
+
console.error(error);
|
|
3572
|
+
}
|
|
3573
|
+
process.exit(1);
|
|
3574
|
+
}
|
|
3575
|
+
const restoredHash = hashInstalledSkillPath(installedPath);
|
|
3576
|
+
const backupMetadata = readSkillBackupMetadata(selectedBackup);
|
|
3577
|
+
const restoredVersion = backupMetadata?.sourceVersion ?? managed.lastKnownVersion ?? config.installedSkills[name]?.version ?? "unknown";
|
|
3578
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3579
|
+
const nextInstalled = {
|
|
3580
|
+
...config.installedSkills[name] ?? {
|
|
3581
|
+
name,
|
|
3582
|
+
version: restoredVersion,
|
|
3583
|
+
installedAt: now,
|
|
3584
|
+
updatedAt: now
|
|
3585
|
+
},
|
|
3586
|
+
name,
|
|
3587
|
+
version: restoredVersion,
|
|
3588
|
+
updatedAt: now
|
|
3589
|
+
};
|
|
3590
|
+
const nextManaged = buildNextManagedSkillState({
|
|
3591
|
+
name,
|
|
3592
|
+
version: restoredVersion,
|
|
3593
|
+
installedPath,
|
|
3594
|
+
previous: managed,
|
|
3595
|
+
backupPath: preRollbackBackup,
|
|
3596
|
+
hash: restoredHash
|
|
3597
|
+
});
|
|
3598
|
+
updateConfig({
|
|
3599
|
+
installedSkills: {
|
|
3600
|
+
[name]: nextInstalled
|
|
3601
|
+
},
|
|
3602
|
+
skillState: {
|
|
3603
|
+
[name]: nextManaged
|
|
3604
|
+
}
|
|
3605
|
+
});
|
|
3606
|
+
if (opts.json) {
|
|
3607
|
+
console.log(
|
|
3608
|
+
JSON.stringify({
|
|
3609
|
+
ok: true,
|
|
3610
|
+
skill: name,
|
|
3611
|
+
restoredFrom: selectedBackup,
|
|
3612
|
+
restoredVersion,
|
|
3613
|
+
installedPath,
|
|
3614
|
+
preRollbackBackup
|
|
3615
|
+
})
|
|
3616
|
+
);
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
console.log(`Rolled back ${name}.`);
|
|
3620
|
+
console.log(` restoredFrom: ${selectedBackup}`);
|
|
3621
|
+
console.log(` restoredVersion: ${restoredVersion}`);
|
|
3622
|
+
console.log(` installedPath: ${installedPath}`);
|
|
3623
|
+
if (preRollbackBackup) {
|
|
3624
|
+
console.log(` preRollbackBackup: ${preRollbackBackup}`);
|
|
3625
|
+
}
|
|
3626
|
+
});
|
|
3627
|
+
return cmd;
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
// src/commands/sync.ts
|
|
3631
|
+
var import_commander12 = require("commander");
|
|
3632
|
+
var import_ora3 = __toESM(require("ora"));
|
|
3633
|
+
function resolveSyncTargets(name, catalogNames, installedNames, updatesOnly) {
|
|
3634
|
+
if (name) {
|
|
3635
|
+
return { mode: "single", targets: [name] };
|
|
3636
|
+
}
|
|
3637
|
+
if (updatesOnly) {
|
|
3638
|
+
return { mode: "bulk", targets: installedNames.sort((a, b) => a.localeCompare(b)) };
|
|
3639
|
+
}
|
|
3640
|
+
return { mode: "bulk", targets: [...catalogNames].sort((a, b) => a.localeCompare(b)) };
|
|
3641
|
+
}
|
|
3642
|
+
var SKIP_EXIT_BLOCKING_STATUSES = /* @__PURE__ */ new Set([
|
|
3643
|
+
"failed",
|
|
3644
|
+
"skipped_compatibility",
|
|
3645
|
+
"skipped_modified"
|
|
3646
|
+
]);
|
|
3647
|
+
var LicenseGate = class {
|
|
3648
|
+
paidAllowed = null;
|
|
3649
|
+
async ensure(forFreeSkill) {
|
|
3650
|
+
if (forFreeSkill) return true;
|
|
3651
|
+
if (this.paidAllowed !== null) return this.paidAllowed;
|
|
3652
|
+
const key = await retrieveLicenseKey();
|
|
3653
|
+
if (!key) {
|
|
3654
|
+
this.paidAllowed = false;
|
|
3655
|
+
return false;
|
|
3656
|
+
}
|
|
3657
|
+
const offlineResult = await verifyOffline(key);
|
|
3658
|
+
if (!offlineResult.valid) {
|
|
3659
|
+
this.paidAllowed = false;
|
|
3660
|
+
return false;
|
|
3661
|
+
}
|
|
3662
|
+
const cache = loadLicenseCache();
|
|
3663
|
+
if (cache) {
|
|
3664
|
+
const status = checkOfflineStatus(cache);
|
|
3665
|
+
if (status.status === "disabled") {
|
|
3666
|
+
this.paidAllowed = false;
|
|
3667
|
+
return false;
|
|
3668
|
+
}
|
|
3669
|
+
if (status.status === "stale" || status.status === "warning") {
|
|
3670
|
+
try {
|
|
3671
|
+
const fresh = await verifyLicenseOnline(key, getMachineFingerprint());
|
|
3672
|
+
if (!fresh.valid) {
|
|
3673
|
+
this.paidAllowed = false;
|
|
3674
|
+
return false;
|
|
3675
|
+
}
|
|
3676
|
+
const refreshed = {
|
|
3677
|
+
...cache,
|
|
3678
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3679
|
+
expiresAt: fresh.updatesExpireAt ?? null,
|
|
3680
|
+
payload: {
|
|
3681
|
+
buyerId: fresh.buyerId ?? cache.payload.buyerId,
|
|
3682
|
+
email: fresh.email ?? cache.payload.email,
|
|
3683
|
+
maxDevices: fresh.maxDevices ?? cache.payload.maxDevices,
|
|
3684
|
+
status: fresh.status ?? cache.payload.status,
|
|
3685
|
+
hasUpdates: fresh.hasUpdates ?? cache.payload.hasUpdates,
|
|
3686
|
+
deviceCount: fresh.deviceCount ?? cache.payload.deviceCount
|
|
3687
|
+
}
|
|
3688
|
+
};
|
|
3689
|
+
saveLicenseCache(refreshed);
|
|
3690
|
+
} catch {
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
this.paidAllowed = true;
|
|
3694
|
+
return true;
|
|
3695
|
+
}
|
|
3696
|
+
try {
|
|
3697
|
+
const fresh = await verifyLicenseOnline(key, getMachineFingerprint());
|
|
3698
|
+
this.paidAllowed = !!fresh.valid;
|
|
3699
|
+
return this.paidAllowed;
|
|
3700
|
+
} catch {
|
|
3701
|
+
this.paidAllowed = false;
|
|
3702
|
+
return false;
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
};
|
|
3706
|
+
async function syncOneSkill(name, opts) {
|
|
3707
|
+
const spinner = opts.json ? null : (0, import_ora3.default)(`Syncing ${name}...`).start();
|
|
3708
|
+
const fail = (status, reasonCode, message, fromVersion) => {
|
|
3709
|
+
spinner?.fail(`${name}: ${message}`);
|
|
3710
|
+
return { skill: name, status, reasonCode, message, fromVersion: fromVersion ?? null };
|
|
3711
|
+
};
|
|
3712
|
+
try {
|
|
3713
|
+
const config = loadConfig();
|
|
3714
|
+
const installed = config.installedSkills[name];
|
|
3715
|
+
const installedVersion = installed?.version ?? null;
|
|
3716
|
+
if (!installed && opts.updatesOnly) {
|
|
3717
|
+
spinner?.info(`${name}: not installed locally (updates-only mode).`);
|
|
3718
|
+
return {
|
|
3719
|
+
skill: name,
|
|
3720
|
+
status: "skipped_not_installed",
|
|
3721
|
+
reasonCode: "UPDATES_ONLY",
|
|
3722
|
+
message: "Skill is not installed and --updates-only is enabled."
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
const manifest = await fetchSkillManifest(name).catch((err) => {
|
|
3726
|
+
throw new Error(`manifest-fetch:${err instanceof Error ? err.message : String(err)}`);
|
|
3727
|
+
});
|
|
3728
|
+
const compatibility = evaluateSkillCompatibility(manifest, { client: "claude" });
|
|
3729
|
+
if (!compatibility.allowed) {
|
|
3730
|
+
if (!(compatibility.requiresOverride && opts.allowBlocked)) {
|
|
3731
|
+
return fail(
|
|
3732
|
+
"skipped_compatibility",
|
|
3733
|
+
"COMPATIBILITY_BLOCKED",
|
|
3734
|
+
`${compatibility.reasonCode}: ${compatibility.message}`,
|
|
3735
|
+
installedVersion
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
spinner?.warn(
|
|
3739
|
+
`${name}: compatibility override enabled (${compatibility.reasonCode}); continuing.`
|
|
3740
|
+
);
|
|
3741
|
+
}
|
|
3742
|
+
const licensed = await opts.licenseGate.ensure(manifest.isFree);
|
|
3743
|
+
if (!licensed) {
|
|
3744
|
+
return fail(
|
|
3745
|
+
"failed",
|
|
3746
|
+
installed ? "UPDATE_FAILED" : "INSTALL_FAILED",
|
|
3747
|
+
"License validation failed for paid skill operation.",
|
|
3748
|
+
installedVersion
|
|
3749
|
+
);
|
|
3750
|
+
}
|
|
3751
|
+
if (!installed) {
|
|
3752
|
+
const targetVersion = manifest.currentVersion ?? "0.0.0";
|
|
3753
|
+
const archive2 = await downloadSkill(name);
|
|
3754
|
+
const { path: path2 } = await installSkill(name, archive2);
|
|
3755
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
3756
|
+
const after = loadConfig();
|
|
3757
|
+
const nextInstalled2 = {
|
|
3758
|
+
...after.installedSkills,
|
|
3759
|
+
[name]: {
|
|
3760
|
+
name,
|
|
3761
|
+
version: targetVersion,
|
|
3762
|
+
installedAt: now2,
|
|
3763
|
+
updatedAt: now2
|
|
3764
|
+
}
|
|
3765
|
+
};
|
|
3766
|
+
const contentHash2 = hashInstalledSkillPath(path2);
|
|
3767
|
+
const nextManaged2 = buildNextManagedSkillState({
|
|
3768
|
+
name,
|
|
3769
|
+
version: targetVersion,
|
|
3770
|
+
installedPath: path2,
|
|
3771
|
+
previous: after.skillState[name],
|
|
3772
|
+
hash: contentHash2
|
|
3773
|
+
});
|
|
3774
|
+
updateConfig({
|
|
3775
|
+
installedSkills: nextInstalled2,
|
|
3776
|
+
skillState: { [name]: nextManaged2 }
|
|
3777
|
+
});
|
|
3778
|
+
spinner?.succeed(`${name}: installed v${targetVersion}.`);
|
|
3779
|
+
return {
|
|
3780
|
+
skill: name,
|
|
3781
|
+
status: "installed",
|
|
3782
|
+
fromVersion: null,
|
|
3783
|
+
toVersion: targetVersion,
|
|
3784
|
+
installedPath: path2
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
const latest = manifest.currentVersion;
|
|
3788
|
+
if (!latest || latest === installedVersion) {
|
|
3789
|
+
spinner?.succeed(`${name}: already up to date (${installedVersion ?? "-"})`);
|
|
3790
|
+
return {
|
|
3791
|
+
skill: name,
|
|
3792
|
+
status: "up_to_date",
|
|
3793
|
+
fromVersion: installedVersion,
|
|
3794
|
+
toVersion: latest ?? installedVersion
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
const previousManaged = config.skillState[name];
|
|
3798
|
+
const installedPathBefore = resolveInstalledSkillPath(name, previousManaged);
|
|
3799
|
+
const currentHash = hashInstalledSkillPath(installedPathBefore);
|
|
3800
|
+
const modified = isLocalSkillModified(previousManaged?.contentHash ?? null, currentHash);
|
|
3801
|
+
if (modified && !opts.forceModified) {
|
|
3802
|
+
return fail(
|
|
3803
|
+
"skipped_modified",
|
|
3804
|
+
"LOCAL_MODIFIED",
|
|
3805
|
+
"Local modifications detected. Re-run with --force-modified to back up and replace.",
|
|
3806
|
+
installedVersion
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
let backupPath = null;
|
|
3810
|
+
if (currentHash) {
|
|
3811
|
+
backupPath = createSkillBackupSnapshot(name, installedPathBefore, {
|
|
3812
|
+
sourceVersion: installedVersion,
|
|
3813
|
+
sourceHash: currentHash
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
const archive = await downloadSkill(name);
|
|
3817
|
+
const { path } = await installSkill(name, archive);
|
|
3818
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3819
|
+
const refreshed = loadConfig();
|
|
3820
|
+
const nextInstalled = {
|
|
3821
|
+
...refreshed.installedSkills,
|
|
3822
|
+
[name]: {
|
|
3823
|
+
name,
|
|
3824
|
+
version: latest,
|
|
3825
|
+
installedAt: refreshed.installedSkills[name]?.installedAt ?? now,
|
|
3826
|
+
updatedAt: now
|
|
3827
|
+
}
|
|
3828
|
+
};
|
|
3829
|
+
const contentHash = hashInstalledSkillPath(path);
|
|
3830
|
+
const nextManaged = buildNextManagedSkillState({
|
|
3831
|
+
name,
|
|
3832
|
+
version: latest,
|
|
3833
|
+
installedPath: path,
|
|
3834
|
+
previous: refreshed.skillState[name],
|
|
3835
|
+
backupPath,
|
|
3836
|
+
hash: contentHash
|
|
3837
|
+
});
|
|
3838
|
+
updateConfig({
|
|
3839
|
+
installedSkills: nextInstalled,
|
|
3840
|
+
skillState: { [name]: nextManaged }
|
|
3841
|
+
});
|
|
3842
|
+
spinner?.succeed(`${name}: updated ${installedVersion ?? "-"} -> ${latest}.`);
|
|
3843
|
+
return {
|
|
3844
|
+
skill: name,
|
|
3845
|
+
status: "updated",
|
|
3846
|
+
fromVersion: installedVersion,
|
|
3847
|
+
toVersion: latest,
|
|
3848
|
+
backupPath,
|
|
3849
|
+
installedPath: path
|
|
3850
|
+
};
|
|
3851
|
+
} catch (err) {
|
|
3852
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3853
|
+
if (message.startsWith("manifest-fetch:")) {
|
|
3854
|
+
return fail(
|
|
3855
|
+
"failed",
|
|
3856
|
+
"MANIFEST_FETCH_FAILED",
|
|
3857
|
+
message.replace("manifest-fetch:", "")
|
|
3858
|
+
);
|
|
3859
|
+
}
|
|
3860
|
+
return fail(
|
|
3861
|
+
"failed",
|
|
3862
|
+
"INSTALL_FAILED",
|
|
3863
|
+
message
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
function syncCommand() {
|
|
3868
|
+
const cmd = new import_commander12.Command("sync").description(
|
|
3869
|
+
"Synchronize local skills with the registry (install missing + update installed)"
|
|
3870
|
+
).argument("[name]", "Optional single skill name to sync").option(
|
|
3871
|
+
"--updates-only",
|
|
3872
|
+
"Only update already-installed skills (skip installs for missing skills)"
|
|
3873
|
+
).option(
|
|
3874
|
+
"--force-modified",
|
|
3875
|
+
"Allow replacing locally modified skills during update (creates backup before overwrite)"
|
|
3876
|
+
).option(
|
|
3877
|
+
"--allow-blocked",
|
|
3878
|
+
"Allow syncing compatibility-gated skills (explicit preview/testing only)"
|
|
3879
|
+
).option("--json", "Output machine-readable JSON").action(
|
|
3880
|
+
async (name, opts) => {
|
|
3881
|
+
const json = !!opts.json;
|
|
3882
|
+
const config = loadConfig();
|
|
3883
|
+
const installedNames = Object.keys(config.installedSkills);
|
|
3884
|
+
let catalogNames = [];
|
|
3885
|
+
if (!name || !opts.updatesOnly) {
|
|
3886
|
+
try {
|
|
3887
|
+
const catalog = await fetchSkillCatalog();
|
|
3888
|
+
catalogNames = catalog.items.map((item) => item.name);
|
|
3889
|
+
} catch (err) {
|
|
3890
|
+
const error = `Could not fetch catalog: ${err instanceof Error ? err.message : String(err)}`;
|
|
3891
|
+
if (json) {
|
|
3892
|
+
console.log(JSON.stringify({ ok: false, error }));
|
|
3893
|
+
} else {
|
|
3894
|
+
console.error(error);
|
|
3895
|
+
}
|
|
3896
|
+
process.exit(1);
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
const resolved = resolveSyncTargets(
|
|
3900
|
+
name,
|
|
3901
|
+
catalogNames,
|
|
3902
|
+
installedNames,
|
|
3903
|
+
!!opts.updatesOnly
|
|
3904
|
+
);
|
|
3905
|
+
if (resolved.targets.length === 0) {
|
|
3906
|
+
if (json) {
|
|
3907
|
+
console.log(
|
|
3908
|
+
JSON.stringify({
|
|
3909
|
+
ok: true,
|
|
3910
|
+
mode: resolved.mode,
|
|
3911
|
+
totals: {
|
|
3912
|
+
total: 0,
|
|
3913
|
+
installed: 0,
|
|
3914
|
+
updated: 0,
|
|
3915
|
+
up_to_date: 0,
|
|
3916
|
+
skipped_not_installed: 0,
|
|
3917
|
+
skipped_compatibility: 0,
|
|
3918
|
+
skipped_modified: 0,
|
|
3919
|
+
failed: 0
|
|
3920
|
+
},
|
|
3921
|
+
results: []
|
|
3922
|
+
})
|
|
3923
|
+
);
|
|
3924
|
+
} else {
|
|
3925
|
+
console.log(
|
|
3926
|
+
opts.updatesOnly ? "No installed skills found to sync." : "No catalog skills found to sync."
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
return;
|
|
3930
|
+
}
|
|
3931
|
+
const licenseGate = new LicenseGate();
|
|
3932
|
+
const results = [];
|
|
3933
|
+
for (const skillName of resolved.targets) {
|
|
3934
|
+
const result = await syncOneSkill(skillName, {
|
|
3935
|
+
updatesOnly: !!opts.updatesOnly,
|
|
3936
|
+
forceModified: !!opts.forceModified,
|
|
3937
|
+
allowBlocked: !!opts.allowBlocked,
|
|
3938
|
+
json,
|
|
3939
|
+
licenseGate
|
|
3940
|
+
});
|
|
3941
|
+
results.push(result);
|
|
3942
|
+
if (!json) {
|
|
3943
|
+
if (result.status === "failed") {
|
|
3944
|
+
console.log(` - ${skillName}: failed (${result.message ?? "unknown error"})`);
|
|
3945
|
+
} else if (result.status === "skipped_compatibility" || result.status === "skipped_modified" || result.status === "skipped_not_installed") {
|
|
3946
|
+
console.log(` - ${skillName}: ${result.status} (${result.message ?? "n/a"})`);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
const totals = {
|
|
3951
|
+
total: results.length,
|
|
3952
|
+
installed: results.filter((x) => x.status === "installed").length,
|
|
3953
|
+
updated: results.filter((x) => x.status === "updated").length,
|
|
3954
|
+
up_to_date: results.filter((x) => x.status === "up_to_date").length,
|
|
3955
|
+
skipped_not_installed: results.filter((x) => x.status === "skipped_not_installed").length,
|
|
3956
|
+
skipped_compatibility: results.filter((x) => x.status === "skipped_compatibility").length,
|
|
3957
|
+
skipped_modified: results.filter((x) => x.status === "skipped_modified").length,
|
|
3958
|
+
failed: results.filter((x) => x.status === "failed").length
|
|
3959
|
+
};
|
|
3960
|
+
const ok = results.every((x) => !SKIP_EXIT_BLOCKING_STATUSES.has(x.status));
|
|
3961
|
+
if (json) {
|
|
3962
|
+
console.log(
|
|
3963
|
+
JSON.stringify(
|
|
3964
|
+
{
|
|
3965
|
+
ok,
|
|
3966
|
+
mode: resolved.mode,
|
|
3967
|
+
updatesOnly: !!opts.updatesOnly,
|
|
3968
|
+
allowBlocked: !!opts.allowBlocked,
|
|
3969
|
+
forceModified: !!opts.forceModified,
|
|
3970
|
+
totals,
|
|
3971
|
+
results
|
|
3972
|
+
},
|
|
3973
|
+
null,
|
|
3974
|
+
2
|
|
3975
|
+
)
|
|
3976
|
+
);
|
|
3977
|
+
} else {
|
|
3978
|
+
console.log("");
|
|
3979
|
+
console.log("Sync summary");
|
|
3980
|
+
console.log(` - total: ${totals.total}`);
|
|
3981
|
+
console.log(` - installed: ${totals.installed}`);
|
|
3982
|
+
console.log(` - updated: ${totals.updated}`);
|
|
3983
|
+
console.log(` - up_to_date: ${totals.up_to_date}`);
|
|
3984
|
+
console.log(` - skipped_not_installed: ${totals.skipped_not_installed}`);
|
|
3985
|
+
console.log(` - skipped_compatibility: ${totals.skipped_compatibility}`);
|
|
3986
|
+
console.log(` - skipped_modified: ${totals.skipped_modified}`);
|
|
3987
|
+
console.log(` - failed: ${totals.failed}`);
|
|
3988
|
+
}
|
|
3989
|
+
if (!ok) {
|
|
3990
|
+
process.exit(1);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
);
|
|
3994
|
+
return cmd;
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
// src/lib/version.ts
|
|
3998
|
+
var import_fs14 = require("fs");
|
|
3999
|
+
var import_path15 = require("path");
|
|
4000
|
+
var FALLBACK_VERSION = "0.0.0";
|
|
4001
|
+
var cachedVersion = null;
|
|
4002
|
+
function candidatePackageJsonPaths(deps) {
|
|
4003
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
4004
|
+
const scriptPath = deps.scriptPath ?? process.argv[1] ?? null;
|
|
4005
|
+
const env = deps.env ?? process.env;
|
|
4006
|
+
const candidates = [
|
|
4007
|
+
typeof env.PUSHREC_CLI_PACKAGE_JSON === "string" ? env.PUSHREC_CLI_PACKAGE_JSON : null,
|
|
4008
|
+
scriptPath ? (0, import_path15.join)((0, import_path15.dirname)((0, import_path15.resolve)(scriptPath)), "..", "package.json") : null,
|
|
4009
|
+
(0, import_path15.join)(cwd, "package.json"),
|
|
4010
|
+
(0, import_path15.join)(cwd, "cli", "package.json")
|
|
4011
|
+
].filter((path) => typeof path === "string" && path.length > 0);
|
|
4012
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4013
|
+
return candidates.filter((path) => {
|
|
4014
|
+
if (seen.has(path)) return false;
|
|
4015
|
+
seen.add(path);
|
|
4016
|
+
return true;
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
4019
|
+
function parseVersionFromPackageJson(raw) {
|
|
4020
|
+
try {
|
|
4021
|
+
const parsed = JSON.parse(raw);
|
|
4022
|
+
if (typeof parsed.version !== "string") return null;
|
|
4023
|
+
const version = parsed.version.trim();
|
|
4024
|
+
return version.length > 0 ? version : null;
|
|
4025
|
+
} catch {
|
|
4026
|
+
return null;
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
function getCliVersion(deps = {}) {
|
|
4030
|
+
if (cachedVersion && deps.cwd === void 0 && deps.scriptPath === void 0 && deps.env === void 0 && deps.existsSyncFn === void 0 && deps.readFileSyncFn === void 0) {
|
|
4031
|
+
return cachedVersion;
|
|
4032
|
+
}
|
|
4033
|
+
const existsFn = deps.existsSyncFn ?? import_fs14.existsSync;
|
|
4034
|
+
const readFn = deps.readFileSyncFn ?? import_fs14.readFileSync;
|
|
4035
|
+
for (const packageJsonPath of candidatePackageJsonPaths(deps)) {
|
|
4036
|
+
if (!existsFn(packageJsonPath)) continue;
|
|
4037
|
+
try {
|
|
4038
|
+
const raw = readFn(packageJsonPath, "utf-8");
|
|
4039
|
+
const version = parseVersionFromPackageJson(raw);
|
|
4040
|
+
if (!version) continue;
|
|
4041
|
+
if (deps.cwd === void 0 && deps.scriptPath === void 0 && deps.env === void 0 && deps.existsSyncFn === void 0 && deps.readFileSyncFn === void 0) {
|
|
4042
|
+
cachedVersion = version;
|
|
4043
|
+
}
|
|
4044
|
+
return version;
|
|
4045
|
+
} catch {
|
|
4046
|
+
continue;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
return FALLBACK_VERSION;
|
|
4050
|
+
}
|
|
4051
|
+
|
|
1795
4052
|
// src/index.ts
|
|
1796
|
-
var program = new
|
|
4053
|
+
var program = new import_commander13.Command();
|
|
1797
4054
|
program.name("pushrec-skills").description(
|
|
1798
4055
|
"Install, update, and manage premium Claude Code skills from Pushrec"
|
|
1799
|
-
).version(
|
|
4056
|
+
).version(getCliVersion());
|
|
1800
4057
|
program.addCommand(authCommand());
|
|
1801
4058
|
program.addCommand(installCommand());
|
|
1802
4059
|
program.addCommand(updateCommand());
|
|
@@ -1806,6 +4063,9 @@ program.addCommand(infoCommand());
|
|
|
1806
4063
|
program.addCommand(createUninstallCommand());
|
|
1807
4064
|
program.addCommand(healthCommand());
|
|
1808
4065
|
program.addCommand(setupCommand());
|
|
4066
|
+
program.addCommand(dashboardCommand());
|
|
4067
|
+
program.addCommand(rollbackCommand());
|
|
4068
|
+
program.addCommand(syncCommand());
|
|
1809
4069
|
program.parseAsync().catch((err) => {
|
|
1810
4070
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1811
4071
|
process.exit(1);
|