@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.
Files changed (263) hide show
  1. package/dist/cli.js +52 -21
  2. package/package.json +1 -1
  3. package/src/ai.ts +77 -130
  4. package/src/cli.tsx +51 -21
  5. package/src/command-validator.ts +11 -0
  6. package/src/context-hints.ts +291 -0
  7. package/src/discover.ts +238 -0
  8. package/src/economy.ts +53 -0
  9. package/src/output-processor.ts +7 -18
  10. package/src/output-store.ts +65 -0
  11. package/src/providers/base.ts +3 -1
  12. package/src/providers/groq.ts +108 -0
  13. package/src/providers/index.ts +26 -2
  14. package/src/providers/providers.test.ts +4 -2
  15. package/src/providers/xai.ts +108 -0
  16. package/src/sessions-db.ts +81 -0
  17. package/temp/rtk/.claude/agents/code-reviewer.md +221 -0
  18. package/temp/rtk/.claude/agents/debugger.md +519 -0
  19. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +461 -0
  20. package/temp/rtk/.claude/agents/rust-rtk.md +511 -0
  21. package/temp/rtk/.claude/agents/technical-writer.md +355 -0
  22. package/temp/rtk/.claude/commands/diagnose.md +352 -0
  23. package/temp/rtk/.claude/commands/test-routing.md +362 -0
  24. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +16 -0
  25. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +70 -0
  26. package/temp/rtk/.claude/hooks/rtk-suggest.sh +152 -0
  27. package/temp/rtk/.claude/rules/cli-testing.md +526 -0
  28. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +348 -0
  29. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +134 -0
  30. package/temp/rtk/.claude/skills/performance.md +435 -0
  31. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +315 -0
  32. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +71 -0
  33. package/temp/rtk/.claude/skills/repo-recap.md +206 -0
  34. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +78 -0
  35. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +124 -0
  36. package/temp/rtk/.claude/skills/security-guardian.md +503 -0
  37. package/temp/rtk/.claude/skills/ship.md +404 -0
  38. package/temp/rtk/.github/workflows/benchmark.yml +34 -0
  39. package/temp/rtk/.github/workflows/dco-check.yaml +12 -0
  40. package/temp/rtk/.github/workflows/release-please.yml +51 -0
  41. package/temp/rtk/.github/workflows/release.yml +343 -0
  42. package/temp/rtk/.github/workflows/security-check.yml +135 -0
  43. package/temp/rtk/.github/workflows/validate-docs.yml +78 -0
  44. package/temp/rtk/.release-please-manifest.json +3 -0
  45. package/temp/rtk/ARCHITECTURE.md +1491 -0
  46. package/temp/rtk/CHANGELOG.md +640 -0
  47. package/temp/rtk/CLAUDE.md +605 -0
  48. package/temp/rtk/CONTRIBUTING.md +199 -0
  49. package/temp/rtk/Cargo.lock +1668 -0
  50. package/temp/rtk/Cargo.toml +64 -0
  51. package/temp/rtk/Formula/rtk.rb +43 -0
  52. package/temp/rtk/INSTALL.md +390 -0
  53. package/temp/rtk/LICENSE +21 -0
  54. package/temp/rtk/README.md +386 -0
  55. package/temp/rtk/README_es.md +159 -0
  56. package/temp/rtk/README_fr.md +197 -0
  57. package/temp/rtk/README_ja.md +159 -0
  58. package/temp/rtk/README_ko.md +159 -0
  59. package/temp/rtk/README_zh.md +167 -0
  60. package/temp/rtk/ROADMAP.md +15 -0
  61. package/temp/rtk/SECURITY.md +217 -0
  62. package/temp/rtk/TEST_EXEC_TIME.md +102 -0
  63. package/temp/rtk/build.rs +57 -0
  64. package/temp/rtk/docs/AUDIT_GUIDE.md +432 -0
  65. package/temp/rtk/docs/FEATURES.md +1410 -0
  66. package/temp/rtk/docs/TROUBLESHOOTING.md +309 -0
  67. package/temp/rtk/docs/filter-workflow.md +102 -0
  68. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  69. package/temp/rtk/docs/tracking.md +583 -0
  70. package/temp/rtk/hooks/opencode-rtk.ts +39 -0
  71. package/temp/rtk/hooks/rtk-awareness.md +29 -0
  72. package/temp/rtk/hooks/rtk-rewrite.sh +61 -0
  73. package/temp/rtk/hooks/test-rtk-rewrite.sh +442 -0
  74. package/temp/rtk/install.sh +124 -0
  75. package/temp/rtk/release-please-config.json +10 -0
  76. package/temp/rtk/scripts/benchmark.sh +592 -0
  77. package/temp/rtk/scripts/check-installation.sh +162 -0
  78. package/temp/rtk/scripts/install-local.sh +37 -0
  79. package/temp/rtk/scripts/rtk-economics.sh +137 -0
  80. package/temp/rtk/scripts/test-all.sh +561 -0
  81. package/temp/rtk/scripts/test-aristote.sh +227 -0
  82. package/temp/rtk/scripts/test-tracking.sh +79 -0
  83. package/temp/rtk/scripts/update-readme-metrics.sh +32 -0
  84. package/temp/rtk/scripts/validate-docs.sh +73 -0
  85. package/temp/rtk/src/aws_cmd.rs +880 -0
  86. package/temp/rtk/src/binlog.rs +1645 -0
  87. package/temp/rtk/src/cargo_cmd.rs +1727 -0
  88. package/temp/rtk/src/cc_economics.rs +1157 -0
  89. package/temp/rtk/src/ccusage.rs +340 -0
  90. package/temp/rtk/src/config.rs +187 -0
  91. package/temp/rtk/src/container.rs +855 -0
  92. package/temp/rtk/src/curl_cmd.rs +134 -0
  93. package/temp/rtk/src/deps.rs +268 -0
  94. package/temp/rtk/src/diff_cmd.rs +367 -0
  95. package/temp/rtk/src/discover/mod.rs +274 -0
  96. package/temp/rtk/src/discover/provider.rs +388 -0
  97. package/temp/rtk/src/discover/registry.rs +2022 -0
  98. package/temp/rtk/src/discover/report.rs +202 -0
  99. package/temp/rtk/src/discover/rules.rs +667 -0
  100. package/temp/rtk/src/display_helpers.rs +402 -0
  101. package/temp/rtk/src/dotnet_cmd.rs +1771 -0
  102. package/temp/rtk/src/dotnet_format_report.rs +133 -0
  103. package/temp/rtk/src/dotnet_trx.rs +593 -0
  104. package/temp/rtk/src/env_cmd.rs +204 -0
  105. package/temp/rtk/src/filter.rs +462 -0
  106. package/temp/rtk/src/filters/README.md +52 -0
  107. package/temp/rtk/src/filters/ansible-playbook.toml +34 -0
  108. package/temp/rtk/src/filters/basedpyright.toml +47 -0
  109. package/temp/rtk/src/filters/biome.toml +45 -0
  110. package/temp/rtk/src/filters/brew-install.toml +37 -0
  111. package/temp/rtk/src/filters/composer-install.toml +40 -0
  112. package/temp/rtk/src/filters/df.toml +16 -0
  113. package/temp/rtk/src/filters/dotnet-build.toml +64 -0
  114. package/temp/rtk/src/filters/du.toml +16 -0
  115. package/temp/rtk/src/filters/fail2ban-client.toml +15 -0
  116. package/temp/rtk/src/filters/gcc.toml +49 -0
  117. package/temp/rtk/src/filters/gcloud.toml +22 -0
  118. package/temp/rtk/src/filters/hadolint.toml +24 -0
  119. package/temp/rtk/src/filters/helm.toml +29 -0
  120. package/temp/rtk/src/filters/iptables.toml +27 -0
  121. package/temp/rtk/src/filters/jj.toml +28 -0
  122. package/temp/rtk/src/filters/jq.toml +24 -0
  123. package/temp/rtk/src/filters/make.toml +41 -0
  124. package/temp/rtk/src/filters/markdownlint.toml +24 -0
  125. package/temp/rtk/src/filters/mix-compile.toml +27 -0
  126. package/temp/rtk/src/filters/mix-format.toml +15 -0
  127. package/temp/rtk/src/filters/mvn-build.toml +44 -0
  128. package/temp/rtk/src/filters/oxlint.toml +43 -0
  129. package/temp/rtk/src/filters/ping.toml +63 -0
  130. package/temp/rtk/src/filters/pio-run.toml +40 -0
  131. package/temp/rtk/src/filters/poetry-install.toml +50 -0
  132. package/temp/rtk/src/filters/pre-commit.toml +35 -0
  133. package/temp/rtk/src/filters/ps.toml +16 -0
  134. package/temp/rtk/src/filters/quarto-render.toml +41 -0
  135. package/temp/rtk/src/filters/rsync.toml +48 -0
  136. package/temp/rtk/src/filters/shellcheck.toml +27 -0
  137. package/temp/rtk/src/filters/shopify-theme.toml +29 -0
  138. package/temp/rtk/src/filters/skopeo.toml +45 -0
  139. package/temp/rtk/src/filters/sops.toml +16 -0
  140. package/temp/rtk/src/filters/ssh.toml +44 -0
  141. package/temp/rtk/src/filters/stat.toml +34 -0
  142. package/temp/rtk/src/filters/swift-build.toml +41 -0
  143. package/temp/rtk/src/filters/systemctl-status.toml +33 -0
  144. package/temp/rtk/src/filters/terraform-plan.toml +35 -0
  145. package/temp/rtk/src/filters/tofu-fmt.toml +16 -0
  146. package/temp/rtk/src/filters/tofu-init.toml +38 -0
  147. package/temp/rtk/src/filters/tofu-plan.toml +35 -0
  148. package/temp/rtk/src/filters/tofu-validate.toml +17 -0
  149. package/temp/rtk/src/filters/trunk-build.toml +39 -0
  150. package/temp/rtk/src/filters/ty.toml +50 -0
  151. package/temp/rtk/src/filters/uv-sync.toml +37 -0
  152. package/temp/rtk/src/filters/xcodebuild.toml +99 -0
  153. package/temp/rtk/src/filters/yamllint.toml +25 -0
  154. package/temp/rtk/src/find_cmd.rs +598 -0
  155. package/temp/rtk/src/format_cmd.rs +386 -0
  156. package/temp/rtk/src/gain.rs +723 -0
  157. package/temp/rtk/src/gh_cmd.rs +1651 -0
  158. package/temp/rtk/src/git.rs +2012 -0
  159. package/temp/rtk/src/go_cmd.rs +592 -0
  160. package/temp/rtk/src/golangci_cmd.rs +254 -0
  161. package/temp/rtk/src/grep_cmd.rs +288 -0
  162. package/temp/rtk/src/gt_cmd.rs +810 -0
  163. package/temp/rtk/src/hook_audit_cmd.rs +283 -0
  164. package/temp/rtk/src/hook_check.rs +171 -0
  165. package/temp/rtk/src/init.rs +1859 -0
  166. package/temp/rtk/src/integrity.rs +537 -0
  167. package/temp/rtk/src/json_cmd.rs +231 -0
  168. package/temp/rtk/src/learn/detector.rs +628 -0
  169. package/temp/rtk/src/learn/mod.rs +119 -0
  170. package/temp/rtk/src/learn/report.rs +184 -0
  171. package/temp/rtk/src/lint_cmd.rs +694 -0
  172. package/temp/rtk/src/local_llm.rs +316 -0
  173. package/temp/rtk/src/log_cmd.rs +248 -0
  174. package/temp/rtk/src/ls.rs +324 -0
  175. package/temp/rtk/src/main.rs +2482 -0
  176. package/temp/rtk/src/mypy_cmd.rs +389 -0
  177. package/temp/rtk/src/next_cmd.rs +241 -0
  178. package/temp/rtk/src/npm_cmd.rs +236 -0
  179. package/temp/rtk/src/parser/README.md +267 -0
  180. package/temp/rtk/src/parser/error.rs +46 -0
  181. package/temp/rtk/src/parser/formatter.rs +336 -0
  182. package/temp/rtk/src/parser/mod.rs +311 -0
  183. package/temp/rtk/src/parser/types.rs +119 -0
  184. package/temp/rtk/src/pip_cmd.rs +302 -0
  185. package/temp/rtk/src/playwright_cmd.rs +479 -0
  186. package/temp/rtk/src/pnpm_cmd.rs +573 -0
  187. package/temp/rtk/src/prettier_cmd.rs +221 -0
  188. package/temp/rtk/src/prisma_cmd.rs +482 -0
  189. package/temp/rtk/src/psql_cmd.rs +382 -0
  190. package/temp/rtk/src/pytest_cmd.rs +384 -0
  191. package/temp/rtk/src/read.rs +217 -0
  192. package/temp/rtk/src/rewrite_cmd.rs +50 -0
  193. package/temp/rtk/src/ruff_cmd.rs +402 -0
  194. package/temp/rtk/src/runner.rs +271 -0
  195. package/temp/rtk/src/summary.rs +297 -0
  196. package/temp/rtk/src/tee.rs +405 -0
  197. package/temp/rtk/src/telemetry.rs +248 -0
  198. package/temp/rtk/src/toml_filter.rs +1655 -0
  199. package/temp/rtk/src/tracking.rs +1416 -0
  200. package/temp/rtk/src/tree.rs +209 -0
  201. package/temp/rtk/src/tsc_cmd.rs +259 -0
  202. package/temp/rtk/src/utils.rs +432 -0
  203. package/temp/rtk/src/verify_cmd.rs +47 -0
  204. package/temp/rtk/src/vitest_cmd.rs +385 -0
  205. package/temp/rtk/src/wc_cmd.rs +401 -0
  206. package/temp/rtk/src/wget_cmd.rs +260 -0
  207. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +11 -0
  208. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +31 -0
  209. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +1 -0
  210. package/temp/rtk/tests/fixtures/dotnet/format_success.json +12 -0
  211. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +18 -0
  212. package/dist/App.js +0 -404
  213. package/dist/Browse.js +0 -79
  214. package/dist/FuzzyPicker.js +0 -47
  215. package/dist/Onboarding.js +0 -51
  216. package/dist/Spinner.js +0 -12
  217. package/dist/StatusBar.js +0 -49
  218. package/dist/ai.js +0 -368
  219. package/dist/cache.js +0 -41
  220. package/dist/command-rewriter.js +0 -64
  221. package/dist/command-validator.js +0 -77
  222. package/dist/compression.js +0 -107
  223. package/dist/diff-cache.js +0 -107
  224. package/dist/economy.js +0 -79
  225. package/dist/expand-store.js +0 -38
  226. package/dist/file-cache.js +0 -72
  227. package/dist/file-index.js +0 -62
  228. package/dist/history.js +0 -62
  229. package/dist/lazy-executor.js +0 -54
  230. package/dist/line-dedup.js +0 -59
  231. package/dist/loop-detector.js +0 -75
  232. package/dist/mcp/install.js +0 -98
  233. package/dist/mcp/server.js +0 -569
  234. package/dist/noise-filter.js +0 -86
  235. package/dist/output-processor.js +0 -136
  236. package/dist/output-router.js +0 -41
  237. package/dist/parsers/base.js +0 -2
  238. package/dist/parsers/build.js +0 -64
  239. package/dist/parsers/errors.js +0 -101
  240. package/dist/parsers/files.js +0 -78
  241. package/dist/parsers/git.js +0 -99
  242. package/dist/parsers/index.js +0 -48
  243. package/dist/parsers/tests.js +0 -89
  244. package/dist/providers/anthropic.js +0 -39
  245. package/dist/providers/base.js +0 -4
  246. package/dist/providers/cerebras.js +0 -95
  247. package/dist/providers/index.js +0 -49
  248. package/dist/recipes/model.js +0 -20
  249. package/dist/recipes/storage.js +0 -136
  250. package/dist/search/content-search.js +0 -68
  251. package/dist/search/file-search.js +0 -61
  252. package/dist/search/filters.js +0 -34
  253. package/dist/search/index.js +0 -5
  254. package/dist/search/semantic.js +0 -320
  255. package/dist/session-boot.js +0 -59
  256. package/dist/session-context.js +0 -55
  257. package/dist/sessions-db.js +0 -120
  258. package/dist/smart-display.js +0 -286
  259. package/dist/snapshots.js +0 -51
  260. package/dist/supervisor.js +0 -112
  261. package/dist/test-watchlist.js +0 -131
  262. package/dist/tree.js +0 -94
  263. package/dist/usage-cache.js +0 -65
package/dist/cli.js CHANGED
@@ -28,6 +28,7 @@ SUBCOMMANDS:
28
28
  collection create|list Recipe collections
29
29
  mcp serve Start MCP server for AI agents
30
30
  mcp install --claude|--codex Install MCP server
31
+ discover [--days=N] [--json] Scan Claude sessions, show token savings potential
31
32
  snapshot Terminal state as JSON
32
33
  --help Show this help
33
34
  --version Show version
@@ -41,7 +42,9 @@ MCP TOOLS (20+):
41
42
  snapshot, token_stats, session_history
42
43
 
43
44
  ENVIRONMENT:
44
- CEREBRAS_API_KEY Cerebras API key (free, open-source default)
45
+ XAI_API_KEY xAI API key (Grok, code-optimized default)
46
+ CEREBRAS_API_KEY Cerebras API key (free, open-source)
47
+ GROQ_API_KEY Groq API key (free, ultra-fast inference)
45
48
  ANTHROPIC_API_KEY Anthropic API key (Claude models)
46
49
  `);
47
50
  process.exit(0);
@@ -251,17 +254,8 @@ else if (args[0] === "collection") {
251
254
  }
252
255
  // ── Stats command ────────────────────────────────────────────────────────────
253
256
  else if (args[0] === "stats") {
254
- const { getEconomyStats, formatTokens } = await import("./economy.js");
255
- const s = getEconomyStats();
256
- console.log("Token Economy:");
257
- console.log(` Total saved: ${formatTokens(s.totalTokensSaved)}`);
258
- console.log(` Total used: ${formatTokens(s.totalTokensUsed)}`);
259
- console.log(` By feature:`);
260
- console.log(` Structured: ${formatTokens(s.savingsByFeature.structured)}`);
261
- console.log(` Compressed: ${formatTokens(s.savingsByFeature.compressed)}`);
262
- console.log(` Diff cache: ${formatTokens(s.savingsByFeature.diff)}`);
263
- console.log(` NL cache: ${formatTokens(s.savingsByFeature.cache)}`);
264
- console.log(` Search: ${formatTokens(s.savingsByFeature.search)}`);
257
+ const { formatEconomicsSummary } = await import("./economy.js");
258
+ console.log(formatEconomicsSummary());
265
259
  }
266
260
  // ── Sessions command ─────────────────────────────────────────────────────────
267
261
  else if (args[0] === "sessions") {
@@ -397,7 +391,7 @@ else if (args[0] === "history") {
397
391
  // ── Explain command ─────────────────────────────────────────────────────────
398
392
  else if (args[0] === "explain" && args[1]) {
399
393
  const command = args.slice(1).join(" ");
400
- if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY) {
394
+ if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY && !process.env.GROQ_API_KEY && !process.env.XAI_API_KEY) {
401
395
  console.error("explain requires an API key");
402
396
  process.exit(1);
403
397
  }
@@ -405,6 +399,19 @@ else if (args[0] === "explain" && args[1]) {
405
399
  const explanation = await explainCommand(command);
406
400
  console.log(explanation);
407
401
  }
402
+ // ── Discover command ─────────────────────────────────────────────────────────
403
+ else if (args[0] === "discover") {
404
+ const { discover, formatDiscoverReport } = await import("./discover.js");
405
+ const days = parseInt(args.find(a => a.startsWith("--days="))?.split("=")[1] ?? "30");
406
+ const json = args.includes("--json");
407
+ const report = discover({ maxAgeDays: days });
408
+ if (json) {
409
+ console.log(JSON.stringify(report, null, 2));
410
+ }
411
+ else {
412
+ console.log(formatDiscoverReport(report));
413
+ }
414
+ }
408
415
  // ── Snapshot command ─────────────────────────────────────────────────────────
409
416
  else if (args[0] === "snapshot") {
410
417
  const { captureSnapshot } = await import("./snapshots.js");
@@ -420,7 +427,7 @@ else if (args[0] === "project" && args[1] === "init") {
420
427
  else if (args.length > 0) {
421
428
  // Everything that doesn't match a subcommand is treated as natural language
422
429
  const prompt = args.join(" ");
423
- const offlineMode = !process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY;
430
+ const offlineMode = !process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY && !process.env.GROQ_API_KEY && !process.env.XAI_API_KEY;
424
431
  const { translateToCommand, checkPermissions, isIrreversible } = await import("./ai.js");
425
432
  const { execSync } = await import("child_process");
426
433
  const { compress, stripAnsi } = await import("./compression.js");
@@ -428,6 +435,7 @@ else if (args.length > 0) {
428
435
  const { processOutput, shouldProcess } = await import("./output-processor.js");
429
436
  const { rewriteCommand } = await import("./command-rewriter.js");
430
437
  const { shouldBeLazy, toLazy } = await import("./lazy-executor.js");
438
+ const { saveOutput, formatOutputHint } = await import("./output-store.js");
431
439
  const { parseOutput, estimateTokens } = await import("./parsers/index.js");
432
440
  const { recordSaving, recordUsage } = await import("./economy.js");
433
441
  const { isTestOutput, trackTests, formatWatchResult } = await import("./test-watchlist.js");
@@ -435,6 +443,7 @@ else if (args.length > 0) {
435
443
  const { loadConfig } = await import("./history.js");
436
444
  const { loadContext, saveContext, formatContext } = await import("./session-context.js");
437
445
  const { getLearned, recordMapping } = await import("./usage-cache.js");
446
+ const { recordCorrection, findSimilarCorrections, recordOutput } = await import("./sessions-db.js");
438
447
  const config = loadConfig();
439
448
  const perms = config.permissions;
440
449
  const sessionCtx = formatContext();
@@ -477,9 +486,10 @@ else if (args.length > 0) {
477
486
  catch { }
478
487
  }
479
488
  }
480
- // "I don't know" honesty — better than wrong answer
489
+ // Show the block reason clearly
481
490
  if (e.message?.startsWith("BLOCKED:")) {
482
- console.log(`I don't know how to do this with shell commands. Try running it directly.`);
491
+ console.log(`⚠ ${e.message}`);
492
+ console.log(` This is a READ-ONLY terminal. Run directly in your shell if you're sure.`);
483
493
  }
484
494
  else {
485
495
  console.error(e.message);
@@ -525,13 +535,22 @@ else if (args.length > 0) {
525
535
  // Auto-retry: re-translate with simpler constraints
526
536
  console.error(`[open-terminal] invalid command detected: ${validation.issues.join(", ")}`);
527
537
  try {
528
- const retryCommand = await translateToCommand(`${prompt} (IMPORTANT: keep it simple. Use basic grep/find/cat/ls/wc commands. No complex awk/sed pipelines. No GNU flags. Verify file paths from the project context.)`, perms, []);
538
+ const retryCommand = await translateToCommand(`${prompt} (Previous command had issues: ${validation.issues.join(", ")}. Fix those specific issues. Keep the approach but correct the errors.)`, perms, []);
529
539
  if (retryCommand && retryCommand !== command) {
530
540
  const retryValidation = validateCommand(retryCommand, process.cwd());
531
541
  if (retryValidation.valid || retryValidation.issues.length < validation.issues.length) {
532
542
  command = retryCommand;
533
543
  console.error(`[open-terminal] retried: $ ${command}`);
534
544
  }
545
+ else {
546
+ // Retry also invalid — use the simpler of the two
547
+ const retryPipes = (retryCommand.match(/\|/g) || []).length;
548
+ const origPipes = (command.match(/\|/g) || []).length;
549
+ if (retryPipes < origPipes) {
550
+ command = retryCommand;
551
+ console.error(`[open-terminal] retried (simpler): $ ${command}`);
552
+ }
553
+ }
535
554
  }
536
555
  }
537
556
  catch { }
@@ -568,7 +587,15 @@ else if (args.length > 0) {
568
587
  if (processed.aiProcessed) {
569
588
  if (processed.tokensSaved > 0)
570
589
  recordSaving("compressed", processed.tokensSaved);
571
- console.log(processed.summary);
590
+ // Save full output for lazy recovery — agents can read the file
591
+ if (processed.tokensSaved > 50) {
592
+ const outputPath = saveOutput(actualCmd, clean);
593
+ console.log(processed.summary);
594
+ console.log(formatOutputHint(outputPath));
595
+ }
596
+ else {
597
+ console.log(processed.summary);
598
+ }
572
599
  if (processed.tokensSaved > 10)
573
600
  console.error(`[open-terminal] ${rawTokens} → ${rawTokens - processed.tokensSaved} tokens (saved ${processed.tokensSaved})`);
574
601
  process.exit(0);
@@ -625,6 +652,8 @@ else if (args.length > 0) {
625
652
  const retryResult = execSync(retryCmd + " #(retry)", { encoding: "utf8", maxBuffer: 10 * 1024 * 1024, cwd: process.cwd() });
626
653
  const retryClean = stripNoise(stripAnsi(retryResult)).cleaned;
627
654
  if (retryClean.length > 5) {
655
+ // Record the correction so we learn from it
656
+ recordCorrection(prompt, actualCmd, errStderr.slice(0, 500), retryCmd, true);
628
657
  const processed = await processOutput(retryCmd, retryClean, prompt);
629
658
  console.log(processed.aiProcessed ? processed.summary : retryClean);
630
659
  process.exit(0);
@@ -652,11 +681,13 @@ else if (args.length > 0) {
652
681
  }
653
682
  // ── TUI mode (no args) ──────────────────────────────────────────────────────
654
683
  else {
655
- if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY) {
684
+ if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY && !process.env.GROQ_API_KEY && !process.env.XAI_API_KEY) {
656
685
  console.error("terminal: No API key found.");
657
686
  console.error("Set one of:");
658
- console.error(" export CEREBRAS_API_KEY=your_key (free, open-source)");
659
- console.error(" export ANTHROPIC_API_KEY=your_key (Claude)");
687
+ console.error(" export XAI_API_KEY=your_key (Grok, code-optimized — default)");
688
+ console.error(" export CEREBRAS_API_KEY=your_key (free, open-source)");
689
+ console.error(" export GROQ_API_KEY=your_key (free, ultra-fast)");
690
+ console.error(" export ANTHROPIC_API_KEY=your_key (Claude)");
660
691
  process.exit(1);
661
692
  }
662
693
  const App = (await import("./App.js")).default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "2.0.5",
3
+ "version": "2.3.0",
4
4
  "description": "Smart terminal wrapper for AI agents and humans — structured output, token compression, MCP server, natural language",
5
5
  "type": "module",
6
6
  "bin": {
package/src/ai.ts CHANGED
@@ -3,37 +3,51 @@ import { cacheGet, cacheSet } from "./cache.js";
3
3
  import { getProvider } from "./providers/index.js";
4
4
  import { existsSync, readFileSync } from "fs";
5
5
  import { join } from "path";
6
+ import { discoverProjectHints, discoverSafetyHints, formatHints } from "./context-hints.js";
6
7
 
7
8
  // ── model routing ─────────────────────────────────────────────────────────────
8
- // Simple queries fast model. Complex/ambiguous smart model.
9
+ // Config-driven model selection. Defaults per provider, user can override in ~/.terminal/config.json
9
10
 
10
11
  const COMPLEX_SIGNALS = [
11
12
  /\b(undo|revert|rollback|previous|last)\b/i,
12
13
  /\b(all files?|recursively|bulk|batch)\b/i,
13
14
  /\b(pipeline|chain|then|and then|after)\b/i,
14
15
  /\b(if|when|unless|only if)\b/i,
15
- /\b(go into|go to|navigate|cd into|enter)\b.*\b(and|then)\b/i, // multi-step navigation
16
- /\b(inside|within|under)\b/i, // relative references need context awareness
17
- /[|&;]{2}/, // pipes / && in NL (unusual = complex intent)
16
+ /\b(go into|go to|navigate|cd into|enter)\b.*\b(and|then)\b/i,
17
+ /\b(inside|within|under)\b/i,
18
+ /[|&;]{2}/,
18
19
  ];
19
20
 
20
- /** Model routing per provider */
21
+ /** Default models per provider — user can override in ~/.terminal/config.json under "models" */
22
+ const MODEL_DEFAULTS: Record<string, { fast: string; smart: string }> = {
23
+ cerebras: { fast: "qwen-3-235b-a22b-instruct-2507", smart: "qwen-3-235b-a22b-instruct-2507" },
24
+ groq: { fast: "openai/gpt-oss-120b", smart: "moonshotai/kimi-k2-instruct" },
25
+ xai: { fast: "grok-code-fast-1", smart: "grok-4-fast-non-reasoning" },
26
+ anthropic: { fast: "claude-haiku-4-5-20251001", smart: "claude-sonnet-4-6" },
27
+ };
28
+
29
+ /** Load user model overrides from ~/.terminal/config.json */
30
+ function loadModelOverrides(): Record<string, { fast?: string; smart?: string }> {
31
+ try {
32
+ const configPath = join(process.env.HOME ?? "~", ".terminal", "config.json");
33
+ if (existsSync(configPath)) {
34
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
35
+ return config.models ?? {};
36
+ }
37
+ } catch {}
38
+ return {};
39
+ }
40
+
41
+ /** Model routing per provider — config-driven with defaults */
21
42
  function pickModel(nl: string): { fast: string; smart: string; pick: "fast" | "smart" } {
22
43
  const isComplex = COMPLEX_SIGNALS.some((r) => r.test(nl)) || nl.split(" ").length > 10;
23
44
  const provider = getProvider();
45
+ const defaults = MODEL_DEFAULTS[provider.name] ?? MODEL_DEFAULTS.cerebras;
46
+ const overrides = loadModelOverrides()[provider.name] ?? {};
24
47
 
25
- if (provider.name === "anthropic") {
26
- return {
27
- fast: "claude-haiku-4-5-20251001",
28
- smart: "claude-sonnet-4-6",
29
- pick: isComplex ? "smart" : "fast",
30
- };
31
- }
32
-
33
- // Cerebras — qwen for everything (llama3.1-8b too unreliable)
34
48
  return {
35
- fast: "qwen-3-235b-a22b-instruct-2507",
36
- smart: "qwen-3-235b-a22b-instruct-2507",
49
+ fast: overrides.fast ?? defaults.fast,
50
+ smart: overrides.smart ?? defaults.smart,
37
51
  pick: isComplex ? "smart" : "fast",
38
52
  };
39
53
  }
@@ -107,123 +121,33 @@ export interface SessionEntry {
107
121
  error?: boolean;
108
122
  }
109
123
 
110
- // ── project context ──────────────────────────────────────────────────────────
124
+ // ── correction memory ───────────────────────────────────────────────────────
111
125
 
112
- function detectProjectContext(): string {
113
- const cwd = process.cwd();
114
- const parts: string[] = [];
115
-
116
- // Node.js / TypeScript
117
- const pkgPath = join(cwd, "package.json");
118
- if (existsSync(pkgPath)) {
119
- try {
120
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
121
- parts.push(`Project: ${pkg.name}@${pkg.version} (Node.js/TypeScript)`);
122
- parts.push(`npm package: ${pkg.name} (use this name for npm view, npm info, etc.)`);
123
- if (pkg.scripts) {
124
- const scripts = Object.entries(pkg.scripts).map(([k, v]) => `${k}: ${v}`).slice(0, 8);
125
- parts.push(`Available scripts: ${scripts.join(", ")}`);
126
- }
127
- if (pkg.dependencies) parts.push(`Dependencies: ${Object.keys(pkg.dependencies).join(", ")}`);
128
- parts.push(`Use npm/bun/pnpm commands, NOT maven/gradle/cargo.`);
129
- } catch {}
130
- }
131
-
132
- // Python
133
- if (existsSync(join(cwd, "pyproject.toml"))) {
134
- try {
135
- const pyproject = readFileSync(join(cwd, "pyproject.toml"), "utf8");
136
- const nameMatch = pyproject.match(/name\s*=\s*"([^"]+)"/);
137
- const versionMatch = pyproject.match(/version\s*=\s*"([^"]+)"/);
138
- parts.push(`Project: ${nameMatch?.[1] ?? "Python"}${versionMatch ? `@${versionMatch[1]}` : ""} (Python)`);
139
- } catch { parts.push("Project: Python (pyproject.toml found)"); }
140
- parts.push("Use pip/python/pytest commands. Test: pytest. Build: python -m build.");
141
- } else if (existsSync(join(cwd, "requirements.txt"))) {
142
- parts.push("Project: Python (requirements.txt). Use pip/python/pytest commands.");
143
- }
144
-
145
- // Go
146
- if (existsSync(join(cwd, "go.mod"))) {
147
- try {
148
- const gomod = readFileSync(join(cwd, "go.mod"), "utf8");
149
- const moduleMatch = gomod.match(/module\s+(\S+)/);
150
- parts.push(`Project: ${moduleMatch?.[1] ?? "Go"} (Go module)`);
151
- } catch { parts.push("Project: Go (go.mod found)"); }
152
- parts.push("Use go build/test/run. Test: go test ./... Build: go build.");
153
- }
154
-
155
- // Rust
156
- if (existsSync(join(cwd, "Cargo.toml"))) {
157
- try {
158
- const cargo = readFileSync(join(cwd, "Cargo.toml"), "utf8");
159
- const nameMatch = cargo.match(/name\s*=\s*"([^"]+)"/);
160
- const versionMatch = cargo.match(/version\s*=\s*"([^"]+)"/);
161
- parts.push(`Project: ${nameMatch?.[1] ?? "Rust"}${versionMatch ? `@${versionMatch[1]}` : ""} (Rust/Cargo)`);
162
- } catch { parts.push("Project: Rust (Cargo.toml found)"); }
163
- parts.push("Use cargo build/test/run. Test: cargo test. Build: cargo build --release.");
164
- }
165
-
166
- // Java
167
- if (existsSync(join(cwd, "pom.xml"))) {
168
- parts.push("Project: Java/Maven. Use mvn commands. Test: mvn test. Build: mvn package.");
169
- }
170
- if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
171
- parts.push("Project: Java/Gradle. Use gradle commands. Test: gradle test. Build: gradle build.");
172
- }
173
-
174
- // Docker
175
- if (existsSync(join(cwd, "Dockerfile")) || existsSync(join(cwd, "docker-compose.yml")) || existsSync(join(cwd, "docker-compose.yaml"))) {
176
- parts.push("Docker: Dockerfile/docker-compose present. Container commands available.");
177
- }
178
-
179
- // Makefile
180
- if (existsSync(join(cwd, "Makefile"))) {
181
- try {
182
- const { execSync: execS } = require("child_process");
183
- const targets = execS("grep -E '^[a-zA-Z_-]+:' Makefile | head -10 | cut -d: -f1", { cwd, encoding: "utf8", timeout: 1000 }).trim();
184
- if (targets) parts.push(`Makefile targets: ${targets.split("\n").join(", ")}`);
185
- } catch {}
186
- }
187
-
188
- // Directory structure — so AI knows actual paths (not guessed ones)
126
+ /** Load past corrections relevant to a prompt — injected as negative examples */
127
+ function loadCorrectionHints(prompt: string): string {
189
128
  try {
190
- const { execSync } = require("child_process");
191
- // Top-level dirs
192
- const topLevel = execSync("ls -1", { cwd, encoding: "utf8", timeout: 2000 }).trim();
193
- parts.push(`Top-level: ${topLevel.split("\n").join(", ")}`);
194
-
195
- // Detect monorepo (packages/ or workspaces in package.json)
196
- const isMonorepo = existsSync(join(cwd, "packages")) || existsSync(join(cwd, "apps"));
197
- if (isMonorepo) {
198
- const pkgDirs = execSync(
199
- `ls -d packages/*/src 2>/dev/null || ls -d apps/*/src 2>/dev/null || echo ""`,
200
- { cwd, encoding: "utf8", timeout: 2000 }
201
- ).trim();
202
- if (pkgDirs) {
203
- parts.push(`MONOREPO: Source is in packages/*/src/, NOT src/. Search packages/ not src/.`);
204
- parts.push(`Package sources:\n${pkgDirs}`);
205
- }
206
- }
129
+ // Dynamic import to avoid circular deps
130
+ const { findSimilarCorrections } = require("./sessions-db.js");
131
+ const corrections = findSimilarCorrections(prompt, 3);
132
+ if (corrections.length === 0) return "";
133
+
134
+ const lines = corrections.map((c: any) =>
135
+ `AVOID: "${c.failed_command}" (failed: ${c.error_type}). USE: "${c.corrected_command}" instead.`
136
+ );
137
+ return `\n\nLEARNED CORRECTIONS (from past failures):\n${lines.join("\n")}`;
138
+ } catch { return ""; }
139
+ }
207
140
 
208
- // src/ structure include FILES so AI knows exact filenames + extensions
209
- for (const srcDir of isMonorepo ? ["packages"] : ["src", "lib", "app"]) {
210
- if (existsSync(join(cwd, srcDir))) {
211
- const tree = execSync(
212
- `find ${srcDir} -maxdepth ${isMonorepo ? 4 : 3} -not -path '*/node_modules/*' -not -path '*/dist/*' -not -name '*.test.*' -not -name '*.spec.*' 2>/dev/null | sort | head -80`,
213
- { cwd, encoding: "utf8", timeout: 3000 }
214
- ).trim();
215
- if (tree) parts.push(`Files in ${srcDir}/:\n${tree}`);
216
- break;
217
- }
218
- }
219
- } catch { /* timeout or no exec — skip */ }
141
+ // ── project context (powered by context-hints) ──────────────────────────────
220
142
 
221
- return parts.length > 0 ? `\n\nPROJECT CONTEXT:\n${parts.join("\n")}` : "";
143
+ function detectProjectContext(): string {
144
+ const hints = discoverProjectHints(process.cwd());
145
+ return hints.length > 0 ? `\n\n${formatHints(hints)}` : "";
222
146
  }
223
147
 
224
148
  // ── system prompt ─────────────────────────────────────────────────────────────
225
149
 
226
- function buildSystemPrompt(perms: Permissions, sessionEntries: SessionEntry[]): string {
150
+ function buildSystemPrompt(perms: Permissions, sessionEntries: SessionEntry[], currentPrompt?: string): string {
227
151
  const restrictions: string[] = [];
228
152
  if (!perms.destructive)
229
153
  restrictions.push("- NEVER generate commands that delete, remove, or overwrite files/data");
@@ -254,10 +178,24 @@ function buildSystemPrompt(perms: Permissions, sessionEntries: SessionEntry[]):
254
178
 
255
179
  const projectContext = detectProjectContext();
256
180
 
181
+ // Inject safety hints for the command being generated (AI sees what's risky)
182
+ const safetyBlock = sessionEntries.length > 0
183
+ ? (() => {
184
+ const lastCmd = sessionEntries[sessionEntries.length - 1]?.cmd;
185
+ if (lastCmd) {
186
+ const safetyHints = discoverSafetyHints(lastCmd);
187
+ return safetyHints.length > 0 ? `\n\nLAST COMMAND SAFETY:\n${safetyHints.join("\n")}` : "";
188
+ }
189
+ return "";
190
+ })()
191
+ : "";
192
+
257
193
  return `You are a terminal assistant. Output ONLY the exact shell command — no explanation, no markdown, no backticks.
258
194
  The user describes what they want in plain English. You translate to the exact shell command.
259
195
 
260
196
  RULES:
197
+ - SIMPLICITY FIRST: Use the simplest command that works. Prefer grep | sort | head over 10-pipe chains. Complex pipelines are OK when needed, but NEVER pass file:line output to wc or xargs without cleaning it first.
198
+ - ALWAYS use grep -rn (with -r) when searching directories. NEVER use grep without -r on src/ or any directory.
261
199
  - When user refers to items from previous output, use the EXACT names shown (e.g., "feature/auth" not "auth", "open-skills" not "open_skills")
262
200
  - When user says "the largest/smallest/first/second", look at the previous output to identify the correct item
263
201
  - When user says "them all" or "combine them", refer to items from the most recent command output
@@ -265,6 +203,7 @@ RULES:
265
203
  - For text search in code, use grep -rn, NOT nm or objdump (those are for compiled binaries)
266
204
  - On macOS: for memory use vm_stat or top -l 1, for disk use df -h, for processes use ps aux
267
205
  - macOS uses BSD tools, NOT GNU. Use: du -d 1 (not --max-depth), ls (not ls --color), sort -r (not sort --reverse), ps aux (not ps --sort)
206
+ - NEVER use grep -P (PCRE). macOS grep has NO -P flag. Use grep -E for extended regex, or sed/awk for complex extraction.
268
207
  - NEVER invent commands that don't exist. Stick to standard Unix/macOS commands.
269
208
  - NEVER install packages (npx, npm install, pip install, brew install). This is a READ-ONLY terminal.
270
209
  - NEVER modify source code (sed -i, codemod, awk with redirect). Only observe, never change.
@@ -272,8 +211,10 @@ RULES:
272
211
  - Use exact file paths from the project context below. Do NOT guess paths.
273
212
  - For "what would break if I deleted X": use grep -rn "from.*X\\|import.*X\\|require.*X" src/ to find all importers.
274
213
  - For "find where X is defined": use grep -rn "export.*function X\\|export.*class X\\|export.*const X" src/
275
- - For "show me the code of function X": use grep -A 20 "function X" src/ to show the function body.
214
+ - For "show me the code of function X": if you know the file, use grep -A 30 "function X" src/file.ts. If not, use grep -rn -A 30 "function X" src/ --include="*.ts"
215
+ - ALWAYS use grep -rn (recursive) when searching directories. NEVER use grep without -r on a directory — it will fail.
276
216
  - For conceptual questions about what code does: use cat on the relevant file, the AI summary will explain it.
217
+ - For DESTRUCTIVE requests (delete, remove, install, push): output BLOCKED: <reason>. NEVER try to execute destructive commands.
277
218
 
278
219
  COMPOUND QUESTIONS: For questions asking multiple things, prefer ONE command that captures all info. Extract multiple answers from a single output.
279
220
  - "how many tests and do they pass" → bun test (extract count AND pass/fail from output)
@@ -287,7 +228,13 @@ BLOCKED ALTERNATIVES: If your preferred command would require installing package
287
228
  - Security scan → grep -rn "eval\\|exec\\|spawn\\|password\\|secret" src/
288
229
  - Dependency audit → cat package.json | grep -A 50 dependencies
289
230
  - Test coverage → bun test --coverage (or npm run test:coverage if available)
290
- NEVER give up. Always try a grep/find/cat read-only alternative.
231
+ NEVER give up. NEVER output BLOCKED for analysis questions. Always try a grep/find/cat/wc/awk read-only alternative.
232
+ - Cyclomatic complexity → grep -rn "if\\|else\\|for\\|while\\|switch\\|case\\|catch\\|&&\\|||" src/ --include="*.ts" | wc -l
233
+ - Unused exports → grep -rn "export function\|export const\|export class" src/ --include="*.ts" | sed 's/.*export [a-z]* //' | sed 's/[(<:].*//' | sort -u
234
+ - Dead code → for each exported name, grep -rn "name" src/ --include="*.ts" | wc -l (if only 1 match = unused)
235
+ - Dependency graph → grep -rn "from " src/ --include="*.ts" | sed 's/:.*from "/→/' | sed 's/".*//' | sort -u
236
+ - Most parameters → grep -rn "function " src/ --include="*.ts" | awk -F'[()]' '{print gsub(/,/,",",$2)+1, $0}' | sort -nr | head -10
237
+ ALWAYS try a heuristic shell approach before giving up. NEVER say BLOCKED for analysis questions.
291
238
 
292
239
  SEMANTIC MAPPING: When the user references a concept, search the file tree for RELATED terms:
293
240
  - Look at directory names: src/agent/ likely contains "agentic" code
@@ -300,7 +247,7 @@ EXISTENCE CHECKS: If the prompt starts with "is there", "does this have", "do we
300
247
 
301
248
  MONOREPO: If the project context says "MONOREPO", search packages/ or apps/ NOT src/. Use: grep -rn "pattern" packages/ --include="*.ts". For specific packages, use packages/PKGNAME/src/.
302
249
  cwd: ${process.cwd()}
303
- shell: zsh / macOS${projectContext}${restrictionBlock}${contextBlock}`;
250
+ shell: zsh / macOS${projectContext}${safetyBlock}${restrictionBlock}${contextBlock}${currentPrompt ? loadCorrectionHints(currentPrompt) : ""}`;
304
251
  }
305
252
 
306
253
  // ── streaming translate ───────────────────────────────────────────────────────
@@ -320,7 +267,7 @@ export async function translateToCommand(
320
267
  const provider = getProvider();
321
268
  const routing = pickModel(nl);
322
269
  const model = routing.pick === "smart" ? routing.smart : routing.fast;
323
- const system = buildSystemPrompt(perms, sessionEntries);
270
+ const system = buildSystemPrompt(perms, sessionEntries, nl);
324
271
 
325
272
  let text: string;
326
273
 
@@ -399,7 +346,7 @@ export async function fixCommand(
399
346
  {
400
347
  model: routing.smart, // always use smart model for fixes
401
348
  maxTokens: 256,
402
- system: buildSystemPrompt(perms, sessionEntries),
349
+ system: buildSystemPrompt(perms, sessionEntries, originalNl),
403
350
  }
404
351
  );
405
352
  if (text.startsWith("BLOCKED:")) throw new Error(text);
package/src/cli.tsx CHANGED
@@ -31,6 +31,7 @@ SUBCOMMANDS:
31
31
  collection create|list Recipe collections
32
32
  mcp serve Start MCP server for AI agents
33
33
  mcp install --claude|--codex Install MCP server
34
+ discover [--days=N] [--json] Scan Claude sessions, show token savings potential
34
35
  snapshot Terminal state as JSON
35
36
  --help Show this help
36
37
  --version Show version
@@ -44,7 +45,9 @@ MCP TOOLS (20+):
44
45
  snapshot, token_stats, session_history
45
46
 
46
47
  ENVIRONMENT:
47
- CEREBRAS_API_KEY Cerebras API key (free, open-source default)
48
+ XAI_API_KEY xAI API key (Grok, code-optimized default)
49
+ CEREBRAS_API_KEY Cerebras API key (free, open-source)
50
+ GROQ_API_KEY Groq API key (free, ultra-fast inference)
48
51
  ANTHROPIC_API_KEY Anthropic API key (Claude models)
49
52
  `);
50
53
  process.exit(0);
@@ -241,17 +244,8 @@ else if (args[0] === "collection") {
241
244
  // ── Stats command ────────────────────────────────────────────────────────────
242
245
 
243
246
  else if (args[0] === "stats") {
244
- const { getEconomyStats, formatTokens } = await import("./economy.js");
245
- const s = getEconomyStats();
246
- console.log("Token Economy:");
247
- console.log(` Total saved: ${formatTokens(s.totalTokensSaved)}`);
248
- console.log(` Total used: ${formatTokens(s.totalTokensUsed)}`);
249
- console.log(` By feature:`);
250
- console.log(` Structured: ${formatTokens(s.savingsByFeature.structured)}`);
251
- console.log(` Compressed: ${formatTokens(s.savingsByFeature.compressed)}`);
252
- console.log(` Diff cache: ${formatTokens(s.savingsByFeature.diff)}`);
253
- console.log(` NL cache: ${formatTokens(s.savingsByFeature.cache)}`);
254
- console.log(` Search: ${formatTokens(s.savingsByFeature.search)}`);
247
+ const { formatEconomicsSummary } = await import("./economy.js");
248
+ console.log(formatEconomicsSummary());
255
249
  }
256
250
 
257
251
  // ── Sessions command ─────────────────────────────────────────────────────────
@@ -371,7 +365,7 @@ else if (args[0] === "history") {
371
365
 
372
366
  else if (args[0] === "explain" && args[1]) {
373
367
  const command = args.slice(1).join(" ");
374
- if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY) {
368
+ if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY && !process.env.GROQ_API_KEY && !process.env.XAI_API_KEY) {
375
369
  console.error("explain requires an API key"); process.exit(1);
376
370
  }
377
371
  const { explainCommand } = await import("./ai.js");
@@ -379,6 +373,20 @@ else if (args[0] === "explain" && args[1]) {
379
373
  console.log(explanation);
380
374
  }
381
375
 
376
+ // ── Discover command ─────────────────────────────────────────────────────────
377
+
378
+ else if (args[0] === "discover") {
379
+ const { discover, formatDiscoverReport } = await import("./discover.js");
380
+ const days = parseInt(args.find(a => a.startsWith("--days="))?.split("=")[1] ?? "30");
381
+ const json = args.includes("--json");
382
+ const report = discover({ maxAgeDays: days });
383
+ if (json) {
384
+ console.log(JSON.stringify(report, null, 2));
385
+ } else {
386
+ console.log(formatDiscoverReport(report));
387
+ }
388
+ }
389
+
382
390
  // ── Snapshot command ─────────────────────────────────────────────────────────
383
391
 
384
392
  else if (args[0] === "snapshot") {
@@ -400,7 +408,7 @@ else if (args.length > 0) {
400
408
  // Everything that doesn't match a subcommand is treated as natural language
401
409
  const prompt = args.join(" ");
402
410
 
403
- const offlineMode = !process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY;
411
+ const offlineMode = !process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY && !process.env.GROQ_API_KEY && !process.env.XAI_API_KEY;
404
412
 
405
413
  const { translateToCommand, checkPermissions, isIrreversible } = await import("./ai.js");
406
414
  const { execSync } = await import("child_process");
@@ -409,6 +417,7 @@ else if (args.length > 0) {
409
417
  const { processOutput, shouldProcess } = await import("./output-processor.js");
410
418
  const { rewriteCommand } = await import("./command-rewriter.js");
411
419
  const { shouldBeLazy, toLazy } = await import("./lazy-executor.js");
420
+ const { saveOutput, formatOutputHint } = await import("./output-store.js");
412
421
  const { parseOutput, estimateTokens } = await import("./parsers/index.js");
413
422
  const { recordSaving, recordUsage } = await import("./economy.js");
414
423
  const { isTestOutput, trackTests, formatWatchResult } = await import("./test-watchlist.js");
@@ -416,6 +425,7 @@ else if (args.length > 0) {
416
425
  const { loadConfig } = await import("./history.js");
417
426
  const { loadContext, saveContext, formatContext } = await import("./session-context.js");
418
427
  const { getLearned, recordMapping } = await import("./usage-cache.js");
428
+ const { recordCorrection, findSimilarCorrections, recordOutput } = await import("./sessions-db.js");
419
429
 
420
430
  const config = loadConfig();
421
431
  const perms = config.permissions;
@@ -458,9 +468,10 @@ else if (args.length > 0) {
458
468
  } catch {}
459
469
  }
460
470
  }
461
- // "I don't know" honesty — better than wrong answer
471
+ // Show the block reason clearly
462
472
  if (e.message?.startsWith("BLOCKED:")) {
463
- console.log(`I don't know how to do this with shell commands. Try running it directly.`);
473
+ console.log(`⚠ ${e.message}`);
474
+ console.log(` This is a READ-ONLY terminal. Run directly in your shell if you're sure.`);
464
475
  } else {
465
476
  console.error(e.message);
466
477
  }
@@ -507,7 +518,7 @@ else if (args.length > 0) {
507
518
  console.error(`[open-terminal] invalid command detected: ${validation.issues.join(", ")}`);
508
519
  try {
509
520
  const retryCommand = await translateToCommand(
510
- `${prompt} (IMPORTANT: keep it simple. Use basic grep/find/cat/ls/wc commands. No complex awk/sed pipelines. No GNU flags. Verify file paths from the project context.)`,
521
+ `${prompt} (Previous command had issues: ${validation.issues.join(", ")}. Fix those specific issues. Keep the approach but correct the errors.)`,
511
522
  perms, []
512
523
  );
513
524
  if (retryCommand && retryCommand !== command) {
@@ -515,6 +526,14 @@ else if (args.length > 0) {
515
526
  if (retryValidation.valid || retryValidation.issues.length < validation.issues.length) {
516
527
  command = retryCommand;
517
528
  console.error(`[open-terminal] retried: $ ${command}`);
529
+ } else {
530
+ // Retry also invalid — use the simpler of the two
531
+ const retryPipes = (retryCommand.match(/\|/g) || []).length;
532
+ const origPipes = (command.match(/\|/g) || []).length;
533
+ if (retryPipes < origPipes) {
534
+ command = retryCommand;
535
+ console.error(`[open-terminal] retried (simpler): $ ${command}`);
536
+ }
518
537
  }
519
538
  }
520
539
  } catch {}
@@ -555,7 +574,14 @@ else if (args.length > 0) {
555
574
  const processed = await processOutput(actualCmd, clean, prompt);
556
575
  if (processed.aiProcessed) {
557
576
  if (processed.tokensSaved > 0) recordSaving("compressed", processed.tokensSaved);
558
- console.log(processed.summary);
577
+ // Save full output for lazy recovery — agents can read the file
578
+ if (processed.tokensSaved > 50) {
579
+ const outputPath = saveOutput(actualCmd, clean);
580
+ console.log(processed.summary);
581
+ console.log(formatOutputHint(outputPath));
582
+ } else {
583
+ console.log(processed.summary);
584
+ }
559
585
  if (processed.tokensSaved > 10) console.error(`[open-terminal] ${rawTokens} → ${rawTokens - processed.tokensSaved} tokens (saved ${processed.tokensSaved})`);
560
586
  process.exit(0);
561
587
  }
@@ -614,6 +640,8 @@ else if (args.length > 0) {
614
640
  const retryResult = execSync(retryCmd + " #(retry)", { encoding: "utf8", maxBuffer: 10 * 1024 * 1024, cwd: process.cwd() });
615
641
  const retryClean = stripNoise(stripAnsi(retryResult)).cleaned;
616
642
  if (retryClean.length > 5) {
643
+ // Record the correction so we learn from it
644
+ recordCorrection(prompt, actualCmd, errStderr.slice(0, 500), retryCmd, true);
617
645
  const processed = await processOutput(retryCmd, retryClean, prompt);
618
646
  console.log(processed.aiProcessed ? processed.summary : retryClean);
619
647
  process.exit(0);
@@ -642,11 +670,13 @@ else if (args.length > 0) {
642
670
  // ── TUI mode (no args) ──────────────────────────────────────────────────────
643
671
 
644
672
  else {
645
- if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY) {
673
+ if (!process.env.ANTHROPIC_API_KEY && !process.env.CEREBRAS_API_KEY && !process.env.GROQ_API_KEY && !process.env.XAI_API_KEY) {
646
674
  console.error("terminal: No API key found.");
647
675
  console.error("Set one of:");
648
- console.error(" export CEREBRAS_API_KEY=your_key (free, open-source)");
649
- console.error(" export ANTHROPIC_API_KEY=your_key (Claude)");
676
+ console.error(" export XAI_API_KEY=your_key (Grok, code-optimized — default)");
677
+ console.error(" export CEREBRAS_API_KEY=your_key (free, open-source)");
678
+ console.error(" export GROQ_API_KEY=your_key (free, ultra-fast)");
679
+ console.error(" export ANTHROPIC_API_KEY=your_key (Claude)");
650
680
  process.exit(1);
651
681
  }
652
682