@ulrichc1/sparn 1.1.0 → 1.2.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/index.cjs CHANGED
@@ -47,6 +47,7 @@ __export(src_exports, {
47
47
  createLogger: () => createLogger,
48
48
  createSessionWatcher: () => createSessionWatcher,
49
49
  createSleepCompressor: () => createSleepCompressor,
50
+ createSparnMcpServer: () => createSparnMcpServer,
50
51
  createSparsePruner: () => createSparsePruner,
51
52
  estimateTokens: () => estimateTokens,
52
53
  hashContent: () => hashContent,
@@ -55,6 +56,10 @@ __export(src_exports, {
55
56
  });
56
57
  module.exports = __toCommonJS(src_exports);
57
58
 
59
+ // node_modules/tsup/assets/cjs_shims.js
60
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
61
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
62
+
58
63
  // src/core/btsp-embedder.ts
59
64
  var import_node_crypto2 = require("crypto");
60
65
 
@@ -1366,6 +1371,7 @@ function createSleepCompressor() {
1366
1371
  var import_node_child_process = require("child_process");
1367
1372
  var import_node_fs2 = require("fs");
1368
1373
  var import_node_path = require("path");
1374
+ var import_node_url = require("url");
1369
1375
  function createDaemonCommand() {
1370
1376
  function isDaemonRunning(pidFile) {
1371
1377
  if (!(0, import_node_fs2.existsSync)(pidFile)) {
@@ -1412,6 +1418,8 @@ function createDaemonCommand() {
1412
1418
  };
1413
1419
  }
1414
1420
  try {
1421
+ const __filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
1422
+ const __dirname = (0, import_node_path.dirname)(__filename2);
1415
1423
  const daemonPath = (0, import_node_path.join)(__dirname, "index.js");
1416
1424
  const child = (0, import_node_child_process.fork)(daemonPath, [], {
1417
1425
  detached: true,
@@ -1761,6 +1769,10 @@ function createSessionWatcher(config) {
1761
1769
  };
1762
1770
  }
1763
1771
 
1772
+ // src/mcp/server.ts
1773
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
1774
+ var import_zod = require("zod");
1775
+
1764
1776
  // src/types/config.ts
1765
1777
  var DEFAULT_CONFIG = {
1766
1778
  pruning: {
@@ -1790,10 +1802,217 @@ var DEFAULT_CONFIG = {
1790
1802
  logFile: ".sparn/daemon.log",
1791
1803
  debounceMs: 5e3,
1792
1804
  incremental: true,
1793
- windowSize: 500
1805
+ windowSize: 500,
1806
+ consolidationInterval: null
1794
1807
  }
1795
1808
  };
1796
1809
 
1810
+ // src/mcp/server.ts
1811
+ function createSparnMcpServer(options) {
1812
+ const { memory, config = DEFAULT_CONFIG } = options;
1813
+ const server = new import_mcp.McpServer({
1814
+ name: "sparn",
1815
+ version: "1.1.1"
1816
+ });
1817
+ registerOptimizeTool(server, memory, config);
1818
+ registerStatsTool(server, memory);
1819
+ registerConsolidateTool(server, memory);
1820
+ return server;
1821
+ }
1822
+ function registerOptimizeTool(server, memory, config) {
1823
+ server.registerTool(
1824
+ "sparn_optimize",
1825
+ {
1826
+ title: "Sparn Optimize",
1827
+ description: "Optimize context using neuroscience-inspired pruning. Applies BTSP detection, engram scoring, confidence states, and sparse pruning to reduce token usage while preserving important information.",
1828
+ inputSchema: {
1829
+ context: import_zod.z.string().describe("The context text to optimize"),
1830
+ dryRun: import_zod.z.boolean().optional().default(false).describe("If true, do not persist changes to the memory store"),
1831
+ verbose: import_zod.z.boolean().optional().default(false).describe("If true, include per-entry details in the response"),
1832
+ threshold: import_zod.z.number().min(0).max(100).optional().describe("Custom pruning threshold (1-100, overrides config)")
1833
+ }
1834
+ },
1835
+ async ({ context, dryRun, verbose, threshold }) => {
1836
+ try {
1837
+ const effectiveConfig = threshold ? { ...config, pruning: { ...config.pruning, threshold } } : config;
1838
+ const adapter = createGenericAdapter(memory, effectiveConfig);
1839
+ const result = await adapter.optimize(context, {
1840
+ dryRun,
1841
+ verbose,
1842
+ threshold
1843
+ });
1844
+ const response = {
1845
+ optimizedContext: result.optimizedContext,
1846
+ tokensBefore: result.tokensBefore,
1847
+ tokensAfter: result.tokensAfter,
1848
+ reduction: `${(result.reduction * 100).toFixed(1)}%`,
1849
+ entriesProcessed: result.entriesProcessed,
1850
+ entriesKept: result.entriesKept,
1851
+ durationMs: result.durationMs,
1852
+ stateDistribution: result.stateDistribution,
1853
+ ...verbose && result.details ? { details: result.details } : {}
1854
+ };
1855
+ return {
1856
+ content: [
1857
+ {
1858
+ type: "text",
1859
+ text: JSON.stringify(response, null, 2)
1860
+ }
1861
+ ]
1862
+ };
1863
+ } catch (error) {
1864
+ const message = error instanceof Error ? error.message : String(error);
1865
+ return {
1866
+ content: [
1867
+ {
1868
+ type: "text",
1869
+ text: JSON.stringify({ error: message })
1870
+ }
1871
+ ],
1872
+ isError: true
1873
+ };
1874
+ }
1875
+ }
1876
+ );
1877
+ }
1878
+ function registerStatsTool(server, memory) {
1879
+ server.registerTool(
1880
+ "sparn_stats",
1881
+ {
1882
+ title: "Sparn Stats",
1883
+ description: "Get optimization statistics including total commands run, tokens saved, and average reduction percentage.",
1884
+ inputSchema: {
1885
+ reset: import_zod.z.boolean().optional().default(false).describe("If true, reset all optimization statistics")
1886
+ }
1887
+ },
1888
+ async ({ reset }) => {
1889
+ try {
1890
+ if (reset) {
1891
+ await memory.clearOptimizationStats();
1892
+ return {
1893
+ content: [
1894
+ {
1895
+ type: "text",
1896
+ text: JSON.stringify(
1897
+ {
1898
+ message: "Optimization statistics have been reset.",
1899
+ totalCommands: 0,
1900
+ totalTokensSaved: 0,
1901
+ averageReduction: "0.0%"
1902
+ },
1903
+ null,
1904
+ 2
1905
+ )
1906
+ }
1907
+ ]
1908
+ };
1909
+ }
1910
+ const stats = await memory.getOptimizationStats();
1911
+ const totalCommands = stats.length;
1912
+ const totalTokensSaved = stats.reduce(
1913
+ (sum, s) => sum + (s.tokens_before - s.tokens_after),
1914
+ 0
1915
+ );
1916
+ const averageReduction = totalCommands > 0 ? stats.reduce((sum, s) => {
1917
+ const reduction = s.tokens_before > 0 ? (s.tokens_before - s.tokens_after) / s.tokens_before : 0;
1918
+ return sum + reduction;
1919
+ }, 0) / totalCommands : 0;
1920
+ const recentOptimizations = stats.slice(0, 10).map((s) => ({
1921
+ timestamp: new Date(s.timestamp).toISOString(),
1922
+ tokensBefore: s.tokens_before,
1923
+ tokensAfter: s.tokens_after,
1924
+ entriesPruned: s.entries_pruned,
1925
+ durationMs: s.duration_ms,
1926
+ reduction: `${((s.tokens_before - s.tokens_after) / Math.max(s.tokens_before, 1) * 100).toFixed(1)}%`
1927
+ }));
1928
+ const response = {
1929
+ totalCommands,
1930
+ totalTokensSaved,
1931
+ averageReduction: `${(averageReduction * 100).toFixed(1)}%`,
1932
+ recentOptimizations
1933
+ };
1934
+ return {
1935
+ content: [
1936
+ {
1937
+ type: "text",
1938
+ text: JSON.stringify(response, null, 2)
1939
+ }
1940
+ ]
1941
+ };
1942
+ } catch (error) {
1943
+ const message = error instanceof Error ? error.message : String(error);
1944
+ return {
1945
+ content: [
1946
+ {
1947
+ type: "text",
1948
+ text: JSON.stringify({ error: message })
1949
+ }
1950
+ ],
1951
+ isError: true
1952
+ };
1953
+ }
1954
+ }
1955
+ );
1956
+ }
1957
+ function registerConsolidateTool(server, memory) {
1958
+ server.registerTool(
1959
+ "sparn_consolidate",
1960
+ {
1961
+ title: "Sparn Consolidate",
1962
+ description: "Run memory consolidation (sleep replay). Removes decayed entries and merges duplicates to reclaim space. Inspired by the neuroscience principle of sleep-based memory consolidation."
1963
+ },
1964
+ async () => {
1965
+ try {
1966
+ const allIds = await memory.list();
1967
+ const allEntries = await Promise.all(
1968
+ allIds.map(async (id) => {
1969
+ const entry = await memory.get(id);
1970
+ return entry;
1971
+ })
1972
+ );
1973
+ const entries = allEntries.filter((e) => e !== null);
1974
+ const compressor = createSleepCompressor();
1975
+ const result = compressor.consolidate(entries);
1976
+ for (const removed of result.removed) {
1977
+ await memory.delete(removed.id);
1978
+ }
1979
+ for (const kept of result.kept) {
1980
+ await memory.put(kept);
1981
+ }
1982
+ await memory.compact();
1983
+ const response = {
1984
+ entriesBefore: result.entriesBefore,
1985
+ entriesAfter: result.entriesAfter,
1986
+ decayedRemoved: result.decayedRemoved,
1987
+ duplicatesRemoved: result.duplicatesRemoved,
1988
+ compressionRatio: `${(result.compressionRatio * 100).toFixed(1)}%`,
1989
+ durationMs: result.durationMs,
1990
+ vacuumCompleted: true
1991
+ };
1992
+ return {
1993
+ content: [
1994
+ {
1995
+ type: "text",
1996
+ text: JSON.stringify(response, null, 2)
1997
+ }
1998
+ ]
1999
+ };
2000
+ } catch (error) {
2001
+ const message = error instanceof Error ? error.message : String(error);
2002
+ return {
2003
+ content: [
2004
+ {
2005
+ type: "text",
2006
+ text: JSON.stringify({ error: message })
2007
+ }
2008
+ ],
2009
+ isError: true
2010
+ };
2011
+ }
2012
+ }
2013
+ );
2014
+ }
2015
+
1797
2016
  // src/utils/logger.ts
1798
2017
  function createLogger(verbose = false) {
1799
2018
  return {
@@ -1832,6 +2051,7 @@ function createLogger(verbose = false) {
1832
2051
  createLogger,
1833
2052
  createSessionWatcher,
1834
2053
  createSleepCompressor,
2054
+ createSparnMcpServer,
1835
2055
  createSparsePruner,
1836
2056
  estimateTokens,
1837
2057
  hashContent,