@pushrec/skills 0.1.0 → 0.3.2
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 +32 -8
- package/bundled-skills/README.md +9 -0
- package/bundled-skills/pushrec-skills/SKILL.md +360 -0
- package/bundled-skills/pushrec-skills/references/prerequisites.yaml +375 -0
- package/bundled-skills/pushrec-skills/scripts/diagnostic.py +232 -0
- package/dist/index.js +531 -4
- 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_commander11 = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/commands/auth.ts
|
|
30
30
|
var import_commander = require("commander");
|
|
@@ -949,6 +949,16 @@ function uninstallSkill(name) {
|
|
|
949
949
|
function isSkillInstalled(name) {
|
|
950
950
|
return (0, import_fs5.existsSync)((0, import_path5.join)(SKILLS_DIR, name, "SKILL.md"));
|
|
951
951
|
}
|
|
952
|
+
function getBundledSkillPath(name) {
|
|
953
|
+
return (0, import_path5.join)(__dirname, "..", "bundled-skills", name);
|
|
954
|
+
}
|
|
955
|
+
function copyBundledSkill(name) {
|
|
956
|
+
const sourcePath = getBundledSkillPath(name);
|
|
957
|
+
if (!(0, import_fs5.existsSync)(sourcePath)) return;
|
|
958
|
+
const targetPath = (0, import_path5.join)(SKILLS_DIR, name);
|
|
959
|
+
if ((0, import_fs5.existsSync)((0, import_path5.join)(targetPath, "SKILL.md"))) return;
|
|
960
|
+
(0, import_fs5.cpSync)(sourcePath, targetPath, { recursive: true });
|
|
961
|
+
}
|
|
952
962
|
|
|
953
963
|
// src/commands/install.ts
|
|
954
964
|
async function ensureLicense(forFreeSkill) {
|
|
@@ -1470,8 +1480,12 @@ function createUninstallCommand() {
|
|
|
1470
1480
|
|
|
1471
1481
|
// src/commands/health.ts
|
|
1472
1482
|
var import_commander8 = require("commander");
|
|
1483
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
1473
1484
|
function healthCommand() {
|
|
1474
|
-
const cmd = new import_commander8.Command("health").description("Check registry server health").option("--json", "Output as JSON").action(async (opts) => {
|
|
1485
|
+
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) => {
|
|
1486
|
+
if (opts.full) {
|
|
1487
|
+
return runFullDiagnostic(opts.json ?? false);
|
|
1488
|
+
}
|
|
1475
1489
|
let response;
|
|
1476
1490
|
try {
|
|
1477
1491
|
response = await registryFetch("/api/health");
|
|
@@ -1514,12 +1528,523 @@ function healthCommand() {
|
|
|
1514
1528
|
});
|
|
1515
1529
|
return cmd;
|
|
1516
1530
|
}
|
|
1531
|
+
async function runFullDiagnostic(json) {
|
|
1532
|
+
const checks = [];
|
|
1533
|
+
try {
|
|
1534
|
+
const response = await registryFetch("/api/health");
|
|
1535
|
+
if (response.ok) {
|
|
1536
|
+
checks.push({ name: "Registry", status: "pass", detail: `OK (${response.status})` });
|
|
1537
|
+
} else {
|
|
1538
|
+
checks.push({ name: "Registry", status: "fail", detail: `Unhealthy (${response.status})` });
|
|
1539
|
+
}
|
|
1540
|
+
} catch (err) {
|
|
1541
|
+
checks.push({ name: "Registry", status: "fail", detail: `Unreachable: ${err instanceof Error ? err.message : String(err)}` });
|
|
1542
|
+
}
|
|
1543
|
+
const key = await retrieveLicenseKey();
|
|
1544
|
+
if (!key) {
|
|
1545
|
+
checks.push({ name: "License", status: "fail", detail: "No license key found. Run: pushrec-skills setup" });
|
|
1546
|
+
checks.push({ name: "Device", status: "skip", detail: "Skipped (no license)" });
|
|
1547
|
+
checks.push({ name: "Skills", status: "skip", detail: "Skipped (no license)" });
|
|
1548
|
+
return printDiagnostic(checks, json);
|
|
1549
|
+
}
|
|
1550
|
+
const offlineResult = await verifyOffline(key);
|
|
1551
|
+
if (!offlineResult.valid) {
|
|
1552
|
+
checks.push({ name: "License", status: "fail", detail: `Invalid signature: ${offlineResult.error}` });
|
|
1553
|
+
checks.push({ name: "Device", status: "skip", detail: "Skipped (invalid license)" });
|
|
1554
|
+
checks.push({ name: "Skills", status: "skip", detail: "Skipped (invalid license)" });
|
|
1555
|
+
return printDiagnostic(checks, json);
|
|
1556
|
+
}
|
|
1557
|
+
const fingerprint = getMachineFingerprint();
|
|
1558
|
+
try {
|
|
1559
|
+
const onlineResult = await verifyLicenseOnline(key, fingerprint);
|
|
1560
|
+
if (onlineResult.valid) {
|
|
1561
|
+
const status = onlineResult.status ?? "active";
|
|
1562
|
+
const devices = `${onlineResult.deviceCount ?? "?"}/${onlineResult.maxDevices ?? "?"}`;
|
|
1563
|
+
checks.push({ name: "License", status: "pass", detail: `${status} | devices: ${devices}` });
|
|
1564
|
+
checks.push({ name: "Device", status: "pass", detail: `Registered (${devices} slots)` });
|
|
1565
|
+
} else {
|
|
1566
|
+
checks.push({ name: "License", status: "fail", detail: `Server rejected: ${onlineResult.error}` });
|
|
1567
|
+
checks.push({ name: "Device", status: "fail", detail: "Not verified (license rejected)" });
|
|
1568
|
+
}
|
|
1569
|
+
} catch {
|
|
1570
|
+
const cache = loadLicenseCache();
|
|
1571
|
+
if (cache) {
|
|
1572
|
+
const offlineStatus = checkOfflineStatus(cache);
|
|
1573
|
+
if (offlineStatus.status === "disabled") {
|
|
1574
|
+
checks.push({ name: "License", status: "fail", detail: `Offline too long: ${offlineStatus.message}` });
|
|
1575
|
+
checks.push({ name: "Device", status: "fail", detail: "Cannot verify offline" });
|
|
1576
|
+
} else {
|
|
1577
|
+
checks.push({ name: "License", status: "warn", detail: `Cached (${offlineStatus.status}): ${offlineStatus.message ?? "OK"}` });
|
|
1578
|
+
checks.push({ name: "Device", status: "warn", detail: "Using cached verification" });
|
|
1579
|
+
}
|
|
1580
|
+
} else {
|
|
1581
|
+
checks.push({ name: "License", status: "fail", detail: "Cannot reach server and no cached verification" });
|
|
1582
|
+
checks.push({ name: "Device", status: "fail", detail: "Cannot verify" });
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
const config = loadConfig();
|
|
1586
|
+
const skillCount = Object.keys(config.installedSkills).length;
|
|
1587
|
+
if (skillCount > 0) {
|
|
1588
|
+
const names = Object.keys(config.installedSkills).join(", ");
|
|
1589
|
+
checks.push({ name: "Skills", status: "pass", detail: `${skillCount} installed: ${names}` });
|
|
1590
|
+
} else {
|
|
1591
|
+
checks.push({ name: "Skills", status: "warn", detail: "No skills installed. Run: pushrec-skills install --all" });
|
|
1592
|
+
}
|
|
1593
|
+
printDiagnostic(checks, json);
|
|
1594
|
+
}
|
|
1595
|
+
function printDiagnostic(checks, json) {
|
|
1596
|
+
if (json) {
|
|
1597
|
+
const allPass = checks.every((c) => c.status === "pass" || c.status === "skip");
|
|
1598
|
+
console.log(JSON.stringify({ ok: allPass, checks }, null, 2));
|
|
1599
|
+
if (!allPass) process.exit(1);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
console.log("");
|
|
1603
|
+
console.log(import_chalk3.default.bold("Pushrec Skills Health Check"));
|
|
1604
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1605
|
+
console.log("");
|
|
1606
|
+
let hasFailure = false;
|
|
1607
|
+
for (const check of checks) {
|
|
1608
|
+
const icon = check.status === "pass" ? import_chalk3.default.green("PASS") : check.status === "fail" ? import_chalk3.default.red("FAIL") : check.status === "warn" ? import_chalk3.default.yellow("WARN") : import_chalk3.default.dim("SKIP");
|
|
1609
|
+
console.log(` ${icon} ${check.name}: ${check.detail}`);
|
|
1610
|
+
if (check.status === "fail") hasFailure = true;
|
|
1611
|
+
}
|
|
1612
|
+
console.log("");
|
|
1613
|
+
if (hasFailure) {
|
|
1614
|
+
console.log(import_chalk3.default.red("Some checks failed. Fix the issues above and run again."));
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
} else {
|
|
1617
|
+
console.log(import_chalk3.default.green("All systems go!"));
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// src/commands/setup.ts
|
|
1622
|
+
var import_commander9 = require("commander");
|
|
1623
|
+
var import_readline2 = require("readline");
|
|
1624
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
1625
|
+
var import_ora2 = __toESM(require("ora"));
|
|
1626
|
+
function readInput2(question) {
|
|
1627
|
+
const rl = (0, import_readline2.createInterface)({
|
|
1628
|
+
input: process.stdin,
|
|
1629
|
+
output: process.stdout
|
|
1630
|
+
});
|
|
1631
|
+
return new Promise((resolve2, reject) => {
|
|
1632
|
+
rl.on("error", (err) => {
|
|
1633
|
+
rl.close();
|
|
1634
|
+
reject(err);
|
|
1635
|
+
});
|
|
1636
|
+
rl.on("close", () => {
|
|
1637
|
+
resolve2("");
|
|
1638
|
+
});
|
|
1639
|
+
rl.question(question, (answer) => {
|
|
1640
|
+
rl.close();
|
|
1641
|
+
resolve2(answer.trim());
|
|
1642
|
+
});
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
function setupCommand() {
|
|
1646
|
+
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("--json", "Output JSON").action(async (opts) => {
|
|
1647
|
+
const existing = await retrieveLicenseKey();
|
|
1648
|
+
if (existing) {
|
|
1649
|
+
if (opts.json) {
|
|
1650
|
+
console.log(JSON.stringify({ ok: true, message: "Already set up" }));
|
|
1651
|
+
} else {
|
|
1652
|
+
console.log(import_chalk4.default.green("Already set up!") + " Run " + import_chalk4.default.cyan("pushrec-skills auth status") + " to check your license.");
|
|
1653
|
+
console.log("To re-setup, run " + import_chalk4.default.cyan("pushrec-skills auth logout") + " first.");
|
|
1654
|
+
}
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (!opts.json) {
|
|
1658
|
+
console.log("");
|
|
1659
|
+
console.log(import_chalk4.default.bold("Pushrec Skills Setup"));
|
|
1660
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1661
|
+
console.log("");
|
|
1662
|
+
}
|
|
1663
|
+
const key = opts.key ?? await readInput2("Paste your license key: ");
|
|
1664
|
+
if (!key) {
|
|
1665
|
+
if (opts.json) {
|
|
1666
|
+
console.log(JSON.stringify({ ok: false, error: "No license key provided" }));
|
|
1667
|
+
} else {
|
|
1668
|
+
console.error("No license key provided.");
|
|
1669
|
+
}
|
|
1670
|
+
process.exit(1);
|
|
1671
|
+
}
|
|
1672
|
+
const verifySpinner = opts.json ? null : (0, import_ora2.default)("Verifying license signature...").start();
|
|
1673
|
+
const offlineResult = await verifyOffline(key);
|
|
1674
|
+
if (!offlineResult.valid) {
|
|
1675
|
+
verifySpinner?.fail(`Invalid license key: ${offlineResult.error}`);
|
|
1676
|
+
if (opts.json) {
|
|
1677
|
+
console.log(JSON.stringify({ ok: false, error: `Invalid license key: ${offlineResult.error}` }));
|
|
1678
|
+
}
|
|
1679
|
+
process.exit(1);
|
|
1680
|
+
}
|
|
1681
|
+
verifySpinner?.succeed("License signature valid");
|
|
1682
|
+
const onlineSpinner = opts.json ? null : (0, import_ora2.default)("Checking with server...").start();
|
|
1683
|
+
const fingerprint = getMachineFingerprint();
|
|
1684
|
+
let onlineResult;
|
|
1685
|
+
try {
|
|
1686
|
+
onlineResult = await verifyLicenseOnline(key, fingerprint);
|
|
1687
|
+
} catch (err) {
|
|
1688
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1689
|
+
onlineSpinner?.fail(`Could not reach server: ${message}`);
|
|
1690
|
+
if (opts.json) {
|
|
1691
|
+
console.log(JSON.stringify({ ok: false, error: `Could not reach server: ${message}` }));
|
|
1692
|
+
}
|
|
1693
|
+
process.exit(1);
|
|
1694
|
+
}
|
|
1695
|
+
if (!onlineResult.valid) {
|
|
1696
|
+
onlineSpinner?.fail(`License rejected: ${onlineResult.error}`);
|
|
1697
|
+
if (opts.json) {
|
|
1698
|
+
console.log(JSON.stringify({ ok: false, error: `License rejected: ${onlineResult.error}` }));
|
|
1699
|
+
}
|
|
1700
|
+
process.exit(1);
|
|
1701
|
+
}
|
|
1702
|
+
onlineSpinner?.succeed("Server confirmed license active");
|
|
1703
|
+
await storeLicenseKey(key);
|
|
1704
|
+
const deviceSpinner = opts.json ? null : (0, import_ora2.default)("Activating this device...").start();
|
|
1705
|
+
let activation;
|
|
1706
|
+
try {
|
|
1707
|
+
activation = await activateDevice(
|
|
1708
|
+
key,
|
|
1709
|
+
fingerprint,
|
|
1710
|
+
getPlatformName(),
|
|
1711
|
+
getHostname()
|
|
1712
|
+
);
|
|
1713
|
+
deviceSpinner?.succeed(`Device activated (${activation.deviceCount}/${activation.maxDevices} slots used)`);
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1716
|
+
deviceSpinner?.fail(`Device activation failed: ${message}`);
|
|
1717
|
+
if (opts.json) {
|
|
1718
|
+
console.log(JSON.stringify({ ok: false, error: `Device activation failed: ${message}` }));
|
|
1719
|
+
}
|
|
1720
|
+
process.exit(1);
|
|
1721
|
+
}
|
|
1722
|
+
const cache = {
|
|
1723
|
+
verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1724
|
+
expiresAt: onlineResult.updatesExpireAt ?? null,
|
|
1725
|
+
fingerprint,
|
|
1726
|
+
payload: {
|
|
1727
|
+
buyerId: onlineResult.buyerId ?? 0,
|
|
1728
|
+
email: onlineResult.email ?? "",
|
|
1729
|
+
maxDevices: onlineResult.maxDevices ?? 3,
|
|
1730
|
+
status: onlineResult.status ?? "active",
|
|
1731
|
+
hasUpdates: onlineResult.hasUpdates ?? true,
|
|
1732
|
+
deviceCount: onlineResult.deviceCount ?? 1
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
saveLicenseCache(cache);
|
|
1736
|
+
if (!opts.json) {
|
|
1737
|
+
console.log("");
|
|
1738
|
+
console.log(import_chalk4.default.bold("Installing skills..."));
|
|
1739
|
+
}
|
|
1740
|
+
let catalog;
|
|
1741
|
+
try {
|
|
1742
|
+
catalog = await fetchSkillCatalog();
|
|
1743
|
+
} catch (err) {
|
|
1744
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1745
|
+
if (opts.json) {
|
|
1746
|
+
console.log(JSON.stringify({ ok: true, activated: true, installed: 0, error: `Could not fetch catalog: ${message}` }));
|
|
1747
|
+
} else {
|
|
1748
|
+
console.error(`Could not fetch catalog: ${message}`);
|
|
1749
|
+
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."));
|
|
1750
|
+
}
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
const toInstall = catalog.items.filter((s) => !isSkillInstalled(s.name));
|
|
1754
|
+
let installed = 0;
|
|
1755
|
+
let failed = 0;
|
|
1756
|
+
for (let i = 0; i < toInstall.length; i++) {
|
|
1757
|
+
const skill = toInstall[i];
|
|
1758
|
+
const prefix = opts.json ? "" : `[${i + 1}/${toInstall.length}] `;
|
|
1759
|
+
const spinner = opts.json ? null : (0, import_ora2.default)(`${prefix}Installing ${skill.name}...`).start();
|
|
1760
|
+
try {
|
|
1761
|
+
const manifest = await fetchSkillManifest(skill.name);
|
|
1762
|
+
const archive = await downloadSkill(skill.name);
|
|
1763
|
+
const { path } = await installSkill(skill.name, archive);
|
|
1764
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1765
|
+
const config = loadConfig();
|
|
1766
|
+
config.installedSkills[skill.name] = {
|
|
1767
|
+
name: skill.name,
|
|
1768
|
+
version: manifest.currentVersion ?? "0.0.0",
|
|
1769
|
+
installedAt: config.installedSkills[skill.name]?.installedAt ?? now,
|
|
1770
|
+
updatedAt: now
|
|
1771
|
+
};
|
|
1772
|
+
updateConfig({ installedSkills: config.installedSkills });
|
|
1773
|
+
spinner?.succeed(`${prefix}${skill.name} v${manifest.currentVersion ?? "latest"} installed to ${path}`);
|
|
1774
|
+
installed++;
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1777
|
+
spinner?.fail(`${prefix}Failed to install ${skill.name}: ${message}`);
|
|
1778
|
+
failed++;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
try {
|
|
1782
|
+
copyBundledSkill("pushrec-skills");
|
|
1783
|
+
} catch {
|
|
1784
|
+
}
|
|
1785
|
+
const skipped = catalog.items.length - toInstall.length;
|
|
1786
|
+
if (opts.json) {
|
|
1787
|
+
console.log(JSON.stringify({
|
|
1788
|
+
ok: true,
|
|
1789
|
+
activated: true,
|
|
1790
|
+
installed,
|
|
1791
|
+
failed,
|
|
1792
|
+
skipped,
|
|
1793
|
+
total: catalog.items.length
|
|
1794
|
+
}));
|
|
1795
|
+
} else {
|
|
1796
|
+
console.log("");
|
|
1797
|
+
console.log(import_chalk4.default.green(" Setup complete!"));
|
|
1798
|
+
console.log(` ${import_chalk4.default.green("+")} ${installed} skills installed`);
|
|
1799
|
+
if (skipped > 0) console.log(` ${import_chalk4.default.dim("-")} ${skipped} already installed`);
|
|
1800
|
+
if (failed > 0) console.log(` ${import_chalk4.default.red("x")} ${failed} failed`);
|
|
1801
|
+
console.log("");
|
|
1802
|
+
console.log(` ${import_chalk4.default.bold("NEXT STEP:")} Open Claude Code and type ${import_chalk4.default.cyan("/pushrec-skills")}`);
|
|
1803
|
+
console.log("");
|
|
1804
|
+
console.log(" Quick reference:");
|
|
1805
|
+
console.log(` ${import_chalk4.default.cyan("/pushrec-skills")} \u2014 Your guided setup (start here)`);
|
|
1806
|
+
console.log(` ${import_chalk4.default.cyan("/hormozi")} \u2014 Business strategy frameworks`);
|
|
1807
|
+
console.log(` ${import_chalk4.default.cyan("/linkedin-copywriting")} \u2014 LinkedIn content creation`);
|
|
1808
|
+
console.log(` ${import_chalk4.default.cyan("/copywriting")} \u2014 Master copywriting foundations`);
|
|
1809
|
+
console.log("");
|
|
1810
|
+
console.log(` Community: ${import_chalk4.default.underline("skool.com/pushrec-2909")}`);
|
|
1811
|
+
console.log(` Full catalog: ${import_chalk4.default.dim("npx @pushrec/skills list")}`);
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1814
|
+
return cmd;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// src/commands/dashboard.ts
|
|
1818
|
+
var import_commander10 = require("commander");
|
|
1819
|
+
var import_http = require("http");
|
|
1820
|
+
var import_fs6 = require("fs");
|
|
1821
|
+
var import_path6 = require("path");
|
|
1822
|
+
var import_os5 = require("os");
|
|
1823
|
+
var import_child_process4 = require("child_process");
|
|
1824
|
+
var DEFAULT_PORT = 5174;
|
|
1825
|
+
var DATA_SOURCES = {
|
|
1826
|
+
"/api/config": "~/.pushrec/config.json",
|
|
1827
|
+
"/api/profile": "~/.claude/skills/pushrec-skills/state/profile.json",
|
|
1828
|
+
"/api/progress": "~/.claude/skills/pushrec-skills/state/progress.json",
|
|
1829
|
+
"/api/catalog": "~/.claude/skills/pushrec-skills/state/catalog.json",
|
|
1830
|
+
"/api/diagnostic": "~/.claude/skills/pushrec-skills/state/diagnostic.json",
|
|
1831
|
+
"/api/teams": "~/.claude/skills/pushrec-skills/state/teams.json",
|
|
1832
|
+
"/api/tasks": "~/.claude/skills/pushrec-skills/state/tasks.json"
|
|
1833
|
+
};
|
|
1834
|
+
var YAML_SOURCES = {
|
|
1835
|
+
"/api/prerequisites": "~/.claude/skills/pushrec-skills/references/prerequisites.yaml"
|
|
1836
|
+
};
|
|
1837
|
+
function expandPath(filepath) {
|
|
1838
|
+
if (filepath.startsWith("~/")) {
|
|
1839
|
+
return (0, import_path6.join)((0, import_os5.homedir)(), filepath.slice(2));
|
|
1840
|
+
}
|
|
1841
|
+
return filepath;
|
|
1842
|
+
}
|
|
1843
|
+
function serveJson(res, data) {
|
|
1844
|
+
res.setHeader("Content-Type", "application/json");
|
|
1845
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1846
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1847
|
+
res.end(data);
|
|
1848
|
+
}
|
|
1849
|
+
function serveError(res, status, message) {
|
|
1850
|
+
res.statusCode = status;
|
|
1851
|
+
res.setHeader("Content-Type", "application/json");
|
|
1852
|
+
res.end(JSON.stringify({ error: message }));
|
|
1853
|
+
}
|
|
1854
|
+
function parseYaml(raw) {
|
|
1855
|
+
try {
|
|
1856
|
+
return JSON.parse(raw);
|
|
1857
|
+
} catch {
|
|
1858
|
+
return { raw };
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
function handleApiRequest(req, res) {
|
|
1862
|
+
const url = req.url ?? "";
|
|
1863
|
+
if (url === "/api/mtime") {
|
|
1864
|
+
const mtimes = {};
|
|
1865
|
+
for (const [endpoint, filepath] of Object.entries(DATA_SOURCES)) {
|
|
1866
|
+
try {
|
|
1867
|
+
mtimes[endpoint] = (0, import_fs6.statSync)(expandPath(filepath)).mtimeMs;
|
|
1868
|
+
} catch {
|
|
1869
|
+
mtimes[endpoint] = 0;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
for (const [endpoint, filepath] of Object.entries(YAML_SOURCES)) {
|
|
1873
|
+
try {
|
|
1874
|
+
mtimes[endpoint] = (0, import_fs6.statSync)(expandPath(filepath)).mtimeMs;
|
|
1875
|
+
} catch {
|
|
1876
|
+
mtimes[endpoint] = 0;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
serveJson(res, JSON.stringify(mtimes));
|
|
1880
|
+
return true;
|
|
1881
|
+
}
|
|
1882
|
+
if (url in DATA_SOURCES) {
|
|
1883
|
+
const filepath = DATA_SOURCES[url];
|
|
1884
|
+
const resolved = expandPath(filepath);
|
|
1885
|
+
try {
|
|
1886
|
+
const data = (0, import_fs6.readFileSync)(resolved, "utf-8");
|
|
1887
|
+
serveJson(res, data);
|
|
1888
|
+
} catch {
|
|
1889
|
+
serveError(res, 500, `Failed to read ${filepath}`);
|
|
1890
|
+
}
|
|
1891
|
+
return true;
|
|
1892
|
+
}
|
|
1893
|
+
if (url in YAML_SOURCES) {
|
|
1894
|
+
const filepath = YAML_SOURCES[url];
|
|
1895
|
+
const resolved = expandPath(filepath);
|
|
1896
|
+
try {
|
|
1897
|
+
const raw = (0, import_fs6.readFileSync)(resolved, "utf-8");
|
|
1898
|
+
const data = parseYaml(raw);
|
|
1899
|
+
serveJson(res, JSON.stringify(data));
|
|
1900
|
+
} catch {
|
|
1901
|
+
serveError(res, 500, `Failed to read ${filepath}`);
|
|
1902
|
+
}
|
|
1903
|
+
return true;
|
|
1904
|
+
}
|
|
1905
|
+
return false;
|
|
1906
|
+
}
|
|
1907
|
+
function findDistDirectory() {
|
|
1908
|
+
const candidates = [
|
|
1909
|
+
(0, import_path6.join)((0, import_os5.homedir)(), ".claude", "skills", "generative-ui", "dist"),
|
|
1910
|
+
(0, import_path6.join)((0, import_os5.homedir)(), ".cache", "genui-dashboard", "dist")
|
|
1911
|
+
];
|
|
1912
|
+
for (const candidate of candidates) {
|
|
1913
|
+
if ((0, import_fs6.existsSync)((0, import_path6.join)(candidate, "index.html"))) {
|
|
1914
|
+
return candidate;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
function serveStatic(distDir, req, res) {
|
|
1920
|
+
let urlPath = (req.url ?? "/").split("?")[0];
|
|
1921
|
+
if (urlPath === "/") urlPath = "/index.html";
|
|
1922
|
+
const filePath = (0, import_path6.join)(distDir, urlPath);
|
|
1923
|
+
if (!filePath.startsWith(distDir)) {
|
|
1924
|
+
serveError(res, 403, "Forbidden");
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
try {
|
|
1928
|
+
const content = (0, import_fs6.readFileSync)(filePath);
|
|
1929
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
1930
|
+
const mimeTypes = {
|
|
1931
|
+
html: "text/html",
|
|
1932
|
+
js: "application/javascript",
|
|
1933
|
+
css: "text/css",
|
|
1934
|
+
json: "application/json",
|
|
1935
|
+
png: "image/png",
|
|
1936
|
+
svg: "image/svg+xml",
|
|
1937
|
+
ico: "image/x-icon",
|
|
1938
|
+
woff2: "font/woff2",
|
|
1939
|
+
woff: "font/woff"
|
|
1940
|
+
};
|
|
1941
|
+
res.setHeader("Content-Type", mimeTypes[ext] ?? "application/octet-stream");
|
|
1942
|
+
res.end(content);
|
|
1943
|
+
} catch {
|
|
1944
|
+
try {
|
|
1945
|
+
const indexContent = (0, import_fs6.readFileSync)((0, import_path6.join)(distDir, "index.html"));
|
|
1946
|
+
res.setHeader("Content-Type", "text/html");
|
|
1947
|
+
res.end(indexContent);
|
|
1948
|
+
} catch {
|
|
1949
|
+
serveError(res, 404, "Not found");
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function openBrowser(url) {
|
|
1954
|
+
const platform4 = process.platform;
|
|
1955
|
+
const command = platform4 === "darwin" ? `open "${url}"` : platform4 === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
1956
|
+
(0, import_child_process4.exec)(command, () => {
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
function dashboardCommand() {
|
|
1960
|
+
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(
|
|
1961
|
+
async (opts) => {
|
|
1962
|
+
const port = parseInt(opts.port, 10);
|
|
1963
|
+
if (isNaN(port) || port < 1024 || port > 65535) {
|
|
1964
|
+
console.error(
|
|
1965
|
+
`ERROR: Port must be between 1024 and 65535, got "${opts.port}".`
|
|
1966
|
+
);
|
|
1967
|
+
process.exit(1);
|
|
1968
|
+
}
|
|
1969
|
+
if (opts.dev) {
|
|
1970
|
+
console.log("Dev mode is not yet supported via CLI.");
|
|
1971
|
+
console.log(
|
|
1972
|
+
"Use the Vite dev server directly: cd dashboard && npm run dev"
|
|
1973
|
+
);
|
|
1974
|
+
process.exit(0);
|
|
1975
|
+
}
|
|
1976
|
+
const distDir = findDistDirectory();
|
|
1977
|
+
if (!distDir) {
|
|
1978
|
+
console.error("ERROR: No built dashboard found.");
|
|
1979
|
+
console.error(
|
|
1980
|
+
" Build the dashboard first, or use --dev mode."
|
|
1981
|
+
);
|
|
1982
|
+
console.error(
|
|
1983
|
+
" Expected: ~/.claude/skills/generative-ui/dist/index.html"
|
|
1984
|
+
);
|
|
1985
|
+
process.exit(1);
|
|
1986
|
+
}
|
|
1987
|
+
const server = (0, import_http.createServer)((req, res) => {
|
|
1988
|
+
if (req.url?.startsWith("/api/")) {
|
|
1989
|
+
const handled = handleApiRequest(req, res);
|
|
1990
|
+
if (handled) return;
|
|
1991
|
+
}
|
|
1992
|
+
serveStatic(distDir, req, res);
|
|
1993
|
+
});
|
|
1994
|
+
server.listen(port, () => {
|
|
1995
|
+
const url = `http://localhost:${port}`;
|
|
1996
|
+
console.log(`Dashboard running at ${url}`);
|
|
1997
|
+
console.log(` Serving: ${distDir}`);
|
|
1998
|
+
console.log(` Press Ctrl+C to stop`);
|
|
1999
|
+
console.log();
|
|
2000
|
+
if (opts.open !== false) {
|
|
2001
|
+
openBrowser(url);
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
server.on("error", (err) => {
|
|
2005
|
+
if (err.code === "EADDRINUSE") {
|
|
2006
|
+
console.error(`ERROR: Port ${port} is already in use.`);
|
|
2007
|
+
console.error(` Run: lsof -i :${port} -t | xargs kill`);
|
|
2008
|
+
} else {
|
|
2009
|
+
console.error(`ERROR: ${err.message}`);
|
|
2010
|
+
}
|
|
2011
|
+
process.exit(1);
|
|
2012
|
+
});
|
|
2013
|
+
const shutdown = () => {
|
|
2014
|
+
console.log("\nShutting down dashboard...");
|
|
2015
|
+
server.close(() => {
|
|
2016
|
+
process.exit(0);
|
|
2017
|
+
});
|
|
2018
|
+
setTimeout(() => process.exit(0), 3e3);
|
|
2019
|
+
};
|
|
2020
|
+
process.on("SIGINT", shutdown);
|
|
2021
|
+
process.on("SIGTERM", shutdown);
|
|
2022
|
+
}
|
|
2023
|
+
);
|
|
2024
|
+
return cmd;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/version.ts
|
|
2028
|
+
var import_node_fs = require("fs");
|
|
2029
|
+
var import_node_path = require("path");
|
|
2030
|
+
function getCliVersion() {
|
|
2031
|
+
try {
|
|
2032
|
+
const packageJsonPath = (0, import_node_path.join)(__dirname, "..", "package.json");
|
|
2033
|
+
const raw = (0, import_node_fs.readFileSync)(packageJsonPath, "utf8");
|
|
2034
|
+
const parsed = JSON.parse(raw);
|
|
2035
|
+
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
2036
|
+
return parsed.version;
|
|
2037
|
+
}
|
|
2038
|
+
} catch {
|
|
2039
|
+
}
|
|
2040
|
+
return "0.0.0";
|
|
2041
|
+
}
|
|
1517
2042
|
|
|
1518
2043
|
// src/index.ts
|
|
1519
|
-
var program = new
|
|
2044
|
+
var program = new import_commander11.Command();
|
|
1520
2045
|
program.name("pushrec-skills").description(
|
|
1521
2046
|
"Install, update, and manage premium Claude Code skills from Pushrec"
|
|
1522
|
-
).version(
|
|
2047
|
+
).version(getCliVersion());
|
|
1523
2048
|
program.addCommand(authCommand());
|
|
1524
2049
|
program.addCommand(installCommand());
|
|
1525
2050
|
program.addCommand(updateCommand());
|
|
@@ -1528,6 +2053,8 @@ program.addCommand(searchCommand());
|
|
|
1528
2053
|
program.addCommand(infoCommand());
|
|
1529
2054
|
program.addCommand(createUninstallCommand());
|
|
1530
2055
|
program.addCommand(healthCommand());
|
|
2056
|
+
program.addCommand(setupCommand());
|
|
2057
|
+
program.addCommand(dashboardCommand());
|
|
1531
2058
|
program.parseAsync().catch((err) => {
|
|
1532
2059
|
console.error(err instanceof Error ? err.message : String(err));
|
|
1533
2060
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushrec/skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Install, update, and manage premium Claude Code skills from Pushrec",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
|
+
"bundled-skills",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
16
17
|
"scripts": {
|