@sidhxntt/token-squasher 1.0.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/index.js +210 -0
- package/package.json +30 -0
package/index.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
4
|
+
import readline from "readline";
|
|
5
|
+
|
|
6
|
+
// ─── ANSI Colors ──────────────────────────────────────────────────────────────
|
|
7
|
+
const c = {
|
|
8
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
9
|
+
red: "\x1b[38;5;203m", yellow: "\x1b[38;5;220m", green: "\x1b[38;5;114m",
|
|
10
|
+
cyan: "\x1b[38;5;117m", magenta: "\x1b[38;5;213m", gray: "\x1b[38;5;245m",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
14
|
+
const dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
15
|
+
const red = (s) => `${c.red}${s}${c.reset}`;
|
|
16
|
+
const yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
17
|
+
const green = (s) => `${c.green}${s}${c.reset}`;
|
|
18
|
+
const cyan = (s) => `${c.cyan}${s}${c.reset}`;
|
|
19
|
+
const magenta = (s) => `${c.magenta}${s}${c.reset}`;
|
|
20
|
+
const gray = (s) => `${c.gray}${s}${c.reset}`;
|
|
21
|
+
|
|
22
|
+
// ─── Token estimate ───────────────────────────────────────────────────────────
|
|
23
|
+
const estimateTokens = (t) => Math.ceil(t.length / 4);
|
|
24
|
+
|
|
25
|
+
// ─── Spinner ──────────────────────────────────────────────────────────────────
|
|
26
|
+
function makeSpinner(msg) {
|
|
27
|
+
const frames = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
|
|
28
|
+
let i = 0;
|
|
29
|
+
const timer = setInterval(() => {
|
|
30
|
+
process.stdout.write(`\r ${cyan(frames[i++ % frames.length])} ${dim(msg)}`);
|
|
31
|
+
}, 80);
|
|
32
|
+
return { stop: () => { clearInterval(timer); process.stdout.write("\r\x1b[2K"); } };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Core squash ──────────────────────────────────────────────────────────────
|
|
36
|
+
async function squashPrompt(promptText) {
|
|
37
|
+
const client = new Anthropic();
|
|
38
|
+
const resp = await client.messages.create({
|
|
39
|
+
model: "claude-sonnet-4-20250514",
|
|
40
|
+
max_tokens: 4096,
|
|
41
|
+
system: `You are a prompt compression expert. Convert verbose system prompts into ultra-dense, instruction-only format.
|
|
42
|
+
|
|
43
|
+
RULES:
|
|
44
|
+
- Strip ALL filler: pleasantries, preambles, rationale, meta-commentary
|
|
45
|
+
- Imperative verbs only: "Return JSON" not "You should return JSON"
|
|
46
|
+
- Collapse repetition: one rule, stated once
|
|
47
|
+
- Symbols: "→" "w/" "&"
|
|
48
|
+
- Numbered lists > prose paragraphs
|
|
49
|
+
- Remove examples unless the ONLY way to convey a rule
|
|
50
|
+
- Preserve ALL logic, constraints, edge cases, behavioral rules — never lose one, never add one
|
|
51
|
+
|
|
52
|
+
Return ONLY:
|
|
53
|
+
<reasoning>[max 8 bullets of what you compressed and how]</reasoning>
|
|
54
|
+
<squashed>[compressed prompt, raw text only]</squashed>`,
|
|
55
|
+
messages: [{ role: "user", content: `Compress this system prompt:\n\n${promptText}` }],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const raw = resp.content[0].text;
|
|
59
|
+
const rM = raw.match(/<reasoning>([\s\S]*?)<\/reasoning>/);
|
|
60
|
+
const sM = raw.match(/<squashed>([\s\S]*?)<\/squashed>/);
|
|
61
|
+
if (!sM) throw new Error("No <squashed> block returned. Try again.");
|
|
62
|
+
return { squashed: sM[1].trim(), reasoning: rM ? rM[1].trim() : null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Stats ────────────────────────────────────────────────────────────────────
|
|
66
|
+
function printStats(orig, sq) {
|
|
67
|
+
const o = estimateTokens(orig), s = estimateTokens(sq);
|
|
68
|
+
const pct = (((o - s) / o) * 100).toFixed(1);
|
|
69
|
+
const filled = Math.round(pct / 100 * 24);
|
|
70
|
+
const bar = `${c.green}${"█".repeat(filled)}${c.reset}${gray("░".repeat(24 - filled))}`;
|
|
71
|
+
console.log();
|
|
72
|
+
console.log(` ${bold("───── TOKEN STATS ─────")}`);
|
|
73
|
+
console.log(` ${gray("Before")} ${yellow(o.toLocaleString())} tokens ${gray(`(${orig.length.toLocaleString()} chars)`)}`);
|
|
74
|
+
console.log(` ${gray("After")} ${green(s.toLocaleString())} tokens ${gray(`(${sq.length.toLocaleString()} chars)`)}`);
|
|
75
|
+
console.log(` ${gray("Crushed")} ${bold(green(pct + "%"))} ${bar}`);
|
|
76
|
+
console.log(` ${bold("───────────────────────")}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Welcome ──────────────────────────────────────────────────────────────────
|
|
80
|
+
function printWelcome() {
|
|
81
|
+
console.clear();
|
|
82
|
+
console.log();
|
|
83
|
+
console.log(` ${bold(cyan("token-squasher"))} ${gray("v2.0.0")}`);
|
|
84
|
+
console.log(` ${gray("Compress verbose system prompts. Keep logic. Crush tokens.")}`);
|
|
85
|
+
console.log();
|
|
86
|
+
|
|
87
|
+
const hasKey = !!process.env.ANTHROPIC_API_KEY;
|
|
88
|
+
if (hasKey) {
|
|
89
|
+
console.log(` ${green("✔")} ${dim("ANTHROPIC_API_KEY detected")}`);
|
|
90
|
+
} else {
|
|
91
|
+
console.log(` ${red("✖")} ${bold("ANTHROPIC_API_KEY not set")} → ${cyan("export ANTHROPIC_API_KEY=sk-ant-...")}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(` ${dim("Paste your system prompt and press")} ${bold("Enter twice")} ${dim("to squash.")}`);
|
|
96
|
+
console.log(` ${dim("Commands:")} ${cyan(":stats")} ${dim("toggle stats")} ${cyan(":verbose")} ${dim("toggle reasoning")} ${cyan(":quit")} ${dim("exit")}`);
|
|
97
|
+
console.log(` ${dim("─".repeat(58))}`);
|
|
98
|
+
console.log();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── REPL ─────────────────────────────────────────────────────────────────────
|
|
102
|
+
async function runRepl() {
|
|
103
|
+
printWelcome();
|
|
104
|
+
|
|
105
|
+
let showStats = false;
|
|
106
|
+
let showVerbose = false;
|
|
107
|
+
let session = 0;
|
|
108
|
+
let buffer = [];
|
|
109
|
+
|
|
110
|
+
const rl = readline.createInterface({
|
|
111
|
+
input: process.stdin,
|
|
112
|
+
output: process.stdout,
|
|
113
|
+
terminal: true,
|
|
114
|
+
prompt: ` ${cyan("›")} `,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const promptNext = () => {
|
|
118
|
+
session++;
|
|
119
|
+
console.log(` ${dim("─".repeat(58))}`);
|
|
120
|
+
console.log(` ${dim(`Prompt #${session} — paste prompt then`)} ${bold("Enter twice")} ${dim("to squash")} ${gray(`[stats:${showStats?"on":"off"} verbose:${showVerbose?"on":"off"}]`)}`);
|
|
121
|
+
console.log();
|
|
122
|
+
rl.prompt();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const flush = async () => {
|
|
126
|
+
const source = buffer.join("\n").trim();
|
|
127
|
+
buffer = [];
|
|
128
|
+
if (!source) return;
|
|
129
|
+
|
|
130
|
+
// ── Commands ──
|
|
131
|
+
if (source === ":quit" || source === ":q") {
|
|
132
|
+
console.log(`\n ${gray("bye. tokens crushed. 👋\n")}`);
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
if (source === ":clear") { printWelcome(); promptNext(); return; }
|
|
136
|
+
if (source === ":stats") { showStats = !showStats; console.log(` ${dim(`stats → ${showStats ? green("on") : "off"}`)}\n`); promptNext(); return; }
|
|
137
|
+
if (source === ":verbose") { showVerbose = !showVerbose; console.log(` ${dim(`verbose → ${showVerbose ? green("on") : "off"}`)}\n`); promptNext(); return; }
|
|
138
|
+
|
|
139
|
+
// ── Squash ──
|
|
140
|
+
console.log();
|
|
141
|
+
const spin = makeSpinner("Squashing tokens…");
|
|
142
|
+
let result;
|
|
143
|
+
try {
|
|
144
|
+
result = await squashPrompt(source);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
spin.stop();
|
|
147
|
+
console.log(` ${red("✖")} ${err.message}\n`);
|
|
148
|
+
promptNext();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
spin.stop();
|
|
152
|
+
|
|
153
|
+
if (showVerbose && result.reasoning) {
|
|
154
|
+
console.log(` ${bold(magenta("REASONING"))}`);
|
|
155
|
+
result.reasoning.split("\n").forEach((l) => console.log(` ${gray(l)}`));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (showStats) printStats(source, result.squashed);
|
|
159
|
+
|
|
160
|
+
console.log();
|
|
161
|
+
console.log(` ${bold(green("─── squashed ") + "─".repeat(45))}`);
|
|
162
|
+
console.log();
|
|
163
|
+
console.log(result.squashed);
|
|
164
|
+
console.log();
|
|
165
|
+
console.log(` ${dim("─".repeat(58))}`);
|
|
166
|
+
|
|
167
|
+
promptNext();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
promptNext();
|
|
171
|
+
|
|
172
|
+
rl.on("line", (line) => {
|
|
173
|
+
// Empty line after content = submit
|
|
174
|
+
if (line.trim() === "" && buffer.length > 0) {
|
|
175
|
+
rl.pause();
|
|
176
|
+
flush().then(() => rl.resume()).catch((e) => {
|
|
177
|
+
console.error(red(` ✖ ${e.message}`));
|
|
178
|
+
rl.resume();
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
buffer.push(line);
|
|
182
|
+
rl.prompt();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
rl.on("close", () => {
|
|
187
|
+
if (buffer.length > 0) {
|
|
188
|
+
flush().then(() => { console.log(`\n ${gray("bye.\n")}`); process.exit(0); });
|
|
189
|
+
} else {
|
|
190
|
+
console.log(`\n ${gray("bye.\n")}`);
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
rl.on("SIGINT", () => {
|
|
196
|
+
if (buffer.length > 0) {
|
|
197
|
+
buffer = [];
|
|
198
|
+
console.log(`\n ${yellow("⚠")} ${gray("Input cleared.")}\n`);
|
|
199
|
+
promptNext();
|
|
200
|
+
} else {
|
|
201
|
+
console.log(`\n ${gray("bye.\n")}`);
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
runRepl().catch((e) => {
|
|
208
|
+
console.error(red(` ✖ ${e.message}`));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sidhxntt/token-squasher",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Compress verbose system prompts into dense, token-efficient instructions without losing logic",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"@sidhxntt/token-squasher": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@anthropic-ai/sdk": "^0.52.0"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"prompt",
|
|
21
|
+
"token",
|
|
22
|
+
"compression",
|
|
23
|
+
"llm",
|
|
24
|
+
"anthropic",
|
|
25
|
+
"claude",
|
|
26
|
+
"ai"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT"
|
|
30
|
+
}
|