@ulrichc1/sparn 1.1.1 → 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/README.md +52 -7
- package/dist/cli/index.cjs +567 -26
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +565 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/index.cjs +728 -159
- package/dist/daemon/index.cjs.map +1 -1
- package/dist/daemon/index.js +697 -150
- package/dist/daemon/index.js.map +1 -1
- package/dist/index.cjs +214 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.js +213 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +957 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +934 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +8 -3
package/dist/cli/index.js
CHANGED
|
@@ -358,7 +358,8 @@ var init_config = __esm({
|
|
|
358
358
|
logFile: ".sparn/daemon.log",
|
|
359
359
|
debounceMs: 5e3,
|
|
360
360
|
incremental: true,
|
|
361
|
-
windowSize: 500
|
|
361
|
+
windowSize: 500,
|
|
362
|
+
consolidationInterval: null
|
|
362
363
|
}
|
|
363
364
|
};
|
|
364
365
|
}
|
|
@@ -918,16 +919,16 @@ __export(optimize_exports, {
|
|
|
918
919
|
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
919
920
|
async function optimizeCommand(options) {
|
|
920
921
|
const { memory, dryRun = false, verbose = false } = options;
|
|
921
|
-
let
|
|
922
|
+
let input2;
|
|
922
923
|
if (options.inputFile) {
|
|
923
|
-
|
|
924
|
+
input2 = await readFile(options.inputFile, "utf-8");
|
|
924
925
|
} else if (options.input) {
|
|
925
|
-
|
|
926
|
+
input2 = options.input;
|
|
926
927
|
} else {
|
|
927
928
|
throw new Error("No input provided. Use --input or --input-file");
|
|
928
929
|
}
|
|
929
930
|
const adapter = createGenericAdapter(memory, DEFAULT_CONFIG);
|
|
930
|
-
const result = await adapter.optimize(
|
|
931
|
+
const result = await adapter.optimize(input2, { dryRun, verbose });
|
|
931
932
|
if (options.outputFile) {
|
|
932
933
|
await writeFile2(options.outputFile, result.optimizedContext, "utf-8");
|
|
933
934
|
}
|
|
@@ -1058,7 +1059,7 @@ async function relayCommand(options) {
|
|
|
1058
1059
|
return result;
|
|
1059
1060
|
}
|
|
1060
1061
|
function executeCommand(command, args) {
|
|
1061
|
-
return new Promise((
|
|
1062
|
+
return new Promise((resolve3) => {
|
|
1062
1063
|
const child = spawn(command, args, {
|
|
1063
1064
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1064
1065
|
});
|
|
@@ -1071,14 +1072,14 @@ function executeCommand(command, args) {
|
|
|
1071
1072
|
stderr += data.toString();
|
|
1072
1073
|
});
|
|
1073
1074
|
child.on("close", (code) => {
|
|
1074
|
-
|
|
1075
|
+
resolve3({
|
|
1075
1076
|
stdout,
|
|
1076
1077
|
stderr,
|
|
1077
1078
|
exitCode: code ?? 0
|
|
1078
1079
|
});
|
|
1079
1080
|
});
|
|
1080
1081
|
child.on("error", (error) => {
|
|
1081
|
-
|
|
1082
|
+
resolve3({
|
|
1082
1083
|
stdout,
|
|
1083
1084
|
stderr: error.message,
|
|
1084
1085
|
exitCode: 1
|
|
@@ -1740,7 +1741,7 @@ function createDaemonCommand() {
|
|
|
1740
1741
|
while (waited < maxWait) {
|
|
1741
1742
|
try {
|
|
1742
1743
|
process.kill(status2.pid, 0);
|
|
1743
|
-
await new Promise((
|
|
1744
|
+
await new Promise((resolve3) => setTimeout(resolve3, interval));
|
|
1744
1745
|
waited += interval;
|
|
1745
1746
|
} catch {
|
|
1746
1747
|
removePidFile(pidFile);
|
|
@@ -1963,22 +1964,526 @@ var init_hooks = __esm({
|
|
|
1963
1964
|
}
|
|
1964
1965
|
});
|
|
1965
1966
|
|
|
1967
|
+
// src/cli/commands/interactive.ts
|
|
1968
|
+
var interactive_exports = {};
|
|
1969
|
+
__export(interactive_exports, {
|
|
1970
|
+
interactiveCommand: () => interactiveCommand
|
|
1971
|
+
});
|
|
1972
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
1973
|
+
import { resolve } from "path";
|
|
1974
|
+
import { confirm, input, number, select } from "@inquirer/prompts";
|
|
1975
|
+
import { load as parseYAML2, dump as stringifyYAML2 } from "js-yaml";
|
|
1976
|
+
function showWelcomeBanner() {
|
|
1977
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
1978
|
+
console.log(brainPink(" \u{1F9E0} Sparn Interactive Mode"));
|
|
1979
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
1980
|
+
console.log(dim(" Conversational configuration and exploration\n"));
|
|
1981
|
+
}
|
|
1982
|
+
async function showMainMenu() {
|
|
1983
|
+
return select({
|
|
1984
|
+
message: "What would you like to do?",
|
|
1985
|
+
choices: [
|
|
1986
|
+
{
|
|
1987
|
+
name: "\u2699\uFE0F Configure Settings",
|
|
1988
|
+
value: "configure",
|
|
1989
|
+
description: "Guided configuration wizard"
|
|
1990
|
+
},
|
|
1991
|
+
{
|
|
1992
|
+
name: "\u{1F50D} Optimize Preview",
|
|
1993
|
+
value: "preview",
|
|
1994
|
+
description: "Preview optimization with confirmation"
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
name: "\u{1F4CA} Stats Dashboard",
|
|
1998
|
+
value: "stats",
|
|
1999
|
+
description: "View metrics and performance data"
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
name: "\u{1F9F9} Memory Consolidation",
|
|
2003
|
+
value: "consolidate",
|
|
2004
|
+
description: "Clean up decayed entries and duplicates"
|
|
2005
|
+
},
|
|
2006
|
+
{
|
|
2007
|
+
name: "\u{1F680} Quick Actions",
|
|
2008
|
+
value: "quick",
|
|
2009
|
+
description: "Common tasks and shortcuts"
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
name: "\u274C Exit",
|
|
2013
|
+
value: "exit",
|
|
2014
|
+
description: "Return to shell"
|
|
2015
|
+
}
|
|
2016
|
+
]
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
async function configureWizard(configPath) {
|
|
2020
|
+
console.log(neuralCyan("\n\u{1F4DD} Configuration Wizard\n"));
|
|
2021
|
+
const configYAML = readFileSync5(configPath, "utf-8");
|
|
2022
|
+
const config = parseYAML2(configYAML);
|
|
2023
|
+
const section = await select({
|
|
2024
|
+
message: "Which settings would you like to configure?",
|
|
2025
|
+
choices: [
|
|
2026
|
+
{ name: "\u{1F52A} Pruning (Sparse Coding)", value: "pruning" },
|
|
2027
|
+
{ name: "\u23F3 Decay (Engram Theory)", value: "decay" },
|
|
2028
|
+
{ name: "\u{1F3AF} States (Multi-State Synapses)", value: "states" },
|
|
2029
|
+
{ name: "\u26A1 Real-time Optimization", value: "realtime" },
|
|
2030
|
+
{ name: "\u{1F3A8} UI Preferences", value: "ui" },
|
|
2031
|
+
{ name: "\u2190 Back to Main Menu", value: "back" }
|
|
2032
|
+
]
|
|
2033
|
+
});
|
|
2034
|
+
if (section === "back") return;
|
|
2035
|
+
switch (section) {
|
|
2036
|
+
case "pruning": {
|
|
2037
|
+
console.log(synapseViolet("\n\u{1F52A} Pruning Configuration"));
|
|
2038
|
+
console.log(dim("Sparse coding: Keep only the most relevant context\n"));
|
|
2039
|
+
const threshold = await number({
|
|
2040
|
+
message: "Pruning threshold (percentage of entries to keep):",
|
|
2041
|
+
default: config.pruning.threshold,
|
|
2042
|
+
min: 1,
|
|
2043
|
+
max: 100
|
|
2044
|
+
});
|
|
2045
|
+
const aggressiveness = await number({
|
|
2046
|
+
message: "Aggressiveness (0-100, affects TF-IDF weighting):",
|
|
2047
|
+
default: config.pruning.aggressiveness,
|
|
2048
|
+
min: 0,
|
|
2049
|
+
max: 100
|
|
2050
|
+
});
|
|
2051
|
+
config.pruning.threshold = threshold ?? config.pruning.threshold;
|
|
2052
|
+
config.pruning.aggressiveness = aggressiveness ?? config.pruning.aggressiveness;
|
|
2053
|
+
console.log(neuralCyan("\n\u2713 Pruning settings updated"));
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
2056
|
+
case "decay": {
|
|
2057
|
+
console.log(synapseViolet("\n\u23F3 Decay Configuration"));
|
|
2058
|
+
console.log(dim("Engram theory: Apply time-based decay to memories\n"));
|
|
2059
|
+
const defaultTTL = await number({
|
|
2060
|
+
message: "Default TTL in hours:",
|
|
2061
|
+
default: config.decay.defaultTTL,
|
|
2062
|
+
min: 1
|
|
2063
|
+
});
|
|
2064
|
+
const decayThreshold = await number({
|
|
2065
|
+
message: "Decay threshold (0.0-1.0, entries below this are pruned):",
|
|
2066
|
+
default: config.decay.decayThreshold,
|
|
2067
|
+
min: 0,
|
|
2068
|
+
max: 1,
|
|
2069
|
+
step: 0.05
|
|
2070
|
+
});
|
|
2071
|
+
config.decay.defaultTTL = defaultTTL ?? config.decay.defaultTTL;
|
|
2072
|
+
config.decay.decayThreshold = decayThreshold ?? config.decay.decayThreshold;
|
|
2073
|
+
console.log(neuralCyan("\n\u2713 Decay settings updated"));
|
|
2074
|
+
break;
|
|
2075
|
+
}
|
|
2076
|
+
case "states": {
|
|
2077
|
+
console.log(synapseViolet("\n\u{1F3AF} State Threshold Configuration"));
|
|
2078
|
+
console.log(dim("Multi-state synapses: Classify entries as active/ready/silent\n"));
|
|
2079
|
+
const activeThreshold = await number({
|
|
2080
|
+
message: "Active state threshold (0.0-1.0):",
|
|
2081
|
+
default: config.states.activeThreshold,
|
|
2082
|
+
min: 0,
|
|
2083
|
+
max: 1,
|
|
2084
|
+
step: 0.05
|
|
2085
|
+
});
|
|
2086
|
+
const readyThreshold = await number({
|
|
2087
|
+
message: "Ready state threshold (0.0-1.0):",
|
|
2088
|
+
default: config.states.readyThreshold,
|
|
2089
|
+
min: 0,
|
|
2090
|
+
max: 1,
|
|
2091
|
+
step: 0.05
|
|
2092
|
+
});
|
|
2093
|
+
config.states.activeThreshold = activeThreshold ?? config.states.activeThreshold;
|
|
2094
|
+
config.states.readyThreshold = readyThreshold ?? config.states.readyThreshold;
|
|
2095
|
+
console.log(neuralCyan("\n\u2713 State settings updated"));
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
case "realtime": {
|
|
2099
|
+
console.log(synapseViolet("\n\u26A1 Real-time Optimization Configuration"));
|
|
2100
|
+
console.log(dim("Daemon settings for automatic optimization\n"));
|
|
2101
|
+
const tokenBudget = await number({
|
|
2102
|
+
message: "Target token budget:",
|
|
2103
|
+
default: config.realtime.tokenBudget,
|
|
2104
|
+
min: 1e3
|
|
2105
|
+
});
|
|
2106
|
+
const autoOptimizeThreshold = await number({
|
|
2107
|
+
message: "Auto-optimize threshold (triggers optimization):",
|
|
2108
|
+
default: config.realtime.autoOptimizeThreshold,
|
|
2109
|
+
min: 1e3
|
|
2110
|
+
});
|
|
2111
|
+
const windowSize = await number({
|
|
2112
|
+
message: "Sliding window size (entries):",
|
|
2113
|
+
default: config.realtime.windowSize,
|
|
2114
|
+
min: 100
|
|
2115
|
+
});
|
|
2116
|
+
const incremental = await confirm({
|
|
2117
|
+
message: "Enable incremental optimization (faster delta processing)?",
|
|
2118
|
+
default: config.realtime.incremental
|
|
2119
|
+
});
|
|
2120
|
+
config.realtime.tokenBudget = tokenBudget ?? config.realtime.tokenBudget;
|
|
2121
|
+
config.realtime.autoOptimizeThreshold = autoOptimizeThreshold ?? config.realtime.autoOptimizeThreshold;
|
|
2122
|
+
config.realtime.windowSize = windowSize ?? config.realtime.windowSize;
|
|
2123
|
+
config.realtime.incremental = incremental;
|
|
2124
|
+
console.log(neuralCyan("\n\u2713 Real-time settings updated"));
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
case "ui": {
|
|
2128
|
+
console.log(synapseViolet("\n\u{1F3A8} UI Preferences"));
|
|
2129
|
+
console.log(dim("Customize terminal output\n"));
|
|
2130
|
+
const colors = await confirm({
|
|
2131
|
+
message: "Enable colored output?",
|
|
2132
|
+
default: config.ui.colors
|
|
2133
|
+
});
|
|
2134
|
+
const verbose = await confirm({
|
|
2135
|
+
message: "Enable verbose logging?",
|
|
2136
|
+
default: config.ui.verbose
|
|
2137
|
+
});
|
|
2138
|
+
config.ui.colors = colors;
|
|
2139
|
+
config.ui.verbose = verbose;
|
|
2140
|
+
console.log(neuralCyan("\n\u2713 UI settings updated"));
|
|
2141
|
+
break;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
const updatedYAML = stringifyYAML2(config);
|
|
2145
|
+
writeFileSync4(configPath, updatedYAML, "utf-8");
|
|
2146
|
+
console.log(neuralCyan(`
|
|
2147
|
+
\u{1F4BE} Configuration saved to ${configPath}
|
|
2148
|
+
`));
|
|
2149
|
+
}
|
|
2150
|
+
async function optimizePreview(memory) {
|
|
2151
|
+
console.log(neuralCyan("\n\u{1F50D} Optimization Preview\n"));
|
|
2152
|
+
const inputFile = await input({
|
|
2153
|
+
message: "Input file path (or press Enter to skip):",
|
|
2154
|
+
default: ""
|
|
2155
|
+
});
|
|
2156
|
+
if (!inputFile) {
|
|
2157
|
+
console.log(dim("\nNo file specified. Returning to menu.\n"));
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
try {
|
|
2161
|
+
const content = readFileSync5(resolve(process.cwd(), inputFile), "utf-8");
|
|
2162
|
+
const tokensBefore = Math.ceil(content.length / 4);
|
|
2163
|
+
console.log(synapseViolet("\n\u{1F4C4} File Preview:"));
|
|
2164
|
+
console.log(dim(` Length: ${content.length} characters`));
|
|
2165
|
+
console.log(dim(` Estimated tokens: ${tokensBefore.toLocaleString()}
|
|
2166
|
+
`));
|
|
2167
|
+
const shouldOptimize = await confirm({
|
|
2168
|
+
message: "Proceed with optimization?",
|
|
2169
|
+
default: true
|
|
2170
|
+
});
|
|
2171
|
+
if (!shouldOptimize) {
|
|
2172
|
+
console.log(dim("\nOptimization cancelled.\n"));
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
console.log(neuralCyan("\n\u26A1 Optimizing...\n"));
|
|
2176
|
+
const { optimizeCommand: optimizeCommand2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
|
|
2177
|
+
const result = await optimizeCommand2({
|
|
2178
|
+
inputFile,
|
|
2179
|
+
memory,
|
|
2180
|
+
dryRun: false,
|
|
2181
|
+
verbose: false
|
|
2182
|
+
});
|
|
2183
|
+
console.log(neuralCyan(`
|
|
2184
|
+
\u2713 Optimization complete in ${result.durationMs}ms!`));
|
|
2185
|
+
console.log(synapseViolet(` Tokens: ${result.tokensBefore} \u2192 ${result.tokensAfter}`));
|
|
2186
|
+
console.log(
|
|
2187
|
+
brainPink(
|
|
2188
|
+
` Saved: ${result.tokensBefore - result.tokensAfter} tokens (${(result.reduction * 100).toFixed(1)}%)
|
|
2189
|
+
`
|
|
2190
|
+
)
|
|
2191
|
+
);
|
|
2192
|
+
const saveOutput = await confirm({
|
|
2193
|
+
message: "Save optimized output to file?",
|
|
2194
|
+
default: false
|
|
2195
|
+
});
|
|
2196
|
+
if (saveOutput) {
|
|
2197
|
+
const outputFile = await input({
|
|
2198
|
+
message: "Output file path:",
|
|
2199
|
+
default: inputFile.replace(/(\.[^.]+)$/, ".optimized$1")
|
|
2200
|
+
});
|
|
2201
|
+
writeFileSync4(resolve(process.cwd(), outputFile), result.output, "utf-8");
|
|
2202
|
+
console.log(neuralCyan(`
|
|
2203
|
+
\u{1F4BE} Saved to ${outputFile}
|
|
2204
|
+
`));
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
console.error(errorRed("\n\u2717 Error:"), error instanceof Error ? error.message : String(error));
|
|
2208
|
+
console.log();
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
async function showStatsDashboard(memory) {
|
|
2212
|
+
console.log(neuralCyan("\n\u{1F4CA} Stats Dashboard\n"));
|
|
2213
|
+
const view = await select({
|
|
2214
|
+
message: "Select view:",
|
|
2215
|
+
choices: [
|
|
2216
|
+
{ name: "\u{1F4C8} Optimization History", value: "history" },
|
|
2217
|
+
{ name: "\u26A1 Real-time Metrics", value: "realtime" },
|
|
2218
|
+
{ name: "\u{1F4BE} Memory Statistics", value: "memory" },
|
|
2219
|
+
{ name: "\u2190 Back to Main Menu", value: "back" }
|
|
2220
|
+
]
|
|
2221
|
+
});
|
|
2222
|
+
if (view === "back") return;
|
|
2223
|
+
switch (view) {
|
|
2224
|
+
case "history": {
|
|
2225
|
+
const stats = await memory.getOptimizationStats();
|
|
2226
|
+
const totalRuns = stats.length;
|
|
2227
|
+
const totalTokensSaved = stats.reduce(
|
|
2228
|
+
(sum, s) => sum + (s.tokens_before - s.tokens_after),
|
|
2229
|
+
0
|
|
2230
|
+
);
|
|
2231
|
+
const avgReduction = totalRuns > 0 ? stats.reduce((sum, s) => {
|
|
2232
|
+
return sum + (s.tokens_before > 0 ? (s.tokens_before - s.tokens_after) / s.tokens_before : 0);
|
|
2233
|
+
}, 0) / totalRuns : 0;
|
|
2234
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2235
|
+
console.log(neuralCyan(" \u{1F4C8} Optimization History"));
|
|
2236
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2237
|
+
console.log(` ${synapseViolet("Total runs:")} ${totalRuns.toLocaleString()}`);
|
|
2238
|
+
console.log(` ${synapseViolet("Tokens saved:")} ${totalTokensSaved.toLocaleString()}`);
|
|
2239
|
+
console.log(` ${synapseViolet("Avg reduction:")} ${(avgReduction * 100).toFixed(1)}%`);
|
|
2240
|
+
if (totalRuns > 0) {
|
|
2241
|
+
console.log(`
|
|
2242
|
+
${dim("Recent optimizations:")}`);
|
|
2243
|
+
const recent = stats.slice(0, 5);
|
|
2244
|
+
for (const stat of recent) {
|
|
2245
|
+
const date = new Date(stat.timestamp).toLocaleString();
|
|
2246
|
+
const reduction = stat.tokens_before > 0 ? (stat.tokens_before - stat.tokens_after) / stat.tokens_before * 100 : 0;
|
|
2247
|
+
console.log(` ${dim(date)} - ${neuralCyan(`${reduction.toFixed(1)}%`)} reduction`);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
console.log(brainPink(`${"\u2501".repeat(60)}
|
|
2251
|
+
`));
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
case "realtime": {
|
|
2255
|
+
const metrics = getMetrics();
|
|
2256
|
+
const snapshot = metrics.getSnapshot();
|
|
2257
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2258
|
+
console.log(neuralCyan(" \u26A1 Real-time Metrics"));
|
|
2259
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2260
|
+
console.log(
|
|
2261
|
+
` ${synapseViolet("Total runs:")} ${snapshot.optimization.totalRuns.toLocaleString()}`
|
|
2262
|
+
);
|
|
2263
|
+
console.log(
|
|
2264
|
+
` ${synapseViolet("Tokens saved:")} ${snapshot.optimization.totalTokensSaved.toLocaleString()}`
|
|
2265
|
+
);
|
|
2266
|
+
console.log(
|
|
2267
|
+
` ${synapseViolet("Avg reduction:")} ${(snapshot.optimization.averageReduction * 100).toFixed(1)}%`
|
|
2268
|
+
);
|
|
2269
|
+
console.log(
|
|
2270
|
+
` ${synapseViolet("P50 latency:")} ${snapshot.optimization.p50Latency.toFixed(0)}ms`
|
|
2271
|
+
);
|
|
2272
|
+
console.log(
|
|
2273
|
+
` ${synapseViolet("P95 latency:")} ${snapshot.optimization.p95Latency.toFixed(0)}ms`
|
|
2274
|
+
);
|
|
2275
|
+
console.log(
|
|
2276
|
+
` ${synapseViolet("P99 latency:")} ${snapshot.optimization.p99Latency.toFixed(0)}ms`
|
|
2277
|
+
);
|
|
2278
|
+
console.log(
|
|
2279
|
+
`
|
|
2280
|
+
${synapseViolet("Cache hit rate:")} ${(snapshot.cache.hitRate * 100).toFixed(1)}%`
|
|
2281
|
+
);
|
|
2282
|
+
console.log(
|
|
2283
|
+
` ${synapseViolet("Cache hits:")} ${snapshot.cache.totalHits.toLocaleString()}`
|
|
2284
|
+
);
|
|
2285
|
+
console.log(
|
|
2286
|
+
` ${synapseViolet("Cache misses:")} ${snapshot.cache.totalMisses.toLocaleString()}`
|
|
2287
|
+
);
|
|
2288
|
+
console.log(brainPink(`${"\u2501".repeat(60)}
|
|
2289
|
+
`));
|
|
2290
|
+
break;
|
|
2291
|
+
}
|
|
2292
|
+
case "memory": {
|
|
2293
|
+
const entries = await memory.query({});
|
|
2294
|
+
const totalEntries = entries.length;
|
|
2295
|
+
const totalSize = entries.reduce((sum, e) => sum + (e.content?.length || 0), 0);
|
|
2296
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2297
|
+
console.log(neuralCyan(" \u{1F4BE} Memory Statistics"));
|
|
2298
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2299
|
+
console.log(` ${synapseViolet("Total entries:")} ${totalEntries.toLocaleString()}`);
|
|
2300
|
+
console.log(` ${synapseViolet("Total size:")} ${(totalSize / 1024).toFixed(1)} KB`);
|
|
2301
|
+
console.log(
|
|
2302
|
+
` ${synapseViolet("Avg entry size:")} ${totalEntries > 0 ? (totalSize / totalEntries).toFixed(0) : 0} bytes`
|
|
2303
|
+
);
|
|
2304
|
+
console.log(brainPink(`${"\u2501".repeat(60)}
|
|
2305
|
+
`));
|
|
2306
|
+
break;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
async function consolidateMemory(memory) {
|
|
2311
|
+
console.log(neuralCyan("\n\u{1F9F9} Memory Consolidation\n"));
|
|
2312
|
+
console.log(dim("This will:"));
|
|
2313
|
+
console.log(dim(" \u2022 Remove decayed entries"));
|
|
2314
|
+
console.log(dim(" \u2022 Merge duplicate entries"));
|
|
2315
|
+
console.log(dim(" \u2022 VACUUM database to reclaim space\n"));
|
|
2316
|
+
const shouldConsolidate = await confirm({
|
|
2317
|
+
message: "Proceed with consolidation?",
|
|
2318
|
+
default: true
|
|
2319
|
+
});
|
|
2320
|
+
if (!shouldConsolidate) {
|
|
2321
|
+
console.log(dim("\nConsolidation cancelled.\n"));
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
const { consolidateCommand: consolidateCommand2 } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
|
|
2325
|
+
console.log(neuralCyan("\n\u26A1 Consolidating...\n"));
|
|
2326
|
+
const result = await consolidateCommand2({ memory });
|
|
2327
|
+
console.log(neuralCyan(`
|
|
2328
|
+
\u2713 Consolidation complete in ${result.durationMs}ms!`));
|
|
2329
|
+
console.log(synapseViolet(` Entries: ${result.entriesBefore} \u2192 ${result.entriesAfter}`));
|
|
2330
|
+
console.log(
|
|
2331
|
+
brainPink(
|
|
2332
|
+
` Removed: ${result.decayedRemoved} decayed, ${result.duplicatesRemoved} duplicates
|
|
2333
|
+
`
|
|
2334
|
+
)
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
async function showQuickActions(memory, configPath) {
|
|
2338
|
+
const action = await select({
|
|
2339
|
+
message: "Quick Actions:",
|
|
2340
|
+
choices: [
|
|
2341
|
+
{ name: "\u{1F504} Reset Statistics", value: "reset-stats" },
|
|
2342
|
+
{ name: "\u{1F4CB} Export Config (JSON)", value: "export-config" },
|
|
2343
|
+
{ name: "\u{1F9EA} Run Test Optimization", value: "test-optimize" },
|
|
2344
|
+
{ name: "\u2190 Back to Main Menu", value: "back" }
|
|
2345
|
+
]
|
|
2346
|
+
});
|
|
2347
|
+
if (action === "back") return;
|
|
2348
|
+
switch (action) {
|
|
2349
|
+
case "reset-stats": {
|
|
2350
|
+
const confirmReset = await confirm({
|
|
2351
|
+
message: "Are you sure you want to reset all statistics?",
|
|
2352
|
+
default: false
|
|
2353
|
+
});
|
|
2354
|
+
if (confirmReset) {
|
|
2355
|
+
await memory.clearOptimizationStats();
|
|
2356
|
+
console.log(neuralCyan("\n\u2713 Statistics cleared\n"));
|
|
2357
|
+
} else {
|
|
2358
|
+
console.log(dim("\nReset cancelled.\n"));
|
|
2359
|
+
}
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2362
|
+
case "export-config": {
|
|
2363
|
+
const configYAML = readFileSync5(configPath, "utf-8");
|
|
2364
|
+
const config = parseYAML2(configYAML);
|
|
2365
|
+
const json = JSON.stringify(config, null, 2);
|
|
2366
|
+
console.log(synapseViolet("\n\u{1F4CB} Configuration (JSON):\n"));
|
|
2367
|
+
console.log(json);
|
|
2368
|
+
console.log();
|
|
2369
|
+
const shouldSave = await confirm({
|
|
2370
|
+
message: "Save to file?",
|
|
2371
|
+
default: false
|
|
2372
|
+
});
|
|
2373
|
+
if (shouldSave) {
|
|
2374
|
+
const outputPath = resolve(configPath.replace(/\.yaml$/, ".json"));
|
|
2375
|
+
writeFileSync4(outputPath, json, "utf-8");
|
|
2376
|
+
console.log(neuralCyan(`
|
|
2377
|
+
\u{1F4BE} Saved to ${outputPath}
|
|
2378
|
+
`));
|
|
2379
|
+
}
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
case "test-optimize": {
|
|
2383
|
+
console.log(neuralCyan("\n\u{1F9EA} Running test optimization...\n"));
|
|
2384
|
+
const testContent = `
|
|
2385
|
+
# Test Context
|
|
2386
|
+
|
|
2387
|
+
This is a test context for optimization.
|
|
2388
|
+
It includes some sample content to demonstrate the optimization process.
|
|
2389
|
+
|
|
2390
|
+
## Features
|
|
2391
|
+
- Token counting
|
|
2392
|
+
- Sparse coding
|
|
2393
|
+
- Decay application
|
|
2394
|
+
- State classification
|
|
2395
|
+
`.trim();
|
|
2396
|
+
const { optimizeCommand: optimizeCommand2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
|
|
2397
|
+
const result = await optimizeCommand2({
|
|
2398
|
+
input: testContent,
|
|
2399
|
+
memory,
|
|
2400
|
+
dryRun: true,
|
|
2401
|
+
verbose: false
|
|
2402
|
+
});
|
|
2403
|
+
console.log(neuralCyan(`
|
|
2404
|
+
\u2713 Test optimization complete in ${result.durationMs}ms!`));
|
|
2405
|
+
console.log(synapseViolet(` Tokens: ${result.tokensBefore} \u2192 ${result.tokensAfter}`));
|
|
2406
|
+
console.log(
|
|
2407
|
+
brainPink(
|
|
2408
|
+
` Saved: ${result.tokensBefore - result.tokensAfter} tokens (${(result.reduction * 100).toFixed(1)}%)
|
|
2409
|
+
`
|
|
2410
|
+
)
|
|
2411
|
+
);
|
|
2412
|
+
break;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
async function interactiveCommand(options) {
|
|
2417
|
+
const { memory, configPath } = options;
|
|
2418
|
+
showWelcomeBanner();
|
|
2419
|
+
let running = true;
|
|
2420
|
+
while (running) {
|
|
2421
|
+
try {
|
|
2422
|
+
const choice = await showMainMenu();
|
|
2423
|
+
switch (choice) {
|
|
2424
|
+
case "configure":
|
|
2425
|
+
await configureWizard(configPath);
|
|
2426
|
+
break;
|
|
2427
|
+
case "preview":
|
|
2428
|
+
await optimizePreview(memory);
|
|
2429
|
+
break;
|
|
2430
|
+
case "stats":
|
|
2431
|
+
await showStatsDashboard(memory);
|
|
2432
|
+
break;
|
|
2433
|
+
case "consolidate":
|
|
2434
|
+
await consolidateMemory(memory);
|
|
2435
|
+
break;
|
|
2436
|
+
case "quick":
|
|
2437
|
+
await showQuickActions(memory, configPath);
|
|
2438
|
+
break;
|
|
2439
|
+
case "exit":
|
|
2440
|
+
running = false;
|
|
2441
|
+
console.log(brainPink("\n\u{1F44B} Thanks for using Sparn!\n"));
|
|
2442
|
+
break;
|
|
2443
|
+
}
|
|
2444
|
+
} catch (error) {
|
|
2445
|
+
if (error.message === "User force closed the prompt") {
|
|
2446
|
+
running = false;
|
|
2447
|
+
console.log(brainPink("\n\u{1F44B} Thanks for using Sparn!\n"));
|
|
2448
|
+
} else {
|
|
2449
|
+
console.error(
|
|
2450
|
+
errorRed("\n\u2717 Error:"),
|
|
2451
|
+
error instanceof Error ? error.message : String(error)
|
|
2452
|
+
);
|
|
2453
|
+
console.log();
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
return {
|
|
2458
|
+
success: true,
|
|
2459
|
+
message: "Interactive session completed"
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
var init_interactive = __esm({
|
|
2463
|
+
"src/cli/commands/interactive.ts"() {
|
|
2464
|
+
"use strict";
|
|
2465
|
+
init_esm_shims();
|
|
2466
|
+
init_metrics();
|
|
2467
|
+
init_colors();
|
|
2468
|
+
}
|
|
2469
|
+
});
|
|
2470
|
+
|
|
1966
2471
|
// src/cli/index.ts
|
|
1967
2472
|
init_esm_shims();
|
|
1968
2473
|
init_banner();
|
|
1969
2474
|
import { spawn as spawn2 } from "child_process";
|
|
1970
|
-
import { readFileSync as
|
|
1971
|
-
import { dirname as dirname4, join as join4, resolve } from "path";
|
|
2475
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
2476
|
+
import { dirname as dirname4, join as join4, resolve as resolve2 } from "path";
|
|
1972
2477
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1973
2478
|
import { Command } from "commander";
|
|
1974
2479
|
function getVersion2() {
|
|
1975
2480
|
try {
|
|
1976
|
-
const pkg = JSON.parse(
|
|
2481
|
+
const pkg = JSON.parse(readFileSync6(join4(process.cwd(), "package.json"), "utf-8"));
|
|
1977
2482
|
return pkg.version;
|
|
1978
2483
|
} catch {
|
|
1979
2484
|
const __filename2 = fileURLToPath5(import.meta.url);
|
|
1980
2485
|
const __dirname2 = dirname4(__filename2);
|
|
1981
|
-
const pkg = JSON.parse(
|
|
2486
|
+
const pkg = JSON.parse(readFileSync6(join4(__dirname2, "../../package.json"), "utf-8"));
|
|
1982
2487
|
return pkg.version;
|
|
1983
2488
|
}
|
|
1984
2489
|
}
|
|
@@ -2087,23 +2592,23 @@ Typical Results:
|
|
|
2087
2592
|
const spinner = createOptimizeSpinner2("\u{1F9E0} Initializing optimization...");
|
|
2088
2593
|
try {
|
|
2089
2594
|
spinner.start();
|
|
2090
|
-
let
|
|
2595
|
+
let input2;
|
|
2091
2596
|
if (!options.input && process.stdin.isTTY === false) {
|
|
2092
2597
|
spinner.text = "\u{1F4D6} Reading context from stdin...";
|
|
2093
2598
|
const chunks = [];
|
|
2094
2599
|
for await (const chunk of process.stdin) {
|
|
2095
2600
|
chunks.push(chunk);
|
|
2096
2601
|
}
|
|
2097
|
-
|
|
2602
|
+
input2 = Buffer.concat(chunks).toString("utf-8");
|
|
2098
2603
|
} else if (options.input) {
|
|
2099
2604
|
spinner.text = `\u{1F4D6} Reading context from ${options.input}...`;
|
|
2100
2605
|
}
|
|
2101
2606
|
spinner.text = "\u{1F4BE} Loading memory database...";
|
|
2102
|
-
const dbPath =
|
|
2607
|
+
const dbPath = resolve2(process.cwd(), ".sparn/memory.db");
|
|
2103
2608
|
const memory = await createKVMemory2(dbPath);
|
|
2104
2609
|
spinner.text = "\u26A1 Applying neuroscience principles...";
|
|
2105
2610
|
const result = await optimizeCommand2({
|
|
2106
|
-
input,
|
|
2611
|
+
input: input2,
|
|
2107
2612
|
inputFile: options.input,
|
|
2108
2613
|
outputFile: options.output,
|
|
2109
2614
|
memory,
|
|
@@ -2163,7 +2668,7 @@ Tracked Metrics:
|
|
|
2163
2668
|
try {
|
|
2164
2669
|
if (spinner) spinner.start();
|
|
2165
2670
|
if (spinner) spinner.text = "\u{1F4BE} Loading optimization history...";
|
|
2166
|
-
const dbPath =
|
|
2671
|
+
const dbPath = resolve2(process.cwd(), ".sparn/memory.db");
|
|
2167
2672
|
const memory = await createKVMemory2(dbPath);
|
|
2168
2673
|
let confirmReset = false;
|
|
2169
2674
|
if (options.reset) {
|
|
@@ -2223,7 +2728,7 @@ The relay command passes the exit code from the wrapped command.
|
|
|
2223
2728
|
const { relayCommand: relayCommand2 } = await Promise.resolve().then(() => (init_relay(), relay_exports));
|
|
2224
2729
|
const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
2225
2730
|
try {
|
|
2226
|
-
const dbPath =
|
|
2731
|
+
const dbPath = resolve2(process.cwd(), ".sparn/memory.db");
|
|
2227
2732
|
const memory = await createKVMemory2(dbPath);
|
|
2228
2733
|
const result = await relayCommand2({
|
|
2229
2734
|
command,
|
|
@@ -2276,7 +2781,7 @@ Typical Results:
|
|
|
2276
2781
|
try {
|
|
2277
2782
|
spinner.start();
|
|
2278
2783
|
spinner.text = "\u{1F4BE} Loading memory database...";
|
|
2279
|
-
const dbPath =
|
|
2784
|
+
const dbPath = resolve2(process.cwd(), ".sparn/memory.db");
|
|
2280
2785
|
const memory = await createKVMemory2(dbPath);
|
|
2281
2786
|
spinner.text = "\u{1F50D} Identifying decayed entries...";
|
|
2282
2787
|
const result = await consolidateCommand2({ memory });
|
|
@@ -2324,7 +2829,7 @@ The config file is located at .sparn/config.yaml
|
|
|
2324
2829
|
const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
2325
2830
|
const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
2326
2831
|
try {
|
|
2327
|
-
const configPath =
|
|
2832
|
+
const configPath = resolve2(process.cwd(), ".sparn/config.yaml");
|
|
2328
2833
|
const result = await configCommand2({
|
|
2329
2834
|
configPath,
|
|
2330
2835
|
subcommand,
|
|
@@ -2379,13 +2884,13 @@ The daemon watches ~/.claude/projects/**/*.jsonl and automatically
|
|
|
2379
2884
|
optimizes contexts when they exceed the configured threshold.
|
|
2380
2885
|
`
|
|
2381
2886
|
).action(async (subcommand) => {
|
|
2382
|
-
const { load:
|
|
2887
|
+
const { load: parseYAML3 } = await import("js-yaml");
|
|
2383
2888
|
const { createDaemonCommand: createDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports));
|
|
2384
2889
|
const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
2385
2890
|
try {
|
|
2386
|
-
const configPath =
|
|
2387
|
-
const configYAML =
|
|
2388
|
-
const config =
|
|
2891
|
+
const configPath = resolve2(process.cwd(), ".sparn/config.yaml");
|
|
2892
|
+
const configYAML = readFileSync6(configPath, "utf-8");
|
|
2893
|
+
const config = parseYAML3(configYAML);
|
|
2389
2894
|
const daemon = createDaemonCommand2();
|
|
2390
2895
|
switch (subcommand) {
|
|
2391
2896
|
case "start": {
|
|
@@ -2495,6 +3000,41 @@ and compress verbose tool results after execution.
|
|
|
2495
3000
|
process.exit(1);
|
|
2496
3001
|
}
|
|
2497
3002
|
});
|
|
3003
|
+
program.command("interactive").alias("i").description("Launch interactive mode for configuration and exploration").addHelpText(
|
|
3004
|
+
"after",
|
|
3005
|
+
`
|
|
3006
|
+
Examples:
|
|
3007
|
+
$ sparn interactive # Launch interactive mode
|
|
3008
|
+
$ sparn i # Short alias
|
|
3009
|
+
|
|
3010
|
+
Features:
|
|
3011
|
+
\u2022 \u{1F4DD} Configuration Wizard - Guided prompts for all settings
|
|
3012
|
+
\u2022 \u{1F50D} Optimization Preview - Test optimization with file preview
|
|
3013
|
+
\u2022 \u{1F4CA} Stats Dashboard - Beautiful metrics display
|
|
3014
|
+
\u2022 \u{1F9F9} Memory Consolidation - Interactive cleanup
|
|
3015
|
+
\u2022 \u{1F680} Quick Actions - Common tasks and shortcuts
|
|
3016
|
+
|
|
3017
|
+
The interactive mode provides a conversational interface for exploring
|
|
3018
|
+
and configuring Sparn without memorizing CLI flags.
|
|
3019
|
+
`
|
|
3020
|
+
).action(async () => {
|
|
3021
|
+
const { createKVMemory: createKVMemory2 } = await Promise.resolve().then(() => (init_kv_memory(), kv_memory_exports));
|
|
3022
|
+
const { interactiveCommand: interactiveCommand2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
3023
|
+
const { errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
3024
|
+
try {
|
|
3025
|
+
const dbPath = resolve2(process.cwd(), ".sparn/memory.db");
|
|
3026
|
+
const memory = await createKVMemory2(dbPath);
|
|
3027
|
+
const configPath = resolve2(process.cwd(), ".sparn/config.yaml");
|
|
3028
|
+
await interactiveCommand2({
|
|
3029
|
+
memory,
|
|
3030
|
+
configPath
|
|
3031
|
+
});
|
|
3032
|
+
await memory.close();
|
|
3033
|
+
} catch (error) {
|
|
3034
|
+
console.error(errorRed2("Error:"), error instanceof Error ? error.message : String(error));
|
|
3035
|
+
process.exit(1);
|
|
3036
|
+
}
|
|
3037
|
+
});
|
|
2498
3038
|
program.on("option:version", () => {
|
|
2499
3039
|
console.log(getBanner(VERSION2));
|
|
2500
3040
|
process.exit(0);
|