@ulrichc1/sparn 1.0.1 → 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.
@@ -372,7 +372,17 @@ var init_config = __esm({
372
372
  sounds: false,
373
373
  verbose: false
374
374
  },
375
- autoConsolidate: null
375
+ autoConsolidate: null,
376
+ realtime: {
377
+ tokenBudget: 5e4,
378
+ autoOptimizeThreshold: 8e4,
379
+ watchPatterns: ["**/*.jsonl"],
380
+ pidFile: ".sparn/daemon.pid",
381
+ logFile: ".sparn/daemon.log",
382
+ debounceMs: 5e3,
383
+ incremental: true,
384
+ windowSize: 500
385
+ }
376
386
  };
377
387
  }
378
388
  });
@@ -389,8 +399,8 @@ function getVersion() {
389
399
  return pkg.version;
390
400
  } catch {
391
401
  const __filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
392
- const __dirname = (0, import_node_path.dirname)(__filename2);
393
- const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path.join)(__dirname, "../../package.json"), "utf-8"));
402
+ const __dirname2 = (0, import_node_path.dirname)(__filename2);
403
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path.join)(__dirname2, "../../package.json"), "utf-8"));
394
404
  return pkg.version;
395
405
  }
396
406
  }
@@ -1485,27 +1495,515 @@ var init_config2 = __esm({
1485
1495
  validate: (v) => v === null || typeof v === "number" && v > 0,
1486
1496
  errorMessage: "autoConsolidate must be a positive number (hours) or null",
1487
1497
  parse: (v) => v === "null" ? null : Number.parseFloat(v)
1498
+ },
1499
+ "realtime.tokenBudget": {
1500
+ path: ["realtime", "tokenBudget"],
1501
+ validate: (v) => typeof v === "number" && v > 0,
1502
+ errorMessage: "tokenBudget must be a positive number",
1503
+ parse: (v) => Number.parseInt(v, 10)
1504
+ },
1505
+ "realtime.autoOptimizeThreshold": {
1506
+ path: ["realtime", "autoOptimizeThreshold"],
1507
+ validate: (v) => typeof v === "number" && v > 0,
1508
+ errorMessage: "autoOptimizeThreshold must be a positive number",
1509
+ parse: (v) => Number.parseInt(v, 10)
1510
+ },
1511
+ "realtime.watchPatterns": {
1512
+ path: ["realtime", "watchPatterns"],
1513
+ validate: (v) => Array.isArray(v) && v.every((p) => typeof p === "string"),
1514
+ errorMessage: "watchPatterns must be an array of strings",
1515
+ parse: (v) => v.split(",").map((p) => p.trim())
1516
+ },
1517
+ "realtime.pidFile": {
1518
+ path: ["realtime", "pidFile"],
1519
+ validate: (v) => typeof v === "string" && v.length > 0,
1520
+ errorMessage: "pidFile must be a non-empty string",
1521
+ parse: (v) => v
1522
+ },
1523
+ "realtime.logFile": {
1524
+ path: ["realtime", "logFile"],
1525
+ validate: (v) => typeof v === "string" && v.length > 0,
1526
+ errorMessage: "logFile must be a non-empty string",
1527
+ parse: (v) => v
1528
+ },
1529
+ "realtime.debounceMs": {
1530
+ path: ["realtime", "debounceMs"],
1531
+ validate: (v) => typeof v === "number" && v >= 0,
1532
+ errorMessage: "debounceMs must be a non-negative number",
1533
+ parse: (v) => Number.parseInt(v, 10)
1534
+ },
1535
+ "realtime.incremental": {
1536
+ path: ["realtime", "incremental"],
1537
+ validate: (v) => typeof v === "boolean",
1538
+ errorMessage: "incremental must be true or false",
1539
+ parse: (v) => v === "true"
1540
+ },
1541
+ "realtime.windowSize": {
1542
+ path: ["realtime", "windowSize"],
1543
+ validate: (v) => typeof v === "number" && v > 0,
1544
+ errorMessage: "windowSize must be a positive number",
1545
+ parse: (v) => Number.parseInt(v, 10)
1546
+ }
1547
+ };
1548
+ }
1549
+ });
1550
+
1551
+ // src/core/metrics.ts
1552
+ function createMetricsCollector() {
1553
+ const optimizations = [];
1554
+ let daemonMetrics = {
1555
+ startTime: Date.now(),
1556
+ sessionsWatched: 0,
1557
+ totalOptimizations: 0,
1558
+ totalTokensSaved: 0,
1559
+ averageLatency: 0,
1560
+ memoryUsage: 0
1561
+ };
1562
+ let cacheHits = 0;
1563
+ let cacheMisses = 0;
1564
+ function recordOptimization(metric) {
1565
+ optimizations.push(metric);
1566
+ daemonMetrics.totalOptimizations++;
1567
+ daemonMetrics.totalTokensSaved += metric.tokensBefore - metric.tokensAfter;
1568
+ if (metric.cacheHitRate > 0) {
1569
+ const hits = Math.round(metric.entriesProcessed * metric.cacheHitRate);
1570
+ cacheHits += hits;
1571
+ cacheMisses += metric.entriesProcessed - hits;
1572
+ }
1573
+ daemonMetrics.averageLatency = (daemonMetrics.averageLatency * (daemonMetrics.totalOptimizations - 1) + metric.duration) / daemonMetrics.totalOptimizations;
1574
+ if (optimizations.length > 1e3) {
1575
+ optimizations.shift();
1576
+ }
1577
+ }
1578
+ function updateDaemon(metric) {
1579
+ daemonMetrics = {
1580
+ ...daemonMetrics,
1581
+ ...metric
1582
+ };
1583
+ }
1584
+ function calculatePercentile(values, percentile) {
1585
+ if (values.length === 0) return 0;
1586
+ const sorted = [...values].sort((a, b) => a - b);
1587
+ const index = Math.ceil(percentile / 100 * sorted.length) - 1;
1588
+ return sorted[index] || 0;
1589
+ }
1590
+ function getSnapshot() {
1591
+ const totalRuns = optimizations.length;
1592
+ const totalDuration = optimizations.reduce((sum, m) => sum + m.duration, 0);
1593
+ const totalTokensSaved = optimizations.reduce(
1594
+ (sum, m) => sum + (m.tokensBefore - m.tokensAfter),
1595
+ 0
1596
+ );
1597
+ const totalTokensBefore = optimizations.reduce((sum, m) => sum + m.tokensBefore, 0);
1598
+ const averageReduction = totalTokensBefore > 0 ? totalTokensSaved / totalTokensBefore : 0;
1599
+ const durations = optimizations.map((m) => m.duration);
1600
+ const totalCacheQueries = cacheHits + cacheMisses;
1601
+ const hitRate = totalCacheQueries > 0 ? cacheHits / totalCacheQueries : 0;
1602
+ return {
1603
+ timestamp: Date.now(),
1604
+ optimization: {
1605
+ totalRuns,
1606
+ totalDuration,
1607
+ totalTokensSaved,
1608
+ averageReduction,
1609
+ p50Latency: calculatePercentile(durations, 50),
1610
+ p95Latency: calculatePercentile(durations, 95),
1611
+ p99Latency: calculatePercentile(durations, 99)
1612
+ },
1613
+ cache: {
1614
+ hitRate,
1615
+ totalHits: cacheHits,
1616
+ totalMisses: cacheMisses,
1617
+ size: optimizations.reduce((sum, m) => sum + m.entriesKept, 0)
1618
+ },
1619
+ daemon: {
1620
+ uptime: Date.now() - daemonMetrics.startTime,
1621
+ sessionsWatched: daemonMetrics.sessionsWatched,
1622
+ memoryUsage: daemonMetrics.memoryUsage
1623
+ }
1624
+ };
1625
+ }
1626
+ function exportMetrics() {
1627
+ return JSON.stringify(getSnapshot(), null, 2);
1628
+ }
1629
+ function reset() {
1630
+ optimizations.length = 0;
1631
+ cacheHits = 0;
1632
+ cacheMisses = 0;
1633
+ daemonMetrics = {
1634
+ startTime: Date.now(),
1635
+ sessionsWatched: 0,
1636
+ totalOptimizations: 0,
1637
+ totalTokensSaved: 0,
1638
+ averageLatency: 0,
1639
+ memoryUsage: 0
1640
+ };
1641
+ }
1642
+ return {
1643
+ recordOptimization,
1644
+ updateDaemon,
1645
+ getSnapshot,
1646
+ export: exportMetrics,
1647
+ reset
1648
+ };
1649
+ }
1650
+ function getMetrics() {
1651
+ if (!globalMetrics) {
1652
+ globalMetrics = createMetricsCollector();
1653
+ }
1654
+ return globalMetrics;
1655
+ }
1656
+ var globalMetrics;
1657
+ var init_metrics = __esm({
1658
+ "src/core/metrics.ts"() {
1659
+ "use strict";
1660
+ init_cjs_shims();
1661
+ globalMetrics = null;
1662
+ }
1663
+ });
1664
+
1665
+ // src/daemon/daemon-process.ts
1666
+ var daemon_process_exports = {};
1667
+ __export(daemon_process_exports, {
1668
+ createDaemonCommand: () => createDaemonCommand
1669
+ });
1670
+ function createDaemonCommand() {
1671
+ function isDaemonRunning(pidFile) {
1672
+ if (!(0, import_node_fs4.existsSync)(pidFile)) {
1673
+ return { running: false };
1674
+ }
1675
+ try {
1676
+ const pidStr = (0, import_node_fs4.readFileSync)(pidFile, "utf-8").trim();
1677
+ const pid = Number.parseInt(pidStr, 10);
1678
+ if (Number.isNaN(pid)) {
1679
+ return { running: false };
1680
+ }
1681
+ try {
1682
+ process.kill(pid, 0);
1683
+ return { running: true, pid };
1684
+ } catch {
1685
+ (0, import_node_fs4.unlinkSync)(pidFile);
1686
+ return { running: false };
1488
1687
  }
1688
+ } catch {
1689
+ return { running: false };
1690
+ }
1691
+ }
1692
+ function writePidFile(pidFile, pid) {
1693
+ const dir = (0, import_node_path2.dirname)(pidFile);
1694
+ if (!(0, import_node_fs4.existsSync)(dir)) {
1695
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
1696
+ }
1697
+ (0, import_node_fs4.writeFileSync)(pidFile, String(pid), "utf-8");
1698
+ }
1699
+ function removePidFile(pidFile) {
1700
+ if ((0, import_node_fs4.existsSync)(pidFile)) {
1701
+ (0, import_node_fs4.unlinkSync)(pidFile);
1702
+ }
1703
+ }
1704
+ async function start(config) {
1705
+ const { pidFile, logFile } = config.realtime;
1706
+ const status2 = isDaemonRunning(pidFile);
1707
+ if (status2.running) {
1708
+ return {
1709
+ success: false,
1710
+ pid: status2.pid,
1711
+ message: `Daemon already running (PID ${status2.pid})`,
1712
+ error: "Already running"
1713
+ };
1714
+ }
1715
+ try {
1716
+ const daemonPath = (0, import_node_path2.join)(__dirname, "index.js");
1717
+ const child = (0, import_node_child_process2.fork)(daemonPath, [], {
1718
+ detached: true,
1719
+ stdio: "ignore",
1720
+ env: {
1721
+ ...process.env,
1722
+ SPARN_CONFIG: JSON.stringify(config),
1723
+ SPARN_PID_FILE: pidFile,
1724
+ SPARN_LOG_FILE: logFile
1725
+ }
1726
+ });
1727
+ child.unref();
1728
+ if (child.pid) {
1729
+ writePidFile(pidFile, child.pid);
1730
+ return {
1731
+ success: true,
1732
+ pid: child.pid,
1733
+ message: `Daemon started (PID ${child.pid})`
1734
+ };
1735
+ }
1736
+ return {
1737
+ success: false,
1738
+ message: "Failed to start daemon (no PID)",
1739
+ error: "No PID"
1740
+ };
1741
+ } catch (error) {
1742
+ return {
1743
+ success: false,
1744
+ message: "Failed to start daemon",
1745
+ error: error instanceof Error ? error.message : String(error)
1746
+ };
1747
+ }
1748
+ }
1749
+ async function stop(config) {
1750
+ const { pidFile } = config.realtime;
1751
+ const status2 = isDaemonRunning(pidFile);
1752
+ if (!status2.running || !status2.pid) {
1753
+ return {
1754
+ success: true,
1755
+ message: "Daemon not running"
1756
+ };
1757
+ }
1758
+ try {
1759
+ process.kill(status2.pid, "SIGTERM");
1760
+ const maxWait = 5e3;
1761
+ const interval = 100;
1762
+ let waited = 0;
1763
+ while (waited < maxWait) {
1764
+ try {
1765
+ process.kill(status2.pid, 0);
1766
+ await new Promise((resolve2) => setTimeout(resolve2, interval));
1767
+ waited += interval;
1768
+ } catch {
1769
+ removePidFile(pidFile);
1770
+ return {
1771
+ success: true,
1772
+ message: `Daemon stopped (PID ${status2.pid})`
1773
+ };
1774
+ }
1775
+ }
1776
+ try {
1777
+ process.kill(status2.pid, "SIGKILL");
1778
+ removePidFile(pidFile);
1779
+ return {
1780
+ success: true,
1781
+ message: `Daemon force killed (PID ${status2.pid})`
1782
+ };
1783
+ } catch {
1784
+ removePidFile(pidFile);
1785
+ return {
1786
+ success: true,
1787
+ message: `Daemon stopped (PID ${status2.pid})`
1788
+ };
1789
+ }
1790
+ } catch (error) {
1791
+ return {
1792
+ success: false,
1793
+ message: "Failed to stop daemon",
1794
+ error: error instanceof Error ? error.message : String(error)
1795
+ };
1796
+ }
1797
+ }
1798
+ async function status(config) {
1799
+ const { pidFile } = config.realtime;
1800
+ const daemonStatus = isDaemonRunning(pidFile);
1801
+ if (!daemonStatus.running || !daemonStatus.pid) {
1802
+ return {
1803
+ running: false,
1804
+ message: "Daemon not running"
1805
+ };
1806
+ }
1807
+ const metrics = getMetrics().getSnapshot();
1808
+ return {
1809
+ running: true,
1810
+ pid: daemonStatus.pid,
1811
+ uptime: metrics.daemon.uptime,
1812
+ sessionsWatched: metrics.daemon.sessionsWatched,
1813
+ tokensSaved: metrics.optimization.totalTokensSaved,
1814
+ message: `Daemon running (PID ${daemonStatus.pid})`
1489
1815
  };
1490
1816
  }
1817
+ return {
1818
+ start,
1819
+ stop,
1820
+ status
1821
+ };
1822
+ }
1823
+ var import_node_child_process2, import_node_fs4, import_node_path2;
1824
+ var init_daemon_process = __esm({
1825
+ "src/daemon/daemon-process.ts"() {
1826
+ "use strict";
1827
+ init_cjs_shims();
1828
+ import_node_child_process2 = require("child_process");
1829
+ import_node_fs4 = require("fs");
1830
+ import_node_path2 = require("path");
1831
+ init_metrics();
1832
+ }
1833
+ });
1834
+
1835
+ // src/cli/commands/hooks.ts
1836
+ var hooks_exports = {};
1837
+ __export(hooks_exports, {
1838
+ hooksCommand: () => hooksCommand
1839
+ });
1840
+ async function hooksCommand(options) {
1841
+ const { subcommand, global } = options;
1842
+ const settingsPath = global ? (0, import_node_path3.join)((0, import_node_os.homedir)(), ".claude", "settings.json") : (0, import_node_path3.join)(process.cwd(), ".claude", "settings.json");
1843
+ const hooksDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(__dirname))), "dist", "hooks");
1844
+ const prePromptPath = (0, import_node_path3.join)(hooksDir, "pre-prompt.js");
1845
+ const postToolResultPath = (0, import_node_path3.join)(hooksDir, "post-tool-result.js");
1846
+ switch (subcommand) {
1847
+ case "install":
1848
+ return await installHooks(settingsPath, prePromptPath, postToolResultPath, global);
1849
+ case "uninstall":
1850
+ return await uninstallHooks(settingsPath, global);
1851
+ case "status":
1852
+ return await hooksStatus(settingsPath, global);
1853
+ default:
1854
+ return {
1855
+ success: false,
1856
+ message: `Unknown subcommand: ${subcommand}`,
1857
+ error: "Invalid subcommand"
1858
+ };
1859
+ }
1860
+ }
1861
+ async function installHooks(settingsPath, prePromptPath, postToolResultPath, global) {
1862
+ try {
1863
+ if (!(0, import_node_fs5.existsSync)(prePromptPath)) {
1864
+ return {
1865
+ success: false,
1866
+ message: `Hook script not found: ${prePromptPath}`,
1867
+ error: "Hook scripts not built. Run `npm run build` first."
1868
+ };
1869
+ }
1870
+ if (!(0, import_node_fs5.existsSync)(postToolResultPath)) {
1871
+ return {
1872
+ success: false,
1873
+ message: `Hook script not found: ${postToolResultPath}`,
1874
+ error: "Hook scripts not built. Run `npm run build` first."
1875
+ };
1876
+ }
1877
+ let settings = {};
1878
+ if ((0, import_node_fs5.existsSync)(settingsPath)) {
1879
+ const settingsJson = (0, import_node_fs5.readFileSync)(settingsPath, "utf-8");
1880
+ settings = JSON.parse(settingsJson);
1881
+ } else {
1882
+ const claudeDir = (0, import_node_path3.dirname)(settingsPath);
1883
+ if (!(0, import_node_fs5.existsSync)(claudeDir)) {
1884
+ (0, import_node_fs5.mkdirSync)(claudeDir, { recursive: true });
1885
+ }
1886
+ }
1887
+ settings["hooks"] = {
1888
+ ...typeof settings["hooks"] === "object" && settings["hooks"] !== null ? settings["hooks"] : {},
1889
+ "pre-prompt": `node ${prePromptPath}`,
1890
+ "post-tool-result": `node ${postToolResultPath}`
1891
+ };
1892
+ (0, import_node_fs5.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1893
+ return {
1894
+ success: true,
1895
+ message: global ? "Hooks installed globally (all projects)" : "Hooks installed for current project",
1896
+ installed: true,
1897
+ hookPaths: {
1898
+ prePrompt: prePromptPath,
1899
+ postToolResult: postToolResultPath
1900
+ }
1901
+ };
1902
+ } catch (error) {
1903
+ return {
1904
+ success: false,
1905
+ message: "Failed to install hooks",
1906
+ error: error instanceof Error ? error.message : String(error)
1907
+ };
1908
+ }
1909
+ }
1910
+ async function uninstallHooks(settingsPath, global) {
1911
+ try {
1912
+ if (!(0, import_node_fs5.existsSync)(settingsPath)) {
1913
+ return {
1914
+ success: true,
1915
+ message: "No hooks installed (settings.json not found)",
1916
+ installed: false
1917
+ };
1918
+ }
1919
+ const settingsJson = (0, import_node_fs5.readFileSync)(settingsPath, "utf-8");
1920
+ const settings = JSON.parse(settingsJson);
1921
+ if (settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null) {
1922
+ const hooks = settings["hooks"];
1923
+ delete hooks["pre-prompt"];
1924
+ delete hooks["post-tool-result"];
1925
+ if (Object.keys(hooks).length === 0) {
1926
+ delete settings["hooks"];
1927
+ }
1928
+ }
1929
+ (0, import_node_fs5.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1930
+ return {
1931
+ success: true,
1932
+ message: global ? "Hooks uninstalled globally" : "Hooks uninstalled from current project",
1933
+ installed: false
1934
+ };
1935
+ } catch (error) {
1936
+ return {
1937
+ success: false,
1938
+ message: "Failed to uninstall hooks",
1939
+ error: error instanceof Error ? error.message : String(error)
1940
+ };
1941
+ }
1942
+ }
1943
+ async function hooksStatus(settingsPath, global) {
1944
+ try {
1945
+ if (!(0, import_node_fs5.existsSync)(settingsPath)) {
1946
+ return {
1947
+ success: true,
1948
+ message: global ? "No global hooks installed (settings.json not found)" : "No project hooks installed (settings.json not found)",
1949
+ installed: false
1950
+ };
1951
+ }
1952
+ const settingsJson = (0, import_node_fs5.readFileSync)(settingsPath, "utf-8");
1953
+ const settings = JSON.parse(settingsJson);
1954
+ const hasHooks = settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null && "pre-prompt" in settings["hooks"] && "post-tool-result" in settings["hooks"];
1955
+ if (!hasHooks) {
1956
+ return {
1957
+ success: true,
1958
+ message: global ? "No global hooks installed" : "No project hooks installed",
1959
+ installed: false
1960
+ };
1961
+ }
1962
+ const hooks = settings["hooks"];
1963
+ return {
1964
+ success: true,
1965
+ message: global ? "Global hooks active" : "Project hooks active",
1966
+ installed: true,
1967
+ hookPaths: {
1968
+ prePrompt: hooks["pre-prompt"] || "",
1969
+ postToolResult: hooks["post-tool-result"] || ""
1970
+ }
1971
+ };
1972
+ } catch (error) {
1973
+ return {
1974
+ success: false,
1975
+ message: "Failed to check hooks status",
1976
+ error: error instanceof Error ? error.message : String(error)
1977
+ };
1978
+ }
1979
+ }
1980
+ var import_node_fs5, import_node_os, import_node_path3;
1981
+ var init_hooks = __esm({
1982
+ "src/cli/commands/hooks.ts"() {
1983
+ "use strict";
1984
+ init_cjs_shims();
1985
+ import_node_fs5 = require("fs");
1986
+ import_node_os = require("os");
1987
+ import_node_path3 = require("path");
1988
+ }
1491
1989
  });
1492
1990
 
1493
1991
  // src/cli/index.ts
1494
1992
  init_cjs_shims();
1495
- var import_node_child_process2 = require("child_process");
1496
- var import_node_fs4 = require("fs");
1497
- var import_node_path2 = require("path");
1993
+ var import_node_child_process3 = require("child_process");
1994
+ var import_node_fs6 = require("fs");
1995
+ var import_node_path4 = require("path");
1498
1996
  var import_node_url2 = require("url");
1499
1997
  var import_commander = require("commander");
1500
1998
  init_banner();
1501
1999
  function getVersion2() {
1502
2000
  try {
1503
- const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path2.join)(process.cwd(), "package.json"), "utf-8"));
2001
+ const pkg = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path4.join)(process.cwd(), "package.json"), "utf-8"));
1504
2002
  return pkg.version;
1505
2003
  } catch {
1506
2004
  const __filename2 = (0, import_node_url2.fileURLToPath)(importMetaUrl);
1507
- const __dirname = (0, import_node_path2.dirname)(__filename2);
1508
- const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path2.join)(__dirname, "../../package.json"), "utf-8"));
2005
+ const __dirname2 = (0, import_node_path4.dirname)(__filename2);
2006
+ const pkg = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path4.join)(__dirname2, "../../package.json"), "utf-8"));
1509
2007
  return pkg.version;
1510
2008
  }
1511
2009
  }
@@ -1626,7 +2124,7 @@ Typical Results:
1626
2124
  spinner.text = `\u{1F4D6} Reading context from ${options.input}...`;
1627
2125
  }
1628
2126
  spinner.text = "\u{1F4BE} Loading memory database...";
1629
- const dbPath = (0, import_node_path2.resolve)(process.cwd(), ".sparn/memory.db");
2127
+ const dbPath = (0, import_node_path4.resolve)(process.cwd(), ".sparn/memory.db");
1630
2128
  const memory = await createKVMemory2(dbPath);
1631
2129
  spinner.text = "\u26A1 Applying neuroscience principles...";
1632
2130
  const result = await optimizeCommand2({
@@ -1690,7 +2188,7 @@ Tracked Metrics:
1690
2188
  try {
1691
2189
  if (spinner) spinner.start();
1692
2190
  if (spinner) spinner.text = "\u{1F4BE} Loading optimization history...";
1693
- const dbPath = (0, import_node_path2.resolve)(process.cwd(), ".sparn/memory.db");
2191
+ const dbPath = (0, import_node_path4.resolve)(process.cwd(), ".sparn/memory.db");
1694
2192
  const memory = await createKVMemory2(dbPath);
1695
2193
  let confirmReset = false;
1696
2194
  if (options.reset) {
@@ -1750,7 +2248,7 @@ The relay command passes the exit code from the wrapped command.
1750
2248
  const { relayCommand: relayCommand2 } = await Promise.resolve().then(() => (init_relay(), relay_exports));
1751
2249
  const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
1752
2250
  try {
1753
- const dbPath = (0, import_node_path2.resolve)(process.cwd(), ".sparn/memory.db");
2251
+ const dbPath = (0, import_node_path4.resolve)(process.cwd(), ".sparn/memory.db");
1754
2252
  const memory = await createKVMemory2(dbPath);
1755
2253
  const result = await relayCommand2({
1756
2254
  command,
@@ -1803,7 +2301,7 @@ Typical Results:
1803
2301
  try {
1804
2302
  spinner.start();
1805
2303
  spinner.text = "\u{1F4BE} Loading memory database...";
1806
- const dbPath = (0, import_node_path2.resolve)(process.cwd(), ".sparn/memory.db");
2304
+ const dbPath = (0, import_node_path4.resolve)(process.cwd(), ".sparn/memory.db");
1807
2305
  const memory = await createKVMemory2(dbPath);
1808
2306
  spinner.text = "\u{1F50D} Identifying decayed entries...";
1809
2307
  const result = await consolidateCommand2({ memory });
@@ -1851,7 +2349,7 @@ The config file is located at .sparn/config.yaml
1851
2349
  const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
1852
2350
  const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
1853
2351
  try {
1854
- const configPath = (0, import_node_path2.resolve)(process.cwd(), ".sparn/config.yaml");
2352
+ const configPath = (0, import_node_path4.resolve)(process.cwd(), ".sparn/config.yaml");
1855
2353
  const result = await configCommand2({
1856
2354
  configPath,
1857
2355
  subcommand,
@@ -1868,7 +2366,7 @@ The config file is located at .sparn/config.yaml
1868
2366
  console.log(neuralCyan2(`
1869
2367
  \u{1F4DD} Opening config in ${editor}...
1870
2368
  `));
1871
- const child = (0, import_node_child_process2.spawn)(editor, [result.editorPath], {
2369
+ const child = (0, import_node_child_process3.spawn)(editor, [result.editorPath], {
1872
2370
  stdio: "inherit"
1873
2371
  });
1874
2372
  child.on("close", (code) => {
@@ -1889,6 +2387,139 @@ The config file is located at .sparn/config.yaml
1889
2387
  process.exit(1);
1890
2388
  }
1891
2389
  });
2390
+ program.command("daemon <subcommand>").description("Manage real-time optimization daemon").addHelpText(
2391
+ "after",
2392
+ `
2393
+ Subcommands:
2394
+ start # Start daemon
2395
+ stop # Stop daemon
2396
+ status # Check daemon status
2397
+
2398
+ Examples:
2399
+ $ sparn daemon start # Start watching Claude Code sessions
2400
+ $ sparn daemon stop # Stop daemon
2401
+ $ sparn daemon status # Check if daemon is running
2402
+
2403
+ The daemon watches ~/.claude/projects/**/*.jsonl and automatically
2404
+ optimizes contexts when they exceed the configured threshold.
2405
+ `
2406
+ ).action(async (subcommand) => {
2407
+ const { load: parseYAML2 } = await import("js-yaml");
2408
+ const { createDaemonCommand: createDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports));
2409
+ const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
2410
+ try {
2411
+ const configPath = (0, import_node_path4.resolve)(process.cwd(), ".sparn/config.yaml");
2412
+ const configYAML = (0, import_node_fs6.readFileSync)(configPath, "utf-8");
2413
+ const config = parseYAML2(configYAML);
2414
+ const daemon = createDaemonCommand2();
2415
+ switch (subcommand) {
2416
+ case "start": {
2417
+ const result = await daemon.start(config);
2418
+ if (result.success) {
2419
+ console.log(neuralCyan2(`
2420
+ \u2713 ${result.message}
2421
+ `));
2422
+ } else {
2423
+ console.error(errorRed2(`
2424
+ \u2717 ${result.message}
2425
+ `));
2426
+ process.exit(1);
2427
+ }
2428
+ break;
2429
+ }
2430
+ case "stop": {
2431
+ const result = await daemon.stop(config);
2432
+ if (result.success) {
2433
+ console.log(neuralCyan2(`
2434
+ \u2713 ${result.message}
2435
+ `));
2436
+ } else {
2437
+ console.error(errorRed2(`
2438
+ \u2717 ${result.message}
2439
+ `));
2440
+ process.exit(1);
2441
+ }
2442
+ break;
2443
+ }
2444
+ case "status": {
2445
+ const result = await daemon.status(config);
2446
+ if (result.running) {
2447
+ console.log(neuralCyan2(`
2448
+ \u2713 ${result.message}`));
2449
+ if (result.sessionsWatched !== void 0) {
2450
+ console.log(` Sessions watched: ${result.sessionsWatched}`);
2451
+ }
2452
+ if (result.tokensSaved !== void 0) {
2453
+ console.log(` Tokens saved: ${result.tokensSaved.toLocaleString()}`);
2454
+ }
2455
+ console.log();
2456
+ } else {
2457
+ console.log(errorRed2(`
2458
+ \u2717 ${result.message}
2459
+ `));
2460
+ }
2461
+ break;
2462
+ }
2463
+ default:
2464
+ console.error(errorRed2(`
2465
+ Unknown subcommand: ${subcommand}
2466
+ `));
2467
+ console.error("Valid subcommands: start, stop, status\n");
2468
+ process.exit(1);
2469
+ }
2470
+ } catch (error) {
2471
+ console.error(errorRed2("Error:"), error instanceof Error ? error.message : String(error));
2472
+ process.exit(1);
2473
+ }
2474
+ });
2475
+ program.command("hooks <subcommand>").description("Manage Claude Code hook integration").option("--global", "Install hooks globally (for all projects)").addHelpText(
2476
+ "after",
2477
+ `
2478
+ Subcommands:
2479
+ install # Install hooks
2480
+ uninstall # Uninstall hooks
2481
+ status # Check hook status
2482
+
2483
+ Examples:
2484
+ $ sparn hooks install # Install hooks for current project
2485
+ $ sparn hooks install --global # Install hooks globally
2486
+ $ sparn hooks uninstall # Uninstall hooks
2487
+ $ sparn hooks status # Check if hooks are active
2488
+
2489
+ Hooks automatically optimize context before each Claude Code prompt
2490
+ and compress verbose tool results after execution.
2491
+ `
2492
+ ).action(async (subcommand, options) => {
2493
+ const { hooksCommand: hooksCommand2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
2494
+ const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
2495
+ try {
2496
+ const result = await hooksCommand2({
2497
+ subcommand,
2498
+ global: options.global || false
2499
+ });
2500
+ if (result.success) {
2501
+ console.log(neuralCyan2(`
2502
+ \u2713 ${result.message}`));
2503
+ if (result.hookPaths) {
2504
+ console.log("\nHook paths:");
2505
+ console.log(` pre-prompt: ${result.hookPaths.prePrompt}`);
2506
+ console.log(` post-tool-result: ${result.hookPaths.postToolResult}`);
2507
+ }
2508
+ console.log();
2509
+ } else {
2510
+ console.error(errorRed2(`
2511
+ \u2717 ${result.message}`));
2512
+ if (result.error) {
2513
+ console.error(` ${result.error}`);
2514
+ }
2515
+ console.log();
2516
+ process.exit(1);
2517
+ }
2518
+ } catch (error) {
2519
+ console.error(errorRed2("Error:"), error instanceof Error ? error.message : String(error));
2520
+ process.exit(1);
2521
+ }
2522
+ });
1892
2523
  program.on("option:version", () => {
1893
2524
  console.log(getBanner(VERSION2));
1894
2525
  process.exit(0);