@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/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 import_commander9 = require("commander");
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 import_commander9.Command();
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("0.1.0");
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.1.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": {