context-compress 2026.3.21 → 2026.5.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 +258 -44
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +2 -10
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/filter.d.ts +52 -0
- package/dist/cli/filter.d.ts.map +1 -0
- package/dist/cli/filter.js +200 -0
- package/dist/cli/filter.js.map +1 -0
- package/dist/cli/index.d.ts +8 -4
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +19 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/lite.d.ts +15 -0
- package/dist/cli/lite.d.ts.map +1 -0
- package/dist/cli/lite.js +37 -0
- package/dist/cli/lite.js.map +1 -0
- package/dist/cli/setup.d.ts +23 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +122 -21
- package/dist/cli/setup.js.map +1 -1
- package/dist/executor.d.ts +7 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +51 -4
- package/dist/executor.js.map +1 -1
- package/dist/filters.d.ts +52 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +719 -0
- package/dist/filters.js.map +1 -0
- package/dist/hooks/pretooluse.js +57 -0
- package/dist/hooks/pretooluse.js.map +1 -1
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +11 -0
- package/dist/network.js.map +1 -1
- package/dist/server.bundle.mjs +1333 -619
- package/dist/server.bundle.mjs.map +4 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +44 -610
- package/dist/server.js.map +1 -1
- package/dist/stats.d.ts +7 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +65 -0
- package/dist/stats.js.map +1 -1
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +15 -2
- package/dist/store.js.map +1 -1
- package/dist/tools/batch-execute.d.ts +4 -0
- package/dist/tools/batch-execute.d.ts.map +1 -0
- package/dist/tools/batch-execute.js +75 -0
- package/dist/tools/batch-execute.js.map +1 -0
- package/dist/tools/context.d.ts +17 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +2 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/discover.d.ts +4 -0
- package/dist/tools/discover.d.ts.map +1 -0
- package/dist/tools/discover.js +65 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/execute-file.d.ts +4 -0
- package/dist/tools/execute-file.d.ts.map +1 -0
- package/dist/tools/execute-file.js +66 -0
- package/dist/tools/execute-file.js.map +1 -0
- package/dist/tools/execute.d.ts +4 -0
- package/dist/tools/execute.d.ts.map +1 -0
- package/dist/tools/execute.js +54 -0
- package/dist/tools/execute.js.map +1 -0
- package/dist/tools/fetch-and-index.d.ts +4 -0
- package/dist/tools/fetch-and-index.d.ts.map +1 -0
- package/dist/tools/fetch-and-index.js +91 -0
- package/dist/tools/fetch-and-index.js.map +1 -0
- package/dist/tools/index-content.d.ts +4 -0
- package/dist/tools/index-content.d.ts.map +1 -0
- package/dist/tools/index-content.js +85 -0
- package/dist/tools/index-content.js.map +1 -0
- package/dist/tools/search.d.ts +4 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +57 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/stats.d.ts +4 -0
- package/dist/tools/stats.d.ts.map +1 -0
- package/dist/tools/stats.js +10 -0
- package/dist/tools/stats.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/util/auto-mode.d.ts +40 -0
- package/dist/util/auto-mode.d.ts.map +1 -0
- package/dist/util/auto-mode.js +181 -0
- package/dist/util/auto-mode.js.map +1 -0
- package/dist/util/fetch-code.d.ts +10 -0
- package/dist/util/fetch-code.d.ts.map +1 -0
- package/dist/util/fetch-code.js +87 -0
- package/dist/util/fetch-code.js.map +1 -0
- package/dist/util/intent-filter.d.ts +17 -0
- package/dist/util/intent-filter.d.ts.map +1 -0
- package/dist/util/intent-filter.js +28 -0
- package/dist/util/intent-filter.js.map +1 -0
- package/dist/util/label.d.ts +4 -0
- package/dist/util/label.d.ts.map +1 -0
- package/dist/util/label.js +14 -0
- package/dist/util/label.js.map +1 -0
- package/dist/util/path.d.ts +8 -0
- package/dist/util/path.d.ts.map +1 -0
- package/dist/util/path.js +21 -0
- package/dist/util/path.js.map +1 -0
- package/dist/util/stream-compress.d.ts +36 -0
- package/dist/util/stream-compress.d.ts.map +1 -0
- package/dist/util/stream-compress.js +104 -0
- package/dist/util/stream-compress.js.map +1 -0
- package/dist/util/version.d.ts +2 -0
- package/dist/util/version.d.ts.map +1 -0
- package/dist/util/version.js +15 -0
- package/dist/util/version.js.map +1 -0
- package/docs/token-reduction-report.md +164 -88
- package/hooks/pretooluse.mjs +38 -0
- package/package.json +5 -4
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto mode: ask an LLM to pick the best compression mode for a given output.
|
|
3
|
+
*
|
|
4
|
+
* Backends, in priority order:
|
|
5
|
+
* 1. Anthropic API (fastest; needs ANTHROPIC_API_KEY)
|
|
6
|
+
* 2. `claude -p` CLI (works in Claude Code environments; ~3s per call)
|
|
7
|
+
* 3. Heuristic (last resort, no LLM)
|
|
8
|
+
*
|
|
9
|
+
* Decisions are cached per command fingerprint with a 24h TTL so repeated
|
|
10
|
+
* commands don't pay the LLM round-trip cost. Cache lives at
|
|
11
|
+
* ~/.context-compress/auto-cache.json so it survives across sessions.
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
const CACHE_PATH = join(homedir(), ".context-compress", "auto-cache.json");
|
|
18
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 8_000;
|
|
20
|
+
const SAMPLE_BYTES = 500;
|
|
21
|
+
function loadCache() {
|
|
22
|
+
if (!existsSync(CACHE_PATH))
|
|
23
|
+
return {};
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(CACHE_PATH, "utf-8"));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function saveCache(cache) {
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(dirname(CACHE_PATH), { recursive: true });
|
|
34
|
+
writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* ignore — cache is best-effort */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Cache key: the first two whitespace-separated tokens of the command.
|
|
42
|
+
* "git log -10 --abbrev-commit" → "git log"
|
|
43
|
+
* Coarse enough to share decisions across argument variants, fine enough
|
|
44
|
+
* that "git log" and "git diff" are tracked separately.
|
|
45
|
+
*/
|
|
46
|
+
function fingerprint(cmd) {
|
|
47
|
+
return cmd.trim().split(/\s+/).slice(0, 2).join(" ");
|
|
48
|
+
}
|
|
49
|
+
function buildPrompt(cmd, sample) {
|
|
50
|
+
return [
|
|
51
|
+
"You pick the best output-compression mode for an AI coding agent.",
|
|
52
|
+
"",
|
|
53
|
+
`Command: ${cmd}`,
|
|
54
|
+
"",
|
|
55
|
+
"Output sample (first 500 chars, may be truncated):",
|
|
56
|
+
"---",
|
|
57
|
+
sample,
|
|
58
|
+
"---",
|
|
59
|
+
"",
|
|
60
|
+
"Modes:",
|
|
61
|
+
"- conservative: preserve everything verbatim (almost no compression)",
|
|
62
|
+
"- balanced: drop universal noise (progress bars, hint lines, ./.., total N), truncate git log bodies past 3 lines, summarize find/ls -R past 20 entries; keeps all metadata",
|
|
63
|
+
"- aggressive: drop metadata too (git log → one-line, ls -la → name+size, find → directory summary, drops file perms/dates)",
|
|
64
|
+
"",
|
|
65
|
+
"Pick whichever fits this output best. Reply with EXACTLY ONE WORD: conservative, balanced, or aggressive.",
|
|
66
|
+
].join("\n");
|
|
67
|
+
}
|
|
68
|
+
function parseMode(text) {
|
|
69
|
+
const t = text.toLowerCase();
|
|
70
|
+
if (t.includes("aggressive"))
|
|
71
|
+
return "aggressive";
|
|
72
|
+
if (t.includes("conservative"))
|
|
73
|
+
return "conservative";
|
|
74
|
+
if (t.includes("balanced"))
|
|
75
|
+
return "balanced";
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
async function callAnthropic(prompt, opts) {
|
|
79
|
+
const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
80
|
+
if (!apiKey)
|
|
81
|
+
throw new Error("no API key");
|
|
82
|
+
const r = await fetch("https://api.anthropic.com/v1/messages", {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
"x-api-key": apiKey,
|
|
87
|
+
"anthropic-version": "2023-06-01",
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
model: opts.model ?? "claude-haiku-4-5-20251001",
|
|
91
|
+
max_tokens: 16,
|
|
92
|
+
messages: [{ role: "user", content: prompt }],
|
|
93
|
+
}),
|
|
94
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
95
|
+
});
|
|
96
|
+
if (!r.ok)
|
|
97
|
+
throw new Error(`Anthropic API ${r.status}`);
|
|
98
|
+
const data = (await r.json());
|
|
99
|
+
const text = data.content?.[0]?.text ?? "";
|
|
100
|
+
const mode = parseMode(text);
|
|
101
|
+
if (!mode)
|
|
102
|
+
throw new Error(`could not parse mode from: ${text}`);
|
|
103
|
+
return mode;
|
|
104
|
+
}
|
|
105
|
+
function callClaudeCli(prompt, timeoutMs) {
|
|
106
|
+
const r = spawnSync("claude", ["-p", prompt], {
|
|
107
|
+
encoding: "utf-8",
|
|
108
|
+
timeout: timeoutMs,
|
|
109
|
+
// Avoid inheriting interactive/tty state that the CLI might react to.
|
|
110
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
111
|
+
});
|
|
112
|
+
if (r.status !== 0 || r.signal)
|
|
113
|
+
throw new Error("claude CLI failed");
|
|
114
|
+
const mode = parseMode(r.stdout);
|
|
115
|
+
if (!mode)
|
|
116
|
+
throw new Error(`could not parse mode from: ${r.stdout}`);
|
|
117
|
+
return mode;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Heuristic last-resort: pick a sensible mode without touching the LLM.
|
|
121
|
+
* This is what auto mode degrades to when both API and CLI are unavailable
|
|
122
|
+
* (or when AutoOptions.noLlm is true).
|
|
123
|
+
*
|
|
124
|
+
* Exported so callers / tests can use the heuristic directly.
|
|
125
|
+
*/
|
|
126
|
+
export function heuristicMode(cmd, output) {
|
|
127
|
+
const len = output.length;
|
|
128
|
+
if (len < 1000)
|
|
129
|
+
return "conservative";
|
|
130
|
+
if (/^git\s+log\b/.test(cmd) && !cmd.includes("--oneline"))
|
|
131
|
+
return "aggressive";
|
|
132
|
+
if (/(test|jest|pytest|vitest)\b/.test(cmd) && len > 5000)
|
|
133
|
+
return "aggressive";
|
|
134
|
+
if (/^find\b/.test(cmd) && len > 2000)
|
|
135
|
+
return "aggressive";
|
|
136
|
+
if (/^ls\b/.test(cmd) && /-l/.test(cmd))
|
|
137
|
+
return "aggressive";
|
|
138
|
+
return "balanced";
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Pick a compression mode for the given command + output. Tries the LLM
|
|
142
|
+
* (API → CLI), falls back to a heuristic. Caches per-command-fingerprint.
|
|
143
|
+
*/
|
|
144
|
+
export async function pickModeAuto(cmd, output, opts = {}) {
|
|
145
|
+
const fp = fingerprint(cmd);
|
|
146
|
+
const cache = opts.noCache ? {} : loadCache();
|
|
147
|
+
const cached = cache[fp];
|
|
148
|
+
if (cached && cached.expires > Date.now()) {
|
|
149
|
+
return { mode: cached.mode, source: "cache" };
|
|
150
|
+
}
|
|
151
|
+
let mode;
|
|
152
|
+
let source;
|
|
153
|
+
if (opts.noLlm) {
|
|
154
|
+
mode = heuristicMode(cmd, output);
|
|
155
|
+
source = "heuristic";
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const sample = output.slice(0, SAMPLE_BYTES);
|
|
159
|
+
const prompt = buildPrompt(cmd, sample);
|
|
160
|
+
try {
|
|
161
|
+
mode = await callAnthropic(prompt, opts);
|
|
162
|
+
source = "api";
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
try {
|
|
166
|
+
mode = callClaudeCli(prompt, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
167
|
+
source = "cli";
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
mode = heuristicMode(cmd, output);
|
|
171
|
+
source = "heuristic";
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!opts.noCache) {
|
|
176
|
+
cache[fp] = { mode, expires: Date.now() + TTL_MS };
|
|
177
|
+
saveCache(cache);
|
|
178
|
+
}
|
|
179
|
+
return { mode, source };
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=auto-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-mode.js","sourceRoot":"","sources":["../../src/util/auto-mode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a self-contained JS snippet that fetches a URL, converts HTML→markdown,
|
|
3
|
+
* and prints the result. Runs inside the sandbox subprocess.
|
|
4
|
+
*
|
|
5
|
+
* When `resolvedIp` is provided, the URL is rewritten to connect to that IP
|
|
6
|
+
* with the original Host header preserved — defeats DNS rebinding (TOCTOU)
|
|
7
|
+
* between the validation step and the actual fetch.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildFetchCode(url: string, resolvedIp?: string | null): string;
|
|
10
|
+
//# sourceMappingURL=fetch-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-code.d.ts","sourceRoot":"","sources":["../../src/util/fetch-code.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CA6E9E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a self-contained JS snippet that fetches a URL, converts HTML→markdown,
|
|
3
|
+
* and prints the result. Runs inside the sandbox subprocess.
|
|
4
|
+
*
|
|
5
|
+
* When `resolvedIp` is provided, the URL is rewritten to connect to that IP
|
|
6
|
+
* with the original Host header preserved — defeats DNS rebinding (TOCTOU)
|
|
7
|
+
* between the validation step and the actual fetch.
|
|
8
|
+
*/
|
|
9
|
+
export function buildFetchCode(url, resolvedIp) {
|
|
10
|
+
let fetchSetup;
|
|
11
|
+
if (resolvedIp) {
|
|
12
|
+
const pinnedUrl = new URL(url);
|
|
13
|
+
const originalHost = pinnedUrl.host;
|
|
14
|
+
// URL.hostname setter requires IPv6 literals to be bracketed; raw forms
|
|
15
|
+
// like "2001:db8::1" parse incorrectly (the first ":" is treated as a
|
|
16
|
+
// port delimiter). Detect IPv6 by colon-presence and wrap.
|
|
17
|
+
const hostnameValue = resolvedIp.includes(":") && !resolvedIp.startsWith("[") ? `[${resolvedIp}]` : resolvedIp;
|
|
18
|
+
pinnedUrl.hostname = hostnameValue;
|
|
19
|
+
fetchSetup = `
|
|
20
|
+
const url = ${JSON.stringify(pinnedUrl.toString())};
|
|
21
|
+
const resp = await fetch(url, { headers: { 'Host': ${JSON.stringify(originalHost)} }, redirect: 'error' });`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
fetchSetup = `
|
|
25
|
+
const url = ${JSON.stringify(url)};
|
|
26
|
+
const resp = await fetch(url, { redirect: 'error' });`;
|
|
27
|
+
}
|
|
28
|
+
return `${fetchSetup}
|
|
29
|
+
if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
|
|
30
|
+
const cl = resp.headers.get('content-length');
|
|
31
|
+
if (cl && parseInt(cl, 10) > 10 * 1024 * 1024) {
|
|
32
|
+
console.error("Response too large: " + cl + " bytes"); process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const html = await resp.text();
|
|
35
|
+
if (html.length > 10 * 1024 * 1024) {
|
|
36
|
+
console.error("Response body too large: " + html.length + " chars"); process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Strip unwanted tags
|
|
40
|
+
let md = html
|
|
41
|
+
.replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, "")
|
|
42
|
+
.replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, "")
|
|
43
|
+
.replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, "")
|
|
44
|
+
.replace(/<header[^>]*>[\\s\\S]*?<\\/header>/gi, "")
|
|
45
|
+
.replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, "");
|
|
46
|
+
|
|
47
|
+
// Convert headings
|
|
48
|
+
md = md.replace(/<h1[^>]*>(.*?)<\\/h1>/gi, "# $1\\n");
|
|
49
|
+
md = md.replace(/<h2[^>]*>(.*?)<\\/h2>/gi, "## $1\\n");
|
|
50
|
+
md = md.replace(/<h3[^>]*>(.*?)<\\/h3>/gi, "### $1\\n");
|
|
51
|
+
md = md.replace(/<h4[^>]*>(.*?)<\\/h4>/gi, "#### $1\\n");
|
|
52
|
+
|
|
53
|
+
// Convert code blocks
|
|
54
|
+
md = md.replace(/<pre[^>]*><code[^>]*>(.*?)<\\/code><\\/pre>/gis, "\`\`\`\\n$1\\n\`\`\`\\n");
|
|
55
|
+
md = md.replace(/<code[^>]*>(.*?)<\\/code>/gi, "\`$1\`");
|
|
56
|
+
|
|
57
|
+
// Convert links
|
|
58
|
+
md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\\/a>/gi, "[$2]($1)");
|
|
59
|
+
|
|
60
|
+
// Convert lists
|
|
61
|
+
md = md.replace(/<li[^>]*>(.*?)<\\/li>/gi, "- $1\\n");
|
|
62
|
+
|
|
63
|
+
// Convert paragraphs
|
|
64
|
+
md = md.replace(/<p[^>]*>(.*?)<\\/p>/gis, "$1\\n\\n");
|
|
65
|
+
md = md.replace(/<br\\s*\\/?>/gi, "\\n");
|
|
66
|
+
|
|
67
|
+
// Strip remaining tags
|
|
68
|
+
md = md.replace(/<[^>]+>/g, "");
|
|
69
|
+
|
|
70
|
+
// Decode entities
|
|
71
|
+
md = md.replace(/</g, "<")
|
|
72
|
+
.replace(/>/g, ">")
|
|
73
|
+
.replace(/"/g, '"')
|
|
74
|
+
.replace(/'/g, "'")
|
|
75
|
+
.replace(/'/g, "'")
|
|
76
|
+
.replace(/ /g, " ")
|
|
77
|
+
.replace(/&#(\\d+);/g, (_, n) => { const c = parseInt(n, 10); return c > 0 && c <= 0x10FFFF ? String.fromCodePoint(c) : ''; })
|
|
78
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => { const c = parseInt(h, 16); return c > 0 && c <= 0x10FFFF ? String.fromCodePoint(c) : ''; })
|
|
79
|
+
.replace(/&/g, "&");
|
|
80
|
+
|
|
81
|
+
// Clean whitespace
|
|
82
|
+
md = md.replace(/\\n{3,}/g, "\\n\\n").trim();
|
|
83
|
+
|
|
84
|
+
console.log(md);
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=fetch-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-code.js","sourceRoot":"","sources":["../../src/util/fetch-code.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,UAA0B;IACrE,IAAI,UAAkB,CAAC;IACvB,IAAI,UAAU,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,YAAY,GAAG,SAAS,CAAC,IAAI,CAAC;QACpC,wEAAwE;QACxE,sEAAsE;QACtE,2DAA2D;QAC3D,MAAM,aAAa,GAClB,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC;QAC1F,SAAS,CAAC,QAAQ,GAAG,aAAa,CAAC;QACnC,UAAU,GAAG;cACD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;qDACG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,2BAA2B,CAAC;IAC5G,CAAC;SAAM,CAAC;QACP,UAAU,GAAG;cACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;sDACqB,CAAC;IACtD,CAAC;IACD,OAAO,GAAG,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDpB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Config } from "../config.js";
|
|
2
|
+
import type { SessionTracker } from "../stats.js";
|
|
3
|
+
import type { ContentStore } from "../store.js";
|
|
4
|
+
interface IntentFilterDeps {
|
|
5
|
+
config: Config;
|
|
6
|
+
store: ContentStore;
|
|
7
|
+
tracker: SessionTracker;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Index large output and return a compact summary keyed to `intent`.
|
|
11
|
+
* For small output (<= config.intentSearchThreshold bytes), returns the
|
|
12
|
+
* original output unchanged so callers don't pay for trivial filtering.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createIntentFilter(deps: IntentFilterDeps): (output: string, intent: string, sourceLabel: string) => string;
|
|
15
|
+
export type ApplyIntentFilter = ReturnType<typeof createIntentFilter>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=intent-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-filter.d.ts","sourceRoot":"","sources":["../../src/util/intent-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,UAAU,gBAAgB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,cAAc,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAGtB,QAAQ,MAAM,EAAE,QAAQ,MAAM,EAAE,aAAa,MAAM,KAAG,MAAM,CAoB9F;AAED,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { compactLabel } from "./label.js";
|
|
2
|
+
/**
|
|
3
|
+
* Index large output and return a compact summary keyed to `intent`.
|
|
4
|
+
* For small output (<= config.intentSearchThreshold bytes), returns the
|
|
5
|
+
* original output unchanged so callers don't pay for trivial filtering.
|
|
6
|
+
*/
|
|
7
|
+
export function createIntentFilter(deps) {
|
|
8
|
+
const { config, store, tracker } = deps;
|
|
9
|
+
return function applyIntentFilter(output, intent, sourceLabel) {
|
|
10
|
+
if (Buffer.byteLength(output) <= config.intentSearchThreshold)
|
|
11
|
+
return output;
|
|
12
|
+
const indexed = store.index(output, sourceLabel);
|
|
13
|
+
tracker.trackIndexed(Buffer.byteLength(output));
|
|
14
|
+
const searchResults = store.search(intent, { limit: 3 });
|
|
15
|
+
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
16
|
+
let filtered = `Indexed ${indexed.totalChunks} sections from ${sourceLabel}.\n`;
|
|
17
|
+
filtered += `${searchResults.results.length} sections matched "${intent}":\n\n`;
|
|
18
|
+
for (const hit of searchResults.results) {
|
|
19
|
+
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}\n`;
|
|
20
|
+
}
|
|
21
|
+
if (terms.length > 0 && config.compressionLevel !== "ultra") {
|
|
22
|
+
filtered += `\nSearchable terms: ${terms.join(", ")}\n`;
|
|
23
|
+
}
|
|
24
|
+
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
25
|
+
return compactLabel(filtered, config.compressionLevel);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=intent-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-filter.js","sourceRoot":"","sources":["../../src/util/intent-filter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ1C;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAsB;IACxD,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAExC,OAAO,SAAS,iBAAiB,CAAC,MAAc,EAAE,MAAc,EAAE,WAAmB;QACpF,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,qBAAqB;YAAE,OAAO,MAAM,CAAC;QAE7E,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACjD,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAEhD,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,KAAK,CAAC,mBAAmB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE1D,IAAI,QAAQ,GAAG,WAAW,OAAO,CAAC,WAAW,kBAAkB,WAAW,KAAK,CAAC;QAChF,QAAQ,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,sBAAsB,MAAM,QAAQ,CAAC;QAChF,KAAK,MAAM,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;YACzC,QAAQ,IAAI,SAAS,GAAG,CAAC,KAAK,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC;QACpE,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,gBAAgB,KAAK,OAAO,EAAE,CAAC;YAC7D,QAAQ,IAAI,uBAAuB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACzD,CAAC;QACD,QAAQ,IAAI,uEAAuE,CAAC;QACpF,OAAO,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACxD,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label.d.ts","sourceRoot":"","sources":["../../src/util/label.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAc5E"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Shorten labels based on compression level */
|
|
2
|
+
export function compactLabel(normal, level) {
|
|
3
|
+
if (level === "ultra") {
|
|
4
|
+
return normal
|
|
5
|
+
.replace(/\*\*/g, "")
|
|
6
|
+
.replace(/Use search\(queries: \[\.\.\.]\) to retrieve.*$/gm, "→ search() for more")
|
|
7
|
+
.replace(/Searchable terms: .+$/gm, "");
|
|
8
|
+
}
|
|
9
|
+
if (level === "compact") {
|
|
10
|
+
return normal.replace(/Use search\(queries: \[\.\.\.]\) to retrieve full content of any section\./, "→ search() for details");
|
|
11
|
+
}
|
|
12
|
+
return normal;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"label.js","sourceRoot":"","sources":["../../src/util/label.ts"],"names":[],"mappings":"AAEA,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,KAAuB;IACnE,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,MAAM;aACX,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;aACpB,OAAO,CAAC,mDAAmD,EAAE,qBAAqB,CAAC;aACnF,OAAO,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,OAAO,CACpB,4EAA4E,EAC5E,wBAAwB,CACxB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when `absPath` resolves inside (or equal to) `projectDir`.
|
|
3
|
+
* Uses realpathSync to defeat symlink-based escapes when the path exists,
|
|
4
|
+
* falling back to a string-prefix check for paths that don't exist yet
|
|
5
|
+
* (e.g. files about to be written).
|
|
6
|
+
*/
|
|
7
|
+
export declare function isWithinProject(absPath: string, projectDir: string): boolean;
|
|
8
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAU5E"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when `absPath` resolves inside (or equal to) `projectDir`.
|
|
5
|
+
* Uses realpathSync to defeat symlink-based escapes when the path exists,
|
|
6
|
+
* falling back to a string-prefix check for paths that don't exist yet
|
|
7
|
+
* (e.g. files about to be written).
|
|
8
|
+
*/
|
|
9
|
+
export function isWithinProject(absPath, projectDir) {
|
|
10
|
+
try {
|
|
11
|
+
const normalized = realpathSync(resolve(absPath));
|
|
12
|
+
const realProjectDir = realpathSync(projectDir);
|
|
13
|
+
return normalized === realProjectDir || normalized.startsWith(`${realProjectDir}/`);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
const normalized = resolve(absPath);
|
|
17
|
+
const normalizedProject = resolve(projectDir);
|
|
18
|
+
return normalized === normalizedProject || normalized.startsWith(`${normalizedProject}/`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,UAAkB;IAClE,IAAI,CAAC;QACJ,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,MAAM,cAAc,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;QAChD,OAAO,UAAU,KAAK,cAAc,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,cAAc,GAAG,CAAC,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAC9C,OAAO,UAAU,KAAK,iBAAiB,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,iBAAiB,GAAG,CAAC,CAAC;IAC3F,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-by-line streaming compressor for long-running commands.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the buffered pipeline (executor.ts / cli/filter.ts), this can emit
|
|
5
|
+
* compressed output before the child process exits. Necessary for
|
|
6
|
+
* `tail -f`, `cargo watch`, build commands with progressive output, etc.
|
|
7
|
+
*
|
|
8
|
+
* Trade-off: only stream-safe transformations are applied —
|
|
9
|
+
* - ANSI stripping (per-line, no state)
|
|
10
|
+
* - Progress/spinner line removal (per-line, no state)
|
|
11
|
+
* - Adjacent-duplicate dedup (single-line lookback)
|
|
12
|
+
*
|
|
13
|
+
* Skipped because they need full output:
|
|
14
|
+
* - applyCommandFilter (needs to detect summary/test markers globally)
|
|
15
|
+
* - groupErrorLines (needs full set of error patterns)
|
|
16
|
+
* - smartTruncate (needs final length)
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Stateful streaming compressor. Feed it chunks via `process(chunk)`, get
|
|
20
|
+
* compressed output back. Call `flush()` at end-of-stream to drain any
|
|
21
|
+
* buffered incomplete line + emit any pending dedup counter.
|
|
22
|
+
*/
|
|
23
|
+
export declare class StreamCompressor {
|
|
24
|
+
private buffer;
|
|
25
|
+
private prevLine;
|
|
26
|
+
private repeatCount;
|
|
27
|
+
/** Process a chunk; returns the filtered output ready to emit (may be empty). */
|
|
28
|
+
process(chunk: string): string;
|
|
29
|
+
/** Drain any remaining buffered line and emit final dedup counter. */
|
|
30
|
+
flush(): string;
|
|
31
|
+
/** Process the partial line in `buffer` (no trailing newline) and clear the buffer. */
|
|
32
|
+
private consumeBufferedLine;
|
|
33
|
+
/** Emit "(×N identical lines)" if a repeat counter is pending; reset state. */
|
|
34
|
+
private drainRepeatCounter;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=stream-compress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-compress.d.ts","sourceRoot":"","sources":["../../src/util/stream-compress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAqBH;;;;GAIG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,WAAW,CAAK;IAExB,iFAAiF;IACjF,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IA2B9B,sEAAsE;IACtE,KAAK,IAAI,MAAM;IASf,uFAAuF;IACvF,OAAO,CAAC,mBAAmB;IAa3B,+EAA+E;IAC/E,OAAO,CAAC,kBAAkB;CAK1B"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-by-line streaming compressor for long-running commands.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the buffered pipeline (executor.ts / cli/filter.ts), this can emit
|
|
5
|
+
* compressed output before the child process exits. Necessary for
|
|
6
|
+
* `tail -f`, `cargo watch`, build commands with progressive output, etc.
|
|
7
|
+
*
|
|
8
|
+
* Trade-off: only stream-safe transformations are applied —
|
|
9
|
+
* - ANSI stripping (per-line, no state)
|
|
10
|
+
* - Progress/spinner line removal (per-line, no state)
|
|
11
|
+
* - Adjacent-duplicate dedup (single-line lookback)
|
|
12
|
+
*
|
|
13
|
+
* Skipped because they need full output:
|
|
14
|
+
* - applyCommandFilter (needs to detect summary/test markers globally)
|
|
15
|
+
* - groupErrorLines (needs full set of error patterns)
|
|
16
|
+
* - smartTruncate (needs final length)
|
|
17
|
+
*/
|
|
18
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape detection requires \x1b
|
|
19
|
+
const ANSI_RE_G = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
20
|
+
const PROGRESS_BAR_RE = /^[\s\[│├└─═━▓░█▒▏▎▍▌▋▊▉\]>=#\-.\d%]+$/;
|
|
21
|
+
const SPINNER_RE = /^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏\-\\|/]\s/;
|
|
22
|
+
const DOWNLOAD_RE = /(?:downloading|uploading|fetching|resolving)\s+[\d.]+\s*[kmg]?b/i;
|
|
23
|
+
const SPEED_ETA_RE = /\d+\.?\d*\s*[kmg]?b\/s/i;
|
|
24
|
+
const ETA_RE = /eta|remaining/i;
|
|
25
|
+
function isProgressLine(line) {
|
|
26
|
+
const t = line.trim();
|
|
27
|
+
if (t === "")
|
|
28
|
+
return false; // keep empty lines (they delimit blocks)
|
|
29
|
+
if (PROGRESS_BAR_RE.test(t) && t.length > 3)
|
|
30
|
+
return true;
|
|
31
|
+
if (SPINNER_RE.test(t))
|
|
32
|
+
return true;
|
|
33
|
+
if (DOWNLOAD_RE.test(t))
|
|
34
|
+
return true;
|
|
35
|
+
if (SPEED_ETA_RE.test(t) && ETA_RE.test(t))
|
|
36
|
+
return true;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Stateful streaming compressor. Feed it chunks via `process(chunk)`, get
|
|
41
|
+
* compressed output back. Call `flush()` at end-of-stream to drain any
|
|
42
|
+
* buffered incomplete line + emit any pending dedup counter.
|
|
43
|
+
*/
|
|
44
|
+
export class StreamCompressor {
|
|
45
|
+
buffer = "";
|
|
46
|
+
prevLine = null;
|
|
47
|
+
repeatCount = 0;
|
|
48
|
+
/** Process a chunk; returns the filtered output ready to emit (may be empty). */
|
|
49
|
+
process(chunk) {
|
|
50
|
+
this.buffer += chunk;
|
|
51
|
+
const lastNl = this.buffer.lastIndexOf("\n");
|
|
52
|
+
if (lastNl < 0)
|
|
53
|
+
return ""; // no complete line yet
|
|
54
|
+
const complete = this.buffer.slice(0, lastNl);
|
|
55
|
+
this.buffer = this.buffer.slice(lastNl + 1);
|
|
56
|
+
let out = "";
|
|
57
|
+
for (const raw of complete.split("\n")) {
|
|
58
|
+
const filtered = raw.replace(ANSI_RE_G, "");
|
|
59
|
+
if (isProgressLine(filtered))
|
|
60
|
+
continue;
|
|
61
|
+
if (filtered === this.prevLine && filtered.trim() !== "") {
|
|
62
|
+
this.repeatCount++;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (this.repeatCount > 1) {
|
|
66
|
+
out += ` ... (×${this.repeatCount} identical lines)\n`;
|
|
67
|
+
}
|
|
68
|
+
this.repeatCount = 0;
|
|
69
|
+
out += `${filtered}\n`;
|
|
70
|
+
this.prevLine = filtered;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** Drain any remaining buffered line and emit final dedup counter. */
|
|
75
|
+
flush() {
|
|
76
|
+
let out = "";
|
|
77
|
+
if (this.buffer.length > 0) {
|
|
78
|
+
out += this.consumeBufferedLine();
|
|
79
|
+
}
|
|
80
|
+
out += this.drainRepeatCounter();
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/** Process the partial line in `buffer` (no trailing newline) and clear the buffer. */
|
|
84
|
+
consumeBufferedLine() {
|
|
85
|
+
const filtered = this.buffer.replace(ANSI_RE_G, "");
|
|
86
|
+
this.buffer = "";
|
|
87
|
+
if (isProgressLine(filtered))
|
|
88
|
+
return "";
|
|
89
|
+
if (filtered === this.prevLine && filtered.trim() !== "") {
|
|
90
|
+
this.repeatCount++;
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
const counter = this.drainRepeatCounter();
|
|
94
|
+
this.prevLine = filtered;
|
|
95
|
+
return counter + filtered + (filtered.endsWith("\n") ? "" : "\n");
|
|
96
|
+
}
|
|
97
|
+
/** Emit "(×N identical lines)" if a repeat counter is pending; reset state. */
|
|
98
|
+
drainRepeatCounter() {
|
|
99
|
+
const out = this.repeatCount > 1 ? ` ... (×${this.repeatCount} identical lines)\n` : "";
|
|
100
|
+
this.repeatCount = 0;
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=stream-compress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-compress.js","sourceRoot":"","sources":["../../src/util/stream-compress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/util/version.ts"],"names":[],"mappings":"AAMA,wBAAgB,UAAU,CAAC,QAAQ,SAAU,GAAG,MAAM,CAQrD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export function getVersion(fallback = "1.0.0") {
|
|
6
|
+
try {
|
|
7
|
+
const pkgPath = resolve(__dirname, "..", "..", "package.json");
|
|
8
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
9
|
+
return pkg.version ?? fallback;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/util/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,MAAM,UAAU,UAAU,CAAC,QAAQ,GAAG,OAAO;IAC5C,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACvD,OAAO,GAAG,CAAC,OAAO,IAAI,QAAQ,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,QAAQ,CAAC;IACjB,CAAC;AACF,CAAC"}
|