@hasna/terminal 2.0.5 → 2.3.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/dist/cli.js +52 -21
- package/package.json +1 -1
- package/src/ai.ts +77 -130
- package/src/cli.tsx +51 -21
- package/src/command-validator.ts +11 -0
- package/src/context-hints.ts +291 -0
- package/src/discover.ts +238 -0
- package/src/economy.ts +53 -0
- package/src/output-processor.ts +7 -18
- package/src/output-store.ts +65 -0
- package/src/providers/base.ts +3 -1
- package/src/providers/groq.ts +108 -0
- package/src/providers/index.ts +26 -2
- package/src/providers/providers.test.ts +4 -2
- package/src/providers/xai.ts +108 -0
- package/src/sessions-db.ts +81 -0
- package/temp/rtk/.claude/agents/code-reviewer.md +221 -0
- package/temp/rtk/.claude/agents/debugger.md +519 -0
- package/temp/rtk/.claude/agents/rtk-testing-specialist.md +461 -0
- package/temp/rtk/.claude/agents/rust-rtk.md +511 -0
- package/temp/rtk/.claude/agents/technical-writer.md +355 -0
- package/temp/rtk/.claude/commands/diagnose.md +352 -0
- package/temp/rtk/.claude/commands/test-routing.md +362 -0
- package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +16 -0
- package/temp/rtk/.claude/hooks/rtk-rewrite.sh +70 -0
- package/temp/rtk/.claude/hooks/rtk-suggest.sh +152 -0
- package/temp/rtk/.claude/rules/cli-testing.md +526 -0
- package/temp/rtk/.claude/skills/issue-triage/SKILL.md +348 -0
- package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +134 -0
- package/temp/rtk/.claude/skills/performance.md +435 -0
- package/temp/rtk/.claude/skills/pr-triage/SKILL.md +315 -0
- package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +71 -0
- package/temp/rtk/.claude/skills/repo-recap.md +206 -0
- package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +78 -0
- package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +124 -0
- package/temp/rtk/.claude/skills/security-guardian.md +503 -0
- package/temp/rtk/.claude/skills/ship.md +404 -0
- package/temp/rtk/.github/workflows/benchmark.yml +34 -0
- package/temp/rtk/.github/workflows/dco-check.yaml +12 -0
- package/temp/rtk/.github/workflows/release-please.yml +51 -0
- package/temp/rtk/.github/workflows/release.yml +343 -0
- package/temp/rtk/.github/workflows/security-check.yml +135 -0
- package/temp/rtk/.github/workflows/validate-docs.yml +78 -0
- package/temp/rtk/.release-please-manifest.json +3 -0
- package/temp/rtk/ARCHITECTURE.md +1491 -0
- package/temp/rtk/CHANGELOG.md +640 -0
- package/temp/rtk/CLAUDE.md +605 -0
- package/temp/rtk/CONTRIBUTING.md +199 -0
- package/temp/rtk/Cargo.lock +1668 -0
- package/temp/rtk/Cargo.toml +64 -0
- package/temp/rtk/Formula/rtk.rb +43 -0
- package/temp/rtk/INSTALL.md +390 -0
- package/temp/rtk/LICENSE +21 -0
- package/temp/rtk/README.md +386 -0
- package/temp/rtk/README_es.md +159 -0
- package/temp/rtk/README_fr.md +197 -0
- package/temp/rtk/README_ja.md +159 -0
- package/temp/rtk/README_ko.md +159 -0
- package/temp/rtk/README_zh.md +167 -0
- package/temp/rtk/ROADMAP.md +15 -0
- package/temp/rtk/SECURITY.md +217 -0
- package/temp/rtk/TEST_EXEC_TIME.md +102 -0
- package/temp/rtk/build.rs +57 -0
- package/temp/rtk/docs/AUDIT_GUIDE.md +432 -0
- package/temp/rtk/docs/FEATURES.md +1410 -0
- package/temp/rtk/docs/TROUBLESHOOTING.md +309 -0
- package/temp/rtk/docs/filter-workflow.md +102 -0
- package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
- package/temp/rtk/docs/tracking.md +583 -0
- package/temp/rtk/hooks/opencode-rtk.ts +39 -0
- package/temp/rtk/hooks/rtk-awareness.md +29 -0
- package/temp/rtk/hooks/rtk-rewrite.sh +61 -0
- package/temp/rtk/hooks/test-rtk-rewrite.sh +442 -0
- package/temp/rtk/install.sh +124 -0
- package/temp/rtk/release-please-config.json +10 -0
- package/temp/rtk/scripts/benchmark.sh +592 -0
- package/temp/rtk/scripts/check-installation.sh +162 -0
- package/temp/rtk/scripts/install-local.sh +37 -0
- package/temp/rtk/scripts/rtk-economics.sh +137 -0
- package/temp/rtk/scripts/test-all.sh +561 -0
- package/temp/rtk/scripts/test-aristote.sh +227 -0
- package/temp/rtk/scripts/test-tracking.sh +79 -0
- package/temp/rtk/scripts/update-readme-metrics.sh +32 -0
- package/temp/rtk/scripts/validate-docs.sh +73 -0
- package/temp/rtk/src/aws_cmd.rs +880 -0
- package/temp/rtk/src/binlog.rs +1645 -0
- package/temp/rtk/src/cargo_cmd.rs +1727 -0
- package/temp/rtk/src/cc_economics.rs +1157 -0
- package/temp/rtk/src/ccusage.rs +340 -0
- package/temp/rtk/src/config.rs +187 -0
- package/temp/rtk/src/container.rs +855 -0
- package/temp/rtk/src/curl_cmd.rs +134 -0
- package/temp/rtk/src/deps.rs +268 -0
- package/temp/rtk/src/diff_cmd.rs +367 -0
- package/temp/rtk/src/discover/mod.rs +274 -0
- package/temp/rtk/src/discover/provider.rs +388 -0
- package/temp/rtk/src/discover/registry.rs +2022 -0
- package/temp/rtk/src/discover/report.rs +202 -0
- package/temp/rtk/src/discover/rules.rs +667 -0
- package/temp/rtk/src/display_helpers.rs +402 -0
- package/temp/rtk/src/dotnet_cmd.rs +1771 -0
- package/temp/rtk/src/dotnet_format_report.rs +133 -0
- package/temp/rtk/src/dotnet_trx.rs +593 -0
- package/temp/rtk/src/env_cmd.rs +204 -0
- package/temp/rtk/src/filter.rs +462 -0
- package/temp/rtk/src/filters/README.md +52 -0
- package/temp/rtk/src/filters/ansible-playbook.toml +34 -0
- package/temp/rtk/src/filters/basedpyright.toml +47 -0
- package/temp/rtk/src/filters/biome.toml +45 -0
- package/temp/rtk/src/filters/brew-install.toml +37 -0
- package/temp/rtk/src/filters/composer-install.toml +40 -0
- package/temp/rtk/src/filters/df.toml +16 -0
- package/temp/rtk/src/filters/dotnet-build.toml +64 -0
- package/temp/rtk/src/filters/du.toml +16 -0
- package/temp/rtk/src/filters/fail2ban-client.toml +15 -0
- package/temp/rtk/src/filters/gcc.toml +49 -0
- package/temp/rtk/src/filters/gcloud.toml +22 -0
- package/temp/rtk/src/filters/hadolint.toml +24 -0
- package/temp/rtk/src/filters/helm.toml +29 -0
- package/temp/rtk/src/filters/iptables.toml +27 -0
- package/temp/rtk/src/filters/jj.toml +28 -0
- package/temp/rtk/src/filters/jq.toml +24 -0
- package/temp/rtk/src/filters/make.toml +41 -0
- package/temp/rtk/src/filters/markdownlint.toml +24 -0
- package/temp/rtk/src/filters/mix-compile.toml +27 -0
- package/temp/rtk/src/filters/mix-format.toml +15 -0
- package/temp/rtk/src/filters/mvn-build.toml +44 -0
- package/temp/rtk/src/filters/oxlint.toml +43 -0
- package/temp/rtk/src/filters/ping.toml +63 -0
- package/temp/rtk/src/filters/pio-run.toml +40 -0
- package/temp/rtk/src/filters/poetry-install.toml +50 -0
- package/temp/rtk/src/filters/pre-commit.toml +35 -0
- package/temp/rtk/src/filters/ps.toml +16 -0
- package/temp/rtk/src/filters/quarto-render.toml +41 -0
- package/temp/rtk/src/filters/rsync.toml +48 -0
- package/temp/rtk/src/filters/shellcheck.toml +27 -0
- package/temp/rtk/src/filters/shopify-theme.toml +29 -0
- package/temp/rtk/src/filters/skopeo.toml +45 -0
- package/temp/rtk/src/filters/sops.toml +16 -0
- package/temp/rtk/src/filters/ssh.toml +44 -0
- package/temp/rtk/src/filters/stat.toml +34 -0
- package/temp/rtk/src/filters/swift-build.toml +41 -0
- package/temp/rtk/src/filters/systemctl-status.toml +33 -0
- package/temp/rtk/src/filters/terraform-plan.toml +35 -0
- package/temp/rtk/src/filters/tofu-fmt.toml +16 -0
- package/temp/rtk/src/filters/tofu-init.toml +38 -0
- package/temp/rtk/src/filters/tofu-plan.toml +35 -0
- package/temp/rtk/src/filters/tofu-validate.toml +17 -0
- package/temp/rtk/src/filters/trunk-build.toml +39 -0
- package/temp/rtk/src/filters/ty.toml +50 -0
- package/temp/rtk/src/filters/uv-sync.toml +37 -0
- package/temp/rtk/src/filters/xcodebuild.toml +99 -0
- package/temp/rtk/src/filters/yamllint.toml +25 -0
- package/temp/rtk/src/find_cmd.rs +598 -0
- package/temp/rtk/src/format_cmd.rs +386 -0
- package/temp/rtk/src/gain.rs +723 -0
- package/temp/rtk/src/gh_cmd.rs +1651 -0
- package/temp/rtk/src/git.rs +2012 -0
- package/temp/rtk/src/go_cmd.rs +592 -0
- package/temp/rtk/src/golangci_cmd.rs +254 -0
- package/temp/rtk/src/grep_cmd.rs +288 -0
- package/temp/rtk/src/gt_cmd.rs +810 -0
- package/temp/rtk/src/hook_audit_cmd.rs +283 -0
- package/temp/rtk/src/hook_check.rs +171 -0
- package/temp/rtk/src/init.rs +1859 -0
- package/temp/rtk/src/integrity.rs +537 -0
- package/temp/rtk/src/json_cmd.rs +231 -0
- package/temp/rtk/src/learn/detector.rs +628 -0
- package/temp/rtk/src/learn/mod.rs +119 -0
- package/temp/rtk/src/learn/report.rs +184 -0
- package/temp/rtk/src/lint_cmd.rs +694 -0
- package/temp/rtk/src/local_llm.rs +316 -0
- package/temp/rtk/src/log_cmd.rs +248 -0
- package/temp/rtk/src/ls.rs +324 -0
- package/temp/rtk/src/main.rs +2482 -0
- package/temp/rtk/src/mypy_cmd.rs +389 -0
- package/temp/rtk/src/next_cmd.rs +241 -0
- package/temp/rtk/src/npm_cmd.rs +236 -0
- package/temp/rtk/src/parser/README.md +267 -0
- package/temp/rtk/src/parser/error.rs +46 -0
- package/temp/rtk/src/parser/formatter.rs +336 -0
- package/temp/rtk/src/parser/mod.rs +311 -0
- package/temp/rtk/src/parser/types.rs +119 -0
- package/temp/rtk/src/pip_cmd.rs +302 -0
- package/temp/rtk/src/playwright_cmd.rs +479 -0
- package/temp/rtk/src/pnpm_cmd.rs +573 -0
- package/temp/rtk/src/prettier_cmd.rs +221 -0
- package/temp/rtk/src/prisma_cmd.rs +482 -0
- package/temp/rtk/src/psql_cmd.rs +382 -0
- package/temp/rtk/src/pytest_cmd.rs +384 -0
- package/temp/rtk/src/read.rs +217 -0
- package/temp/rtk/src/rewrite_cmd.rs +50 -0
- package/temp/rtk/src/ruff_cmd.rs +402 -0
- package/temp/rtk/src/runner.rs +271 -0
- package/temp/rtk/src/summary.rs +297 -0
- package/temp/rtk/src/tee.rs +405 -0
- package/temp/rtk/src/telemetry.rs +248 -0
- package/temp/rtk/src/toml_filter.rs +1655 -0
- package/temp/rtk/src/tracking.rs +1416 -0
- package/temp/rtk/src/tree.rs +209 -0
- package/temp/rtk/src/tsc_cmd.rs +259 -0
- package/temp/rtk/src/utils.rs +432 -0
- package/temp/rtk/src/verify_cmd.rs +47 -0
- package/temp/rtk/src/vitest_cmd.rs +385 -0
- package/temp/rtk/src/wc_cmd.rs +401 -0
- package/temp/rtk/src/wget_cmd.rs +260 -0
- package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +11 -0
- package/temp/rtk/tests/fixtures/dotnet/format_changes.json +31 -0
- package/temp/rtk/tests/fixtures/dotnet/format_empty.json +1 -0
- package/temp/rtk/tests/fixtures/dotnet/format_success.json +12 -0
- package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +18 -0
- package/dist/App.js +0 -404
- package/dist/Browse.js +0 -79
- package/dist/FuzzyPicker.js +0 -47
- package/dist/Onboarding.js +0 -51
- package/dist/Spinner.js +0 -12
- package/dist/StatusBar.js +0 -49
- package/dist/ai.js +0 -368
- package/dist/cache.js +0 -41
- package/dist/command-rewriter.js +0 -64
- package/dist/command-validator.js +0 -77
- package/dist/compression.js +0 -107
- package/dist/diff-cache.js +0 -107
- package/dist/economy.js +0 -79
- package/dist/expand-store.js +0 -38
- package/dist/file-cache.js +0 -72
- package/dist/file-index.js +0 -62
- package/dist/history.js +0 -62
- package/dist/lazy-executor.js +0 -54
- package/dist/line-dedup.js +0 -59
- package/dist/loop-detector.js +0 -75
- package/dist/mcp/install.js +0 -98
- package/dist/mcp/server.js +0 -569
- package/dist/noise-filter.js +0 -86
- package/dist/output-processor.js +0 -136
- package/dist/output-router.js +0 -41
- package/dist/parsers/base.js +0 -2
- package/dist/parsers/build.js +0 -64
- package/dist/parsers/errors.js +0 -101
- package/dist/parsers/files.js +0 -78
- package/dist/parsers/git.js +0 -99
- package/dist/parsers/index.js +0 -48
- package/dist/parsers/tests.js +0 -89
- package/dist/providers/anthropic.js +0 -39
- package/dist/providers/base.js +0 -4
- package/dist/providers/cerebras.js +0 -95
- package/dist/providers/index.js +0 -49
- package/dist/recipes/model.js +0 -20
- package/dist/recipes/storage.js +0 -136
- package/dist/search/content-search.js +0 -68
- package/dist/search/file-search.js +0 -61
- package/dist/search/filters.js +0 -34
- package/dist/search/index.js +0 -5
- package/dist/search/semantic.js +0 -320
- package/dist/session-boot.js +0 -59
- package/dist/session-context.js +0 -55
- package/dist/sessions-db.js +0 -120
- package/dist/smart-display.js +0 -286
- package/dist/snapshots.js +0 -51
- package/dist/supervisor.js +0 -112
- package/dist/test-watchlist.js +0 -131
- package/dist/tree.js +0 -94
- package/dist/usage-cache.js +0 -65
package/dist/compression.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// Token compression engine — reduces CLI output to fit within token budgets
|
|
2
|
-
import { parseOutput, estimateTokens, tokenSavings } from "./parsers/index.js";
|
|
3
|
-
/** Strip ANSI escape codes from text */
|
|
4
|
-
export function stripAnsi(text) {
|
|
5
|
-
// eslint-disable-next-line no-control-regex
|
|
6
|
-
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
|
|
7
|
-
}
|
|
8
|
-
/** Deduplicate consecutive similar lines (e.g., "Compiling X... Compiling Y...") */
|
|
9
|
-
function deduplicateLines(lines) {
|
|
10
|
-
if (lines.length <= 3)
|
|
11
|
-
return lines;
|
|
12
|
-
const result = [];
|
|
13
|
-
let repeatCount = 0;
|
|
14
|
-
let repeatPattern = "";
|
|
15
|
-
for (let i = 0; i < lines.length; i++) {
|
|
16
|
-
const line = lines[i];
|
|
17
|
-
// Extract a "pattern" — the line without numbers, paths, specific identifiers
|
|
18
|
-
const pattern = line.replace(/[0-9]+/g, "N").replace(/\/\S+/g, "/PATH").replace(/\s+/g, " ").trim();
|
|
19
|
-
if (pattern === repeatPattern) {
|
|
20
|
-
repeatCount++;
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
if (repeatCount > 2) {
|
|
24
|
-
result.push(` ... (${repeatCount} similar lines)`);
|
|
25
|
-
}
|
|
26
|
-
else if (repeatCount > 0) {
|
|
27
|
-
// Push the skipped lines back
|
|
28
|
-
for (let j = i - repeatCount; j < i; j++) {
|
|
29
|
-
result.push(lines[j]);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
result.push(line);
|
|
33
|
-
repeatPattern = pattern;
|
|
34
|
-
repeatCount = 0;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (repeatCount > 2) {
|
|
38
|
-
result.push(` ... (${repeatCount} similar lines)`);
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
for (let j = lines.length - repeatCount; j < lines.length; j++) {
|
|
42
|
-
result.push(lines[j]);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
/** Smart truncation: keep first N + last M lines */
|
|
48
|
-
function smartTruncate(text, maxTokens) {
|
|
49
|
-
const lines = text.split("\n");
|
|
50
|
-
const currentTokens = estimateTokens(text);
|
|
51
|
-
if (currentTokens <= maxTokens)
|
|
52
|
-
return text;
|
|
53
|
-
// Keep proportional first/last, with first getting more
|
|
54
|
-
const targetLines = Math.floor((maxTokens * lines.length) / currentTokens);
|
|
55
|
-
const firstCount = Math.ceil(targetLines * 0.6);
|
|
56
|
-
const lastCount = Math.floor(targetLines * 0.4);
|
|
57
|
-
if (firstCount + lastCount >= lines.length)
|
|
58
|
-
return text;
|
|
59
|
-
const first = lines.slice(0, firstCount);
|
|
60
|
-
const last = lines.slice(-lastCount);
|
|
61
|
-
const hiddenCount = lines.length - firstCount - lastCount;
|
|
62
|
-
return [...first, `\n--- ${hiddenCount} lines hidden ---\n`, ...last].join("\n");
|
|
63
|
-
}
|
|
64
|
-
/** Compress command output to fit within a token budget */
|
|
65
|
-
export function compress(command, output, options = {}) {
|
|
66
|
-
const { maxTokens, format = "text", stripAnsi: doStrip = true } = options;
|
|
67
|
-
const originalTokens = estimateTokens(output);
|
|
68
|
-
// Step 1: Strip ANSI codes
|
|
69
|
-
let text = doStrip ? stripAnsi(output) : output;
|
|
70
|
-
// Step 2: Try structured parsing (format=json or when it saves tokens)
|
|
71
|
-
if (format === "json" || format === "summary") {
|
|
72
|
-
const parsed = parseOutput(command, text);
|
|
73
|
-
if (parsed) {
|
|
74
|
-
const json = JSON.stringify(parsed.data, null, format === "summary" ? 0 : 2);
|
|
75
|
-
const savings = tokenSavings(output, parsed.data);
|
|
76
|
-
const compressedTokens = estimateTokens(json);
|
|
77
|
-
// ONLY use JSON if it actually saves tokens (never return larger output)
|
|
78
|
-
if (savings.saved > 0 && (!maxTokens || compressedTokens <= maxTokens)) {
|
|
79
|
-
return {
|
|
80
|
-
content: json,
|
|
81
|
-
format: "json",
|
|
82
|
-
originalTokens,
|
|
83
|
-
compressedTokens,
|
|
84
|
-
tokensSaved: savings.saved,
|
|
85
|
-
savingsPercent: savings.percent,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// Step 3: Deduplicate similar lines
|
|
91
|
-
const lines = text.split("\n");
|
|
92
|
-
const deduped = deduplicateLines(lines);
|
|
93
|
-
text = deduped.join("\n");
|
|
94
|
-
// Step 4: Smart truncation if over budget
|
|
95
|
-
if (maxTokens) {
|
|
96
|
-
text = smartTruncate(text, maxTokens);
|
|
97
|
-
}
|
|
98
|
-
const compressedTokens = estimateTokens(text);
|
|
99
|
-
return {
|
|
100
|
-
content: text,
|
|
101
|
-
format: "text",
|
|
102
|
-
originalTokens,
|
|
103
|
-
compressedTokens,
|
|
104
|
-
tokensSaved: Math.max(0, originalTokens - compressedTokens),
|
|
105
|
-
savingsPercent: originalTokens > 0 ? Math.round(((originalTokens - compressedTokens) / originalTokens) * 100) : 0,
|
|
106
|
-
};
|
|
107
|
-
}
|
package/dist/diff-cache.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// Diff-aware output caching — when same command runs again, return only what changed
|
|
2
|
-
import { estimateTokens } from "./parsers/index.js";
|
|
3
|
-
const cache = new Map();
|
|
4
|
-
function cacheKey(command, cwd) {
|
|
5
|
-
return `${cwd}:${command}`;
|
|
6
|
-
}
|
|
7
|
-
/** Compute a simple line diff between two outputs */
|
|
8
|
-
function lineDiff(prev, curr) {
|
|
9
|
-
const prevLines = new Set(prev.split("\n"));
|
|
10
|
-
const currLines = curr.split("\n");
|
|
11
|
-
const added = [];
|
|
12
|
-
const removed = [];
|
|
13
|
-
let unchanged = 0;
|
|
14
|
-
for (const line of currLines) {
|
|
15
|
-
if (prevLines.has(line)) {
|
|
16
|
-
unchanged++;
|
|
17
|
-
prevLines.delete(line);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
added.push(line);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
for (const line of prevLines) {
|
|
24
|
-
removed.push(line);
|
|
25
|
-
}
|
|
26
|
-
return { added, removed, unchanged };
|
|
27
|
-
}
|
|
28
|
-
/** Generate a human-readable diff summary */
|
|
29
|
-
function summarizeDiff(diff) {
|
|
30
|
-
const parts = [];
|
|
31
|
-
if (diff.added.length > 0)
|
|
32
|
-
parts.push(`+${diff.added.length} new lines`);
|
|
33
|
-
if (diff.removed.length > 0)
|
|
34
|
-
parts.push(`-${diff.removed.length} removed lines`);
|
|
35
|
-
parts.push(`${diff.unchanged} unchanged`);
|
|
36
|
-
return parts.join(", ");
|
|
37
|
-
}
|
|
38
|
-
/** Run diffing on command output. Caches the output for next comparison. */
|
|
39
|
-
export function diffOutput(command, cwd, output) {
|
|
40
|
-
const key = cacheKey(command, cwd);
|
|
41
|
-
const prev = cache.get(key);
|
|
42
|
-
// Store current for next time
|
|
43
|
-
cache.set(key, { command, cwd, output, timestamp: Date.now() });
|
|
44
|
-
if (!prev) {
|
|
45
|
-
return {
|
|
46
|
-
full: output,
|
|
47
|
-
hasPrevious: false,
|
|
48
|
-
added: [],
|
|
49
|
-
removed: [],
|
|
50
|
-
diffSummary: "first run",
|
|
51
|
-
unchanged: false,
|
|
52
|
-
tokensSaved: 0,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
if (prev.output === output) {
|
|
56
|
-
const fullTokens = estimateTokens(output);
|
|
57
|
-
return {
|
|
58
|
-
full: output,
|
|
59
|
-
hasPrevious: true,
|
|
60
|
-
added: [],
|
|
61
|
-
removed: [],
|
|
62
|
-
diffSummary: "identical to previous run",
|
|
63
|
-
unchanged: true,
|
|
64
|
-
tokensSaved: fullTokens - 10, // ~10 tokens for the "unchanged" message
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const diff = lineDiff(prev.output, output);
|
|
68
|
-
const total = diff.added.length + diff.removed.length + diff.unchanged;
|
|
69
|
-
const similarity = total > 0 ? diff.unchanged / total : 0;
|
|
70
|
-
// Fuzzy threshold: if >80% similar, return diff-only (massive token savings)
|
|
71
|
-
const fullTokens = estimateTokens(output);
|
|
72
|
-
if (similarity > 0.8 && diff.added.length + diff.removed.length > 0) {
|
|
73
|
-
const diffContent = [
|
|
74
|
-
...diff.added.map(l => `+ ${l}`),
|
|
75
|
-
...diff.removed.map(l => `- ${l}`),
|
|
76
|
-
].join("\n");
|
|
77
|
-
const diffTokens = estimateTokens(diffContent);
|
|
78
|
-
return {
|
|
79
|
-
full: output,
|
|
80
|
-
hasPrevious: true,
|
|
81
|
-
added: diff.added,
|
|
82
|
-
removed: diff.removed,
|
|
83
|
-
diffSummary: `${Math.round(similarity * 100)}% similar — ${summarizeDiff(diff)}`,
|
|
84
|
-
unchanged: false,
|
|
85
|
-
tokensSaved: Math.max(0, fullTokens - diffTokens),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
// Less than 80% similar — return full output with diff info
|
|
89
|
-
const diffContent = [
|
|
90
|
-
...diff.added.map(l => `+ ${l}`),
|
|
91
|
-
...diff.removed.map(l => `- ${l}`),
|
|
92
|
-
].join("\n");
|
|
93
|
-
const diffTokens = estimateTokens(diffContent);
|
|
94
|
-
return {
|
|
95
|
-
full: output,
|
|
96
|
-
hasPrevious: true,
|
|
97
|
-
added: diff.added,
|
|
98
|
-
removed: diff.removed,
|
|
99
|
-
diffSummary: summarizeDiff(diff),
|
|
100
|
-
unchanged: false,
|
|
101
|
-
tokensSaved: Math.max(0, fullTokens - diffTokens),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/** Clear the diff cache */
|
|
105
|
-
export function clearDiffCache() {
|
|
106
|
-
cache.clear();
|
|
107
|
-
}
|
package/dist/economy.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// Token economy tracker — tracks token savings across all interactions
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
const DIR = join(homedir(), ".terminal");
|
|
6
|
-
const ECONOMY_FILE = join(DIR, "economy.json");
|
|
7
|
-
let stats = null;
|
|
8
|
-
function ensureDir() {
|
|
9
|
-
if (!existsSync(DIR))
|
|
10
|
-
mkdirSync(DIR, { recursive: true });
|
|
11
|
-
}
|
|
12
|
-
function loadStats() {
|
|
13
|
-
if (stats)
|
|
14
|
-
return stats;
|
|
15
|
-
ensureDir();
|
|
16
|
-
if (existsSync(ECONOMY_FILE)) {
|
|
17
|
-
try {
|
|
18
|
-
const saved = JSON.parse(readFileSync(ECONOMY_FILE, "utf8"));
|
|
19
|
-
stats = {
|
|
20
|
-
totalTokensSaved: saved.totalTokensSaved ?? 0,
|
|
21
|
-
totalTokensUsed: saved.totalTokensUsed ?? 0,
|
|
22
|
-
savingsByFeature: {
|
|
23
|
-
structured: saved.savingsByFeature?.structured ?? 0,
|
|
24
|
-
compressed: saved.savingsByFeature?.compressed ?? 0,
|
|
25
|
-
diff: saved.savingsByFeature?.diff ?? 0,
|
|
26
|
-
cache: saved.savingsByFeature?.cache ?? 0,
|
|
27
|
-
search: saved.savingsByFeature?.search ?? 0,
|
|
28
|
-
},
|
|
29
|
-
sessionStart: Date.now(),
|
|
30
|
-
sessionSaved: 0,
|
|
31
|
-
sessionUsed: 0,
|
|
32
|
-
};
|
|
33
|
-
return stats;
|
|
34
|
-
}
|
|
35
|
-
catch { }
|
|
36
|
-
}
|
|
37
|
-
stats = {
|
|
38
|
-
totalTokensSaved: 0,
|
|
39
|
-
totalTokensUsed: 0,
|
|
40
|
-
savingsByFeature: { structured: 0, compressed: 0, diff: 0, cache: 0, search: 0 },
|
|
41
|
-
sessionStart: Date.now(),
|
|
42
|
-
sessionSaved: 0,
|
|
43
|
-
sessionUsed: 0,
|
|
44
|
-
};
|
|
45
|
-
return stats;
|
|
46
|
-
}
|
|
47
|
-
function saveStats() {
|
|
48
|
-
ensureDir();
|
|
49
|
-
if (stats) {
|
|
50
|
-
writeFileSync(ECONOMY_FILE, JSON.stringify(stats, null, 2));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/** Record token savings from a feature */
|
|
54
|
-
export function recordSaving(feature, tokensSaved) {
|
|
55
|
-
const s = loadStats();
|
|
56
|
-
s.totalTokensSaved += tokensSaved;
|
|
57
|
-
s.sessionSaved += tokensSaved;
|
|
58
|
-
s.savingsByFeature[feature] += tokensSaved;
|
|
59
|
-
saveStats();
|
|
60
|
-
}
|
|
61
|
-
/** Record tokens used (for AI calls) */
|
|
62
|
-
export function recordUsage(tokens) {
|
|
63
|
-
const s = loadStats();
|
|
64
|
-
s.totalTokensUsed += tokens;
|
|
65
|
-
s.sessionUsed += tokens;
|
|
66
|
-
saveStats();
|
|
67
|
-
}
|
|
68
|
-
/** Get current economy stats */
|
|
69
|
-
export function getEconomyStats() {
|
|
70
|
-
return { ...loadStats() };
|
|
71
|
-
}
|
|
72
|
-
/** Format token count for display */
|
|
73
|
-
export function formatTokens(n) {
|
|
74
|
-
if (n >= 1_000_000)
|
|
75
|
-
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
76
|
-
if (n >= 1_000)
|
|
77
|
-
return `${(n / 1_000).toFixed(1)}K`;
|
|
78
|
-
return `${n}`;
|
|
79
|
-
}
|
package/dist/expand-store.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Expand store — keeps full output for progressive disclosure
|
|
2
|
-
// Agents get summary first, call expand(key) only if they need details
|
|
3
|
-
const MAX_ENTRIES = 50;
|
|
4
|
-
const store = new Map();
|
|
5
|
-
let counter = 0;
|
|
6
|
-
/** Store full output and return a retrieval key */
|
|
7
|
-
export function storeOutput(command, output) {
|
|
8
|
-
const key = `out_${++counter}`;
|
|
9
|
-
// Evict oldest if over limit
|
|
10
|
-
if (store.size >= MAX_ENTRIES) {
|
|
11
|
-
const oldest = store.keys().next().value;
|
|
12
|
-
if (oldest)
|
|
13
|
-
store.delete(oldest);
|
|
14
|
-
}
|
|
15
|
-
store.set(key, { command, output, timestamp: Date.now() });
|
|
16
|
-
return key;
|
|
17
|
-
}
|
|
18
|
-
/** Retrieve full output by key, optionally filtered */
|
|
19
|
-
export function expandOutput(key, grep) {
|
|
20
|
-
const entry = store.get(key);
|
|
21
|
-
if (!entry)
|
|
22
|
-
return { found: false };
|
|
23
|
-
let output = entry.output;
|
|
24
|
-
if (grep) {
|
|
25
|
-
const pattern = new RegExp(grep, "i");
|
|
26
|
-
output = output.split("\n").filter(l => pattern.test(l)).join("\n");
|
|
27
|
-
}
|
|
28
|
-
return { found: true, output, lines: output.split("\n").length };
|
|
29
|
-
}
|
|
30
|
-
/** List available stored outputs */
|
|
31
|
-
export function listStored() {
|
|
32
|
-
return [...store.entries()].map(([key, entry]) => ({
|
|
33
|
-
key,
|
|
34
|
-
command: entry.command.slice(0, 60),
|
|
35
|
-
lines: entry.output.split("\n").length,
|
|
36
|
-
age: Date.now() - entry.timestamp,
|
|
37
|
-
}));
|
|
38
|
-
}
|
package/dist/file-cache.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Universal session file cache — cache any file read, serve from memory on repeat
|
|
2
|
-
import { statSync, readFileSync } from "fs";
|
|
3
|
-
const cache = new Map();
|
|
4
|
-
/** Read a file with session caching. Returns content + cache metadata. */
|
|
5
|
-
export function cachedRead(filePath, options = {}) {
|
|
6
|
-
const { offset, limit } = options;
|
|
7
|
-
try {
|
|
8
|
-
const stat = statSync(filePath);
|
|
9
|
-
const mtime = stat.mtimeMs;
|
|
10
|
-
const existing = cache.get(filePath);
|
|
11
|
-
// Cache hit — file unchanged
|
|
12
|
-
if (existing && existing.mtime === mtime) {
|
|
13
|
-
existing.readCount++;
|
|
14
|
-
existing.lastReadAt = Date.now();
|
|
15
|
-
const lines = existing.content.split("\n");
|
|
16
|
-
if (offset !== undefined || limit !== undefined) {
|
|
17
|
-
const start = offset ?? 0;
|
|
18
|
-
const end = limit !== undefined ? start + limit : lines.length;
|
|
19
|
-
return {
|
|
20
|
-
content: lines.slice(start, end).join("\n"),
|
|
21
|
-
cached: true,
|
|
22
|
-
readCount: existing.readCount,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return { content: existing.content, cached: true, readCount: existing.readCount };
|
|
26
|
-
}
|
|
27
|
-
// Cache miss or stale — read from disk
|
|
28
|
-
const content = readFileSync(filePath, "utf8");
|
|
29
|
-
cache.set(filePath, {
|
|
30
|
-
content,
|
|
31
|
-
mtime,
|
|
32
|
-
readCount: 1,
|
|
33
|
-
firstReadAt: Date.now(),
|
|
34
|
-
lastReadAt: Date.now(),
|
|
35
|
-
});
|
|
36
|
-
const lines = content.split("\n");
|
|
37
|
-
if (offset !== undefined || limit !== undefined) {
|
|
38
|
-
const start = offset ?? 0;
|
|
39
|
-
const end = limit !== undefined ? start + limit : lines.length;
|
|
40
|
-
return { content: lines.slice(start, end).join("\n"), cached: false, readCount: 1 };
|
|
41
|
-
}
|
|
42
|
-
return { content, cached: false, readCount: 1 };
|
|
43
|
-
}
|
|
44
|
-
catch (e) {
|
|
45
|
-
return { content: `Error: ${e.message}`, cached: false, readCount: 0 };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/** Invalidate cache for a file (call after writes) */
|
|
49
|
-
export function invalidateFile(filePath) {
|
|
50
|
-
cache.delete(filePath);
|
|
51
|
-
}
|
|
52
|
-
/** Invalidate all files matching a pattern */
|
|
53
|
-
export function invalidatePattern(pattern) {
|
|
54
|
-
for (const key of cache.keys()) {
|
|
55
|
-
if (pattern.test(key))
|
|
56
|
-
cache.delete(key);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/** Get cache stats */
|
|
60
|
-
export function cacheStats() {
|
|
61
|
-
let totalReads = 0;
|
|
62
|
-
let cacheHits = 0;
|
|
63
|
-
for (const entry of cache.values()) {
|
|
64
|
-
totalReads += entry.readCount;
|
|
65
|
-
cacheHits += Math.max(0, entry.readCount - 1); // first read is never cached
|
|
66
|
-
}
|
|
67
|
-
return { files: cache.size, totalReads, cacheHits };
|
|
68
|
-
}
|
|
69
|
-
/** Clear the entire cache */
|
|
70
|
-
export function clearFileCache() {
|
|
71
|
-
cache.clear();
|
|
72
|
-
}
|
package/dist/file-index.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// Pre-computed file index — build once, serve search from memory
|
|
2
|
-
// Eliminates subprocess spawning for repeat file queries
|
|
3
|
-
import { spawn } from "child_process";
|
|
4
|
-
let index = null;
|
|
5
|
-
let indexCwd = "";
|
|
6
|
-
let indexTime = 0;
|
|
7
|
-
let watcher = null;
|
|
8
|
-
const INDEX_TTL = 30_000; // 30 seconds
|
|
9
|
-
function exec(command, cwd) {
|
|
10
|
-
return new Promise((resolve) => {
|
|
11
|
-
const proc = spawn("/bin/zsh", ["-c", command], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
12
|
-
let out = "";
|
|
13
|
-
proc.stdout?.on("data", (d) => { out += d.toString(); });
|
|
14
|
-
proc.on("close", () => resolve(out));
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
/** Build or return cached file index */
|
|
18
|
-
export async function getFileIndex(cwd) {
|
|
19
|
-
// Return cached if fresh
|
|
20
|
-
if (index && indexCwd === cwd && Date.now() - indexTime < INDEX_TTL) {
|
|
21
|
-
return index;
|
|
22
|
-
}
|
|
23
|
-
const raw = await exec("find . -type f -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/.next/*' -not -path '*/build/*' 2>/dev/null", cwd);
|
|
24
|
-
index = raw.split("\n").filter(l => l.trim()).map(p => {
|
|
25
|
-
const path = p.trim();
|
|
26
|
-
const parts = path.split("/");
|
|
27
|
-
const name = parts[parts.length - 1] ?? path;
|
|
28
|
-
const dir = parts.slice(0, -1).join("/") || ".";
|
|
29
|
-
const ext = name.includes(".") ? "." + name.split(".").pop() : "";
|
|
30
|
-
return { path, dir, name, ext };
|
|
31
|
-
});
|
|
32
|
-
indexCwd = cwd;
|
|
33
|
-
indexTime = Date.now();
|
|
34
|
-
return index;
|
|
35
|
-
}
|
|
36
|
-
/** Search file index by glob pattern (in-memory, no subprocess) */
|
|
37
|
-
export async function searchIndex(cwd, pattern) {
|
|
38
|
-
const idx = await getFileIndex(cwd);
|
|
39
|
-
// Convert glob to regex
|
|
40
|
-
const regex = new RegExp("^" + pattern
|
|
41
|
-
.replace(/\./g, "\\.")
|
|
42
|
-
.replace(/\*/g, ".*")
|
|
43
|
-
.replace(/\?/g, ".")
|
|
44
|
-
+ "$", "i");
|
|
45
|
-
return idx.filter(e => regex.test(e.name) || regex.test(e.path)).map(e => e.path);
|
|
46
|
-
}
|
|
47
|
-
/** Get file index stats */
|
|
48
|
-
export async function indexStats(cwd) {
|
|
49
|
-
const idx = await getFileIndex(cwd);
|
|
50
|
-
const byExt = {};
|
|
51
|
-
const byDir = {};
|
|
52
|
-
for (const e of idx) {
|
|
53
|
-
byExt[e.ext || "(none)"] = (byExt[e.ext || "(none)"] ?? 0) + 1;
|
|
54
|
-
const topDir = e.dir.split("/").slice(0, 2).join("/");
|
|
55
|
-
byDir[topDir] = (byDir[topDir] ?? 0) + 1;
|
|
56
|
-
}
|
|
57
|
-
return { totalFiles: idx.length, byExtension: byExt, byDir };
|
|
58
|
-
}
|
|
59
|
-
/** Invalidate index */
|
|
60
|
-
export function invalidateIndex() {
|
|
61
|
-
index = null;
|
|
62
|
-
}
|
package/dist/history.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
const DIR = join(homedir(), ".terminal");
|
|
5
|
-
const HISTORY_FILE = join(DIR, "history.json");
|
|
6
|
-
const CONFIG_FILE = join(DIR, "config.json");
|
|
7
|
-
export const DEFAULT_PERMISSIONS = {
|
|
8
|
-
destructive: true,
|
|
9
|
-
network: true,
|
|
10
|
-
sudo: true,
|
|
11
|
-
write_outside_cwd: true,
|
|
12
|
-
install: true,
|
|
13
|
-
};
|
|
14
|
-
export const DEFAULT_CONFIG = {
|
|
15
|
-
onboarded: false,
|
|
16
|
-
confirm: false,
|
|
17
|
-
permissions: DEFAULT_PERMISSIONS,
|
|
18
|
-
};
|
|
19
|
-
function ensureDir() {
|
|
20
|
-
if (!existsSync(DIR))
|
|
21
|
-
mkdirSync(DIR, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
export function loadHistory() {
|
|
24
|
-
ensureDir();
|
|
25
|
-
if (!existsSync(HISTORY_FILE))
|
|
26
|
-
return [];
|
|
27
|
-
try {
|
|
28
|
-
return JSON.parse(readFileSync(HISTORY_FILE, "utf8"));
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export function saveHistory(entries) {
|
|
35
|
-
ensureDir();
|
|
36
|
-
writeFileSync(HISTORY_FILE, JSON.stringify(entries.slice(-500), null, 2));
|
|
37
|
-
}
|
|
38
|
-
export function appendHistory(entry) {
|
|
39
|
-
const existing = loadHistory();
|
|
40
|
-
saveHistory([...existing, entry]);
|
|
41
|
-
}
|
|
42
|
-
export function loadConfig() {
|
|
43
|
-
ensureDir();
|
|
44
|
-
if (!existsSync(CONFIG_FILE))
|
|
45
|
-
return { ...DEFAULT_CONFIG };
|
|
46
|
-
try {
|
|
47
|
-
const saved = JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
48
|
-
return {
|
|
49
|
-
...DEFAULT_CONFIG,
|
|
50
|
-
...saved,
|
|
51
|
-
confirm: saved.confirm ?? false,
|
|
52
|
-
permissions: { ...DEFAULT_PERMISSIONS, ...(saved.permissions ?? {}) },
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return { ...DEFAULT_CONFIG };
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
export function saveConfig(config) {
|
|
60
|
-
ensureDir();
|
|
61
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
62
|
-
}
|
package/dist/lazy-executor.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// Lazy execution — for large result sets, return count + sample + categories
|
|
2
|
-
// instead of full output. Agent requests slices on demand.
|
|
3
|
-
import { dirname } from "path";
|
|
4
|
-
const LAZY_THRESHOLD = 200; // lines before switching to lazy mode (was 100, too aggressive)
|
|
5
|
-
// Commands where the user explicitly wants full output — never lazify
|
|
6
|
-
const PASSTHROUGH_COMMANDS = [
|
|
7
|
-
// File reading — user explicitly wants content
|
|
8
|
-
/\bcat\b/, /\bhead\b/, /\btail\b/, /\bbat\b/, /\bless\b/, /\bmore\b/,
|
|
9
|
-
// Git review commands — truncating diffs/patches loses semantic meaning
|
|
10
|
-
/\bgit\s+diff\b/, /\bgit\s+show\b/, /\bgit\s+log\s+-p\b/, /\bgit\s+log\s+--patch\b/,
|
|
11
|
-
// Summary/report commands — summarizing a summary is pointless
|
|
12
|
-
/\bsummary\b/i, /\bstatus\b/i, /\breport\b/i, /\bstats\b/i,
|
|
13
|
-
/\bweek\b/i, /\btoday\b/i, /\bdashboard\b/i,
|
|
14
|
-
];
|
|
15
|
-
/** Check if output should use lazy mode */
|
|
16
|
-
export function shouldBeLazy(output, command) {
|
|
17
|
-
// Never lazify explicit read commands or summary commands
|
|
18
|
-
if (command && PASSTHROUGH_COMMANDS.some(p => p.test(command)))
|
|
19
|
-
return false;
|
|
20
|
-
return output.split("\n").filter(l => l.trim()).length > LAZY_THRESHOLD;
|
|
21
|
-
}
|
|
22
|
-
/** Convert large output to lazy format: count + sample + categories */
|
|
23
|
-
export function toLazy(output, command) {
|
|
24
|
-
const lines = output.split("\n").filter(l => l.trim());
|
|
25
|
-
const sample = lines.slice(0, 20);
|
|
26
|
-
// Try to categorize by directory (for file-like output)
|
|
27
|
-
const categories = {};
|
|
28
|
-
const isFilePaths = lines.filter(l => l.includes("/")).length > lines.length * 0.5;
|
|
29
|
-
if (isFilePaths) {
|
|
30
|
-
for (const line of lines) {
|
|
31
|
-
const dir = dirname(line.trim()) || ".";
|
|
32
|
-
// Group by top-level dir
|
|
33
|
-
const topDir = dir.split("/").slice(0, 2).join("/");
|
|
34
|
-
categories[topDir] = (categories[topDir] ?? 0) + 1;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
lazy: true,
|
|
39
|
-
count: lines.length,
|
|
40
|
-
sample,
|
|
41
|
-
categories: Object.keys(categories).length > 1 ? categories : undefined,
|
|
42
|
-
hint: `${lines.length} results. Showing first 20. Use a more specific query to narrow results.`,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/** Get a slice of output */
|
|
46
|
-
export function getSlice(output, offset, limit) {
|
|
47
|
-
const allLines = output.split("\n").filter(l => l.trim());
|
|
48
|
-
const slice = allLines.slice(offset, offset + limit);
|
|
49
|
-
return {
|
|
50
|
-
lines: slice,
|
|
51
|
-
total: allLines.length,
|
|
52
|
-
hasMore: offset + limit < allLines.length,
|
|
53
|
-
};
|
|
54
|
-
}
|
package/dist/line-dedup.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Cross-command line deduplication — track lines already shown to agent
|
|
2
|
-
// When new output contains >50% already-seen lines, suppress them
|
|
3
|
-
const seenLines = new Set();
|
|
4
|
-
const MAX_SEEN = 5000;
|
|
5
|
-
function normalize(line) {
|
|
6
|
-
return line.trim().toLowerCase();
|
|
7
|
-
}
|
|
8
|
-
/** Deduplicate output lines against session history */
|
|
9
|
-
export function dedup(output) {
|
|
10
|
-
const lines = output.split("\n");
|
|
11
|
-
if (lines.length < 5) {
|
|
12
|
-
// Short output — add to seen, don't dedup
|
|
13
|
-
for (const l of lines) {
|
|
14
|
-
if (l.trim())
|
|
15
|
-
seenLines.add(normalize(l));
|
|
16
|
-
}
|
|
17
|
-
return { output, novelCount: lines.length, seenCount: 0, deduplicated: false };
|
|
18
|
-
}
|
|
19
|
-
let novelCount = 0;
|
|
20
|
-
let seenCount = 0;
|
|
21
|
-
const novel = [];
|
|
22
|
-
for (const line of lines) {
|
|
23
|
-
const norm = normalize(line);
|
|
24
|
-
if (!norm) {
|
|
25
|
-
novel.push(line);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
if (seenLines.has(norm)) {
|
|
29
|
-
seenCount++;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
novelCount++;
|
|
33
|
-
novel.push(line);
|
|
34
|
-
seenLines.add(norm);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// Evict oldest if too large
|
|
38
|
-
if (seenLines.size > MAX_SEEN) {
|
|
39
|
-
const entries = [...seenLines];
|
|
40
|
-
for (let i = 0; i < entries.length - MAX_SEEN; i++) {
|
|
41
|
-
seenLines.delete(entries[i]);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Only dedup if >50% were already seen
|
|
45
|
-
if (seenCount > lines.length * 0.5) {
|
|
46
|
-
const result = novel.join("\n");
|
|
47
|
-
return { output: result + `\n(${seenCount} lines already shown, omitted)`, novelCount, seenCount, deduplicated: true };
|
|
48
|
-
}
|
|
49
|
-
// Add all to seen but return full output
|
|
50
|
-
for (const l of lines) {
|
|
51
|
-
if (l.trim())
|
|
52
|
-
seenLines.add(normalize(l));
|
|
53
|
-
}
|
|
54
|
-
return { output, novelCount: lines.length, seenCount: 0, deduplicated: false };
|
|
55
|
-
}
|
|
56
|
-
/** Clear dedup history */
|
|
57
|
-
export function clearDedup() {
|
|
58
|
-
seenLines.clear();
|
|
59
|
-
}
|