@ulrichc1/sparn 1.0.0 → 1.1.0

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/cli/index.js CHANGED
@@ -12,9 +12,13 @@ var __export = (target, all) => {
12
12
  // node_modules/tsup/assets/esm_shims.js
13
13
  import path from "path";
14
14
  import { fileURLToPath } from "url";
15
+ var getFilename, getDirname, __dirname;
15
16
  var init_esm_shims = __esm({
16
17
  "node_modules/tsup/assets/esm_shims.js"() {
17
18
  "use strict";
19
+ getFilename = () => fileURLToPath(import.meta.url);
20
+ getDirname = () => path.dirname(getFilename());
21
+ __dirname = /* @__PURE__ */ getDirname();
18
22
  }
19
23
  });
20
24
 
@@ -349,7 +353,17 @@ var init_config = __esm({
349
353
  sounds: false,
350
354
  verbose: false
351
355
  },
352
- autoConsolidate: null
356
+ autoConsolidate: null,
357
+ realtime: {
358
+ tokenBudget: 5e4,
359
+ autoOptimizeThreshold: 8e4,
360
+ watchPatterns: ["**/*.jsonl"],
361
+ pidFile: ".sparn/daemon.pid",
362
+ logFile: ".sparn/daemon.log",
363
+ debounceMs: 5e3,
364
+ incremental: true,
365
+ windowSize: 500
366
+ }
353
367
  };
354
368
  }
355
369
  });
@@ -372,7 +386,7 @@ function getVersion() {
372
386
  } catch {
373
387
  const __filename2 = fileURLToPath2(import.meta.url);
374
388
  const __dirname2 = dirname(__filename2);
375
- const pkg = JSON.parse(readFileSync(join(__dirname2, "../../../package.json"), "utf-8"));
389
+ const pkg = JSON.parse(readFileSync(join(__dirname2, "../../package.json"), "utf-8"));
376
390
  return pkg.version;
377
391
  }
378
392
  }
@@ -1456,8 +1470,494 @@ var init_config2 = __esm({
1456
1470
  validate: (v) => v === null || typeof v === "number" && v > 0,
1457
1471
  errorMessage: "autoConsolidate must be a positive number (hours) or null",
1458
1472
  parse: (v) => v === "null" ? null : Number.parseFloat(v)
1473
+ },
1474
+ "realtime.tokenBudget": {
1475
+ path: ["realtime", "tokenBudget"],
1476
+ validate: (v) => typeof v === "number" && v > 0,
1477
+ errorMessage: "tokenBudget must be a positive number",
1478
+ parse: (v) => Number.parseInt(v, 10)
1479
+ },
1480
+ "realtime.autoOptimizeThreshold": {
1481
+ path: ["realtime", "autoOptimizeThreshold"],
1482
+ validate: (v) => typeof v === "number" && v > 0,
1483
+ errorMessage: "autoOptimizeThreshold must be a positive number",
1484
+ parse: (v) => Number.parseInt(v, 10)
1485
+ },
1486
+ "realtime.watchPatterns": {
1487
+ path: ["realtime", "watchPatterns"],
1488
+ validate: (v) => Array.isArray(v) && v.every((p) => typeof p === "string"),
1489
+ errorMessage: "watchPatterns must be an array of strings",
1490
+ parse: (v) => v.split(",").map((p) => p.trim())
1491
+ },
1492
+ "realtime.pidFile": {
1493
+ path: ["realtime", "pidFile"],
1494
+ validate: (v) => typeof v === "string" && v.length > 0,
1495
+ errorMessage: "pidFile must be a non-empty string",
1496
+ parse: (v) => v
1497
+ },
1498
+ "realtime.logFile": {
1499
+ path: ["realtime", "logFile"],
1500
+ validate: (v) => typeof v === "string" && v.length > 0,
1501
+ errorMessage: "logFile must be a non-empty string",
1502
+ parse: (v) => v
1503
+ },
1504
+ "realtime.debounceMs": {
1505
+ path: ["realtime", "debounceMs"],
1506
+ validate: (v) => typeof v === "number" && v >= 0,
1507
+ errorMessage: "debounceMs must be a non-negative number",
1508
+ parse: (v) => Number.parseInt(v, 10)
1509
+ },
1510
+ "realtime.incremental": {
1511
+ path: ["realtime", "incremental"],
1512
+ validate: (v) => typeof v === "boolean",
1513
+ errorMessage: "incremental must be true or false",
1514
+ parse: (v) => v === "true"
1515
+ },
1516
+ "realtime.windowSize": {
1517
+ path: ["realtime", "windowSize"],
1518
+ validate: (v) => typeof v === "number" && v > 0,
1519
+ errorMessage: "windowSize must be a positive number",
1520
+ parse: (v) => Number.parseInt(v, 10)
1521
+ }
1522
+ };
1523
+ }
1524
+ });
1525
+
1526
+ // src/core/metrics.ts
1527
+ function createMetricsCollector() {
1528
+ const optimizations = [];
1529
+ let daemonMetrics = {
1530
+ startTime: Date.now(),
1531
+ sessionsWatched: 0,
1532
+ totalOptimizations: 0,
1533
+ totalTokensSaved: 0,
1534
+ averageLatency: 0,
1535
+ memoryUsage: 0
1536
+ };
1537
+ let cacheHits = 0;
1538
+ let cacheMisses = 0;
1539
+ function recordOptimization(metric) {
1540
+ optimizations.push(metric);
1541
+ daemonMetrics.totalOptimizations++;
1542
+ daemonMetrics.totalTokensSaved += metric.tokensBefore - metric.tokensAfter;
1543
+ if (metric.cacheHitRate > 0) {
1544
+ const hits = Math.round(metric.entriesProcessed * metric.cacheHitRate);
1545
+ cacheHits += hits;
1546
+ cacheMisses += metric.entriesProcessed - hits;
1547
+ }
1548
+ daemonMetrics.averageLatency = (daemonMetrics.averageLatency * (daemonMetrics.totalOptimizations - 1) + metric.duration) / daemonMetrics.totalOptimizations;
1549
+ if (optimizations.length > 1e3) {
1550
+ optimizations.shift();
1551
+ }
1552
+ }
1553
+ function updateDaemon(metric) {
1554
+ daemonMetrics = {
1555
+ ...daemonMetrics,
1556
+ ...metric
1557
+ };
1558
+ }
1559
+ function calculatePercentile(values, percentile) {
1560
+ if (values.length === 0) return 0;
1561
+ const sorted = [...values].sort((a, b) => a - b);
1562
+ const index = Math.ceil(percentile / 100 * sorted.length) - 1;
1563
+ return sorted[index] || 0;
1564
+ }
1565
+ function getSnapshot() {
1566
+ const totalRuns = optimizations.length;
1567
+ const totalDuration = optimizations.reduce((sum, m) => sum + m.duration, 0);
1568
+ const totalTokensSaved = optimizations.reduce(
1569
+ (sum, m) => sum + (m.tokensBefore - m.tokensAfter),
1570
+ 0
1571
+ );
1572
+ const totalTokensBefore = optimizations.reduce((sum, m) => sum + m.tokensBefore, 0);
1573
+ const averageReduction = totalTokensBefore > 0 ? totalTokensSaved / totalTokensBefore : 0;
1574
+ const durations = optimizations.map((m) => m.duration);
1575
+ const totalCacheQueries = cacheHits + cacheMisses;
1576
+ const hitRate = totalCacheQueries > 0 ? cacheHits / totalCacheQueries : 0;
1577
+ return {
1578
+ timestamp: Date.now(),
1579
+ optimization: {
1580
+ totalRuns,
1581
+ totalDuration,
1582
+ totalTokensSaved,
1583
+ averageReduction,
1584
+ p50Latency: calculatePercentile(durations, 50),
1585
+ p95Latency: calculatePercentile(durations, 95),
1586
+ p99Latency: calculatePercentile(durations, 99)
1587
+ },
1588
+ cache: {
1589
+ hitRate,
1590
+ totalHits: cacheHits,
1591
+ totalMisses: cacheMisses,
1592
+ size: optimizations.reduce((sum, m) => sum + m.entriesKept, 0)
1593
+ },
1594
+ daemon: {
1595
+ uptime: Date.now() - daemonMetrics.startTime,
1596
+ sessionsWatched: daemonMetrics.sessionsWatched,
1597
+ memoryUsage: daemonMetrics.memoryUsage
1598
+ }
1599
+ };
1600
+ }
1601
+ function exportMetrics() {
1602
+ return JSON.stringify(getSnapshot(), null, 2);
1603
+ }
1604
+ function reset() {
1605
+ optimizations.length = 0;
1606
+ cacheHits = 0;
1607
+ cacheMisses = 0;
1608
+ daemonMetrics = {
1609
+ startTime: Date.now(),
1610
+ sessionsWatched: 0,
1611
+ totalOptimizations: 0,
1612
+ totalTokensSaved: 0,
1613
+ averageLatency: 0,
1614
+ memoryUsage: 0
1615
+ };
1616
+ }
1617
+ return {
1618
+ recordOptimization,
1619
+ updateDaemon,
1620
+ getSnapshot,
1621
+ export: exportMetrics,
1622
+ reset
1623
+ };
1624
+ }
1625
+ function getMetrics() {
1626
+ if (!globalMetrics) {
1627
+ globalMetrics = createMetricsCollector();
1628
+ }
1629
+ return globalMetrics;
1630
+ }
1631
+ var globalMetrics;
1632
+ var init_metrics = __esm({
1633
+ "src/core/metrics.ts"() {
1634
+ "use strict";
1635
+ init_esm_shims();
1636
+ globalMetrics = null;
1637
+ }
1638
+ });
1639
+
1640
+ // src/daemon/daemon-process.ts
1641
+ var daemon_process_exports = {};
1642
+ __export(daemon_process_exports, {
1643
+ createDaemonCommand: () => createDaemonCommand
1644
+ });
1645
+ import { fork } from "child_process";
1646
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1647
+ import { dirname as dirname2, join as join2 } from "path";
1648
+ function createDaemonCommand() {
1649
+ function isDaemonRunning(pidFile) {
1650
+ if (!existsSync2(pidFile)) {
1651
+ return { running: false };
1652
+ }
1653
+ try {
1654
+ const pidStr = readFileSync3(pidFile, "utf-8").trim();
1655
+ const pid = Number.parseInt(pidStr, 10);
1656
+ if (Number.isNaN(pid)) {
1657
+ return { running: false };
1658
+ }
1659
+ try {
1660
+ process.kill(pid, 0);
1661
+ return { running: true, pid };
1662
+ } catch {
1663
+ unlinkSync(pidFile);
1664
+ return { running: false };
1665
+ }
1666
+ } catch {
1667
+ return { running: false };
1668
+ }
1669
+ }
1670
+ function writePidFile(pidFile, pid) {
1671
+ const dir = dirname2(pidFile);
1672
+ if (!existsSync2(dir)) {
1673
+ mkdirSync(dir, { recursive: true });
1674
+ }
1675
+ writeFileSync2(pidFile, String(pid), "utf-8");
1676
+ }
1677
+ function removePidFile(pidFile) {
1678
+ if (existsSync2(pidFile)) {
1679
+ unlinkSync(pidFile);
1680
+ }
1681
+ }
1682
+ async function start(config) {
1683
+ const { pidFile, logFile } = config.realtime;
1684
+ const status2 = isDaemonRunning(pidFile);
1685
+ if (status2.running) {
1686
+ return {
1687
+ success: false,
1688
+ pid: status2.pid,
1689
+ message: `Daemon already running (PID ${status2.pid})`,
1690
+ error: "Already running"
1691
+ };
1692
+ }
1693
+ try {
1694
+ const daemonPath = join2(__dirname, "index.js");
1695
+ const child = fork(daemonPath, [], {
1696
+ detached: true,
1697
+ stdio: "ignore",
1698
+ env: {
1699
+ ...process.env,
1700
+ SPARN_CONFIG: JSON.stringify(config),
1701
+ SPARN_PID_FILE: pidFile,
1702
+ SPARN_LOG_FILE: logFile
1703
+ }
1704
+ });
1705
+ child.unref();
1706
+ if (child.pid) {
1707
+ writePidFile(pidFile, child.pid);
1708
+ return {
1709
+ success: true,
1710
+ pid: child.pid,
1711
+ message: `Daemon started (PID ${child.pid})`
1712
+ };
1713
+ }
1714
+ return {
1715
+ success: false,
1716
+ message: "Failed to start daemon (no PID)",
1717
+ error: "No PID"
1718
+ };
1719
+ } catch (error) {
1720
+ return {
1721
+ success: false,
1722
+ message: "Failed to start daemon",
1723
+ error: error instanceof Error ? error.message : String(error)
1724
+ };
1725
+ }
1726
+ }
1727
+ async function stop(config) {
1728
+ const { pidFile } = config.realtime;
1729
+ const status2 = isDaemonRunning(pidFile);
1730
+ if (!status2.running || !status2.pid) {
1731
+ return {
1732
+ success: true,
1733
+ message: "Daemon not running"
1734
+ };
1735
+ }
1736
+ try {
1737
+ process.kill(status2.pid, "SIGTERM");
1738
+ const maxWait = 5e3;
1739
+ const interval = 100;
1740
+ let waited = 0;
1741
+ while (waited < maxWait) {
1742
+ try {
1743
+ process.kill(status2.pid, 0);
1744
+ await new Promise((resolve2) => setTimeout(resolve2, interval));
1745
+ waited += interval;
1746
+ } catch {
1747
+ removePidFile(pidFile);
1748
+ return {
1749
+ success: true,
1750
+ message: `Daemon stopped (PID ${status2.pid})`
1751
+ };
1752
+ }
1753
+ }
1754
+ try {
1755
+ process.kill(status2.pid, "SIGKILL");
1756
+ removePidFile(pidFile);
1757
+ return {
1758
+ success: true,
1759
+ message: `Daemon force killed (PID ${status2.pid})`
1760
+ };
1761
+ } catch {
1762
+ removePidFile(pidFile);
1763
+ return {
1764
+ success: true,
1765
+ message: `Daemon stopped (PID ${status2.pid})`
1766
+ };
1767
+ }
1768
+ } catch (error) {
1769
+ return {
1770
+ success: false,
1771
+ message: "Failed to stop daemon",
1772
+ error: error instanceof Error ? error.message : String(error)
1773
+ };
1774
+ }
1775
+ }
1776
+ async function status(config) {
1777
+ const { pidFile } = config.realtime;
1778
+ const daemonStatus = isDaemonRunning(pidFile);
1779
+ if (!daemonStatus.running || !daemonStatus.pid) {
1780
+ return {
1781
+ running: false,
1782
+ message: "Daemon not running"
1783
+ };
1784
+ }
1785
+ const metrics = getMetrics().getSnapshot();
1786
+ return {
1787
+ running: true,
1788
+ pid: daemonStatus.pid,
1789
+ uptime: metrics.daemon.uptime,
1790
+ sessionsWatched: metrics.daemon.sessionsWatched,
1791
+ tokensSaved: metrics.optimization.totalTokensSaved,
1792
+ message: `Daemon running (PID ${daemonStatus.pid})`
1793
+ };
1794
+ }
1795
+ return {
1796
+ start,
1797
+ stop,
1798
+ status
1799
+ };
1800
+ }
1801
+ var init_daemon_process = __esm({
1802
+ "src/daemon/daemon-process.ts"() {
1803
+ "use strict";
1804
+ init_esm_shims();
1805
+ init_metrics();
1806
+ }
1807
+ });
1808
+
1809
+ // src/cli/commands/hooks.ts
1810
+ var hooks_exports = {};
1811
+ __export(hooks_exports, {
1812
+ hooksCommand: () => hooksCommand
1813
+ });
1814
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1815
+ import { homedir } from "os";
1816
+ import { dirname as dirname3, join as join3 } from "path";
1817
+ async function hooksCommand(options) {
1818
+ const { subcommand, global } = options;
1819
+ const settingsPath = global ? join3(homedir(), ".claude", "settings.json") : join3(process.cwd(), ".claude", "settings.json");
1820
+ const hooksDir = join3(dirname3(dirname3(dirname3(__dirname))), "dist", "hooks");
1821
+ const prePromptPath = join3(hooksDir, "pre-prompt.js");
1822
+ const postToolResultPath = join3(hooksDir, "post-tool-result.js");
1823
+ switch (subcommand) {
1824
+ case "install":
1825
+ return await installHooks(settingsPath, prePromptPath, postToolResultPath, global);
1826
+ case "uninstall":
1827
+ return await uninstallHooks(settingsPath, global);
1828
+ case "status":
1829
+ return await hooksStatus(settingsPath, global);
1830
+ default:
1831
+ return {
1832
+ success: false,
1833
+ message: `Unknown subcommand: ${subcommand}`,
1834
+ error: "Invalid subcommand"
1835
+ };
1836
+ }
1837
+ }
1838
+ async function installHooks(settingsPath, prePromptPath, postToolResultPath, global) {
1839
+ try {
1840
+ if (!existsSync3(prePromptPath)) {
1841
+ return {
1842
+ success: false,
1843
+ message: `Hook script not found: ${prePromptPath}`,
1844
+ error: "Hook scripts not built. Run `npm run build` first."
1845
+ };
1846
+ }
1847
+ if (!existsSync3(postToolResultPath)) {
1848
+ return {
1849
+ success: false,
1850
+ message: `Hook script not found: ${postToolResultPath}`,
1851
+ error: "Hook scripts not built. Run `npm run build` first."
1852
+ };
1853
+ }
1854
+ let settings = {};
1855
+ if (existsSync3(settingsPath)) {
1856
+ const settingsJson = readFileSync4(settingsPath, "utf-8");
1857
+ settings = JSON.parse(settingsJson);
1858
+ } else {
1859
+ const claudeDir = dirname3(settingsPath);
1860
+ if (!existsSync3(claudeDir)) {
1861
+ mkdirSync2(claudeDir, { recursive: true });
1862
+ }
1863
+ }
1864
+ settings["hooks"] = {
1865
+ ...typeof settings["hooks"] === "object" && settings["hooks"] !== null ? settings["hooks"] : {},
1866
+ "pre-prompt": `node ${prePromptPath}`,
1867
+ "post-tool-result": `node ${postToolResultPath}`
1868
+ };
1869
+ writeFileSync3(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1870
+ return {
1871
+ success: true,
1872
+ message: global ? "Hooks installed globally (all projects)" : "Hooks installed for current project",
1873
+ installed: true,
1874
+ hookPaths: {
1875
+ prePrompt: prePromptPath,
1876
+ postToolResult: postToolResultPath
1459
1877
  }
1460
1878
  };
1879
+ } catch (error) {
1880
+ return {
1881
+ success: false,
1882
+ message: "Failed to install hooks",
1883
+ error: error instanceof Error ? error.message : String(error)
1884
+ };
1885
+ }
1886
+ }
1887
+ async function uninstallHooks(settingsPath, global) {
1888
+ try {
1889
+ if (!existsSync3(settingsPath)) {
1890
+ return {
1891
+ success: true,
1892
+ message: "No hooks installed (settings.json not found)",
1893
+ installed: false
1894
+ };
1895
+ }
1896
+ const settingsJson = readFileSync4(settingsPath, "utf-8");
1897
+ const settings = JSON.parse(settingsJson);
1898
+ if (settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null) {
1899
+ const hooks = settings["hooks"];
1900
+ delete hooks["pre-prompt"];
1901
+ delete hooks["post-tool-result"];
1902
+ if (Object.keys(hooks).length === 0) {
1903
+ delete settings["hooks"];
1904
+ }
1905
+ }
1906
+ writeFileSync3(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1907
+ return {
1908
+ success: true,
1909
+ message: global ? "Hooks uninstalled globally" : "Hooks uninstalled from current project",
1910
+ installed: false
1911
+ };
1912
+ } catch (error) {
1913
+ return {
1914
+ success: false,
1915
+ message: "Failed to uninstall hooks",
1916
+ error: error instanceof Error ? error.message : String(error)
1917
+ };
1918
+ }
1919
+ }
1920
+ async function hooksStatus(settingsPath, global) {
1921
+ try {
1922
+ if (!existsSync3(settingsPath)) {
1923
+ return {
1924
+ success: true,
1925
+ message: global ? "No global hooks installed (settings.json not found)" : "No project hooks installed (settings.json not found)",
1926
+ installed: false
1927
+ };
1928
+ }
1929
+ const settingsJson = readFileSync4(settingsPath, "utf-8");
1930
+ const settings = JSON.parse(settingsJson);
1931
+ const hasHooks = settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null && "pre-prompt" in settings["hooks"] && "post-tool-result" in settings["hooks"];
1932
+ if (!hasHooks) {
1933
+ return {
1934
+ success: true,
1935
+ message: global ? "No global hooks installed" : "No project hooks installed",
1936
+ installed: false
1937
+ };
1938
+ }
1939
+ const hooks = settings["hooks"];
1940
+ return {
1941
+ success: true,
1942
+ message: global ? "Global hooks active" : "Project hooks active",
1943
+ installed: true,
1944
+ hookPaths: {
1945
+ prePrompt: hooks["pre-prompt"] || "",
1946
+ postToolResult: hooks["post-tool-result"] || ""
1947
+ }
1948
+ };
1949
+ } catch (error) {
1950
+ return {
1951
+ success: false,
1952
+ message: "Failed to check hooks status",
1953
+ error: error instanceof Error ? error.message : String(error)
1954
+ };
1955
+ }
1956
+ }
1957
+ var init_hooks = __esm({
1958
+ "src/cli/commands/hooks.ts"() {
1959
+ "use strict";
1960
+ init_esm_shims();
1461
1961
  }
1462
1962
  });
1463
1963
 
@@ -1465,18 +1965,18 @@ var init_config2 = __esm({
1465
1965
  init_esm_shims();
1466
1966
  init_banner();
1467
1967
  import { spawn as spawn2 } from "child_process";
1468
- import { readFileSync as readFileSync3 } from "fs";
1469
- import { dirname as dirname2, join as join2, resolve } from "path";
1968
+ import { readFileSync as readFileSync5 } from "fs";
1969
+ import { dirname as dirname4, join as join4, resolve } from "path";
1470
1970
  import { fileURLToPath as fileURLToPath3 } from "url";
1471
1971
  import { Command } from "commander";
1472
1972
  function getVersion2() {
1473
1973
  try {
1474
- const pkg = JSON.parse(readFileSync3(join2(process.cwd(), "package.json"), "utf-8"));
1974
+ const pkg = JSON.parse(readFileSync5(join4(process.cwd(), "package.json"), "utf-8"));
1475
1975
  return pkg.version;
1476
1976
  } catch {
1477
1977
  const __filename2 = fileURLToPath3(import.meta.url);
1478
- const __dirname2 = dirname2(__filename2);
1479
- const pkg = JSON.parse(readFileSync3(join2(__dirname2, "../../package.json"), "utf-8"));
1978
+ const __dirname2 = dirname4(__filename2);
1979
+ const pkg = JSON.parse(readFileSync5(join4(__dirname2, "../../package.json"), "utf-8"));
1480
1980
  return pkg.version;
1481
1981
  }
1482
1982
  }
@@ -1860,6 +2360,139 @@ The config file is located at .sparn/config.yaml
1860
2360
  process.exit(1);
1861
2361
  }
1862
2362
  });
2363
+ program.command("daemon <subcommand>").description("Manage real-time optimization daemon").addHelpText(
2364
+ "after",
2365
+ `
2366
+ Subcommands:
2367
+ start # Start daemon
2368
+ stop # Stop daemon
2369
+ status # Check daemon status
2370
+
2371
+ Examples:
2372
+ $ sparn daemon start # Start watching Claude Code sessions
2373
+ $ sparn daemon stop # Stop daemon
2374
+ $ sparn daemon status # Check if daemon is running
2375
+
2376
+ The daemon watches ~/.claude/projects/**/*.jsonl and automatically
2377
+ optimizes contexts when they exceed the configured threshold.
2378
+ `
2379
+ ).action(async (subcommand) => {
2380
+ const { load: parseYAML2 } = await import("js-yaml");
2381
+ const { createDaemonCommand: createDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports));
2382
+ const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
2383
+ try {
2384
+ const configPath = resolve(process.cwd(), ".sparn/config.yaml");
2385
+ const configYAML = readFileSync5(configPath, "utf-8");
2386
+ const config = parseYAML2(configYAML);
2387
+ const daemon = createDaemonCommand2();
2388
+ switch (subcommand) {
2389
+ case "start": {
2390
+ const result = await daemon.start(config);
2391
+ if (result.success) {
2392
+ console.log(neuralCyan2(`
2393
+ \u2713 ${result.message}
2394
+ `));
2395
+ } else {
2396
+ console.error(errorRed2(`
2397
+ \u2717 ${result.message}
2398
+ `));
2399
+ process.exit(1);
2400
+ }
2401
+ break;
2402
+ }
2403
+ case "stop": {
2404
+ const result = await daemon.stop(config);
2405
+ if (result.success) {
2406
+ console.log(neuralCyan2(`
2407
+ \u2713 ${result.message}
2408
+ `));
2409
+ } else {
2410
+ console.error(errorRed2(`
2411
+ \u2717 ${result.message}
2412
+ `));
2413
+ process.exit(1);
2414
+ }
2415
+ break;
2416
+ }
2417
+ case "status": {
2418
+ const result = await daemon.status(config);
2419
+ if (result.running) {
2420
+ console.log(neuralCyan2(`
2421
+ \u2713 ${result.message}`));
2422
+ if (result.sessionsWatched !== void 0) {
2423
+ console.log(` Sessions watched: ${result.sessionsWatched}`);
2424
+ }
2425
+ if (result.tokensSaved !== void 0) {
2426
+ console.log(` Tokens saved: ${result.tokensSaved.toLocaleString()}`);
2427
+ }
2428
+ console.log();
2429
+ } else {
2430
+ console.log(errorRed2(`
2431
+ \u2717 ${result.message}
2432
+ `));
2433
+ }
2434
+ break;
2435
+ }
2436
+ default:
2437
+ console.error(errorRed2(`
2438
+ Unknown subcommand: ${subcommand}
2439
+ `));
2440
+ console.error("Valid subcommands: start, stop, status\n");
2441
+ process.exit(1);
2442
+ }
2443
+ } catch (error) {
2444
+ console.error(errorRed2("Error:"), error instanceof Error ? error.message : String(error));
2445
+ process.exit(1);
2446
+ }
2447
+ });
2448
+ program.command("hooks <subcommand>").description("Manage Claude Code hook integration").option("--global", "Install hooks globally (for all projects)").addHelpText(
2449
+ "after",
2450
+ `
2451
+ Subcommands:
2452
+ install # Install hooks
2453
+ uninstall # Uninstall hooks
2454
+ status # Check hook status
2455
+
2456
+ Examples:
2457
+ $ sparn hooks install # Install hooks for current project
2458
+ $ sparn hooks install --global # Install hooks globally
2459
+ $ sparn hooks uninstall # Uninstall hooks
2460
+ $ sparn hooks status # Check if hooks are active
2461
+
2462
+ Hooks automatically optimize context before each Claude Code prompt
2463
+ and compress verbose tool results after execution.
2464
+ `
2465
+ ).action(async (subcommand, options) => {
2466
+ const { hooksCommand: hooksCommand2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
2467
+ const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
2468
+ try {
2469
+ const result = await hooksCommand2({
2470
+ subcommand,
2471
+ global: options.global || false
2472
+ });
2473
+ if (result.success) {
2474
+ console.log(neuralCyan2(`
2475
+ \u2713 ${result.message}`));
2476
+ if (result.hookPaths) {
2477
+ console.log("\nHook paths:");
2478
+ console.log(` pre-prompt: ${result.hookPaths.prePrompt}`);
2479
+ console.log(` post-tool-result: ${result.hookPaths.postToolResult}`);
2480
+ }
2481
+ console.log();
2482
+ } else {
2483
+ console.error(errorRed2(`
2484
+ \u2717 ${result.message}`));
2485
+ if (result.error) {
2486
+ console.error(` ${result.error}`);
2487
+ }
2488
+ console.log();
2489
+ process.exit(1);
2490
+ }
2491
+ } catch (error) {
2492
+ console.error(errorRed2("Error:"), error instanceof Error ? error.message : String(error));
2493
+ process.exit(1);
2494
+ }
2495
+ });
1863
2496
  program.on("option:version", () => {
1864
2497
  console.log(getBanner(VERSION2));
1865
2498
  process.exit(0);