@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.
Files changed (2) hide show
  1. package/index.js +210 -0
  2. 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
+ }