@hasna/terminal 2.3.0 → 2.3.2

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 (267) hide show
  1. package/dist/App.js +404 -0
  2. package/dist/Browse.js +79 -0
  3. package/dist/FuzzyPicker.js +47 -0
  4. package/dist/Onboarding.js +51 -0
  5. package/dist/Spinner.js +12 -0
  6. package/dist/StatusBar.js +49 -0
  7. package/dist/ai.js +322 -0
  8. package/dist/cache.js +41 -0
  9. package/dist/cli.js +64 -16
  10. package/dist/command-rewriter.js +64 -0
  11. package/dist/command-validator.js +86 -0
  12. package/dist/compression.js +107 -0
  13. package/dist/context-hints.js +275 -0
  14. package/dist/diff-cache.js +107 -0
  15. package/dist/discover.js +212 -0
  16. package/dist/economy.js +123 -0
  17. package/dist/expand-store.js +38 -0
  18. package/dist/file-cache.js +72 -0
  19. package/dist/file-index.js +62 -0
  20. package/dist/history.js +62 -0
  21. package/dist/lazy-executor.js +54 -0
  22. package/dist/line-dedup.js +59 -0
  23. package/dist/loop-detector.js +75 -0
  24. package/dist/mcp/install.js +98 -0
  25. package/dist/mcp/server.js +569 -0
  26. package/dist/noise-filter.js +86 -0
  27. package/dist/output-processor.js +129 -0
  28. package/dist/output-router.js +41 -0
  29. package/dist/output-store.js +111 -0
  30. package/dist/parsers/base.js +2 -0
  31. package/dist/parsers/build.js +64 -0
  32. package/dist/parsers/errors.js +101 -0
  33. package/dist/parsers/files.js +78 -0
  34. package/dist/parsers/git.js +99 -0
  35. package/dist/parsers/index.js +48 -0
  36. package/dist/parsers/tests.js +89 -0
  37. package/dist/providers/anthropic.js +39 -0
  38. package/dist/providers/base.js +4 -0
  39. package/dist/providers/cerebras.js +95 -0
  40. package/dist/providers/groq.js +95 -0
  41. package/dist/providers/index.js +73 -0
  42. package/dist/providers/xai.js +95 -0
  43. package/dist/recipes/model.js +20 -0
  44. package/dist/recipes/storage.js +136 -0
  45. package/dist/search/content-search.js +68 -0
  46. package/dist/search/file-search.js +61 -0
  47. package/dist/search/filters.js +34 -0
  48. package/dist/search/index.js +5 -0
  49. package/dist/search/semantic.js +320 -0
  50. package/dist/session-boot.js +59 -0
  51. package/dist/session-context.js +55 -0
  52. package/dist/sessions-db.js +173 -0
  53. package/dist/smart-display.js +286 -0
  54. package/dist/snapshots.js +51 -0
  55. package/dist/supervisor.js +112 -0
  56. package/dist/test-watchlist.js +131 -0
  57. package/dist/tool-profiles.js +122 -0
  58. package/dist/tree.js +94 -0
  59. package/dist/usage-cache.js +65 -0
  60. package/package.json +8 -1
  61. package/src/ai.ts +8 -0
  62. package/src/cli.tsx +57 -18
  63. package/src/output-processor.ts +6 -1
  64. package/src/output-store.ts +58 -12
  65. package/src/tool-profiles.ts +139 -0
  66. package/.claude/scheduled_tasks.lock +0 -1
  67. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  68. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  69. package/CONTRIBUTING.md +0 -80
  70. package/benchmarks/benchmark.mjs +0 -115
  71. package/imported_modules.txt +0 -0
  72. package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
  73. package/temp/rtk/.claude/agents/debugger.md +0 -519
  74. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
  75. package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
  76. package/temp/rtk/.claude/agents/technical-writer.md +0 -355
  77. package/temp/rtk/.claude/commands/diagnose.md +0 -352
  78. package/temp/rtk/.claude/commands/test-routing.md +0 -362
  79. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
  80. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
  81. package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
  82. package/temp/rtk/.claude/rules/cli-testing.md +0 -526
  83. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
  84. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
  85. package/temp/rtk/.claude/skills/performance.md +0 -435
  86. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
  87. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
  88. package/temp/rtk/.claude/skills/repo-recap.md +0 -206
  89. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
  90. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
  91. package/temp/rtk/.claude/skills/security-guardian.md +0 -503
  92. package/temp/rtk/.claude/skills/ship.md +0 -404
  93. package/temp/rtk/.github/workflows/benchmark.yml +0 -34
  94. package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
  95. package/temp/rtk/.github/workflows/release-please.yml +0 -51
  96. package/temp/rtk/.github/workflows/release.yml +0 -343
  97. package/temp/rtk/.github/workflows/security-check.yml +0 -135
  98. package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
  99. package/temp/rtk/.release-please-manifest.json +0 -3
  100. package/temp/rtk/ARCHITECTURE.md +0 -1491
  101. package/temp/rtk/CHANGELOG.md +0 -640
  102. package/temp/rtk/CLAUDE.md +0 -605
  103. package/temp/rtk/CONTRIBUTING.md +0 -199
  104. package/temp/rtk/Cargo.lock +0 -1668
  105. package/temp/rtk/Cargo.toml +0 -64
  106. package/temp/rtk/Formula/rtk.rb +0 -43
  107. package/temp/rtk/INSTALL.md +0 -390
  108. package/temp/rtk/LICENSE +0 -21
  109. package/temp/rtk/README.md +0 -386
  110. package/temp/rtk/README_es.md +0 -159
  111. package/temp/rtk/README_fr.md +0 -197
  112. package/temp/rtk/README_ja.md +0 -159
  113. package/temp/rtk/README_ko.md +0 -159
  114. package/temp/rtk/README_zh.md +0 -167
  115. package/temp/rtk/ROADMAP.md +0 -15
  116. package/temp/rtk/SECURITY.md +0 -217
  117. package/temp/rtk/TEST_EXEC_TIME.md +0 -102
  118. package/temp/rtk/build.rs +0 -57
  119. package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
  120. package/temp/rtk/docs/FEATURES.md +0 -1410
  121. package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
  122. package/temp/rtk/docs/filter-workflow.md +0 -102
  123. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  124. package/temp/rtk/docs/tracking.md +0 -583
  125. package/temp/rtk/hooks/opencode-rtk.ts +0 -39
  126. package/temp/rtk/hooks/rtk-awareness.md +0 -29
  127. package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
  128. package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
  129. package/temp/rtk/install.sh +0 -124
  130. package/temp/rtk/release-please-config.json +0 -10
  131. package/temp/rtk/scripts/benchmark.sh +0 -592
  132. package/temp/rtk/scripts/check-installation.sh +0 -162
  133. package/temp/rtk/scripts/install-local.sh +0 -37
  134. package/temp/rtk/scripts/rtk-economics.sh +0 -137
  135. package/temp/rtk/scripts/test-all.sh +0 -561
  136. package/temp/rtk/scripts/test-aristote.sh +0 -227
  137. package/temp/rtk/scripts/test-tracking.sh +0 -79
  138. package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
  139. package/temp/rtk/scripts/validate-docs.sh +0 -73
  140. package/temp/rtk/src/aws_cmd.rs +0 -880
  141. package/temp/rtk/src/binlog.rs +0 -1645
  142. package/temp/rtk/src/cargo_cmd.rs +0 -1727
  143. package/temp/rtk/src/cc_economics.rs +0 -1157
  144. package/temp/rtk/src/ccusage.rs +0 -340
  145. package/temp/rtk/src/config.rs +0 -187
  146. package/temp/rtk/src/container.rs +0 -855
  147. package/temp/rtk/src/curl_cmd.rs +0 -134
  148. package/temp/rtk/src/deps.rs +0 -268
  149. package/temp/rtk/src/diff_cmd.rs +0 -367
  150. package/temp/rtk/src/discover/mod.rs +0 -274
  151. package/temp/rtk/src/discover/provider.rs +0 -388
  152. package/temp/rtk/src/discover/registry.rs +0 -2022
  153. package/temp/rtk/src/discover/report.rs +0 -202
  154. package/temp/rtk/src/discover/rules.rs +0 -667
  155. package/temp/rtk/src/display_helpers.rs +0 -402
  156. package/temp/rtk/src/dotnet_cmd.rs +0 -1771
  157. package/temp/rtk/src/dotnet_format_report.rs +0 -133
  158. package/temp/rtk/src/dotnet_trx.rs +0 -593
  159. package/temp/rtk/src/env_cmd.rs +0 -204
  160. package/temp/rtk/src/filter.rs +0 -462
  161. package/temp/rtk/src/filters/README.md +0 -52
  162. package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
  163. package/temp/rtk/src/filters/basedpyright.toml +0 -47
  164. package/temp/rtk/src/filters/biome.toml +0 -45
  165. package/temp/rtk/src/filters/brew-install.toml +0 -37
  166. package/temp/rtk/src/filters/composer-install.toml +0 -40
  167. package/temp/rtk/src/filters/df.toml +0 -16
  168. package/temp/rtk/src/filters/dotnet-build.toml +0 -64
  169. package/temp/rtk/src/filters/du.toml +0 -16
  170. package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
  171. package/temp/rtk/src/filters/gcc.toml +0 -49
  172. package/temp/rtk/src/filters/gcloud.toml +0 -22
  173. package/temp/rtk/src/filters/hadolint.toml +0 -24
  174. package/temp/rtk/src/filters/helm.toml +0 -29
  175. package/temp/rtk/src/filters/iptables.toml +0 -27
  176. package/temp/rtk/src/filters/jj.toml +0 -28
  177. package/temp/rtk/src/filters/jq.toml +0 -24
  178. package/temp/rtk/src/filters/make.toml +0 -41
  179. package/temp/rtk/src/filters/markdownlint.toml +0 -24
  180. package/temp/rtk/src/filters/mix-compile.toml +0 -27
  181. package/temp/rtk/src/filters/mix-format.toml +0 -15
  182. package/temp/rtk/src/filters/mvn-build.toml +0 -44
  183. package/temp/rtk/src/filters/oxlint.toml +0 -43
  184. package/temp/rtk/src/filters/ping.toml +0 -63
  185. package/temp/rtk/src/filters/pio-run.toml +0 -40
  186. package/temp/rtk/src/filters/poetry-install.toml +0 -50
  187. package/temp/rtk/src/filters/pre-commit.toml +0 -35
  188. package/temp/rtk/src/filters/ps.toml +0 -16
  189. package/temp/rtk/src/filters/quarto-render.toml +0 -41
  190. package/temp/rtk/src/filters/rsync.toml +0 -48
  191. package/temp/rtk/src/filters/shellcheck.toml +0 -27
  192. package/temp/rtk/src/filters/shopify-theme.toml +0 -29
  193. package/temp/rtk/src/filters/skopeo.toml +0 -45
  194. package/temp/rtk/src/filters/sops.toml +0 -16
  195. package/temp/rtk/src/filters/ssh.toml +0 -44
  196. package/temp/rtk/src/filters/stat.toml +0 -34
  197. package/temp/rtk/src/filters/swift-build.toml +0 -41
  198. package/temp/rtk/src/filters/systemctl-status.toml +0 -33
  199. package/temp/rtk/src/filters/terraform-plan.toml +0 -35
  200. package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
  201. package/temp/rtk/src/filters/tofu-init.toml +0 -38
  202. package/temp/rtk/src/filters/tofu-plan.toml +0 -35
  203. package/temp/rtk/src/filters/tofu-validate.toml +0 -17
  204. package/temp/rtk/src/filters/trunk-build.toml +0 -39
  205. package/temp/rtk/src/filters/ty.toml +0 -50
  206. package/temp/rtk/src/filters/uv-sync.toml +0 -37
  207. package/temp/rtk/src/filters/xcodebuild.toml +0 -99
  208. package/temp/rtk/src/filters/yamllint.toml +0 -25
  209. package/temp/rtk/src/find_cmd.rs +0 -598
  210. package/temp/rtk/src/format_cmd.rs +0 -386
  211. package/temp/rtk/src/gain.rs +0 -723
  212. package/temp/rtk/src/gh_cmd.rs +0 -1651
  213. package/temp/rtk/src/git.rs +0 -2012
  214. package/temp/rtk/src/go_cmd.rs +0 -592
  215. package/temp/rtk/src/golangci_cmd.rs +0 -254
  216. package/temp/rtk/src/grep_cmd.rs +0 -288
  217. package/temp/rtk/src/gt_cmd.rs +0 -810
  218. package/temp/rtk/src/hook_audit_cmd.rs +0 -283
  219. package/temp/rtk/src/hook_check.rs +0 -171
  220. package/temp/rtk/src/init.rs +0 -1859
  221. package/temp/rtk/src/integrity.rs +0 -537
  222. package/temp/rtk/src/json_cmd.rs +0 -231
  223. package/temp/rtk/src/learn/detector.rs +0 -628
  224. package/temp/rtk/src/learn/mod.rs +0 -119
  225. package/temp/rtk/src/learn/report.rs +0 -184
  226. package/temp/rtk/src/lint_cmd.rs +0 -694
  227. package/temp/rtk/src/local_llm.rs +0 -316
  228. package/temp/rtk/src/log_cmd.rs +0 -248
  229. package/temp/rtk/src/ls.rs +0 -324
  230. package/temp/rtk/src/main.rs +0 -2482
  231. package/temp/rtk/src/mypy_cmd.rs +0 -389
  232. package/temp/rtk/src/next_cmd.rs +0 -241
  233. package/temp/rtk/src/npm_cmd.rs +0 -236
  234. package/temp/rtk/src/parser/README.md +0 -267
  235. package/temp/rtk/src/parser/error.rs +0 -46
  236. package/temp/rtk/src/parser/formatter.rs +0 -336
  237. package/temp/rtk/src/parser/mod.rs +0 -311
  238. package/temp/rtk/src/parser/types.rs +0 -119
  239. package/temp/rtk/src/pip_cmd.rs +0 -302
  240. package/temp/rtk/src/playwright_cmd.rs +0 -479
  241. package/temp/rtk/src/pnpm_cmd.rs +0 -573
  242. package/temp/rtk/src/prettier_cmd.rs +0 -221
  243. package/temp/rtk/src/prisma_cmd.rs +0 -482
  244. package/temp/rtk/src/psql_cmd.rs +0 -382
  245. package/temp/rtk/src/pytest_cmd.rs +0 -384
  246. package/temp/rtk/src/read.rs +0 -217
  247. package/temp/rtk/src/rewrite_cmd.rs +0 -50
  248. package/temp/rtk/src/ruff_cmd.rs +0 -402
  249. package/temp/rtk/src/runner.rs +0 -271
  250. package/temp/rtk/src/summary.rs +0 -297
  251. package/temp/rtk/src/tee.rs +0 -405
  252. package/temp/rtk/src/telemetry.rs +0 -248
  253. package/temp/rtk/src/toml_filter.rs +0 -1655
  254. package/temp/rtk/src/tracking.rs +0 -1416
  255. package/temp/rtk/src/tree.rs +0 -209
  256. package/temp/rtk/src/tsc_cmd.rs +0 -259
  257. package/temp/rtk/src/utils.rs +0 -432
  258. package/temp/rtk/src/verify_cmd.rs +0 -47
  259. package/temp/rtk/src/vitest_cmd.rs +0 -385
  260. package/temp/rtk/src/wc_cmd.rs +0 -401
  261. package/temp/rtk/src/wget_cmd.rs +0 -260
  262. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
  263. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
  264. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
  265. package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
  266. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
  267. package/tsconfig.json +0 -15
@@ -0,0 +1,173 @@
1
+ // SQLite session database — tracks every terminal interaction
2
+ // @ts-ignore — bun:sqlite is a bun built-in
3
+ import { Database } from "bun:sqlite";
4
+ import { existsSync, mkdirSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
7
+ import { randomUUID } from "crypto";
8
+ const DIR = join(homedir(), ".terminal");
9
+ const DB_PATH = join(DIR, "sessions.db");
10
+ let db = null;
11
+ function getDb() {
12
+ if (db)
13
+ return db;
14
+ if (!existsSync(DIR))
15
+ mkdirSync(DIR, { recursive: true });
16
+ db = new Database(DB_PATH);
17
+ db.exec("PRAGMA journal_mode = WAL");
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS sessions (
20
+ id TEXT PRIMARY KEY,
21
+ started_at INTEGER NOT NULL,
22
+ ended_at INTEGER,
23
+ cwd TEXT NOT NULL,
24
+ provider TEXT,
25
+ model TEXT
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS interactions (
29
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
30
+ session_id TEXT NOT NULL REFERENCES sessions(id),
31
+ nl TEXT NOT NULL,
32
+ command TEXT,
33
+ output TEXT,
34
+ exit_code INTEGER,
35
+ tokens_used INTEGER DEFAULT 0,
36
+ tokens_saved INTEGER DEFAULT 0,
37
+ duration_ms INTEGER,
38
+ model TEXT,
39
+ cached INTEGER DEFAULT 0,
40
+ created_at INTEGER NOT NULL
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_interactions_session ON interactions(session_id);
44
+ CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);
45
+
46
+ CREATE TABLE IF NOT EXISTS corrections (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ prompt TEXT NOT NULL,
49
+ failed_command TEXT NOT NULL,
50
+ error_output TEXT,
51
+ corrected_command TEXT NOT NULL,
52
+ worked INTEGER DEFAULT 1,
53
+ error_type TEXT,
54
+ created_at INTEGER NOT NULL
55
+ );
56
+
57
+ CREATE TABLE IF NOT EXISTS outputs (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ session_id TEXT,
60
+ command TEXT NOT NULL,
61
+ raw_output_path TEXT,
62
+ compressed_summary TEXT,
63
+ tokens_raw INTEGER DEFAULT 0,
64
+ tokens_compressed INTEGER DEFAULT 0,
65
+ provider TEXT,
66
+ model TEXT,
67
+ created_at INTEGER NOT NULL
68
+ );
69
+
70
+ CREATE INDEX IF NOT EXISTS idx_corrections_prompt ON corrections(prompt);
71
+ `);
72
+ return db;
73
+ }
74
+ // ── Sessions ─────────────────────────────────────────────────────────────────
75
+ export function createSession(cwd, provider, model) {
76
+ const id = randomUUID();
77
+ getDb().prepare("INSERT INTO sessions (id, started_at, cwd, provider, model) VALUES (?, ?, ?, ?, ?)").run(id, Date.now(), cwd, provider ?? null, model ?? null);
78
+ return id;
79
+ }
80
+ export function endSession(sessionId) {
81
+ getDb().prepare("UPDATE sessions SET ended_at = ? WHERE id = ?").run(Date.now(), sessionId);
82
+ }
83
+ export function listSessions(limit = 20) {
84
+ return getDb().prepare("SELECT * FROM sessions ORDER BY started_at DESC LIMIT ?").all(limit);
85
+ }
86
+ export function getSession(id) {
87
+ return getDb().prepare("SELECT * FROM sessions WHERE id = ?").get(id);
88
+ }
89
+ export function logInteraction(sessionId, data) {
90
+ const result = getDb().prepare(`INSERT INTO interactions (session_id, nl, command, output, exit_code, tokens_used, tokens_saved, duration_ms, model, cached, created_at)
91
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, data.nl, data.command ?? null, data.output ? data.output.slice(0, 500) : null, data.exitCode ?? null, data.tokensUsed ?? 0, data.tokensSaved ?? 0, data.durationMs ?? null, data.model ?? null, data.cached ? 1 : 0, Date.now());
92
+ // bun:sqlite — lastInsertRowid is a property on the statement after run()
93
+ const lastId = getDb().prepare("SELECT last_insert_rowid() as id").get();
94
+ return lastId?.id ?? 0;
95
+ }
96
+ export function updateInteraction(id, data) {
97
+ const sets = [];
98
+ const vals = [];
99
+ if (data.command !== undefined) {
100
+ sets.push("command = ?");
101
+ vals.push(data.command);
102
+ }
103
+ if (data.output !== undefined) {
104
+ sets.push("output = ?");
105
+ vals.push(data.output.slice(0, 500));
106
+ }
107
+ if (data.exitCode !== undefined) {
108
+ sets.push("exit_code = ?");
109
+ vals.push(data.exitCode);
110
+ }
111
+ if (data.tokensSaved !== undefined) {
112
+ sets.push("tokens_saved = ?");
113
+ vals.push(data.tokensSaved);
114
+ }
115
+ if (sets.length === 0)
116
+ return;
117
+ vals.push(id);
118
+ getDb().prepare(`UPDATE interactions SET ${sets.join(", ")} WHERE id = ?`).run(...vals);
119
+ }
120
+ export function getSessionInteractions(sessionId) {
121
+ return getDb().prepare("SELECT * FROM interactions WHERE session_id = ? ORDER BY created_at ASC").all(sessionId);
122
+ }
123
+ export function getSessionStats() {
124
+ const d = getDb();
125
+ const sessions = d.prepare("SELECT COUNT(*) as c FROM sessions").get();
126
+ const interactions = d.prepare("SELECT COUNT(*) as c, SUM(tokens_saved) as saved, SUM(tokens_used) as used FROM interactions").get();
127
+ const cached = d.prepare("SELECT COUNT(*) as c FROM interactions WHERE cached = 1").get();
128
+ const errors = d.prepare("SELECT COUNT(*) as c FROM interactions WHERE exit_code IS NOT NULL AND exit_code != 0").get();
129
+ const totalInteractions = interactions.c ?? 0;
130
+ return {
131
+ totalSessions: sessions.c ?? 0,
132
+ totalInteractions,
133
+ totalTokensSaved: interactions.saved ?? 0,
134
+ totalTokensUsed: interactions.used ?? 0,
135
+ cacheHitRate: totalInteractions > 0 ? (cached.c ?? 0) / totalInteractions : 0,
136
+ avgInteractionsPerSession: sessions.c > 0 ? totalInteractions / sessions.c : 0,
137
+ errorRate: totalInteractions > 0 ? (errors.c ?? 0) / totalInteractions : 0,
138
+ };
139
+ }
140
+ // ── Corrections ─────────────────────────────────────────────────────────────
141
+ /** Record a correction: command failed, then AI retried with a better one */
142
+ export function recordCorrection(prompt, failedCommand, errorOutput, correctedCommand, worked, errorType) {
143
+ getDb().prepare("INSERT INTO corrections (prompt, failed_command, error_output, corrected_command, worked, error_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(prompt, failedCommand, errorOutput?.slice(0, 2000) ?? "", correctedCommand, worked ? 1 : 0, errorType ?? null, Date.now());
144
+ }
145
+ /** Find similar corrections for a prompt — used to inject as negative examples */
146
+ export function findSimilarCorrections(prompt, limit = 5) {
147
+ // Simple keyword matching — extract significant words from prompt
148
+ const words = prompt.toLowerCase().split(/\s+/).filter(w => w.length > 3);
149
+ if (words.length === 0)
150
+ return [];
151
+ // Search corrections where the prompt shares keywords
152
+ const all = getDb().prepare("SELECT prompt, failed_command, corrected_command, error_type FROM corrections WHERE worked = 1 ORDER BY created_at DESC LIMIT 100").all();
153
+ return all
154
+ .filter(c => {
155
+ const cWords = c.prompt.toLowerCase().split(/\s+/);
156
+ const overlap = words.filter((w) => cWords.some((cw) => cw.includes(w) || w.includes(cw)));
157
+ return overlap.length >= Math.min(2, words.length);
158
+ })
159
+ .slice(0, limit)
160
+ .map(c => ({ failed_command: c.failed_command, corrected_command: c.corrected_command, error_type: c.error_type ?? "unknown" }));
161
+ }
162
+ // ── Output tracking ─────────────────────────────────────────────────────────
163
+ /** Record a compressed output for audit trail */
164
+ export function recordOutput(command, rawOutputPath, compressedSummary, tokensRaw, tokensCompressed, provider, model, sessionId) {
165
+ getDb().prepare("INSERT INTO outputs (session_id, command, raw_output_path, compressed_summary, tokens_raw, tokens_compressed, provider, model, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(sessionId ?? null, command, rawOutputPath ?? null, compressedSummary?.slice(0, 5000) ?? "", tokensRaw, tokensCompressed, provider ?? null, model ?? null, Date.now());
166
+ }
167
+ /** Close the database connection */
168
+ export function closeDb() {
169
+ if (db) {
170
+ db.close();
171
+ db = null;
172
+ }
173
+ }
@@ -0,0 +1,286 @@
1
+ // Smart output display — compress repetitive output into grouped patterns
2
+ import { dirname, basename } from "path";
3
+ /** Detect if lines look like file paths */
4
+ function looksLikePaths(lines) {
5
+ if (lines.length < 3)
6
+ return false;
7
+ const pathLike = lines.filter(l => l.trim().match(/^\.?\//) || l.trim().includes("/"));
8
+ return pathLike.length > lines.length * 0.6;
9
+ }
10
+ /** Find the varying part between similar strings and create a glob pattern */
11
+ function findPattern(items) {
12
+ if (items.length < 2)
13
+ return null;
14
+ const first = items[0];
15
+ const last = items[items.length - 1];
16
+ // Find common prefix
17
+ let prefixLen = 0;
18
+ while (prefixLen < first.length && prefixLen < last.length && first[prefixLen] === last[prefixLen]) {
19
+ prefixLen++;
20
+ }
21
+ // Find common suffix
22
+ let suffixLen = 0;
23
+ while (suffixLen < first.length - prefixLen &&
24
+ suffixLen < last.length - prefixLen &&
25
+ first[first.length - 1 - suffixLen] === last[last.length - 1 - suffixLen]) {
26
+ suffixLen++;
27
+ }
28
+ const prefix = first.slice(0, prefixLen);
29
+ const suffix = suffixLen > 0 ? first.slice(-suffixLen) : "";
30
+ if (prefix.length + suffix.length < first.length * 0.3)
31
+ return null; // too different
32
+ return `${prefix}*${suffix}`;
33
+ }
34
+ /** Group file paths by directory */
35
+ function groupByDir(paths) {
36
+ const groups = new Map();
37
+ for (const p of paths) {
38
+ const dir = dirname(p.trim());
39
+ const file = basename(p.trim());
40
+ if (!groups.has(dir))
41
+ groups.set(dir, []);
42
+ groups.get(dir).push(file);
43
+ }
44
+ return groups;
45
+ }
46
+ /** Detect duplicate filenames across directories */
47
+ function findDuplicates(paths) {
48
+ const byName = new Map();
49
+ for (const p of paths) {
50
+ const file = basename(p.trim());
51
+ if (!byName.has(file))
52
+ byName.set(file, []);
53
+ byName.get(file).push(dirname(p.trim()));
54
+ }
55
+ // Only return files that appear in 2+ dirs
56
+ const dupes = new Map();
57
+ for (const [file, dirs] of byName) {
58
+ if (dirs.length >= 2)
59
+ dupes.set(file, dirs);
60
+ }
61
+ return dupes;
62
+ }
63
+ /** Collapse node_modules paths */
64
+ function collapseNodeModules(paths) {
65
+ const nodeModulesPaths = [];
66
+ const otherPaths = [];
67
+ for (const p of paths) {
68
+ if (p.includes("node_modules")) {
69
+ nodeModulesPaths.push(p);
70
+ }
71
+ else {
72
+ otherPaths.push(p);
73
+ }
74
+ }
75
+ return { nodeModulesPaths, otherPaths };
76
+ }
77
+ /** Smart display: compress file path output into grouped patterns */
78
+ export function smartDisplay(lines) {
79
+ if (lines.length <= 5)
80
+ return lines;
81
+ // Try ls -la table compression first
82
+ const lsCompressed = compressLsTable(lines);
83
+ if (lsCompressed)
84
+ return lsCompressed;
85
+ if (!looksLikePaths(lines))
86
+ return compressGeneric(lines);
87
+ const paths = lines.map(l => l.trim()).filter(l => l);
88
+ const result = [];
89
+ // Step 1: Separate node_modules
90
+ const { nodeModulesPaths, otherPaths } = collapseNodeModules(paths);
91
+ // Step 2: Find duplicates in non-node_modules paths
92
+ const dupes = findDuplicates(otherPaths);
93
+ const handledPaths = new Set();
94
+ // Show duplicates first
95
+ for (const [file, dirs] of dupes) {
96
+ if (dirs.length >= 3) {
97
+ result.push(` **/${file} ×${dirs.length}`);
98
+ result.push(` ${dirs.slice(0, 5).join(", ")}${dirs.length > 5 ? ` +${dirs.length - 5} more` : ""}`);
99
+ for (const d of dirs) {
100
+ handledPaths.add(`${d}/${file}`);
101
+ }
102
+ }
103
+ }
104
+ // Step 3: Group remaining by directory
105
+ const remaining = otherPaths.filter(p => !handledPaths.has(p.trim()));
106
+ const dirGroups = groupByDir(remaining);
107
+ for (const [dir, files] of dirGroups) {
108
+ if (files.length === 1) {
109
+ result.push(` ${dir}/${files[0]}`);
110
+ }
111
+ else if (files.length <= 3) {
112
+ result.push(` ${dir}/`);
113
+ for (const f of files)
114
+ result.push(` ${f}`);
115
+ }
116
+ else {
117
+ // Try to find a pattern
118
+ const sorted = files.sort();
119
+ const pattern = findPattern(sorted);
120
+ if (pattern) {
121
+ const dateRange = collapseDateRange(sorted);
122
+ const rangeStr = dateRange ? ` (${dateRange})` : "";
123
+ result.push(` ${dir}/${pattern} ×${files.length}${rangeStr}`);
124
+ }
125
+ else {
126
+ result.push(` ${dir}/ (${files.length} files)`);
127
+ // Show first 2 + count
128
+ result.push(` ${sorted[0]}, ${sorted[1]}${files.length > 2 ? `, +${files.length - 2} more` : ""}`);
129
+ }
130
+ }
131
+ }
132
+ // Step 4: Collapsed node_modules summary
133
+ if (nodeModulesPaths.length > 0) {
134
+ if (nodeModulesPaths.length <= 2) {
135
+ for (const p of nodeModulesPaths)
136
+ result.push(` ${p}`);
137
+ }
138
+ else {
139
+ // Group node_modules by package name
140
+ const nmGroups = new Map();
141
+ for (const p of nodeModulesPaths) {
142
+ // Extract package name from path: ./X/node_modules/PKG/...
143
+ const match = p.match(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/);
144
+ const pkg = match ? match[1] : "other";
145
+ nmGroups.set(pkg, (nmGroups.get(pkg) ?? 0) + 1);
146
+ }
147
+ result.push(` node_modules/ (${nodeModulesPaths.length} matches)`);
148
+ const topPkgs = [...nmGroups.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
149
+ for (const [pkg, count] of topPkgs) {
150
+ result.push(` ${pkg} ×${count}`);
151
+ }
152
+ if (nmGroups.size > 3) {
153
+ result.push(` +${nmGroups.size - 3} more packages`);
154
+ }
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ /** Detect date range in timestamps and collapse */
160
+ function collapseDateRange(files) {
161
+ const timestamps = [];
162
+ for (const f of files) {
163
+ const match = f.match(/(\d{4})-(\d{2})-(\d{2})T?(\d{2})?/);
164
+ if (match) {
165
+ const [, y, m, d, h] = match;
166
+ timestamps.push(new Date(`${y}-${m}-${d}T${h ?? "00"}:00:00`));
167
+ }
168
+ }
169
+ if (timestamps.length < 2)
170
+ return null;
171
+ timestamps.sort((a, b) => a.getTime() - b.getTime());
172
+ const first = timestamps[0];
173
+ const last = timestamps[timestamps.length - 1];
174
+ const fmt = (d) => `${d.getMonth() + 1}/${d.getDate()}`;
175
+ if (first.toDateString() === last.toDateString()) {
176
+ return `${fmt(first)}`;
177
+ }
178
+ return `${fmt(first)}–${fmt(last)}`;
179
+ }
180
+ /** Detect and compress ls -la style table output */
181
+ function compressLsTable(lines) {
182
+ // Detect ls -la format: permissions size date name
183
+ const lsPattern = /^[dlcbps-][rwxsStT-]{9}\s+\d+\s+\S+\s+\S+\s+\S+\s+\w+\s+\d+\s+[\d:]+\s+.+$/;
184
+ const isLsOutput = lines.filter(l => lsPattern.test(l.trim())).length > lines.length * 0.5;
185
+ if (!isLsOutput)
186
+ return null;
187
+ const result = [];
188
+ const dirs = [];
189
+ const files = [];
190
+ let totalSize = 0;
191
+ for (const line of lines) {
192
+ const match = line.trim().match(/^([dlcbps-])[rwxsStT-]{9}\s+\d+\s+\S+\s+\S+\s+(\S+)\s+\w+\s+\d+\s+[\d:]+\s+(.+)$/);
193
+ if (!match) {
194
+ if (line.trim().startsWith("total "))
195
+ continue;
196
+ result.push(line);
197
+ continue;
198
+ }
199
+ const [, type, sizeStr, name] = match;
200
+ const size = parseInt(sizeStr) || 0;
201
+ totalSize += size;
202
+ if (type === "d") {
203
+ dirs.push(name);
204
+ }
205
+ else {
206
+ files.push({ name, size: formatSize(size) });
207
+ }
208
+ }
209
+ // Compact display
210
+ if (dirs.length > 0) {
211
+ result.push(` 📁 ${dirs.join(" ")}${dirs.length > 5 ? ` (+${dirs.length - 5} more)` : ""}`);
212
+ }
213
+ if (files.length <= 8) {
214
+ for (const f of files) {
215
+ result.push(` ${f.size.padStart(6)} ${f.name}`);
216
+ }
217
+ }
218
+ else {
219
+ // Show top 5 by size + count
220
+ const sorted = files.sort((a, b) => parseSize(b.size) - parseSize(a.size));
221
+ for (const f of sorted.slice(0, 5)) {
222
+ result.push(` ${f.size.padStart(6)} ${f.name}`);
223
+ }
224
+ result.push(` ... +${files.length - 5} more files (${formatSize(totalSize)} total)`);
225
+ }
226
+ return result;
227
+ }
228
+ function formatSize(bytes) {
229
+ if (bytes >= 1_000_000)
230
+ return `${(bytes / 1_000_000).toFixed(1)}M`;
231
+ if (bytes >= 1_000)
232
+ return `${(bytes / 1_000).toFixed(1)}K`;
233
+ return `${bytes}B`;
234
+ }
235
+ function parseSize(s) {
236
+ const match = s.match(/([\d.]+)([BKMG])?/);
237
+ if (!match)
238
+ return 0;
239
+ const n = parseFloat(match[1]);
240
+ const unit = match[2];
241
+ if (unit === "K")
242
+ return n * 1000;
243
+ if (unit === "M")
244
+ return n * 1000000;
245
+ if (unit === "G")
246
+ return n * 1000000000;
247
+ return n;
248
+ }
249
+ /** Compress non-path generic output by deduplicating similar lines */
250
+ function compressGeneric(lines) {
251
+ if (lines.length <= 10)
252
+ return lines;
253
+ const result = [];
254
+ let repeatCount = 0;
255
+ let lastPattern = "";
256
+ for (let i = 0; i < lines.length; i++) {
257
+ const line = lines[i];
258
+ // Normalize: remove numbers, timestamps, hashes for pattern matching
259
+ const pattern = line
260
+ .replace(/\d{4}-\d{2}-\d{2}T[\d:.-]+Z?/g, "TIMESTAMP")
261
+ .replace(/\b[0-9a-f]{7,40}\b/g, "HASH")
262
+ .replace(/\b\d+\b/g, "N")
263
+ .trim();
264
+ if (pattern === lastPattern && i > 0) {
265
+ repeatCount++;
266
+ }
267
+ else {
268
+ if (repeatCount > 1) {
269
+ result.push(` ... ×${repeatCount} similar`);
270
+ }
271
+ else if (repeatCount === 1) {
272
+ result.push(lines[i - 1]);
273
+ }
274
+ result.push(line);
275
+ lastPattern = pattern;
276
+ repeatCount = 0;
277
+ }
278
+ }
279
+ if (repeatCount > 1) {
280
+ result.push(` ... ×${repeatCount} similar`);
281
+ }
282
+ else if (repeatCount === 1) {
283
+ result.push(lines[lines.length - 1]);
284
+ }
285
+ return result;
286
+ }
@@ -0,0 +1,51 @@
1
+ // Session snapshots — capture terminal state for agent context handoff
2
+ import { loadHistory } from "./history.js";
3
+ import { bgStatus } from "./supervisor.js";
4
+ import { getEconomyStats, formatTokens } from "./economy.js";
5
+ import { listRecipes } from "./recipes/storage.js";
6
+ /** Capture a compact snapshot of the current terminal state */
7
+ export function captureSnapshot() {
8
+ // Filtered env — only relevant vars, no secrets
9
+ const safeEnvKeys = [
10
+ "PATH", "HOME", "USER", "SHELL", "NODE_ENV", "PWD", "LANG",
11
+ "TERM", "EDITOR", "VISUAL",
12
+ ];
13
+ const env = {};
14
+ for (const key of safeEnvKeys) {
15
+ if (process.env[key])
16
+ env[key] = process.env[key];
17
+ }
18
+ // Running processes
19
+ const processes = bgStatus().map(p => ({
20
+ pid: p.pid,
21
+ command: p.command,
22
+ port: p.port,
23
+ uptime: Date.now() - p.startedAt,
24
+ }));
25
+ // Recent commands (last 10, compressed)
26
+ const history = loadHistory().slice(-10);
27
+ const recentCommands = history.map(h => ({
28
+ cmd: h.cmd,
29
+ exitCode: h.error,
30
+ summary: h.nl !== h.cmd ? h.nl : undefined,
31
+ }));
32
+ // Project recipes
33
+ const recipes = listRecipes(process.cwd()).slice(0, 10).map(r => ({
34
+ name: r.name,
35
+ command: r.command,
36
+ }));
37
+ // Economy
38
+ const econ = getEconomyStats();
39
+ return {
40
+ cwd: process.cwd(),
41
+ env,
42
+ runningProcesses: processes,
43
+ recentCommands,
44
+ recipes,
45
+ economy: {
46
+ tokensSaved: formatTokens(econ.totalTokensSaved),
47
+ tokensUsed: formatTokens(econ.totalTokensUsed),
48
+ },
49
+ timestamp: Date.now(),
50
+ };
51
+ }
@@ -0,0 +1,112 @@
1
+ // Process supervisor — manages background processes for agents and humans
2
+ import { spawn } from "child_process";
3
+ import { createConnection } from "net";
4
+ const processes = new Map();
5
+ /** Auto-detect port from common commands */
6
+ function detectPort(command) {
7
+ // "next dev -p 3001", "vite --port 4000", etc.
8
+ const portMatch = command.match(/-p\s+(\d+)|--port\s+(\d+)|PORT=(\d+)/);
9
+ if (portMatch)
10
+ return parseInt(portMatch[1] ?? portMatch[2] ?? portMatch[3]);
11
+ // Common defaults
12
+ if (/\bnext\s+dev\b/.test(command))
13
+ return 3000;
14
+ if (/\bvite\b/.test(command))
15
+ return 5173;
16
+ if (/\bnuxt\s+dev\b/.test(command))
17
+ return 3000;
18
+ if (/\bremix\s+dev\b/.test(command))
19
+ return 5173;
20
+ return undefined;
21
+ }
22
+ /** Start a background process */
23
+ export function bgStart(command, cwd) {
24
+ const workDir = cwd ?? process.cwd();
25
+ const proc = spawn("/bin/zsh", ["-c", command], {
26
+ cwd: workDir,
27
+ stdio: ["ignore", "pipe", "pipe"],
28
+ detached: false,
29
+ });
30
+ const meta = {
31
+ pid: proc.pid,
32
+ command,
33
+ cwd: workDir,
34
+ port: detectPort(command),
35
+ startedAt: Date.now(),
36
+ lastOutput: [],
37
+ };
38
+ const pushOutput = (d) => {
39
+ const lines = d.toString().split("\n").filter(l => l.trim());
40
+ meta.lastOutput.push(...lines);
41
+ // Keep last 50 lines
42
+ if (meta.lastOutput.length > 50) {
43
+ meta.lastOutput = meta.lastOutput.slice(-50);
44
+ }
45
+ };
46
+ proc.stdout?.on("data", pushOutput);
47
+ proc.stderr?.on("data", pushOutput);
48
+ proc.on("close", (code) => {
49
+ meta.exitCode = code ?? 0;
50
+ });
51
+ processes.set(proc.pid, { proc, meta });
52
+ return meta;
53
+ }
54
+ /** List all managed processes */
55
+ export function bgStatus() {
56
+ const result = [];
57
+ for (const [pid, { proc, meta }] of processes) {
58
+ // Check if still alive
59
+ try {
60
+ process.kill(pid, 0);
61
+ result.push({ ...meta, lastOutput: meta.lastOutput.slice(-5) });
62
+ }
63
+ catch {
64
+ // Process is dead
65
+ result.push({ ...meta, exitCode: meta.exitCode ?? -1, lastOutput: meta.lastOutput.slice(-5) });
66
+ }
67
+ }
68
+ return result;
69
+ }
70
+ /** Stop a background process */
71
+ export function bgStop(pid) {
72
+ const entry = processes.get(pid);
73
+ if (!entry)
74
+ return false;
75
+ try {
76
+ entry.proc.kill("SIGTERM");
77
+ processes.delete(pid);
78
+ return true;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ /** Get logs for a background process */
85
+ export function bgLogs(pid, tail = 20) {
86
+ const entry = processes.get(pid);
87
+ if (!entry)
88
+ return [];
89
+ return entry.meta.lastOutput.slice(-tail);
90
+ }
91
+ /** Wait for a port to be ready */
92
+ export function bgWaitPort(port, timeoutMs = 30000) {
93
+ return new Promise((resolve) => {
94
+ const start = Date.now();
95
+ const check = () => {
96
+ if (Date.now() - start > timeoutMs) {
97
+ resolve(false);
98
+ return;
99
+ }
100
+ const sock = createConnection({ port, host: "127.0.0.1" });
101
+ sock.on("connect", () => {
102
+ sock.destroy();
103
+ resolve(true);
104
+ });
105
+ sock.on("error", () => {
106
+ sock.destroy();
107
+ setTimeout(check, 500);
108
+ });
109
+ };
110
+ check();
111
+ });
112
+ }