@ulrichc1/sparn 1.1.1 → 1.2.1
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 +574 -33
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +572 -32
- 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.cjs
CHANGED
|
@@ -381,7 +381,8 @@ var init_config = __esm({
|
|
|
381
381
|
logFile: ".sparn/daemon.log",
|
|
382
382
|
debounceMs: 5e3,
|
|
383
383
|
incremental: true,
|
|
384
|
-
windowSize: 500
|
|
384
|
+
windowSize: 500,
|
|
385
|
+
consolidationInterval: null
|
|
385
386
|
}
|
|
386
387
|
};
|
|
387
388
|
}
|
|
@@ -944,16 +945,16 @@ __export(optimize_exports, {
|
|
|
944
945
|
});
|
|
945
946
|
async function optimizeCommand(options) {
|
|
946
947
|
const { memory, dryRun = false, verbose = false } = options;
|
|
947
|
-
let
|
|
948
|
+
let input2;
|
|
948
949
|
if (options.inputFile) {
|
|
949
|
-
|
|
950
|
+
input2 = await (0, import_promises2.readFile)(options.inputFile, "utf-8");
|
|
950
951
|
} else if (options.input) {
|
|
951
|
-
|
|
952
|
+
input2 = options.input;
|
|
952
953
|
} else {
|
|
953
954
|
throw new Error("No input provided. Use --input or --input-file");
|
|
954
955
|
}
|
|
955
956
|
const adapter = createGenericAdapter(memory, DEFAULT_CONFIG);
|
|
956
|
-
const result = await adapter.optimize(
|
|
957
|
+
const result = await adapter.optimize(input2, { dryRun, verbose });
|
|
957
958
|
if (options.outputFile) {
|
|
958
959
|
await (0, import_promises2.writeFile)(options.outputFile, result.optimizedContext, "utf-8");
|
|
959
960
|
}
|
|
@@ -1085,7 +1086,7 @@ async function relayCommand(options) {
|
|
|
1085
1086
|
return result;
|
|
1086
1087
|
}
|
|
1087
1088
|
function executeCommand(command, args) {
|
|
1088
|
-
return new Promise((
|
|
1089
|
+
return new Promise((resolve3) => {
|
|
1089
1090
|
const child = (0, import_node_child_process.spawn)(command, args, {
|
|
1090
1091
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1091
1092
|
});
|
|
@@ -1098,14 +1099,14 @@ function executeCommand(command, args) {
|
|
|
1098
1099
|
stderr += data.toString();
|
|
1099
1100
|
});
|
|
1100
1101
|
child.on("close", (code) => {
|
|
1101
|
-
|
|
1102
|
+
resolve3({
|
|
1102
1103
|
stdout,
|
|
1103
1104
|
stderr,
|
|
1104
1105
|
exitCode: code ?? 0
|
|
1105
1106
|
});
|
|
1106
1107
|
});
|
|
1107
1108
|
child.on("error", (error) => {
|
|
1108
|
-
|
|
1109
|
+
resolve3({
|
|
1109
1110
|
stdout,
|
|
1110
1111
|
stderr: error.message,
|
|
1111
1112
|
exitCode: 1
|
|
@@ -1765,7 +1766,7 @@ function createDaemonCommand() {
|
|
|
1765
1766
|
while (waited < maxWait) {
|
|
1766
1767
|
try {
|
|
1767
1768
|
process.kill(status2.pid, 0);
|
|
1768
|
-
await new Promise((
|
|
1769
|
+
await new Promise((resolve3) => setTimeout(resolve3, interval));
|
|
1769
1770
|
waited += interval;
|
|
1770
1771
|
} catch {
|
|
1771
1772
|
removePidFile(pidFile);
|
|
@@ -1891,8 +1892,8 @@ async function installHooks(settingsPath, prePromptPath, postToolResultPath, glo
|
|
|
1891
1892
|
}
|
|
1892
1893
|
settings["hooks"] = {
|
|
1893
1894
|
...typeof settings["hooks"] === "object" && settings["hooks"] !== null ? settings["hooks"] : {},
|
|
1894
|
-
|
|
1895
|
-
|
|
1895
|
+
prePrompt: `node ${prePromptPath}`,
|
|
1896
|
+
postToolResult: `node ${postToolResultPath}`
|
|
1896
1897
|
};
|
|
1897
1898
|
(0, import_node_fs5.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
1898
1899
|
return {
|
|
@@ -1925,8 +1926,8 @@ async function uninstallHooks(settingsPath, global) {
|
|
|
1925
1926
|
const settings = JSON.parse(settingsJson);
|
|
1926
1927
|
if (settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null) {
|
|
1927
1928
|
const hooks = settings["hooks"];
|
|
1928
|
-
delete hooks["
|
|
1929
|
-
delete hooks["
|
|
1929
|
+
delete hooks["prePrompt"];
|
|
1930
|
+
delete hooks["postToolResult"];
|
|
1930
1931
|
if (Object.keys(hooks).length === 0) {
|
|
1931
1932
|
delete settings["hooks"];
|
|
1932
1933
|
}
|
|
@@ -1956,7 +1957,7 @@ async function hooksStatus(settingsPath, global) {
|
|
|
1956
1957
|
}
|
|
1957
1958
|
const settingsJson = (0, import_node_fs5.readFileSync)(settingsPath, "utf-8");
|
|
1958
1959
|
const settings = JSON.parse(settingsJson);
|
|
1959
|
-
const hasHooks = settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null && "
|
|
1960
|
+
const hasHooks = settings["hooks"] && typeof settings["hooks"] === "object" && settings["hooks"] !== null && "prePrompt" in settings["hooks"] && "postToolResult" in settings["hooks"];
|
|
1960
1961
|
if (!hasHooks) {
|
|
1961
1962
|
return {
|
|
1962
1963
|
success: true,
|
|
@@ -1970,8 +1971,8 @@ async function hooksStatus(settingsPath, global) {
|
|
|
1970
1971
|
message: global ? "Global hooks active" : "Project hooks active",
|
|
1971
1972
|
installed: true,
|
|
1972
1973
|
hookPaths: {
|
|
1973
|
-
prePrompt: hooks["
|
|
1974
|
-
postToolResult: hooks["
|
|
1974
|
+
prePrompt: hooks["prePrompt"] || "",
|
|
1975
|
+
postToolResult: hooks["postToolResult"] || ""
|
|
1975
1976
|
}
|
|
1976
1977
|
};
|
|
1977
1978
|
} catch (error) {
|
|
@@ -1994,22 +1995,527 @@ var init_hooks = __esm({
|
|
|
1994
1995
|
}
|
|
1995
1996
|
});
|
|
1996
1997
|
|
|
1998
|
+
// src/cli/commands/interactive.ts
|
|
1999
|
+
var interactive_exports = {};
|
|
2000
|
+
__export(interactive_exports, {
|
|
2001
|
+
interactiveCommand: () => interactiveCommand
|
|
2002
|
+
});
|
|
2003
|
+
function showWelcomeBanner() {
|
|
2004
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2005
|
+
console.log(brainPink(" \u{1F9E0} Sparn Interactive Mode"));
|
|
2006
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2007
|
+
console.log(dim(" Conversational configuration and exploration\n"));
|
|
2008
|
+
}
|
|
2009
|
+
async function showMainMenu() {
|
|
2010
|
+
return (0, import_prompts.select)({
|
|
2011
|
+
message: "What would you like to do?",
|
|
2012
|
+
choices: [
|
|
2013
|
+
{
|
|
2014
|
+
name: "\u2699\uFE0F Configure Settings",
|
|
2015
|
+
value: "configure",
|
|
2016
|
+
description: "Guided configuration wizard"
|
|
2017
|
+
},
|
|
2018
|
+
{
|
|
2019
|
+
name: "\u{1F50D} Optimize Preview",
|
|
2020
|
+
value: "preview",
|
|
2021
|
+
description: "Preview optimization with confirmation"
|
|
2022
|
+
},
|
|
2023
|
+
{
|
|
2024
|
+
name: "\u{1F4CA} Stats Dashboard",
|
|
2025
|
+
value: "stats",
|
|
2026
|
+
description: "View metrics and performance data"
|
|
2027
|
+
},
|
|
2028
|
+
{
|
|
2029
|
+
name: "\u{1F9F9} Memory Consolidation",
|
|
2030
|
+
value: "consolidate",
|
|
2031
|
+
description: "Clean up decayed entries and duplicates"
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
name: "\u{1F680} Quick Actions",
|
|
2035
|
+
value: "quick",
|
|
2036
|
+
description: "Common tasks and shortcuts"
|
|
2037
|
+
},
|
|
2038
|
+
{
|
|
2039
|
+
name: "\u274C Exit",
|
|
2040
|
+
value: "exit",
|
|
2041
|
+
description: "Return to shell"
|
|
2042
|
+
}
|
|
2043
|
+
]
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
async function configureWizard(configPath) {
|
|
2047
|
+
console.log(neuralCyan("\n\u{1F4DD} Configuration Wizard\n"));
|
|
2048
|
+
const configYAML = (0, import_node_fs6.readFileSync)(configPath, "utf-8");
|
|
2049
|
+
const config = (0, import_js_yaml3.load)(configYAML);
|
|
2050
|
+
const section = await (0, import_prompts.select)({
|
|
2051
|
+
message: "Which settings would you like to configure?",
|
|
2052
|
+
choices: [
|
|
2053
|
+
{ name: "\u{1F52A} Pruning (Sparse Coding)", value: "pruning" },
|
|
2054
|
+
{ name: "\u23F3 Decay (Engram Theory)", value: "decay" },
|
|
2055
|
+
{ name: "\u{1F3AF} States (Multi-State Synapses)", value: "states" },
|
|
2056
|
+
{ name: "\u26A1 Real-time Optimization", value: "realtime" },
|
|
2057
|
+
{ name: "\u{1F3A8} UI Preferences", value: "ui" },
|
|
2058
|
+
{ name: "\u2190 Back to Main Menu", value: "back" }
|
|
2059
|
+
]
|
|
2060
|
+
});
|
|
2061
|
+
if (section === "back") return;
|
|
2062
|
+
switch (section) {
|
|
2063
|
+
case "pruning": {
|
|
2064
|
+
console.log(synapseViolet("\n\u{1F52A} Pruning Configuration"));
|
|
2065
|
+
console.log(dim("Sparse coding: Keep only the most relevant context\n"));
|
|
2066
|
+
const threshold = await (0, import_prompts.number)({
|
|
2067
|
+
message: "Pruning threshold (percentage of entries to keep):",
|
|
2068
|
+
default: config.pruning.threshold,
|
|
2069
|
+
min: 1,
|
|
2070
|
+
max: 100
|
|
2071
|
+
});
|
|
2072
|
+
const aggressiveness = await (0, import_prompts.number)({
|
|
2073
|
+
message: "Aggressiveness (0-100, affects TF-IDF weighting):",
|
|
2074
|
+
default: config.pruning.aggressiveness,
|
|
2075
|
+
min: 0,
|
|
2076
|
+
max: 100
|
|
2077
|
+
});
|
|
2078
|
+
config.pruning.threshold = threshold ?? config.pruning.threshold;
|
|
2079
|
+
config.pruning.aggressiveness = aggressiveness ?? config.pruning.aggressiveness;
|
|
2080
|
+
console.log(neuralCyan("\n\u2713 Pruning settings updated"));
|
|
2081
|
+
break;
|
|
2082
|
+
}
|
|
2083
|
+
case "decay": {
|
|
2084
|
+
console.log(synapseViolet("\n\u23F3 Decay Configuration"));
|
|
2085
|
+
console.log(dim("Engram theory: Apply time-based decay to memories\n"));
|
|
2086
|
+
const defaultTTL = await (0, import_prompts.number)({
|
|
2087
|
+
message: "Default TTL in hours:",
|
|
2088
|
+
default: config.decay.defaultTTL,
|
|
2089
|
+
min: 1
|
|
2090
|
+
});
|
|
2091
|
+
const decayThreshold = await (0, import_prompts.number)({
|
|
2092
|
+
message: "Decay threshold (0.0-1.0, entries below this are pruned):",
|
|
2093
|
+
default: config.decay.decayThreshold,
|
|
2094
|
+
min: 0,
|
|
2095
|
+
max: 1,
|
|
2096
|
+
step: 0.05
|
|
2097
|
+
});
|
|
2098
|
+
config.decay.defaultTTL = defaultTTL ?? config.decay.defaultTTL;
|
|
2099
|
+
config.decay.decayThreshold = decayThreshold ?? config.decay.decayThreshold;
|
|
2100
|
+
console.log(neuralCyan("\n\u2713 Decay settings updated"));
|
|
2101
|
+
break;
|
|
2102
|
+
}
|
|
2103
|
+
case "states": {
|
|
2104
|
+
console.log(synapseViolet("\n\u{1F3AF} State Threshold Configuration"));
|
|
2105
|
+
console.log(dim("Multi-state synapses: Classify entries as active/ready/silent\n"));
|
|
2106
|
+
const activeThreshold = await (0, import_prompts.number)({
|
|
2107
|
+
message: "Active state threshold (0.0-1.0):",
|
|
2108
|
+
default: config.states.activeThreshold,
|
|
2109
|
+
min: 0,
|
|
2110
|
+
max: 1,
|
|
2111
|
+
step: 0.05
|
|
2112
|
+
});
|
|
2113
|
+
const readyThreshold = await (0, import_prompts.number)({
|
|
2114
|
+
message: "Ready state threshold (0.0-1.0):",
|
|
2115
|
+
default: config.states.readyThreshold,
|
|
2116
|
+
min: 0,
|
|
2117
|
+
max: 1,
|
|
2118
|
+
step: 0.05
|
|
2119
|
+
});
|
|
2120
|
+
config.states.activeThreshold = activeThreshold ?? config.states.activeThreshold;
|
|
2121
|
+
config.states.readyThreshold = readyThreshold ?? config.states.readyThreshold;
|
|
2122
|
+
console.log(neuralCyan("\n\u2713 State settings updated"));
|
|
2123
|
+
break;
|
|
2124
|
+
}
|
|
2125
|
+
case "realtime": {
|
|
2126
|
+
console.log(synapseViolet("\n\u26A1 Real-time Optimization Configuration"));
|
|
2127
|
+
console.log(dim("Daemon settings for automatic optimization\n"));
|
|
2128
|
+
const tokenBudget = await (0, import_prompts.number)({
|
|
2129
|
+
message: "Target token budget:",
|
|
2130
|
+
default: config.realtime.tokenBudget,
|
|
2131
|
+
min: 1e3
|
|
2132
|
+
});
|
|
2133
|
+
const autoOptimizeThreshold = await (0, import_prompts.number)({
|
|
2134
|
+
message: "Auto-optimize threshold (triggers optimization):",
|
|
2135
|
+
default: config.realtime.autoOptimizeThreshold,
|
|
2136
|
+
min: 1e3
|
|
2137
|
+
});
|
|
2138
|
+
const windowSize = await (0, import_prompts.number)({
|
|
2139
|
+
message: "Sliding window size (entries):",
|
|
2140
|
+
default: config.realtime.windowSize,
|
|
2141
|
+
min: 100
|
|
2142
|
+
});
|
|
2143
|
+
const incremental = await (0, import_prompts.confirm)({
|
|
2144
|
+
message: "Enable incremental optimization (faster delta processing)?",
|
|
2145
|
+
default: config.realtime.incremental
|
|
2146
|
+
});
|
|
2147
|
+
config.realtime.tokenBudget = tokenBudget ?? config.realtime.tokenBudget;
|
|
2148
|
+
config.realtime.autoOptimizeThreshold = autoOptimizeThreshold ?? config.realtime.autoOptimizeThreshold;
|
|
2149
|
+
config.realtime.windowSize = windowSize ?? config.realtime.windowSize;
|
|
2150
|
+
config.realtime.incremental = incremental;
|
|
2151
|
+
console.log(neuralCyan("\n\u2713 Real-time settings updated"));
|
|
2152
|
+
break;
|
|
2153
|
+
}
|
|
2154
|
+
case "ui": {
|
|
2155
|
+
console.log(synapseViolet("\n\u{1F3A8} UI Preferences"));
|
|
2156
|
+
console.log(dim("Customize terminal output\n"));
|
|
2157
|
+
const colors = await (0, import_prompts.confirm)({
|
|
2158
|
+
message: "Enable colored output?",
|
|
2159
|
+
default: config.ui.colors
|
|
2160
|
+
});
|
|
2161
|
+
const verbose = await (0, import_prompts.confirm)({
|
|
2162
|
+
message: "Enable verbose logging?",
|
|
2163
|
+
default: config.ui.verbose
|
|
2164
|
+
});
|
|
2165
|
+
config.ui.colors = colors;
|
|
2166
|
+
config.ui.verbose = verbose;
|
|
2167
|
+
console.log(neuralCyan("\n\u2713 UI settings updated"));
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const updatedYAML = (0, import_js_yaml3.dump)(config);
|
|
2172
|
+
(0, import_node_fs6.writeFileSync)(configPath, updatedYAML, "utf-8");
|
|
2173
|
+
console.log(neuralCyan(`
|
|
2174
|
+
\u{1F4BE} Configuration saved to ${configPath}
|
|
2175
|
+
`));
|
|
2176
|
+
}
|
|
2177
|
+
async function optimizePreview(memory) {
|
|
2178
|
+
console.log(neuralCyan("\n\u{1F50D} Optimization Preview\n"));
|
|
2179
|
+
const inputFile = await (0, import_prompts.input)({
|
|
2180
|
+
message: "Input file path (or press Enter to skip):",
|
|
2181
|
+
default: ""
|
|
2182
|
+
});
|
|
2183
|
+
if (!inputFile) {
|
|
2184
|
+
console.log(dim("\nNo file specified. Returning to menu.\n"));
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
2187
|
+
try {
|
|
2188
|
+
const content = (0, import_node_fs6.readFileSync)((0, import_node_path4.resolve)(process.cwd(), inputFile), "utf-8");
|
|
2189
|
+
const tokensBefore = Math.ceil(content.length / 4);
|
|
2190
|
+
console.log(synapseViolet("\n\u{1F4C4} File Preview:"));
|
|
2191
|
+
console.log(dim(` Length: ${content.length} characters`));
|
|
2192
|
+
console.log(dim(` Estimated tokens: ${tokensBefore.toLocaleString()}
|
|
2193
|
+
`));
|
|
2194
|
+
const shouldOptimize = await (0, import_prompts.confirm)({
|
|
2195
|
+
message: "Proceed with optimization?",
|
|
2196
|
+
default: true
|
|
2197
|
+
});
|
|
2198
|
+
if (!shouldOptimize) {
|
|
2199
|
+
console.log(dim("\nOptimization cancelled.\n"));
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
console.log(neuralCyan("\n\u26A1 Optimizing...\n"));
|
|
2203
|
+
const { optimizeCommand: optimizeCommand2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
|
|
2204
|
+
const result = await optimizeCommand2({
|
|
2205
|
+
inputFile,
|
|
2206
|
+
memory,
|
|
2207
|
+
dryRun: false,
|
|
2208
|
+
verbose: false
|
|
2209
|
+
});
|
|
2210
|
+
console.log(neuralCyan(`
|
|
2211
|
+
\u2713 Optimization complete in ${result.durationMs}ms!`));
|
|
2212
|
+
console.log(synapseViolet(` Tokens: ${result.tokensBefore} \u2192 ${result.tokensAfter}`));
|
|
2213
|
+
console.log(
|
|
2214
|
+
brainPink(
|
|
2215
|
+
` Saved: ${result.tokensBefore - result.tokensAfter} tokens (${(result.reduction * 100).toFixed(1)}%)
|
|
2216
|
+
`
|
|
2217
|
+
)
|
|
2218
|
+
);
|
|
2219
|
+
const saveOutput = await (0, import_prompts.confirm)({
|
|
2220
|
+
message: "Save optimized output to file?",
|
|
2221
|
+
default: false
|
|
2222
|
+
});
|
|
2223
|
+
if (saveOutput) {
|
|
2224
|
+
const outputFile = await (0, import_prompts.input)({
|
|
2225
|
+
message: "Output file path:",
|
|
2226
|
+
default: inputFile.replace(/(\.[^.]+)$/, ".optimized$1")
|
|
2227
|
+
});
|
|
2228
|
+
(0, import_node_fs6.writeFileSync)((0, import_node_path4.resolve)(process.cwd(), outputFile), result.output, "utf-8");
|
|
2229
|
+
console.log(neuralCyan(`
|
|
2230
|
+
\u{1F4BE} Saved to ${outputFile}
|
|
2231
|
+
`));
|
|
2232
|
+
}
|
|
2233
|
+
} catch (error) {
|
|
2234
|
+
console.error(errorRed("\n\u2717 Error:"), error instanceof Error ? error.message : String(error));
|
|
2235
|
+
console.log();
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
async function showStatsDashboard(memory) {
|
|
2239
|
+
console.log(neuralCyan("\n\u{1F4CA} Stats Dashboard\n"));
|
|
2240
|
+
const view = await (0, import_prompts.select)({
|
|
2241
|
+
message: "Select view:",
|
|
2242
|
+
choices: [
|
|
2243
|
+
{ name: "\u{1F4C8} Optimization History", value: "history" },
|
|
2244
|
+
{ name: "\u26A1 Real-time Metrics", value: "realtime" },
|
|
2245
|
+
{ name: "\u{1F4BE} Memory Statistics", value: "memory" },
|
|
2246
|
+
{ name: "\u2190 Back to Main Menu", value: "back" }
|
|
2247
|
+
]
|
|
2248
|
+
});
|
|
2249
|
+
if (view === "back") return;
|
|
2250
|
+
switch (view) {
|
|
2251
|
+
case "history": {
|
|
2252
|
+
const stats = await memory.getOptimizationStats();
|
|
2253
|
+
const totalRuns = stats.length;
|
|
2254
|
+
const totalTokensSaved = stats.reduce(
|
|
2255
|
+
(sum, s) => sum + (s.tokens_before - s.tokens_after),
|
|
2256
|
+
0
|
|
2257
|
+
);
|
|
2258
|
+
const avgReduction = totalRuns > 0 ? stats.reduce((sum, s) => {
|
|
2259
|
+
return sum + (s.tokens_before > 0 ? (s.tokens_before - s.tokens_after) / s.tokens_before : 0);
|
|
2260
|
+
}, 0) / totalRuns : 0;
|
|
2261
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2262
|
+
console.log(neuralCyan(" \u{1F4C8} Optimization History"));
|
|
2263
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2264
|
+
console.log(` ${synapseViolet("Total runs:")} ${totalRuns.toLocaleString()}`);
|
|
2265
|
+
console.log(` ${synapseViolet("Tokens saved:")} ${totalTokensSaved.toLocaleString()}`);
|
|
2266
|
+
console.log(` ${synapseViolet("Avg reduction:")} ${(avgReduction * 100).toFixed(1)}%`);
|
|
2267
|
+
if (totalRuns > 0) {
|
|
2268
|
+
console.log(`
|
|
2269
|
+
${dim("Recent optimizations:")}`);
|
|
2270
|
+
const recent = stats.slice(0, 5);
|
|
2271
|
+
for (const stat of recent) {
|
|
2272
|
+
const date = new Date(stat.timestamp).toLocaleString();
|
|
2273
|
+
const reduction = stat.tokens_before > 0 ? (stat.tokens_before - stat.tokens_after) / stat.tokens_before * 100 : 0;
|
|
2274
|
+
console.log(` ${dim(date)} - ${neuralCyan(`${reduction.toFixed(1)}%`)} reduction`);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
console.log(brainPink(`${"\u2501".repeat(60)}
|
|
2278
|
+
`));
|
|
2279
|
+
break;
|
|
2280
|
+
}
|
|
2281
|
+
case "realtime": {
|
|
2282
|
+
const metrics = getMetrics();
|
|
2283
|
+
const snapshot = metrics.getSnapshot();
|
|
2284
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2285
|
+
console.log(neuralCyan(" \u26A1 Real-time Metrics"));
|
|
2286
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2287
|
+
console.log(
|
|
2288
|
+
` ${synapseViolet("Total runs:")} ${snapshot.optimization.totalRuns.toLocaleString()}`
|
|
2289
|
+
);
|
|
2290
|
+
console.log(
|
|
2291
|
+
` ${synapseViolet("Tokens saved:")} ${snapshot.optimization.totalTokensSaved.toLocaleString()}`
|
|
2292
|
+
);
|
|
2293
|
+
console.log(
|
|
2294
|
+
` ${synapseViolet("Avg reduction:")} ${(snapshot.optimization.averageReduction * 100).toFixed(1)}%`
|
|
2295
|
+
);
|
|
2296
|
+
console.log(
|
|
2297
|
+
` ${synapseViolet("P50 latency:")} ${snapshot.optimization.p50Latency.toFixed(0)}ms`
|
|
2298
|
+
);
|
|
2299
|
+
console.log(
|
|
2300
|
+
` ${synapseViolet("P95 latency:")} ${snapshot.optimization.p95Latency.toFixed(0)}ms`
|
|
2301
|
+
);
|
|
2302
|
+
console.log(
|
|
2303
|
+
` ${synapseViolet("P99 latency:")} ${snapshot.optimization.p99Latency.toFixed(0)}ms`
|
|
2304
|
+
);
|
|
2305
|
+
console.log(
|
|
2306
|
+
`
|
|
2307
|
+
${synapseViolet("Cache hit rate:")} ${(snapshot.cache.hitRate * 100).toFixed(1)}%`
|
|
2308
|
+
);
|
|
2309
|
+
console.log(
|
|
2310
|
+
` ${synapseViolet("Cache hits:")} ${snapshot.cache.totalHits.toLocaleString()}`
|
|
2311
|
+
);
|
|
2312
|
+
console.log(
|
|
2313
|
+
` ${synapseViolet("Cache misses:")} ${snapshot.cache.totalMisses.toLocaleString()}`
|
|
2314
|
+
);
|
|
2315
|
+
console.log(brainPink(`${"\u2501".repeat(60)}
|
|
2316
|
+
`));
|
|
2317
|
+
break;
|
|
2318
|
+
}
|
|
2319
|
+
case "memory": {
|
|
2320
|
+
const entries = await memory.query({});
|
|
2321
|
+
const totalEntries = entries.length;
|
|
2322
|
+
const totalSize = entries.reduce((sum, e) => sum + (e.content?.length || 0), 0);
|
|
2323
|
+
console.log(brainPink("\n\u2501".repeat(60)));
|
|
2324
|
+
console.log(neuralCyan(" \u{1F4BE} Memory Statistics"));
|
|
2325
|
+
console.log(brainPink("\u2501".repeat(60)));
|
|
2326
|
+
console.log(` ${synapseViolet("Total entries:")} ${totalEntries.toLocaleString()}`);
|
|
2327
|
+
console.log(` ${synapseViolet("Total size:")} ${(totalSize / 1024).toFixed(1)} KB`);
|
|
2328
|
+
console.log(
|
|
2329
|
+
` ${synapseViolet("Avg entry size:")} ${totalEntries > 0 ? (totalSize / totalEntries).toFixed(0) : 0} bytes`
|
|
2330
|
+
);
|
|
2331
|
+
console.log(brainPink(`${"\u2501".repeat(60)}
|
|
2332
|
+
`));
|
|
2333
|
+
break;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
async function consolidateMemory(memory) {
|
|
2338
|
+
console.log(neuralCyan("\n\u{1F9F9} Memory Consolidation\n"));
|
|
2339
|
+
console.log(dim("This will:"));
|
|
2340
|
+
console.log(dim(" \u2022 Remove decayed entries"));
|
|
2341
|
+
console.log(dim(" \u2022 Merge duplicate entries"));
|
|
2342
|
+
console.log(dim(" \u2022 VACUUM database to reclaim space\n"));
|
|
2343
|
+
const shouldConsolidate = await (0, import_prompts.confirm)({
|
|
2344
|
+
message: "Proceed with consolidation?",
|
|
2345
|
+
default: true
|
|
2346
|
+
});
|
|
2347
|
+
if (!shouldConsolidate) {
|
|
2348
|
+
console.log(dim("\nConsolidation cancelled.\n"));
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
const { consolidateCommand: consolidateCommand2 } = await Promise.resolve().then(() => (init_consolidate(), consolidate_exports));
|
|
2352
|
+
console.log(neuralCyan("\n\u26A1 Consolidating...\n"));
|
|
2353
|
+
const result = await consolidateCommand2({ memory });
|
|
2354
|
+
console.log(neuralCyan(`
|
|
2355
|
+
\u2713 Consolidation complete in ${result.durationMs}ms!`));
|
|
2356
|
+
console.log(synapseViolet(` Entries: ${result.entriesBefore} \u2192 ${result.entriesAfter}`));
|
|
2357
|
+
console.log(
|
|
2358
|
+
brainPink(
|
|
2359
|
+
` Removed: ${result.decayedRemoved} decayed, ${result.duplicatesRemoved} duplicates
|
|
2360
|
+
`
|
|
2361
|
+
)
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
async function showQuickActions(memory, configPath) {
|
|
2365
|
+
const action = await (0, import_prompts.select)({
|
|
2366
|
+
message: "Quick Actions:",
|
|
2367
|
+
choices: [
|
|
2368
|
+
{ name: "\u{1F504} Reset Statistics", value: "reset-stats" },
|
|
2369
|
+
{ name: "\u{1F4CB} Export Config (JSON)", value: "export-config" },
|
|
2370
|
+
{ name: "\u{1F9EA} Run Test Optimization", value: "test-optimize" },
|
|
2371
|
+
{ name: "\u2190 Back to Main Menu", value: "back" }
|
|
2372
|
+
]
|
|
2373
|
+
});
|
|
2374
|
+
if (action === "back") return;
|
|
2375
|
+
switch (action) {
|
|
2376
|
+
case "reset-stats": {
|
|
2377
|
+
const confirmReset = await (0, import_prompts.confirm)({
|
|
2378
|
+
message: "Are you sure you want to reset all statistics?",
|
|
2379
|
+
default: false
|
|
2380
|
+
});
|
|
2381
|
+
if (confirmReset) {
|
|
2382
|
+
await memory.clearOptimizationStats();
|
|
2383
|
+
console.log(neuralCyan("\n\u2713 Statistics cleared\n"));
|
|
2384
|
+
} else {
|
|
2385
|
+
console.log(dim("\nReset cancelled.\n"));
|
|
2386
|
+
}
|
|
2387
|
+
break;
|
|
2388
|
+
}
|
|
2389
|
+
case "export-config": {
|
|
2390
|
+
const configYAML = (0, import_node_fs6.readFileSync)(configPath, "utf-8");
|
|
2391
|
+
const config = (0, import_js_yaml3.load)(configYAML);
|
|
2392
|
+
const json = JSON.stringify(config, null, 2);
|
|
2393
|
+
console.log(synapseViolet("\n\u{1F4CB} Configuration (JSON):\n"));
|
|
2394
|
+
console.log(json);
|
|
2395
|
+
console.log();
|
|
2396
|
+
const shouldSave = await (0, import_prompts.confirm)({
|
|
2397
|
+
message: "Save to file?",
|
|
2398
|
+
default: false
|
|
2399
|
+
});
|
|
2400
|
+
if (shouldSave) {
|
|
2401
|
+
const outputPath = (0, import_node_path4.resolve)(configPath.replace(/\.yaml$/, ".json"));
|
|
2402
|
+
(0, import_node_fs6.writeFileSync)(outputPath, json, "utf-8");
|
|
2403
|
+
console.log(neuralCyan(`
|
|
2404
|
+
\u{1F4BE} Saved to ${outputPath}
|
|
2405
|
+
`));
|
|
2406
|
+
}
|
|
2407
|
+
break;
|
|
2408
|
+
}
|
|
2409
|
+
case "test-optimize": {
|
|
2410
|
+
console.log(neuralCyan("\n\u{1F9EA} Running test optimization...\n"));
|
|
2411
|
+
const testContent = `
|
|
2412
|
+
# Test Context
|
|
2413
|
+
|
|
2414
|
+
This is a test context for optimization.
|
|
2415
|
+
It includes some sample content to demonstrate the optimization process.
|
|
2416
|
+
|
|
2417
|
+
## Features
|
|
2418
|
+
- Token counting
|
|
2419
|
+
- Sparse coding
|
|
2420
|
+
- Decay application
|
|
2421
|
+
- State classification
|
|
2422
|
+
`.trim();
|
|
2423
|
+
const { optimizeCommand: optimizeCommand2 } = await Promise.resolve().then(() => (init_optimize(), optimize_exports));
|
|
2424
|
+
const result = await optimizeCommand2({
|
|
2425
|
+
input: testContent,
|
|
2426
|
+
memory,
|
|
2427
|
+
dryRun: true,
|
|
2428
|
+
verbose: false
|
|
2429
|
+
});
|
|
2430
|
+
console.log(neuralCyan(`
|
|
2431
|
+
\u2713 Test optimization complete in ${result.durationMs}ms!`));
|
|
2432
|
+
console.log(synapseViolet(` Tokens: ${result.tokensBefore} \u2192 ${result.tokensAfter}`));
|
|
2433
|
+
console.log(
|
|
2434
|
+
brainPink(
|
|
2435
|
+
` Saved: ${result.tokensBefore - result.tokensAfter} tokens (${(result.reduction * 100).toFixed(1)}%)
|
|
2436
|
+
`
|
|
2437
|
+
)
|
|
2438
|
+
);
|
|
2439
|
+
break;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
async function interactiveCommand(options) {
|
|
2444
|
+
const { memory, configPath } = options;
|
|
2445
|
+
showWelcomeBanner();
|
|
2446
|
+
let running = true;
|
|
2447
|
+
while (running) {
|
|
2448
|
+
try {
|
|
2449
|
+
const choice = await showMainMenu();
|
|
2450
|
+
switch (choice) {
|
|
2451
|
+
case "configure":
|
|
2452
|
+
await configureWizard(configPath);
|
|
2453
|
+
break;
|
|
2454
|
+
case "preview":
|
|
2455
|
+
await optimizePreview(memory);
|
|
2456
|
+
break;
|
|
2457
|
+
case "stats":
|
|
2458
|
+
await showStatsDashboard(memory);
|
|
2459
|
+
break;
|
|
2460
|
+
case "consolidate":
|
|
2461
|
+
await consolidateMemory(memory);
|
|
2462
|
+
break;
|
|
2463
|
+
case "quick":
|
|
2464
|
+
await showQuickActions(memory, configPath);
|
|
2465
|
+
break;
|
|
2466
|
+
case "exit":
|
|
2467
|
+
running = false;
|
|
2468
|
+
console.log(brainPink("\n\u{1F44B} Thanks for using Sparn!\n"));
|
|
2469
|
+
break;
|
|
2470
|
+
}
|
|
2471
|
+
} catch (error) {
|
|
2472
|
+
if (error.message === "User force closed the prompt") {
|
|
2473
|
+
running = false;
|
|
2474
|
+
console.log(brainPink("\n\u{1F44B} Thanks for using Sparn!\n"));
|
|
2475
|
+
} else {
|
|
2476
|
+
console.error(
|
|
2477
|
+
errorRed("\n\u2717 Error:"),
|
|
2478
|
+
error instanceof Error ? error.message : String(error)
|
|
2479
|
+
);
|
|
2480
|
+
console.log();
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
return {
|
|
2485
|
+
success: true,
|
|
2486
|
+
message: "Interactive session completed"
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
var import_node_fs6, import_node_path4, import_prompts, import_js_yaml3;
|
|
2490
|
+
var init_interactive = __esm({
|
|
2491
|
+
"src/cli/commands/interactive.ts"() {
|
|
2492
|
+
"use strict";
|
|
2493
|
+
init_cjs_shims();
|
|
2494
|
+
import_node_fs6 = require("fs");
|
|
2495
|
+
import_node_path4 = require("path");
|
|
2496
|
+
import_prompts = require("@inquirer/prompts");
|
|
2497
|
+
import_js_yaml3 = require("js-yaml");
|
|
2498
|
+
init_metrics();
|
|
2499
|
+
init_colors();
|
|
2500
|
+
}
|
|
2501
|
+
});
|
|
2502
|
+
|
|
1997
2503
|
// src/cli/index.ts
|
|
1998
2504
|
init_cjs_shims();
|
|
1999
2505
|
var import_node_child_process3 = require("child_process");
|
|
2000
|
-
var
|
|
2001
|
-
var
|
|
2506
|
+
var import_node_fs7 = require("fs");
|
|
2507
|
+
var import_node_path5 = require("path");
|
|
2002
2508
|
var import_node_url4 = require("url");
|
|
2003
2509
|
var import_commander = require("commander");
|
|
2004
2510
|
init_banner();
|
|
2005
2511
|
function getVersion2() {
|
|
2006
2512
|
try {
|
|
2007
|
-
const pkg = JSON.parse((0,
|
|
2513
|
+
const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path5.join)(process.cwd(), "package.json"), "utf-8"));
|
|
2008
2514
|
return pkg.version;
|
|
2009
2515
|
} catch {
|
|
2010
2516
|
const __filename2 = (0, import_node_url4.fileURLToPath)(importMetaUrl);
|
|
2011
|
-
const __dirname = (0,
|
|
2012
|
-
const pkg = JSON.parse((0,
|
|
2517
|
+
const __dirname = (0, import_node_path5.dirname)(__filename2);
|
|
2518
|
+
const pkg = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path5.join)(__dirname, "../../package.json"), "utf-8"));
|
|
2013
2519
|
return pkg.version;
|
|
2014
2520
|
}
|
|
2015
2521
|
}
|
|
@@ -2118,23 +2624,23 @@ Typical Results:
|
|
|
2118
2624
|
const spinner = createOptimizeSpinner2("\u{1F9E0} Initializing optimization...");
|
|
2119
2625
|
try {
|
|
2120
2626
|
spinner.start();
|
|
2121
|
-
let
|
|
2627
|
+
let input2;
|
|
2122
2628
|
if (!options.input && process.stdin.isTTY === false) {
|
|
2123
2629
|
spinner.text = "\u{1F4D6} Reading context from stdin...";
|
|
2124
2630
|
const chunks = [];
|
|
2125
2631
|
for await (const chunk of process.stdin) {
|
|
2126
2632
|
chunks.push(chunk);
|
|
2127
2633
|
}
|
|
2128
|
-
|
|
2634
|
+
input2 = Buffer.concat(chunks).toString("utf-8");
|
|
2129
2635
|
} else if (options.input) {
|
|
2130
2636
|
spinner.text = `\u{1F4D6} Reading context from ${options.input}...`;
|
|
2131
2637
|
}
|
|
2132
2638
|
spinner.text = "\u{1F4BE} Loading memory database...";
|
|
2133
|
-
const dbPath = (0,
|
|
2639
|
+
const dbPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/memory.db");
|
|
2134
2640
|
const memory = await createKVMemory2(dbPath);
|
|
2135
2641
|
spinner.text = "\u26A1 Applying neuroscience principles...";
|
|
2136
2642
|
const result = await optimizeCommand2({
|
|
2137
|
-
input,
|
|
2643
|
+
input: input2,
|
|
2138
2644
|
inputFile: options.input,
|
|
2139
2645
|
outputFile: options.output,
|
|
2140
2646
|
memory,
|
|
@@ -2194,7 +2700,7 @@ Tracked Metrics:
|
|
|
2194
2700
|
try {
|
|
2195
2701
|
if (spinner) spinner.start();
|
|
2196
2702
|
if (spinner) spinner.text = "\u{1F4BE} Loading optimization history...";
|
|
2197
|
-
const dbPath = (0,
|
|
2703
|
+
const dbPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/memory.db");
|
|
2198
2704
|
const memory = await createKVMemory2(dbPath);
|
|
2199
2705
|
let confirmReset = false;
|
|
2200
2706
|
if (options.reset) {
|
|
@@ -2254,7 +2760,7 @@ The relay command passes the exit code from the wrapped command.
|
|
|
2254
2760
|
const { relayCommand: relayCommand2 } = await Promise.resolve().then(() => (init_relay(), relay_exports));
|
|
2255
2761
|
const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
2256
2762
|
try {
|
|
2257
|
-
const dbPath = (0,
|
|
2763
|
+
const dbPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/memory.db");
|
|
2258
2764
|
const memory = await createKVMemory2(dbPath);
|
|
2259
2765
|
const result = await relayCommand2({
|
|
2260
2766
|
command,
|
|
@@ -2307,7 +2813,7 @@ Typical Results:
|
|
|
2307
2813
|
try {
|
|
2308
2814
|
spinner.start();
|
|
2309
2815
|
spinner.text = "\u{1F4BE} Loading memory database...";
|
|
2310
|
-
const dbPath = (0,
|
|
2816
|
+
const dbPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/memory.db");
|
|
2311
2817
|
const memory = await createKVMemory2(dbPath);
|
|
2312
2818
|
spinner.text = "\u{1F50D} Identifying decayed entries...";
|
|
2313
2819
|
const result = await consolidateCommand2({ memory });
|
|
@@ -2355,7 +2861,7 @@ The config file is located at .sparn/config.yaml
|
|
|
2355
2861
|
const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
2356
2862
|
const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
2357
2863
|
try {
|
|
2358
|
-
const configPath = (0,
|
|
2864
|
+
const configPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/config.yaml");
|
|
2359
2865
|
const result = await configCommand2({
|
|
2360
2866
|
configPath,
|
|
2361
2867
|
subcommand,
|
|
@@ -2410,13 +2916,13 @@ The daemon watches ~/.claude/projects/**/*.jsonl and automatically
|
|
|
2410
2916
|
optimizes contexts when they exceed the configured threshold.
|
|
2411
2917
|
`
|
|
2412
2918
|
).action(async (subcommand) => {
|
|
2413
|
-
const { load:
|
|
2919
|
+
const { load: parseYAML3 } = await import("js-yaml");
|
|
2414
2920
|
const { createDaemonCommand: createDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon_process(), daemon_process_exports));
|
|
2415
2921
|
const { neuralCyan: neuralCyan2, errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
2416
2922
|
try {
|
|
2417
|
-
const configPath = (0,
|
|
2418
|
-
const configYAML = (0,
|
|
2419
|
-
const config =
|
|
2923
|
+
const configPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/config.yaml");
|
|
2924
|
+
const configYAML = (0, import_node_fs7.readFileSync)(configPath, "utf-8");
|
|
2925
|
+
const config = parseYAML3(configYAML);
|
|
2420
2926
|
const daemon = createDaemonCommand2();
|
|
2421
2927
|
switch (subcommand) {
|
|
2422
2928
|
case "start": {
|
|
@@ -2526,6 +3032,41 @@ and compress verbose tool results after execution.
|
|
|
2526
3032
|
process.exit(1);
|
|
2527
3033
|
}
|
|
2528
3034
|
});
|
|
3035
|
+
program.command("interactive").alias("i").description("Launch interactive mode for configuration and exploration").addHelpText(
|
|
3036
|
+
"after",
|
|
3037
|
+
`
|
|
3038
|
+
Examples:
|
|
3039
|
+
$ sparn interactive # Launch interactive mode
|
|
3040
|
+
$ sparn i # Short alias
|
|
3041
|
+
|
|
3042
|
+
Features:
|
|
3043
|
+
\u2022 \u{1F4DD} Configuration Wizard - Guided prompts for all settings
|
|
3044
|
+
\u2022 \u{1F50D} Optimization Preview - Test optimization with file preview
|
|
3045
|
+
\u2022 \u{1F4CA} Stats Dashboard - Beautiful metrics display
|
|
3046
|
+
\u2022 \u{1F9F9} Memory Consolidation - Interactive cleanup
|
|
3047
|
+
\u2022 \u{1F680} Quick Actions - Common tasks and shortcuts
|
|
3048
|
+
|
|
3049
|
+
The interactive mode provides a conversational interface for exploring
|
|
3050
|
+
and configuring Sparn without memorizing CLI flags.
|
|
3051
|
+
`
|
|
3052
|
+
).action(async () => {
|
|
3053
|
+
const { createKVMemory: createKVMemory2 } = await Promise.resolve().then(() => (init_kv_memory(), kv_memory_exports));
|
|
3054
|
+
const { interactiveCommand: interactiveCommand2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
3055
|
+
const { errorRed: errorRed2 } = await Promise.resolve().then(() => (init_colors(), colors_exports));
|
|
3056
|
+
try {
|
|
3057
|
+
const dbPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/memory.db");
|
|
3058
|
+
const memory = await createKVMemory2(dbPath);
|
|
3059
|
+
const configPath = (0, import_node_path5.resolve)(process.cwd(), ".sparn/config.yaml");
|
|
3060
|
+
await interactiveCommand2({
|
|
3061
|
+
memory,
|
|
3062
|
+
configPath
|
|
3063
|
+
});
|
|
3064
|
+
await memory.close();
|
|
3065
|
+
} catch (error) {
|
|
3066
|
+
console.error(errorRed2("Error:"), error instanceof Error ? error.message : String(error));
|
|
3067
|
+
process.exit(1);
|
|
3068
|
+
}
|
|
3069
|
+
});
|
|
2529
3070
|
program.on("option:version", () => {
|
|
2530
3071
|
console.log(getBanner(VERSION2));
|
|
2531
3072
|
process.exit(0);
|