@hasna/terminal 2.3.0 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/dist/App.js +404 -0
  2. package/dist/Browse.js +79 -0
  3. package/dist/FuzzyPicker.js +47 -0
  4. package/dist/Onboarding.js +51 -0
  5. package/dist/Spinner.js +12 -0
  6. package/dist/StatusBar.js +49 -0
  7. package/dist/ai.js +322 -0
  8. package/dist/cache.js +41 -0
  9. package/dist/cli.js +64 -16
  10. package/dist/command-rewriter.js +64 -0
  11. package/dist/command-validator.js +86 -0
  12. package/dist/compression.js +107 -0
  13. package/dist/context-hints.js +275 -0
  14. package/dist/diff-cache.js +107 -0
  15. package/dist/discover.js +212 -0
  16. package/dist/economy.js +123 -0
  17. package/dist/expand-store.js +38 -0
  18. package/dist/file-cache.js +72 -0
  19. package/dist/file-index.js +62 -0
  20. package/dist/history.js +62 -0
  21. package/dist/lazy-executor.js +54 -0
  22. package/dist/line-dedup.js +59 -0
  23. package/dist/loop-detector.js +75 -0
  24. package/dist/mcp/install.js +98 -0
  25. package/dist/mcp/server.js +569 -0
  26. package/dist/noise-filter.js +86 -0
  27. package/dist/output-processor.js +129 -0
  28. package/dist/output-router.js +41 -0
  29. package/dist/output-store.js +111 -0
  30. package/dist/parsers/base.js +2 -0
  31. package/dist/parsers/build.js +64 -0
  32. package/dist/parsers/errors.js +101 -0
  33. package/dist/parsers/files.js +78 -0
  34. package/dist/parsers/git.js +99 -0
  35. package/dist/parsers/index.js +48 -0
  36. package/dist/parsers/tests.js +89 -0
  37. package/dist/providers/anthropic.js +39 -0
  38. package/dist/providers/base.js +4 -0
  39. package/dist/providers/cerebras.js +95 -0
  40. package/dist/providers/groq.js +95 -0
  41. package/dist/providers/index.js +73 -0
  42. package/dist/providers/xai.js +95 -0
  43. package/dist/recipes/model.js +20 -0
  44. package/dist/recipes/storage.js +136 -0
  45. package/dist/search/content-search.js +68 -0
  46. package/dist/search/file-search.js +61 -0
  47. package/dist/search/filters.js +34 -0
  48. package/dist/search/index.js +5 -0
  49. package/dist/search/semantic.js +320 -0
  50. package/dist/session-boot.js +59 -0
  51. package/dist/session-context.js +55 -0
  52. package/dist/sessions-db.js +173 -0
  53. package/dist/smart-display.js +286 -0
  54. package/dist/snapshots.js +51 -0
  55. package/dist/supervisor.js +112 -0
  56. package/dist/test-watchlist.js +131 -0
  57. package/dist/tool-profiles.js +122 -0
  58. package/dist/tree.js +94 -0
  59. package/dist/usage-cache.js +65 -0
  60. package/package.json +8 -1
  61. package/src/ai.ts +8 -0
  62. package/src/cli.tsx +57 -18
  63. package/src/output-processor.ts +6 -1
  64. package/src/output-store.ts +58 -12
  65. package/src/tool-profiles.ts +139 -0
  66. package/.claude/scheduled_tasks.lock +0 -1
  67. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  68. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  69. package/CONTRIBUTING.md +0 -80
  70. package/benchmarks/benchmark.mjs +0 -115
  71. package/imported_modules.txt +0 -0
  72. package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
  73. package/temp/rtk/.claude/agents/debugger.md +0 -519
  74. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
  75. package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
  76. package/temp/rtk/.claude/agents/technical-writer.md +0 -355
  77. package/temp/rtk/.claude/commands/diagnose.md +0 -352
  78. package/temp/rtk/.claude/commands/test-routing.md +0 -362
  79. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
  80. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
  81. package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
  82. package/temp/rtk/.claude/rules/cli-testing.md +0 -526
  83. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
  84. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
  85. package/temp/rtk/.claude/skills/performance.md +0 -435
  86. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
  87. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
  88. package/temp/rtk/.claude/skills/repo-recap.md +0 -206
  89. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
  90. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
  91. package/temp/rtk/.claude/skills/security-guardian.md +0 -503
  92. package/temp/rtk/.claude/skills/ship.md +0 -404
  93. package/temp/rtk/.github/workflows/benchmark.yml +0 -34
  94. package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
  95. package/temp/rtk/.github/workflows/release-please.yml +0 -51
  96. package/temp/rtk/.github/workflows/release.yml +0 -343
  97. package/temp/rtk/.github/workflows/security-check.yml +0 -135
  98. package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
  99. package/temp/rtk/.release-please-manifest.json +0 -3
  100. package/temp/rtk/ARCHITECTURE.md +0 -1491
  101. package/temp/rtk/CHANGELOG.md +0 -640
  102. package/temp/rtk/CLAUDE.md +0 -605
  103. package/temp/rtk/CONTRIBUTING.md +0 -199
  104. package/temp/rtk/Cargo.lock +0 -1668
  105. package/temp/rtk/Cargo.toml +0 -64
  106. package/temp/rtk/Formula/rtk.rb +0 -43
  107. package/temp/rtk/INSTALL.md +0 -390
  108. package/temp/rtk/LICENSE +0 -21
  109. package/temp/rtk/README.md +0 -386
  110. package/temp/rtk/README_es.md +0 -159
  111. package/temp/rtk/README_fr.md +0 -197
  112. package/temp/rtk/README_ja.md +0 -159
  113. package/temp/rtk/README_ko.md +0 -159
  114. package/temp/rtk/README_zh.md +0 -167
  115. package/temp/rtk/ROADMAP.md +0 -15
  116. package/temp/rtk/SECURITY.md +0 -217
  117. package/temp/rtk/TEST_EXEC_TIME.md +0 -102
  118. package/temp/rtk/build.rs +0 -57
  119. package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
  120. package/temp/rtk/docs/FEATURES.md +0 -1410
  121. package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
  122. package/temp/rtk/docs/filter-workflow.md +0 -102
  123. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  124. package/temp/rtk/docs/tracking.md +0 -583
  125. package/temp/rtk/hooks/opencode-rtk.ts +0 -39
  126. package/temp/rtk/hooks/rtk-awareness.md +0 -29
  127. package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
  128. package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
  129. package/temp/rtk/install.sh +0 -124
  130. package/temp/rtk/release-please-config.json +0 -10
  131. package/temp/rtk/scripts/benchmark.sh +0 -592
  132. package/temp/rtk/scripts/check-installation.sh +0 -162
  133. package/temp/rtk/scripts/install-local.sh +0 -37
  134. package/temp/rtk/scripts/rtk-economics.sh +0 -137
  135. package/temp/rtk/scripts/test-all.sh +0 -561
  136. package/temp/rtk/scripts/test-aristote.sh +0 -227
  137. package/temp/rtk/scripts/test-tracking.sh +0 -79
  138. package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
  139. package/temp/rtk/scripts/validate-docs.sh +0 -73
  140. package/temp/rtk/src/aws_cmd.rs +0 -880
  141. package/temp/rtk/src/binlog.rs +0 -1645
  142. package/temp/rtk/src/cargo_cmd.rs +0 -1727
  143. package/temp/rtk/src/cc_economics.rs +0 -1157
  144. package/temp/rtk/src/ccusage.rs +0 -340
  145. package/temp/rtk/src/config.rs +0 -187
  146. package/temp/rtk/src/container.rs +0 -855
  147. package/temp/rtk/src/curl_cmd.rs +0 -134
  148. package/temp/rtk/src/deps.rs +0 -268
  149. package/temp/rtk/src/diff_cmd.rs +0 -367
  150. package/temp/rtk/src/discover/mod.rs +0 -274
  151. package/temp/rtk/src/discover/provider.rs +0 -388
  152. package/temp/rtk/src/discover/registry.rs +0 -2022
  153. package/temp/rtk/src/discover/report.rs +0 -202
  154. package/temp/rtk/src/discover/rules.rs +0 -667
  155. package/temp/rtk/src/display_helpers.rs +0 -402
  156. package/temp/rtk/src/dotnet_cmd.rs +0 -1771
  157. package/temp/rtk/src/dotnet_format_report.rs +0 -133
  158. package/temp/rtk/src/dotnet_trx.rs +0 -593
  159. package/temp/rtk/src/env_cmd.rs +0 -204
  160. package/temp/rtk/src/filter.rs +0 -462
  161. package/temp/rtk/src/filters/README.md +0 -52
  162. package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
  163. package/temp/rtk/src/filters/basedpyright.toml +0 -47
  164. package/temp/rtk/src/filters/biome.toml +0 -45
  165. package/temp/rtk/src/filters/brew-install.toml +0 -37
  166. package/temp/rtk/src/filters/composer-install.toml +0 -40
  167. package/temp/rtk/src/filters/df.toml +0 -16
  168. package/temp/rtk/src/filters/dotnet-build.toml +0 -64
  169. package/temp/rtk/src/filters/du.toml +0 -16
  170. package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
  171. package/temp/rtk/src/filters/gcc.toml +0 -49
  172. package/temp/rtk/src/filters/gcloud.toml +0 -22
  173. package/temp/rtk/src/filters/hadolint.toml +0 -24
  174. package/temp/rtk/src/filters/helm.toml +0 -29
  175. package/temp/rtk/src/filters/iptables.toml +0 -27
  176. package/temp/rtk/src/filters/jj.toml +0 -28
  177. package/temp/rtk/src/filters/jq.toml +0 -24
  178. package/temp/rtk/src/filters/make.toml +0 -41
  179. package/temp/rtk/src/filters/markdownlint.toml +0 -24
  180. package/temp/rtk/src/filters/mix-compile.toml +0 -27
  181. package/temp/rtk/src/filters/mix-format.toml +0 -15
  182. package/temp/rtk/src/filters/mvn-build.toml +0 -44
  183. package/temp/rtk/src/filters/oxlint.toml +0 -43
  184. package/temp/rtk/src/filters/ping.toml +0 -63
  185. package/temp/rtk/src/filters/pio-run.toml +0 -40
  186. package/temp/rtk/src/filters/poetry-install.toml +0 -50
  187. package/temp/rtk/src/filters/pre-commit.toml +0 -35
  188. package/temp/rtk/src/filters/ps.toml +0 -16
  189. package/temp/rtk/src/filters/quarto-render.toml +0 -41
  190. package/temp/rtk/src/filters/rsync.toml +0 -48
  191. package/temp/rtk/src/filters/shellcheck.toml +0 -27
  192. package/temp/rtk/src/filters/shopify-theme.toml +0 -29
  193. package/temp/rtk/src/filters/skopeo.toml +0 -45
  194. package/temp/rtk/src/filters/sops.toml +0 -16
  195. package/temp/rtk/src/filters/ssh.toml +0 -44
  196. package/temp/rtk/src/filters/stat.toml +0 -34
  197. package/temp/rtk/src/filters/swift-build.toml +0 -41
  198. package/temp/rtk/src/filters/systemctl-status.toml +0 -33
  199. package/temp/rtk/src/filters/terraform-plan.toml +0 -35
  200. package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
  201. package/temp/rtk/src/filters/tofu-init.toml +0 -38
  202. package/temp/rtk/src/filters/tofu-plan.toml +0 -35
  203. package/temp/rtk/src/filters/tofu-validate.toml +0 -17
  204. package/temp/rtk/src/filters/trunk-build.toml +0 -39
  205. package/temp/rtk/src/filters/ty.toml +0 -50
  206. package/temp/rtk/src/filters/uv-sync.toml +0 -37
  207. package/temp/rtk/src/filters/xcodebuild.toml +0 -99
  208. package/temp/rtk/src/filters/yamllint.toml +0 -25
  209. package/temp/rtk/src/find_cmd.rs +0 -598
  210. package/temp/rtk/src/format_cmd.rs +0 -386
  211. package/temp/rtk/src/gain.rs +0 -723
  212. package/temp/rtk/src/gh_cmd.rs +0 -1651
  213. package/temp/rtk/src/git.rs +0 -2012
  214. package/temp/rtk/src/go_cmd.rs +0 -592
  215. package/temp/rtk/src/golangci_cmd.rs +0 -254
  216. package/temp/rtk/src/grep_cmd.rs +0 -288
  217. package/temp/rtk/src/gt_cmd.rs +0 -810
  218. package/temp/rtk/src/hook_audit_cmd.rs +0 -283
  219. package/temp/rtk/src/hook_check.rs +0 -171
  220. package/temp/rtk/src/init.rs +0 -1859
  221. package/temp/rtk/src/integrity.rs +0 -537
  222. package/temp/rtk/src/json_cmd.rs +0 -231
  223. package/temp/rtk/src/learn/detector.rs +0 -628
  224. package/temp/rtk/src/learn/mod.rs +0 -119
  225. package/temp/rtk/src/learn/report.rs +0 -184
  226. package/temp/rtk/src/lint_cmd.rs +0 -694
  227. package/temp/rtk/src/local_llm.rs +0 -316
  228. package/temp/rtk/src/log_cmd.rs +0 -248
  229. package/temp/rtk/src/ls.rs +0 -324
  230. package/temp/rtk/src/main.rs +0 -2482
  231. package/temp/rtk/src/mypy_cmd.rs +0 -389
  232. package/temp/rtk/src/next_cmd.rs +0 -241
  233. package/temp/rtk/src/npm_cmd.rs +0 -236
  234. package/temp/rtk/src/parser/README.md +0 -267
  235. package/temp/rtk/src/parser/error.rs +0 -46
  236. package/temp/rtk/src/parser/formatter.rs +0 -336
  237. package/temp/rtk/src/parser/mod.rs +0 -311
  238. package/temp/rtk/src/parser/types.rs +0 -119
  239. package/temp/rtk/src/pip_cmd.rs +0 -302
  240. package/temp/rtk/src/playwright_cmd.rs +0 -479
  241. package/temp/rtk/src/pnpm_cmd.rs +0 -573
  242. package/temp/rtk/src/prettier_cmd.rs +0 -221
  243. package/temp/rtk/src/prisma_cmd.rs +0 -482
  244. package/temp/rtk/src/psql_cmd.rs +0 -382
  245. package/temp/rtk/src/pytest_cmd.rs +0 -384
  246. package/temp/rtk/src/read.rs +0 -217
  247. package/temp/rtk/src/rewrite_cmd.rs +0 -50
  248. package/temp/rtk/src/ruff_cmd.rs +0 -402
  249. package/temp/rtk/src/runner.rs +0 -271
  250. package/temp/rtk/src/summary.rs +0 -297
  251. package/temp/rtk/src/tee.rs +0 -405
  252. package/temp/rtk/src/telemetry.rs +0 -248
  253. package/temp/rtk/src/toml_filter.rs +0 -1655
  254. package/temp/rtk/src/tracking.rs +0 -1416
  255. package/temp/rtk/src/tree.rs +0 -209
  256. package/temp/rtk/src/tsc_cmd.rs +0 -259
  257. package/temp/rtk/src/utils.rs +0 -432
  258. package/temp/rtk/src/verify_cmd.rs +0 -47
  259. package/temp/rtk/src/vitest_cmd.rs +0 -385
  260. package/temp/rtk/src/wc_cmd.rs +0 -401
  261. package/temp/rtk/src/wget_cmd.rs +0 -260
  262. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
  263. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
  264. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
  265. package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
  266. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
  267. package/tsconfig.json +0 -15
@@ -0,0 +1,89 @@
1
+ // Parser for test runner output (jest, vitest, bun test, pytest, go test)
2
+ export const testParser = {
3
+ name: "test",
4
+ detect(command, output) {
5
+ if (/\b(jest|vitest|bun\s+test|pytest|go\s+test|mocha|ava|tap)\b/.test(command))
6
+ return true;
7
+ if (/\b(npm|bun|pnpm|yarn)\s+(run\s+)?test\b/.test(command))
8
+ return true;
9
+ // Detect by output patterns
10
+ return /Tests:\s+\d+/.test(output) || /\d+\s+(passing|passed|failed)/.test(output) || /PASS|FAIL/.test(output);
11
+ },
12
+ parse(_command, output) {
13
+ const failures = [];
14
+ let passed = 0, failed = 0, skipped = 0, duration;
15
+ // Jest/Vitest style: Tests: 5 passed, 2 failed, 7 total
16
+ const jestMatch = output.match(/Tests:\s+(?:(\d+)\s+passed)?[,\s]*(?:(\d+)\s+failed)?[,\s]*(?:(\d+)\s+skipped)?[,\s]*(\d+)\s+total/);
17
+ if (jestMatch) {
18
+ passed = parseInt(jestMatch[1] ?? "0");
19
+ failed = parseInt(jestMatch[2] ?? "0");
20
+ skipped = parseInt(jestMatch[3] ?? "0");
21
+ }
22
+ // Bun test style: 5 pass, 2 fail
23
+ const bunMatch = output.match(/(\d+)\s+pass.*?(\d+)\s+fail/);
24
+ if (!jestMatch && bunMatch) {
25
+ passed = parseInt(bunMatch[1]);
26
+ failed = parseInt(bunMatch[2]);
27
+ }
28
+ // Pytest style: 5 passed, 2 failed
29
+ const pytestMatch = output.match(/(\d+)\s+passed(?:.*?(\d+)\s+failed)?/);
30
+ if (!jestMatch && !bunMatch && pytestMatch) {
31
+ passed = parseInt(pytestMatch[1]);
32
+ failed = parseInt(pytestMatch[2] ?? "0");
33
+ }
34
+ // Go test: ok/FAIL + count
35
+ const goPassMatch = output.match(/ok\s+\S+\s+([\d.]+s)/);
36
+ const goFailMatch = output.match(/FAIL\s+\S+/);
37
+ if (!jestMatch && !bunMatch && !pytestMatch && (goPassMatch || goFailMatch)) {
38
+ const passLines = (output.match(/--- PASS/g) || []).length;
39
+ const failLines = (output.match(/--- FAIL/g) || []).length;
40
+ passed = passLines;
41
+ failed = failLines;
42
+ if (goPassMatch)
43
+ duration = goPassMatch[1];
44
+ }
45
+ // Duration
46
+ const timeMatch = output.match(/Time:\s+([\d.]+\s*(?:s|ms|m))/i) || output.match(/in\s+([\d.]+\s*(?:s|ms|m))/i);
47
+ if (timeMatch)
48
+ duration = timeMatch[1];
49
+ // Extract failure details: lines starting with FAIL or ✗ or ×
50
+ const lines = output.split("\n");
51
+ let capturingFailure = false;
52
+ let currentTest = "";
53
+ let currentError = [];
54
+ for (const line of lines) {
55
+ const failMatch = line.match(/(?:FAIL|✗|×|✕)\s+(.+)/);
56
+ if (failMatch) {
57
+ if (capturingFailure && currentTest) {
58
+ failures.push({ test: currentTest, error: currentError.join("\n").trim() });
59
+ }
60
+ currentTest = failMatch[1].trim();
61
+ currentError = [];
62
+ capturingFailure = true;
63
+ continue;
64
+ }
65
+ if (capturingFailure) {
66
+ if (line.match(/^(PASS|✓|✔|FAIL|✗|×|✕)\s/) || line.match(/^Tests:|^\d+ pass/)) {
67
+ failures.push({ test: currentTest, error: currentError.join("\n").trim() });
68
+ capturingFailure = false;
69
+ currentTest = "";
70
+ currentError = [];
71
+ }
72
+ else {
73
+ currentError.push(line);
74
+ }
75
+ }
76
+ }
77
+ if (capturingFailure && currentTest) {
78
+ failures.push({ test: currentTest, error: currentError.join("\n").trim() });
79
+ }
80
+ return {
81
+ passed,
82
+ failed,
83
+ skipped,
84
+ total: passed + failed + skipped,
85
+ duration,
86
+ failures,
87
+ };
88
+ },
89
+ };
@@ -0,0 +1,39 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ export class AnthropicProvider {
3
+ name = "anthropic";
4
+ client;
5
+ constructor() {
6
+ this.client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
7
+ }
8
+ isAvailable() {
9
+ return !!process.env.ANTHROPIC_API_KEY;
10
+ }
11
+ async complete(prompt, options) {
12
+ const message = await this.client.messages.create({
13
+ model: options.model ?? "claude-haiku-4-5-20251001",
14
+ max_tokens: options.maxTokens ?? 256,
15
+ system: options.system,
16
+ messages: [{ role: "user", content: prompt }],
17
+ });
18
+ const block = message.content[0];
19
+ if (block.type !== "text")
20
+ throw new Error("Unexpected response type");
21
+ return block.text.trim();
22
+ }
23
+ async stream(prompt, options, callbacks) {
24
+ let result = "";
25
+ const stream = await this.client.messages.stream({
26
+ model: options.model ?? "claude-haiku-4-5-20251001",
27
+ max_tokens: options.maxTokens ?? 256,
28
+ system: options.system,
29
+ messages: [{ role: "user", content: prompt }],
30
+ });
31
+ for await (const chunk of stream) {
32
+ if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
33
+ result += chunk.delta.text;
34
+ callbacks.onToken(result.trim());
35
+ }
36
+ }
37
+ return result.trim();
38
+ }
39
+ }
@@ -0,0 +1,4 @@
1
+ // Provider interface for LLM backends (Anthropic, Cerebras, etc.)
2
+ export const DEFAULT_PROVIDER_CONFIG = {
3
+ provider: "auto",
4
+ };
@@ -0,0 +1,95 @@
1
+ // Cerebras provider — uses OpenAI-compatible API
2
+ // Default for open-source users. Fast inference on Llama models.
3
+ const CEREBRAS_BASE_URL = "https://api.cerebras.ai/v1";
4
+ const DEFAULT_MODEL = "qwen-3-235b-a22b-instruct-2507";
5
+ export class CerebrasProvider {
6
+ name = "cerebras";
7
+ apiKey;
8
+ constructor() {
9
+ this.apiKey = process.env.CEREBRAS_API_KEY ?? "";
10
+ }
11
+ isAvailable() {
12
+ return !!process.env.CEREBRAS_API_KEY;
13
+ }
14
+ async complete(prompt, options) {
15
+ const model = options.model ?? DEFAULT_MODEL;
16
+ const res = await fetch(`${CEREBRAS_BASE_URL}/chat/completions`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Authorization: `Bearer ${this.apiKey}`,
21
+ },
22
+ body: JSON.stringify({
23
+ model,
24
+ max_tokens: options.maxTokens ?? 256,
25
+ messages: [
26
+ { role: "system", content: options.system },
27
+ { role: "user", content: prompt },
28
+ ],
29
+ }),
30
+ });
31
+ if (!res.ok) {
32
+ const text = await res.text();
33
+ throw new Error(`Cerebras API error ${res.status}: ${text}`);
34
+ }
35
+ const json = (await res.json());
36
+ return (json.choices?.[0]?.message?.content ?? "").trim();
37
+ }
38
+ async stream(prompt, options, callbacks) {
39
+ const model = options.model ?? DEFAULT_MODEL;
40
+ const res = await fetch(`${CEREBRAS_BASE_URL}/chat/completions`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ Authorization: `Bearer ${this.apiKey}`,
45
+ },
46
+ body: JSON.stringify({
47
+ model,
48
+ max_tokens: options.maxTokens ?? 256,
49
+ stream: true,
50
+ messages: [
51
+ { role: "system", content: options.system },
52
+ { role: "user", content: prompt },
53
+ ],
54
+ }),
55
+ });
56
+ if (!res.ok) {
57
+ const text = await res.text();
58
+ throw new Error(`Cerebras API error ${res.status}: ${text}`);
59
+ }
60
+ let result = "";
61
+ const reader = res.body?.getReader();
62
+ if (!reader)
63
+ throw new Error("No response body");
64
+ const decoder = new TextDecoder();
65
+ let buffer = "";
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done)
69
+ break;
70
+ buffer += decoder.decode(value, { stream: true });
71
+ const lines = buffer.split("\n");
72
+ buffer = lines.pop() ?? "";
73
+ for (const line of lines) {
74
+ const trimmed = line.trim();
75
+ if (!trimmed.startsWith("data: "))
76
+ continue;
77
+ const data = trimmed.slice(6);
78
+ if (data === "[DONE]")
79
+ break;
80
+ try {
81
+ const parsed = JSON.parse(data);
82
+ const delta = parsed.choices?.[0]?.delta?.content;
83
+ if (delta) {
84
+ result += delta;
85
+ callbacks.onToken(result.trim());
86
+ }
87
+ }
88
+ catch {
89
+ // skip malformed chunks
90
+ }
91
+ }
92
+ }
93
+ return result.trim();
94
+ }
95
+ }
@@ -0,0 +1,95 @@
1
+ // Groq provider — uses OpenAI-compatible API
2
+ // Ultra-fast inference. Supports Llama, Qwen, Kimi models.
3
+ const GROQ_BASE_URL = "https://api.groq.com/openai/v1";
4
+ const DEFAULT_MODEL = "openai/gpt-oss-120b";
5
+ export class GroqProvider {
6
+ name = "groq";
7
+ apiKey;
8
+ constructor() {
9
+ this.apiKey = process.env.GROQ_API_KEY ?? "";
10
+ }
11
+ isAvailable() {
12
+ return !!process.env.GROQ_API_KEY;
13
+ }
14
+ async complete(prompt, options) {
15
+ const model = options.model ?? DEFAULT_MODEL;
16
+ const res = await fetch(`${GROQ_BASE_URL}/chat/completions`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Authorization: `Bearer ${this.apiKey}`,
21
+ },
22
+ body: JSON.stringify({
23
+ model,
24
+ max_tokens: options.maxTokens ?? 256,
25
+ messages: [
26
+ { role: "system", content: options.system },
27
+ { role: "user", content: prompt },
28
+ ],
29
+ }),
30
+ });
31
+ if (!res.ok) {
32
+ const text = await res.text();
33
+ throw new Error(`Groq API error ${res.status}: ${text}`);
34
+ }
35
+ const json = (await res.json());
36
+ return (json.choices?.[0]?.message?.content ?? "").trim();
37
+ }
38
+ async stream(prompt, options, callbacks) {
39
+ const model = options.model ?? DEFAULT_MODEL;
40
+ const res = await fetch(`${GROQ_BASE_URL}/chat/completions`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ Authorization: `Bearer ${this.apiKey}`,
45
+ },
46
+ body: JSON.stringify({
47
+ model,
48
+ max_tokens: options.maxTokens ?? 256,
49
+ stream: true,
50
+ messages: [
51
+ { role: "system", content: options.system },
52
+ { role: "user", content: prompt },
53
+ ],
54
+ }),
55
+ });
56
+ if (!res.ok) {
57
+ const text = await res.text();
58
+ throw new Error(`Groq API error ${res.status}: ${text}`);
59
+ }
60
+ let result = "";
61
+ const reader = res.body?.getReader();
62
+ if (!reader)
63
+ throw new Error("No response body");
64
+ const decoder = new TextDecoder();
65
+ let buffer = "";
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done)
69
+ break;
70
+ buffer += decoder.decode(value, { stream: true });
71
+ const lines = buffer.split("\n");
72
+ buffer = lines.pop() ?? "";
73
+ for (const line of lines) {
74
+ const trimmed = line.trim();
75
+ if (!trimmed.startsWith("data: "))
76
+ continue;
77
+ const data = trimmed.slice(6);
78
+ if (data === "[DONE]")
79
+ break;
80
+ try {
81
+ const parsed = JSON.parse(data);
82
+ const delta = parsed.choices?.[0]?.delta?.content;
83
+ if (delta) {
84
+ result += delta;
85
+ callbacks.onToken(result.trim());
86
+ }
87
+ }
88
+ catch {
89
+ // skip malformed chunks
90
+ }
91
+ }
92
+ }
93
+ return result.trim();
94
+ }
95
+ }
@@ -0,0 +1,73 @@
1
+ // Provider auto-detection and management
2
+ import { DEFAULT_PROVIDER_CONFIG } from "./base.js";
3
+ import { AnthropicProvider } from "./anthropic.js";
4
+ import { CerebrasProvider } from "./cerebras.js";
5
+ import { GroqProvider } from "./groq.js";
6
+ import { XaiProvider } from "./xai.js";
7
+ export { DEFAULT_PROVIDER_CONFIG } from "./base.js";
8
+ let _provider = null;
9
+ /** Get the active LLM provider. Auto-detects based on available API keys. */
10
+ export function getProvider(config) {
11
+ if (_provider)
12
+ return _provider;
13
+ const cfg = config ?? DEFAULT_PROVIDER_CONFIG;
14
+ _provider = resolveProvider(cfg);
15
+ return _provider;
16
+ }
17
+ /** Reset the cached provider (useful when config changes). */
18
+ export function resetProvider() {
19
+ _provider = null;
20
+ }
21
+ function resolveProvider(config) {
22
+ if (config.provider === "cerebras") {
23
+ const p = new CerebrasProvider();
24
+ if (!p.isAvailable())
25
+ throw new Error("CEREBRAS_API_KEY not set. Run: export CEREBRAS_API_KEY=your-key");
26
+ return p;
27
+ }
28
+ if (config.provider === "anthropic") {
29
+ const p = new AnthropicProvider();
30
+ if (!p.isAvailable())
31
+ throw new Error("ANTHROPIC_API_KEY not set. Run: export ANTHROPIC_API_KEY=your-key");
32
+ return p;
33
+ }
34
+ if (config.provider === "groq") {
35
+ const p = new GroqProvider();
36
+ if (!p.isAvailable())
37
+ throw new Error("GROQ_API_KEY not set. Run: export GROQ_API_KEY=your-key");
38
+ return p;
39
+ }
40
+ if (config.provider === "xai") {
41
+ const p = new XaiProvider();
42
+ if (!p.isAvailable())
43
+ throw new Error("XAI_API_KEY not set. Run: export XAI_API_KEY=your-key");
44
+ return p;
45
+ }
46
+ // auto: prefer Cerebras (qwen-235b, fast + accurate), then xAI, then Groq, then Anthropic
47
+ const cerebras = new CerebrasProvider();
48
+ if (cerebras.isAvailable())
49
+ return cerebras;
50
+ const xai = new XaiProvider();
51
+ if (xai.isAvailable())
52
+ return xai;
53
+ const groq = new GroqProvider();
54
+ if (groq.isAvailable())
55
+ return groq;
56
+ const anthropic = new AnthropicProvider();
57
+ if (anthropic.isAvailable())
58
+ return anthropic;
59
+ throw new Error("No API key found. Set one of:\n" +
60
+ " export CEREBRAS_API_KEY=your-key (free, open-source)\n" +
61
+ " export GROQ_API_KEY=your-key (free, fast)\n" +
62
+ " export XAI_API_KEY=your-key (Grok, code-optimized)\n" +
63
+ " export ANTHROPIC_API_KEY=your-key (Claude)");
64
+ }
65
+ /** List available providers (for onboarding UI). */
66
+ export function availableProviders() {
67
+ return [
68
+ { name: "cerebras", available: new CerebrasProvider().isAvailable() },
69
+ { name: "groq", available: new GroqProvider().isAvailable() },
70
+ { name: "xai", available: new XaiProvider().isAvailable() },
71
+ { name: "anthropic", available: new AnthropicProvider().isAvailable() },
72
+ ];
73
+ }
@@ -0,0 +1,95 @@
1
+ // xAI/Grok provider — uses OpenAI-compatible API
2
+ // grok-code-fast-1 for code tasks, grok-4-fast for general queries.
3
+ const XAI_BASE_URL = "https://api.x.ai/v1";
4
+ const DEFAULT_MODEL = "grok-code-fast-1";
5
+ export class XaiProvider {
6
+ name = "xai";
7
+ apiKey;
8
+ constructor() {
9
+ this.apiKey = process.env.XAI_API_KEY ?? "";
10
+ }
11
+ isAvailable() {
12
+ return !!process.env.XAI_API_KEY;
13
+ }
14
+ async complete(prompt, options) {
15
+ const model = options.model ?? DEFAULT_MODEL;
16
+ const res = await fetch(`${XAI_BASE_URL}/chat/completions`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Authorization: `Bearer ${this.apiKey}`,
21
+ },
22
+ body: JSON.stringify({
23
+ model,
24
+ max_tokens: options.maxTokens ?? 256,
25
+ messages: [
26
+ { role: "system", content: options.system },
27
+ { role: "user", content: prompt },
28
+ ],
29
+ }),
30
+ });
31
+ if (!res.ok) {
32
+ const text = await res.text();
33
+ throw new Error(`xAI API error ${res.status}: ${text}`);
34
+ }
35
+ const json = (await res.json());
36
+ return (json.choices?.[0]?.message?.content ?? "").trim();
37
+ }
38
+ async stream(prompt, options, callbacks) {
39
+ const model = options.model ?? DEFAULT_MODEL;
40
+ const res = await fetch(`${XAI_BASE_URL}/chat/completions`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ Authorization: `Bearer ${this.apiKey}`,
45
+ },
46
+ body: JSON.stringify({
47
+ model,
48
+ max_tokens: options.maxTokens ?? 256,
49
+ stream: true,
50
+ messages: [
51
+ { role: "system", content: options.system },
52
+ { role: "user", content: prompt },
53
+ ],
54
+ }),
55
+ });
56
+ if (!res.ok) {
57
+ const text = await res.text();
58
+ throw new Error(`xAI API error ${res.status}: ${text}`);
59
+ }
60
+ let result = "";
61
+ const reader = res.body?.getReader();
62
+ if (!reader)
63
+ throw new Error("No response body");
64
+ const decoder = new TextDecoder();
65
+ let buffer = "";
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done)
69
+ break;
70
+ buffer += decoder.decode(value, { stream: true });
71
+ const lines = buffer.split("\n");
72
+ buffer = lines.pop() ?? "";
73
+ for (const line of lines) {
74
+ const trimmed = line.trim();
75
+ if (!trimmed.startsWith("data: "))
76
+ continue;
77
+ const data = trimmed.slice(6);
78
+ if (data === "[DONE]")
79
+ break;
80
+ try {
81
+ const parsed = JSON.parse(data);
82
+ const delta = parsed.choices?.[0]?.delta?.content;
83
+ if (delta) {
84
+ result += delta;
85
+ callbacks.onToken(result.trim());
86
+ }
87
+ }
88
+ catch {
89
+ // skip malformed chunks
90
+ }
91
+ }
92
+ }
93
+ return result.trim();
94
+ }
95
+ }
@@ -0,0 +1,20 @@
1
+ // Recipes data model — reusable command templates with collections and projects
2
+ /** Generate a short random ID */
3
+ export function genId() {
4
+ return Math.random().toString(36).slice(2, 10);
5
+ }
6
+ /** Substitute variables in a command template */
7
+ export function substituteVariables(command, vars) {
8
+ let result = command;
9
+ for (const [name, value] of Object.entries(vars)) {
10
+ result = result.replace(new RegExp(`\\{${name}\\}`, "g"), value);
11
+ }
12
+ return result;
13
+ }
14
+ /** Extract variable placeholders from a command */
15
+ export function extractVariables(command) {
16
+ const matches = command.match(/\{(\w+)\}/g);
17
+ if (!matches)
18
+ return [];
19
+ return [...new Set(matches.map(m => m.slice(1, -1)))];
20
+ }
@@ -0,0 +1,136 @@
1
+ // Recipes storage — global (~/.terminal/recipes.json) + per-project (.terminal/recipes.json)
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ import { genId, extractVariables } from "./model.js";
6
+ const GLOBAL_DIR = join(homedir(), ".terminal");
7
+ const GLOBAL_FILE = join(GLOBAL_DIR, "recipes.json");
8
+ function projectFile(projectPath) {
9
+ return join(projectPath, ".terminal", "recipes.json");
10
+ }
11
+ function loadStore(filePath) {
12
+ if (!existsSync(filePath))
13
+ return { recipes: [], collections: [] };
14
+ try {
15
+ return JSON.parse(readFileSync(filePath, "utf8"));
16
+ }
17
+ catch {
18
+ return { recipes: [], collections: [] };
19
+ }
20
+ }
21
+ function saveStore(filePath, store) {
22
+ const dir = filePath.replace(/\/[^/]+$/, "");
23
+ if (!existsSync(dir))
24
+ mkdirSync(dir, { recursive: true });
25
+ writeFileSync(filePath, JSON.stringify(store, null, 2));
26
+ }
27
+ // ── CRUD operations ──────────────────────────────────────────────────────────
28
+ /** Get all recipes (merged: global + project-scoped) */
29
+ export function listRecipes(projectPath) {
30
+ const global = loadStore(GLOBAL_FILE).recipes;
31
+ if (!projectPath)
32
+ return global;
33
+ const project = loadStore(projectFile(projectPath)).recipes;
34
+ return [...project, ...global]; // project recipes first (higher priority)
35
+ }
36
+ /** Get recipes filtered by collection */
37
+ export function listByCollection(collection, projectPath) {
38
+ return listRecipes(projectPath).filter(r => r.collection === collection);
39
+ }
40
+ /** Get a recipe by name */
41
+ export function getRecipe(name, projectPath) {
42
+ return listRecipes(projectPath).find(r => r.name === name) ?? null;
43
+ }
44
+ /** Create a recipe */
45
+ export function createRecipe(opts) {
46
+ const filePath = opts.project ? projectFile(opts.project) : GLOBAL_FILE;
47
+ const store = loadStore(filePath);
48
+ // Prevent duplicates — update existing if same name
49
+ const existingIdx = store.recipes.findIndex(r => r.name === opts.name);
50
+ if (existingIdx >= 0) {
51
+ store.recipes[existingIdx].command = opts.command;
52
+ store.recipes[existingIdx].updatedAt = Date.now();
53
+ if (opts.description)
54
+ store.recipes[existingIdx].description = opts.description;
55
+ if (opts.tags)
56
+ store.recipes[existingIdx].tags = opts.tags;
57
+ if (opts.collection)
58
+ store.recipes[existingIdx].collection = opts.collection;
59
+ saveStore(filePath, store);
60
+ return store.recipes[existingIdx];
61
+ }
62
+ // Auto-detect variables from command if not explicitly provided
63
+ const detectedVars = extractVariables(opts.command);
64
+ const variables = opts.variables ?? detectedVars.map(name => ({ name, required: true }));
65
+ const recipe = {
66
+ id: genId(),
67
+ name: opts.name,
68
+ description: opts.description,
69
+ command: opts.command,
70
+ tags: opts.tags ?? [],
71
+ collection: opts.collection,
72
+ project: opts.project,
73
+ variables,
74
+ createdAt: Date.now(),
75
+ updatedAt: Date.now(),
76
+ };
77
+ store.recipes.push(recipe);
78
+ saveStore(filePath, store);
79
+ return recipe;
80
+ }
81
+ /** Delete a recipe by name — tries project first, then global */
82
+ export function deleteRecipe(name, projectPath) {
83
+ // Try project-scoped first
84
+ if (projectPath) {
85
+ const pFile = projectFile(projectPath);
86
+ const pStore = loadStore(pFile);
87
+ const before = pStore.recipes.length;
88
+ pStore.recipes = pStore.recipes.filter(r => r.name !== name);
89
+ if (pStore.recipes.length < before) {
90
+ saveStore(pFile, pStore);
91
+ return true;
92
+ }
93
+ }
94
+ // Fall back to global
95
+ const store = loadStore(GLOBAL_FILE);
96
+ const before = store.recipes.length;
97
+ store.recipes = store.recipes.filter(r => r.name !== name);
98
+ if (store.recipes.length < before) {
99
+ saveStore(GLOBAL_FILE, store);
100
+ return true;
101
+ }
102
+ return false;
103
+ }
104
+ // ── Collections ──────────────────────────────────────────────────────────────
105
+ export function listCollections(projectPath) {
106
+ const global = loadStore(GLOBAL_FILE).collections;
107
+ if (!projectPath)
108
+ return global;
109
+ const project = loadStore(projectFile(projectPath)).collections;
110
+ return [...project, ...global];
111
+ }
112
+ export function createCollection(opts) {
113
+ const filePath = opts.project ? projectFile(opts.project) : GLOBAL_FILE;
114
+ const store = loadStore(filePath);
115
+ // Prevent duplicates — return existing if same name
116
+ const existing = store.collections.find(c => c.name === opts.name);
117
+ if (existing)
118
+ return existing;
119
+ const collection = {
120
+ id: genId(),
121
+ name: opts.name,
122
+ description: opts.description,
123
+ project: opts.project,
124
+ createdAt: Date.now(),
125
+ };
126
+ store.collections.push(collection);
127
+ saveStore(filePath, store);
128
+ return collection;
129
+ }
130
+ /** Initialize project-scoped recipes file */
131
+ export function initProject(projectPath) {
132
+ const file = projectFile(projectPath);
133
+ if (!existsSync(file)) {
134
+ saveStore(file, { recipes: [], collections: [] });
135
+ }
136
+ }