@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/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
|
-
|
|
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 {
|
|
255
|
-
|
|
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
|
-
//
|
|
489
|
+
// Show the block reason clearly
|
|
481
490
|
if (e.message?.startsWith("BLOCKED:")) {
|
|
482
|
-
console.log(
|
|
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} (
|
|
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
|
-
|
|
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
|
|
659
|
-
console.error(" export
|
|
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
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
|
-
//
|
|
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,
|
|
16
|
-
/\b(inside|within|under)\b/i,
|
|
17
|
-
/[|&;]{2}/,
|
|
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
|
-
/**
|
|
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:
|
|
36
|
-
smart:
|
|
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
|
-
// ──
|
|
124
|
+
// ── correction memory ───────────────────────────────────────────────────────
|
|
111
125
|
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
245
|
-
|
|
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
|
-
//
|
|
471
|
+
// Show the block reason clearly
|
|
462
472
|
if (e.message?.startsWith("BLOCKED:")) {
|
|
463
|
-
console.log(
|
|
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} (
|
|
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
|
-
|
|
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
|
|
649
|
-
console.error(" export
|
|
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
|
|