fixo-cli 1.0.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/LICENSE +201 -0
- package/README.md +530 -0
- package/dist/agent/agent-client.d.ts +108 -0
- package/dist/agent/agent-client.d.ts.map +1 -0
- package/dist/agent/agent-client.js +1247 -0
- package/dist/agent/agent-client.js.map +1 -0
- package/dist/agent/agent-pool.d.ts +20 -0
- package/dist/agent/agent-pool.d.ts.map +1 -0
- package/dist/agent/agent-pool.js +217 -0
- package/dist/agent/agent-pool.js.map +1 -0
- package/dist/agent/background-awareness.d.ts +55 -0
- package/dist/agent/background-awareness.d.ts.map +1 -0
- package/dist/agent/background-awareness.js +104 -0
- package/dist/agent/background-awareness.js.map +1 -0
- package/dist/agent/command-parser.d.ts +33 -0
- package/dist/agent/command-parser.d.ts.map +1 -0
- package/dist/agent/command-parser.js +120 -0
- package/dist/agent/command-parser.js.map +1 -0
- package/dist/agent/context-budget.d.ts +91 -0
- package/dist/agent/context-budget.d.ts.map +1 -0
- package/dist/agent/context-budget.js +219 -0
- package/dist/agent/context-budget.js.map +1 -0
- package/dist/agent/conversation.d.ts +190 -0
- package/dist/agent/conversation.d.ts.map +1 -0
- package/dist/agent/conversation.js +547 -0
- package/dist/agent/conversation.js.map +1 -0
- package/dist/agent/hooks.d.ts +72 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +214 -0
- package/dist/agent/hooks.js.map +1 -0
- package/dist/agent/mcp-bridge.d.ts +13 -0
- package/dist/agent/mcp-bridge.d.ts.map +1 -0
- package/dist/agent/mcp-bridge.js +86 -0
- package/dist/agent/mcp-bridge.js.map +1 -0
- package/dist/agent/mcp-client.d.ts +24 -0
- package/dist/agent/mcp-client.d.ts.map +1 -0
- package/dist/agent/mcp-client.js +146 -0
- package/dist/agent/mcp-client.js.map +1 -0
- package/dist/agent/mcp-manager.d.ts +13 -0
- package/dist/agent/mcp-manager.d.ts.map +1 -0
- package/dist/agent/mcp-manager.js +84 -0
- package/dist/agent/mcp-manager.js.map +1 -0
- package/dist/agent/mcp-registry.d.ts +45 -0
- package/dist/agent/mcp-registry.d.ts.map +1 -0
- package/dist/agent/mcp-registry.js +98 -0
- package/dist/agent/mcp-registry.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +14 -0
- package/dist/agent/orchestrator.d.ts.map +1 -0
- package/dist/agent/orchestrator.js +118 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/parser-adapter.d.ts +120 -0
- package/dist/agent/parser-adapter.d.ts.map +1 -0
- package/dist/agent/parser-adapter.js +265 -0
- package/dist/agent/parser-adapter.js.map +1 -0
- package/dist/agent/parsers/imports.d.ts +11 -0
- package/dist/agent/parsers/imports.d.ts.map +1 -0
- package/dist/agent/parsers/imports.js +94 -0
- package/dist/agent/parsers/imports.js.map +1 -0
- package/dist/agent/parsers/shell.d.ts +23 -0
- package/dist/agent/parsers/shell.d.ts.map +1 -0
- package/dist/agent/parsers/shell.js +200 -0
- package/dist/agent/parsers/shell.js.map +1 -0
- package/dist/agent/parsers/symbols.d.ts +17 -0
- package/dist/agent/parsers/symbols.d.ts.map +1 -0
- package/dist/agent/parsers/symbols.js +103 -0
- package/dist/agent/parsers/symbols.js.map +1 -0
- package/dist/agent/permissions.d.ts +65 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +219 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/predictive-gate.d.ts +69 -0
- package/dist/agent/predictive-gate.d.ts.map +1 -0
- package/dist/agent/predictive-gate.js +128 -0
- package/dist/agent/predictive-gate.js.map +1 -0
- package/dist/agent/provider-cooldown.d.ts +144 -0
- package/dist/agent/provider-cooldown.d.ts.map +1 -0
- package/dist/agent/provider-cooldown.js +300 -0
- package/dist/agent/provider-cooldown.js.map +1 -0
- package/dist/agent/providers-manager.d.ts +109 -0
- package/dist/agent/providers-manager.d.ts.map +1 -0
- package/dist/agent/providers-manager.js +464 -0
- package/dist/agent/providers-manager.js.map +1 -0
- package/dist/agent/repo-map.d.ts +6 -0
- package/dist/agent/repo-map.d.ts.map +1 -0
- package/dist/agent/repo-map.js +221 -0
- package/dist/agent/repo-map.js.map +1 -0
- package/dist/agent/retry.d.ts +103 -0
- package/dist/agent/retry.d.ts.map +1 -0
- package/dist/agent/retry.js +276 -0
- package/dist/agent/retry.js.map +1 -0
- package/dist/agent/search/index.d.ts +61 -0
- package/dist/agent/search/index.d.ts.map +1 -0
- package/dist/agent/search/index.js +314 -0
- package/dist/agent/search/index.js.map +1 -0
- package/dist/agent/single-agent.d.ts +76 -0
- package/dist/agent/single-agent.d.ts.map +1 -0
- package/dist/agent/single-agent.js +697 -0
- package/dist/agent/single-agent.js.map +1 -0
- package/dist/agent/skills.d.ts +22 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +139 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/stream-glue.d.ts +85 -0
- package/dist/agent/stream-glue.d.ts.map +1 -0
- package/dist/agent/stream-glue.js +120 -0
- package/dist/agent/stream-glue.js.map +1 -0
- package/dist/agent/subagent.d.ts +72 -0
- package/dist/agent/subagent.d.ts.map +1 -0
- package/dist/agent/subagent.js +193 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/telemetry.d.ts +192 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +400 -0
- package/dist/agent/telemetry.js.map +1 -0
- package/dist/agent/tokenizer.d.ts +42 -0
- package/dist/agent/tokenizer.d.ts.map +1 -0
- package/dist/agent/tokenizer.js +107 -0
- package/dist/agent/tokenizer.js.map +1 -0
- package/dist/agent/tool-executor.d.ts +289 -0
- package/dist/agent/tool-executor.d.ts.map +1 -0
- package/dist/agent/tool-executor.js +2519 -0
- package/dist/agent/tool-executor.js.map +1 -0
- package/dist/agent/web-impl.d.ts +2 -0
- package/dist/agent/web-impl.d.ts.map +1 -0
- package/dist/agent/web-impl.js +34 -0
- package/dist/agent/web-impl.js.map +1 -0
- package/dist/agent/web.d.ts +8 -0
- package/dist/agent/web.d.ts.map +1 -0
- package/dist/agent/web.js +8 -0
- package/dist/agent/web.js.map +1 -0
- package/dist/agent/worker-agent.d.ts +27 -0
- package/dist/agent/worker-agent.d.ts.map +1 -0
- package/dist/agent/worker-agent.js +503 -0
- package/dist/agent/worker-agent.js.map +1 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +138 -0
- package/dist/config.js.map +1 -0
- package/dist/context/fixo-md-watcher.d.ts +42 -0
- package/dist/context/fixo-md-watcher.d.ts.map +1 -0
- package/dist/context/fixo-md-watcher.js +126 -0
- package/dist/context/fixo-md-watcher.js.map +1 -0
- package/dist/context/fixo-md.d.ts +50 -0
- package/dist/context/fixo-md.d.ts.map +1 -0
- package/dist/context/fixo-md.js +118 -0
- package/dist/context/fixo-md.js.map +1 -0
- package/dist/context/todo.d.ts +65 -0
- package/dist/context/todo.d.ts.map +1 -0
- package/dist/context/todo.js +194 -0
- package/dist/context/todo.js.map +1 -0
- package/dist/git/git-manager.d.ts +33 -0
- package/dist/git/git-manager.d.ts.map +1 -0
- package/dist/git/git-manager.js +293 -0
- package/dist/git/git-manager.js.map +1 -0
- package/dist/git/git-ops.d.ts +10 -0
- package/dist/git/git-ops.d.ts.map +1 -0
- package/dist/git/git-ops.js +131 -0
- package/dist/git/git-ops.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +30 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +273 -0
- package/dist/indexer.js.map +1 -0
- package/dist/lsp/lsp-client.d.ts +24 -0
- package/dist/lsp/lsp-client.d.ts.map +1 -0
- package/dist/lsp/lsp-client.js +205 -0
- package/dist/lsp/lsp-client.js.map +1 -0
- package/dist/lsp/lsp-manager.d.ts +17 -0
- package/dist/lsp/lsp-manager.d.ts.map +1 -0
- package/dist/lsp/lsp-manager.js +154 -0
- package/dist/lsp/lsp-manager.js.map +1 -0
- package/dist/lsp/lsp-pre-save.d.ts +137 -0
- package/dist/lsp/lsp-pre-save.d.ts.map +1 -0
- package/dist/lsp/lsp-pre-save.js +245 -0
- package/dist/lsp/lsp-pre-save.js.map +1 -0
- package/dist/lsp/syntax-fallback.d.ts +83 -0
- package/dist/lsp/syntax-fallback.d.ts.map +1 -0
- package/dist/lsp/syntax-fallback.js +275 -0
- package/dist/lsp/syntax-fallback.js.map +1 -0
- package/dist/model-outcomes.d.ts +12 -0
- package/dist/model-outcomes.d.ts.map +1 -0
- package/dist/model-outcomes.js +46 -0
- package/dist/model-outcomes.js.map +1 -0
- package/dist/planner.d.ts +32 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +163 -0
- package/dist/planner.js.map +1 -0
- package/dist/project-memory.d.ts +29 -0
- package/dist/project-memory.d.ts.map +1 -0
- package/dist/project-memory.js +349 -0
- package/dist/project-memory.js.map +1 -0
- package/dist/review.d.ts +2 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +61 -0
- package/dist/review.js.map +1 -0
- package/dist/runtime/background-jobs.d.ts +97 -0
- package/dist/runtime/background-jobs.d.ts.map +1 -0
- package/dist/runtime/background-jobs.js +331 -0
- package/dist/runtime/background-jobs.js.map +1 -0
- package/dist/runtime/credential-vault.d.ts +124 -0
- package/dist/runtime/credential-vault.d.ts.map +1 -0
- package/dist/runtime/credential-vault.js +184 -0
- package/dist/runtime/credential-vault.js.map +1 -0
- package/dist/runtime/loop-trap.d.ts +197 -0
- package/dist/runtime/loop-trap.d.ts.map +1 -0
- package/dist/runtime/loop-trap.js +420 -0
- package/dist/runtime/loop-trap.js.map +1 -0
- package/dist/runtime/policy.d.ts +15 -0
- package/dist/runtime/policy.d.ts.map +1 -0
- package/dist/runtime/policy.js +60 -0
- package/dist/runtime/policy.js.map +1 -0
- package/dist/runtime/redaction.d.ts +66 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +155 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/session-snapshots.d.ts +76 -0
- package/dist/runtime/session-snapshots.d.ts.map +1 -0
- package/dist/runtime/session-snapshots.js +166 -0
- package/dist/runtime/session-snapshots.js.map +1 -0
- package/dist/runtime/staging.d.ts +205 -0
- package/dist/runtime/staging.d.ts.map +1 -0
- package/dist/runtime/staging.js +526 -0
- package/dist/runtime/staging.js.map +1 -0
- package/dist/runtime/task-session.d.ts +95 -0
- package/dist/runtime/task-session.d.ts.map +1 -0
- package/dist/runtime/task-session.js +263 -0
- package/dist/runtime/task-session.js.map +1 -0
- package/dist/runtime/worktree.d.ts +55 -0
- package/dist/runtime/worktree.d.ts.map +1 -0
- package/dist/runtime/worktree.js +175 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/setup-wizard.d.ts +8 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +73 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/shared/content.d.ts +43 -0
- package/dist/shared/content.d.ts.map +1 -0
- package/dist/shared/content.js +61 -0
- package/dist/shared/content.js.map +1 -0
- package/dist/shared/types.d.ts +217 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/test-runner.d.ts +5 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +42 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/ascii.d.ts +23 -0
- package/dist/ui/ascii.d.ts.map +1 -0
- package/dist/ui/ascii.js +45 -0
- package/dist/ui/ascii.js.map +1 -0
- package/dist/ui/colors.d.ts +111 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +166 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/image-attach.d.ts +27 -0
- package/dist/ui/image-attach.d.ts.map +1 -0
- package/dist/ui/image-attach.js +100 -0
- package/dist/ui/image-attach.js.map +1 -0
- package/dist/ui/index.d.ts +18 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/markdown-stream.d.ts +91 -0
- package/dist/ui/markdown-stream.d.ts.map +1 -0
- package/dist/ui/markdown-stream.js +524 -0
- package/dist/ui/markdown-stream.js.map +1 -0
- package/dist/ui/plan-renderer.d.ts +36 -0
- package/dist/ui/plan-renderer.d.ts.map +1 -0
- package/dist/ui/plan-renderer.js +79 -0
- package/dist/ui/plan-renderer.js.map +1 -0
- package/dist/ui/prompt.d.ts +11 -0
- package/dist/ui/prompt.d.ts.map +1 -0
- package/dist/ui/prompt.js +1960 -0
- package/dist/ui/prompt.js.map +1 -0
- package/dist/ui/render-primitives.d.ts +117 -0
- package/dist/ui/render-primitives.d.ts.map +1 -0
- package/dist/ui/render-primitives.js +322 -0
- package/dist/ui/render-primitives.js.map +1 -0
- package/dist/ui/render.d.ts +133 -0
- package/dist/ui/render.d.ts.map +1 -0
- package/dist/ui/render.js +547 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/session-header.d.ts +30 -0
- package/dist/ui/session-header.d.ts.map +1 -0
- package/dist/ui/session-header.js +74 -0
- package/dist/ui/session-header.js.map +1 -0
- package/dist/workspace-guard.d.ts +68 -0
- package/dist/workspace-guard.d.ts.map +1 -0
- package/dist/workspace-guard.js +168 -0
- package/dist/workspace-guard.js.map +1 -0
- package/dist/workspace-lock.d.ts +27 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +95 -0
- package/dist/workspace-lock.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# ⚡ Fixo CLI
|
|
2
|
+
> **Autonomous, Free, Multi-Provider LLM Coding Agent CLI**
|
|
3
|
+
|
|
4
|
+
[](https://www.typescriptlang.org/)
|
|
5
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
6
|
+
[](https://tree-sitter.github.io/tree-sitter/)
|
|
7
|
+
[]()
|
|
8
|
+
|
|
9
|
+
Fixo CLI is a terminal-based autonomous coding assistant designed to execute complex programming tasks directly in your workspace. Built as a self-correcting agent, it analyzes code using abstract syntax trees (AST), writes implementation plans, edits code files, runs test suites, and iterates until the goal is fully achieved.
|
|
10
|
+
|
|
11
|
+
Fixo CLI integrates seamlessly with **FreeLLMAPI**, automatically load-balancing and failing over across **20+ free LLM providers** (such as Gemini, Groq, SambaNova, Cerebras, and NVIDIA NIM) for zero-cost, state-of-the-art agentic coding.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 📊 Fixo CLI vs. Other Market Leaders
|
|
16
|
+
|
|
17
|
+
Here is how Fixo CLI compares against other prominent terminal and editor-based coding agents:
|
|
18
|
+
|
|
19
|
+
| Feature / Metric | **Fixo CLI** | **Claude Code** | **Aider** | **Cline** |
|
|
20
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
21
|
+
| **API Cost** | 💰 **100% Free** (via FreeLLMAPI) | 💸 **Paid** (Anthropic API charges) | 💸 **Paid** (Requires personal keys) | 💸 **Paid** (Requires personal keys) |
|
|
22
|
+
| **Multi-Provider Fallback**| 🔄 **Automatic Failover** (No interruptions) | ❌ None (Locked to Anthropic) | ❌ Manual (Requires editing configs) | ❌ Manual (Drops request on 429) |
|
|
23
|
+
| **Workspace Indexing** | 🌳 **AST / Tree-Sitter** (Semantic map) | 🔍 Regex / basic grep | 🗺️ Git/ctags-based map | 🔍 Basic file search |
|
|
24
|
+
| **Autonomy Loops** | 🤖 **Multi-agent / Planning Mode** | 🤖 Agent loops | 💬 Interactive / chat-driven | 💬 Prompt-to-action loops |
|
|
25
|
+
| **Self-Correction** | 🧪 **Built-in test runner & loops** | ❌ Manual trigger | ❌ Requires manual input | ❌ Requires manual input |
|
|
26
|
+
| **No-Card Verification** | ✅ **Yes** (Zero billing required) | ❌ No (Requires credit card) | ❌ No (Requires paid API keys) | ❌ No (Requires paid API keys) |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## ⚙️ Architecture & Lifecycle Flow
|
|
31
|
+
|
|
32
|
+
Fixo CLI separates concerns between code understanding (AST parser), task coordination (Planner), and execution (Agent).
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
sequenceDiagram
|
|
36
|
+
autonumber
|
|
37
|
+
actor User as Developer
|
|
38
|
+
participant CLI as Fixo CLI
|
|
39
|
+
participant Indexer as AST Workspace Indexer
|
|
40
|
+
participant Planner as Plan Engine
|
|
41
|
+
participant Agent as Autonomous Agent
|
|
42
|
+
participant Proxy as FreeLLMAPI Proxy
|
|
43
|
+
participant LLM as Provider (Groq/Gemini/NIM)
|
|
44
|
+
|
|
45
|
+
User->>CLI: Request task (e.g. "Fix auth bug")
|
|
46
|
+
CLI->>Indexer: Scan repository & generate AST maps
|
|
47
|
+
Indexer-->>CLI: Return semantic codebase layout
|
|
48
|
+
CLI->>Planner: Propose implementation plan
|
|
49
|
+
Planner->>Proxy: Fetch reasoning (smart routing)
|
|
50
|
+
Proxy->>LLM: Try highest ranked provider
|
|
51
|
+
LLM-->>Planner: Return initial plan
|
|
52
|
+
Planner-->>User: Present plan for approval
|
|
53
|
+
User->>CLI: Plan Approved!
|
|
54
|
+
CLI->>Agent: Execute code changes
|
|
55
|
+
loop Iterative Execution
|
|
56
|
+
Agent->>Proxy: Request edit / test run
|
|
57
|
+
Proxy->>LLM: Fallback routing (failover on error)
|
|
58
|
+
LLM-->>Agent: Code output / Command to run
|
|
59
|
+
Agent->>CLI: Apply file changes & execute tests
|
|
60
|
+
end
|
|
61
|
+
Agent-->>User: Task completed successfully!
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🌟 Key Features
|
|
67
|
+
|
|
68
|
+
* **Autonomous Agent Loop:** Fixo CLI runs an agent loop that defines planning sub-agents, writes files, runs shell commands, reads compiler output, and self-corrects until tests pass.
|
|
69
|
+
* **Workspace AST Indexer:** Uses **Tree-Sitter** to parse JavaScript, TypeScript, Python, and Go codebases, generating a semantic repository map for precise context insertion.
|
|
70
|
+
* **Free Multi-Provider Routing:** Connects to your FreeLLMAPI server to query models like Llama 3.3, Qwen 3, and Gemini 2.5/3.1 without incurring high API costs.
|
|
71
|
+
* **Smart Cooldown & Failover:** The CLI automatically tracks rate-limited providers (429/402/404) and switches to working alternatives in the fallback chain mid-request.
|
|
72
|
+
* **Resilience Stack:** Stream recovery, provider cooldown, context-budget enforcement, and a local telemetry sink work together so the agent stays productive on flaky networks and large codebases. See [Resilience](#-resilience) below.
|
|
73
|
+
* **Built-in Workspace Guard:** Safely manages workspace locks, preventing concurrent file writes and ensuring git safety.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🛠 Tool Reference (Phase 1–3 + Phase 4)
|
|
78
|
+
|
|
79
|
+
The agent dispatches the following tools through the unified
|
|
80
|
+
`executeTool()` entrypoint in `src/agent/tool-executor.ts`.
|
|
81
|
+
Every call is gated by the granular permission engine
|
|
82
|
+
(`checkPermission`) before any side effect runs. `Mode`
|
|
83
|
+
indicates which execution modes the tool is callable from
|
|
84
|
+
(`PLAN`, `BUILD`, `EXPLORE`, `SCOUT`); tools that mutate the
|
|
85
|
+
workspace are blocked outside `BUILD`.
|
|
86
|
+
|
|
87
|
+
| Tool | Phase | Description | Required args | Mode | Pillar gates | Default permission |
|
|
88
|
+
| :--- | :---: | :--- | :--- | :--- | :--- | :--- |
|
|
89
|
+
| `str_replace` | 1 | Surgical line-level edit with uniqueness check on `find`. Atomic via `applySurgicalReplace`. | `path`, `find`, `replace` | BUILD | Staging + LSP pre-save + workspace guard | `ask` (default-ask) |
|
|
90
|
+
| `glob_files` | 1 | Pattern-based file finder (e.g. `src/**/*.ts`). | `pattern` | EXPLORE, SCOUT, BUILD | Workspace guard | `ask` (default-ask) |
|
|
91
|
+
| `todo_write` / `todo_read` | 2 | Mutable task checklist persisted under `.fixo/`. | `items?` (write) / — (read) | PLAN, BUILD | Workspace guard + staging | `ask` / `allow` |
|
|
92
|
+
| `run_command_async` | 3 | Non-blocking shell execution; returns a job id. | `command`, `cwd?` | BUILD | Command-parser AST + permissions | `ask` (default-ask) |
|
|
93
|
+
| `poll_command_status` | 3 | Poll a previously-spawned async job for status + ring-buffered stdout/stderr. | `id` | BUILD, EXPLORE | n/a (read-only metadata) | `ask` (default-ask) |
|
|
94
|
+
| `kill_command` | 3 | Send `SIGTERM` to a running async job. | `id` | BUILD | Command-parser invariants | `ask` (default-ask) |
|
|
95
|
+
| `spawn_subagent` | 3 | Context-isolated sub-orchestrator with its own conversation budget; inherits parent policy + vault. | `prompt`, `tools?` | PLAN, BUILD | Inherits all four pillars | `ask` |
|
|
96
|
+
| `/mcp` console | 3 | Slash command (`/mcp list`, `/mcp add`, `/mcp restart`) for MCP server management. | — | EXPLORE, BUILD | Config-only (no workspace touch) | n/a |
|
|
97
|
+
| Worktree annotations | 3 | Parsed from assistant text (`[worktree:create branch=x]`, `[worktree:merge branch=x]`, `[worktree:remove path=...]`). Not a tool — a capability the executor extracts post-stream. | n/a (annotation in text) | BUILD | `execFileSync('git', …)` — no shell expansion | `ask` (parsed by the single-agent loop) |
|
|
98
|
+
|
|
99
|
+
### Predictive Context-Budget Gate (Phase 4)
|
|
100
|
+
|
|
101
|
+
A token-aware predictive gate sits in front of `read_file`. Before
|
|
102
|
+
the byte gate runs, the gate projects the file's token cost via
|
|
103
|
+
`gpt-tokenizer`, adds the current conversation token count, and
|
|
104
|
+
defers the read with a `[Context-Budget Guard]` directive if the
|
|
105
|
+
projected total would exceed `predictiveBudgetPct` of the model's
|
|
106
|
+
input window (default `0.85`). The directive routes the model to
|
|
107
|
+
`extract_symbols` / `extract_imports` / `str_replace` instead of
|
|
108
|
+
reading the full file. Configurable via `preferences.safety.predictiveBudgetPct` —
|
|
109
|
+
set to `1.0` to disable.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🛡 Resilience
|
|
114
|
+
|
|
115
|
+
Fixo CLI is built for hostile environments: free-tier rate limits, dropped SSE streams, providers that 502 mid-response, and codebases larger than any single context window. The resilience stack is organised into four independent pillars, each of which can be tuned or disabled individually via `~/.fixocli/config.json`.
|
|
116
|
+
|
|
117
|
+
| Pillar | Module | What it does | Default |
|
|
118
|
+
| :--- | :--- | :--- | :--- |
|
|
119
|
+
| **Stream Recovery** | `src/agent/stream-glue.ts` | Detects mid-stream SSE cuts *after* at least one chunk has been yielded, rebuilds the message list with the partial response, and re-issues the request transparently. | `auto` (up to 3 attempts) |
|
|
120
|
+
| **Provider Cooldown** | `src/agent/provider-cooldown.ts` | Tracks per-provider success/failure rates. On 429/5xx, applies an exponential cooldown (30/60/120/240/300s for rate limits, 10/20/40/80/120s for server errors) and steers subsequent requests to healthier providers. | always on |
|
|
121
|
+
| **Context Budgeting** | `src/agent/context-budget.ts` | Counts real BPE tokens (via `gpt-tokenizer`, cl100k / o200k) before every LLM call. When the next request would overflow, runs a tiered trim (prune tool outputs → drop oldest turn-pairs → truncate tool args) and, if still over, marks the conversation for LLM-based compaction. | `auto` at 80% of model window |
|
|
122
|
+
| **Telemetry** | `src/agent/telemetry.ts` | Append-only NDJSON sink at `~/.fixocli/telemetry.jsonl` (rotated at 1 MiB). Emits structured events for retries, cooldowns, stream resumes, context compactions, tool failures, and provider errors. `diagnoseFailures()` reads the recent window and surfaces remediation hints. | `local: on, remote: off` |
|
|
123
|
+
|
|
124
|
+
### ResilienceConfig schema
|
|
125
|
+
|
|
126
|
+
All four pillars are controlled by the `preferences.resilience` block in your config. Every field has a safe default, so you can omit the entire block if you want the shipped behaviour.
|
|
127
|
+
|
|
128
|
+
```jsonc
|
|
129
|
+
{
|
|
130
|
+
"preferences": {
|
|
131
|
+
"telemetry": true, // Master switch for *all* telemetry
|
|
132
|
+
"telemetryLocal": true, // Append events to ~/.fixocli/telemetry.jsonl
|
|
133
|
+
"telemetryRemote": false, // POST events to the FreeLLMAPI server (anonymous)
|
|
134
|
+
"resilience": {
|
|
135
|
+
"streamResume": "auto", // "auto" | "never" (kill-switch for stream recovery)
|
|
136
|
+
"maxResumeAttempts": 3, // additional attempts after a mid-stream cut
|
|
137
|
+
"useWithRetry": true, // use the withRetry engine for non-streaming calls
|
|
138
|
+
"contextBudget": "auto", // "auto" | "truncate" | "never" (kill-switch for budget enforcement)
|
|
139
|
+
"contextBudgetRatio": 0.8 // fraction of model window used as the hard cap
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
| Field | Type | Default | Behaviour |
|
|
146
|
+
| :--- | :--- | :--- | :--- |
|
|
147
|
+
| `streamResume` | `"auto" \| "never"` | `"auto"` | When `never`, `chatStream` is called directly and cuts bubble up to the caller as `StreamResumeExhaustedError`. |
|
|
148
|
+
| `maxResumeAttempts` | `number` | `3` | How many additional attempts the resume engine makes after a mid-stream cut. 0 disables resume. |
|
|
149
|
+
| `useWithRetry` | `boolean` | `true` | Toggle the exponential-backoff `withRetry` engine for non-streaming calls. |
|
|
150
|
+
| `contextBudget` | `"auto" \| "truncate" \| "never"` | `"auto"` | `auto` = enforce + LLM-compact; `truncate` = enforce only (no LLM fallback); `never` = skip the enforcer entirely. |
|
|
151
|
+
| `contextBudgetRatio` | `number` (0–1) | `0.8` | Fraction of the model's input window used as the hard cap. 0.8 leaves 20% headroom for the response. |
|
|
152
|
+
| `telemetryLocal` | `boolean` | `true` | Disable to skip the local NDJSON sink while keeping the remote one (if enabled). |
|
|
153
|
+
| `telemetryRemote` | `boolean` | `false` | Opt in to the legacy HTTP poster for anonymous usage stats. |
|
|
154
|
+
|
|
155
|
+
### Diagnosing a bad session
|
|
156
|
+
|
|
157
|
+
After a session that didn't go well, run the diagnostic from the CLI:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
fixo --diagnose
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or read the log directly:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
tail -50 ~/.fixocli/telemetry.jsonl | jq .
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`diagnoseFailures()` looks at the last hour by default and reports patterns such as:
|
|
170
|
+
- 3+ retries in the window → likely flaky network or rate-limit
|
|
171
|
+
- provider cooldown → at least one provider is rate-limiting; others are being preferred automatically
|
|
172
|
+
- stream-resume exhaustion → raise `maxResumeAttempts` or check your network
|
|
173
|
+
- repeated tool failures → the same tool has failed 3+ times; check its inputs
|
|
174
|
+
- 5+ provider errors → likely a provider outage
|
|
175
|
+
|
|
176
|
+
See [`docs/RESILIENCE.md`](docs/RESILIENCE.md) for the pillar-by-pillar design notes.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 🛡 Operational Resilience & Enterprise Hardening
|
|
181
|
+
|
|
182
|
+
The Resilience section above covers **uptime** (how Fixo stays
|
|
183
|
+
alive through network noise). This section covers **integrity**
|
|
184
|
+
— how Fixo protects the user's workspace and secrets. The two
|
|
185
|
+
are deliberately orthogonal and live in separate configuration
|
|
186
|
+
blocks:
|
|
187
|
+
|
|
188
|
+
| Concern | Configuration block | Purpose |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| **Resilience** | `preferences.resilience` | Stream resume, context budgeting, retry engine. |
|
|
191
|
+
| **Safety** | `preferences.safety` | Loop detection, atomic file writes, LSP pre-save, credential vault. |
|
|
192
|
+
|
|
193
|
+
### Safety configuration schema
|
|
194
|
+
|
|
195
|
+
The full schema lives under `preferences.safety` in
|
|
196
|
+
`~/.fixocli/config.json`. Defaults are safe for interactive
|
|
197
|
+
use; tighten them for CI / unattended runs.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"preferences": {
|
|
202
|
+
"safety": {
|
|
203
|
+
"atomicStaging": true,
|
|
204
|
+
"stagingTtlMs": 86400000,
|
|
205
|
+
"lspPreSave": "warn",
|
|
206
|
+
"loopTrap": {
|
|
207
|
+
"triggerCount": 3,
|
|
208
|
+
"hardAbortCount": 6,
|
|
209
|
+
"toolResultTailBytes": 1024,
|
|
210
|
+
"maxHistory": 64,
|
|
211
|
+
"enabled": true
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
| Field | Type | Default | Behaviour |
|
|
219
|
+
|---|---|---|---|
|
|
220
|
+
| `atomicStaging` | `boolean` | `true` | Route every file write through the shadow-staging pipeline. When `false`, the executor falls back to the legacy direct-write path (faster, but non-atomic). |
|
|
221
|
+
| `stagingTtlMs` | `number` (ms) | `86400000` (24h) | Staged writes older than this are eligible for auto-GC. |
|
|
222
|
+
| `lspPreSave` | `"off" \| "warn" \| "block"` | `"warn"` | `off` = no-op; `warn` = log diagnostics, allow commit; `block` = throw on any `error`-severity diagnostic and roll back. |
|
|
223
|
+
| `loopTrap.triggerCount` | `number` | `3` | Consecutive equivalent turns that fire the `[Loop-Trap]` directive. |
|
|
224
|
+
| `loopTrap.hardAbortCount` | `number` | `6` | Consecutive equivalent turns that throw `LoopTrapAbortedError`. |
|
|
225
|
+
| `loopTrap.toolResultTailBytes` | `number` (bytes) | `1024` | Length of tool-result tail that is hashed. |
|
|
226
|
+
| `loopTrap.maxHistory` | `number` | `64` | Cap on in-memory detector history. |
|
|
227
|
+
| `loopTrap.enabled` | `boolean` | `true` | Master kill-switch for the detector. |
|
|
228
|
+
| `semanticLoopTrap.enabled` | `boolean` | `true` | Master kill-switch for the *semantic* detector (tracks per-file frequency in a sliding window). |
|
|
229
|
+
| `semanticLoopTrap.windowSize` | `number` | `5` | Width of the sliding window. |
|
|
230
|
+
| `semanticLoopTrap.triggerCount` | `number` | `3` | File accesses inside the window that fire the `[Safety-Alert]` directive. |
|
|
231
|
+
| `semanticLoopTrap.hardAbortCount` | `number` | `6` | File accesses inside the window that throw `SemanticLoopAbortedError` (and roll back any staged writes). |
|
|
232
|
+
| `largeFileGateBytes` | `number` (bytes) | `15360` (15 KiB) | `read_file` returns a `[Context-Budget Guard]` directive when a file exceeds this size. |
|
|
233
|
+
| `largeFileGateLines` | `number` | `350` | Same gate, line-count threshold. |
|
|
234
|
+
|
|
235
|
+
### Hardening profiles
|
|
236
|
+
|
|
237
|
+
| Profile | `atomicStaging` | `lspPreSave` | `loopTrap` |
|
|
238
|
+
|---|---|---|---|
|
|
239
|
+
| **Interactive dev** *(default)* | `true` | `"warn"` | `3 / 6` |
|
|
240
|
+
| **CI / unattended** | `true` | `"block"` | `2 / 4` |
|
|
241
|
+
| **Trusted fine-tune** | `false` | `"off"` | `5 / 10` |
|
|
242
|
+
| **Benchmarking** | `false` | `"off"` | kill-switch |
|
|
243
|
+
|
|
244
|
+
### The four safety pillars
|
|
245
|
+
|
|
246
|
+
| # | Pillar | What it stops | Where it lives |
|
|
247
|
+
|---|---|---|---|
|
|
248
|
+
| 1 | **Deterministic Loop-Trap Defenses** (composite + semantic) | An LLM that re-issues equivalent tool calls *or* hammers the same file with varied arguments. | `src/runtime/loop-trap.ts` |
|
|
249
|
+
| 2 | **Atomic Workspace Shadow Staging** | A process kill mid-write leaving the user's file truncated. | `src/runtime/staging.ts` |
|
|
250
|
+
| 3 | **Live Pre-Save LSP Compilation Check** + **Context-Budget Guard** | An LLM writing syntactically valid but semantically broken code, *or* flooding the context window with a single 200KB file. | `src/lsp/lsp-pre-save.ts` + `src/lsp/syntax-fallback.ts` + the large-file gate in `src/agent/tool-executor.ts` |
|
|
251
|
+
| 4 | **Restricted Credential Sandboxing & Redaction** | Direct-provider API keys leaking into a tool result, log line, or model prompt. | `src/runtime/credential-vault.ts` + `src/runtime/redaction.ts` |
|
|
252
|
+
|
|
253
|
+
#### Pillar 1 — Composite *and* semantic loop detection
|
|
254
|
+
|
|
255
|
+
Two detectors run in parallel:
|
|
256
|
+
|
|
257
|
+
- **Composite** (`LoopTrapDetector`) — fingerprints the tool call
|
|
258
|
+
*arguments* and the tail of the tool *result* and trips when
|
|
259
|
+
three consecutive turns hash to the same value. Defends
|
|
260
|
+
against a deterministic "stare at one line" loop.
|
|
261
|
+
- **Semantic** (`SemanticLoopDetector`) — fingerprints the
|
|
262
|
+
*target file path* of every file-mutating tool and trips when
|
|
263
|
+
the same path appears three times inside a sliding 5-turn
|
|
264
|
+
window. Defends against an LLM that varies its search
|
|
265
|
+
arguments (different line numbers, different patterns) but
|
|
266
|
+
keeps hammering the same file.
|
|
267
|
+
|
|
268
|
+
On `triggerCount` the detector injects a `[Safety-Alert]`
|
|
269
|
+
directive into the next system prompt. On `hardAbortCount` it
|
|
270
|
+
throws `SemanticLoopAbortedError`, which the agent catches and
|
|
271
|
+
translates into a clean `AtomicStagingManager.rollbackAll()` so
|
|
272
|
+
no half-edited file survives.
|
|
273
|
+
|
|
274
|
+
#### Pillar 3 — Context-Budget Guard
|
|
275
|
+
|
|
276
|
+
`read_file` is gated by both a byte threshold (`largeFileGateBytes`,
|
|
277
|
+
default 15 KiB) and a line threshold (`largeFileGateLines`,
|
|
278
|
+
default 350). When a file exceeds either, the LLM receives a
|
|
279
|
+
synthetic `[Context-Budget Guard]` directive that points it at
|
|
280
|
+
the new structural pre-scan tools:
|
|
281
|
+
|
|
282
|
+
- `extract_symbols(path)` — top-level declarations only (cap
|
|
283
|
+
100 per file).
|
|
284
|
+
- `extract_imports(path)` — dependency list only (cap 100 per
|
|
285
|
+
file).
|
|
286
|
+
|
|
287
|
+
Each pre-scan call records the result in the
|
|
288
|
+
`TaskSession.structuralMaps` map so the LLM's later reads can
|
|
289
|
+
prove they were narrowed first.
|
|
290
|
+
|
|
291
|
+
#### Pillar 3 — LSP syntax fallback
|
|
292
|
+
|
|
293
|
+
When no language server (`typescript-language-server`,
|
|
294
|
+
`gopls`, `rust-analyzer`, …) is on the `PATH`, the pre-save
|
|
295
|
+
gate falls back to `syntaxHealthCheck` — a pure-JS
|
|
296
|
+
brace/paren/bracket balance check that runs in microseconds.
|
|
297
|
+
Set `FIXO_LSP_FALLBACK=syntax-only` to force the fallback even
|
|
298
|
+
when a real LSP is available (useful for sandboxed CI). The
|
|
299
|
+
boot sequence also surfaces a one-time warning when neither is
|
|
300
|
+
present.
|
|
301
|
+
|
|
302
|
+
#### Pillar 4 — Three redaction modes
|
|
303
|
+
|
|
304
|
+
`src/runtime/redaction.ts` exposes three helpers for the three
|
|
305
|
+
distinct places redaction is needed:
|
|
306
|
+
|
|
307
|
+
- `stripAnsi(value)` — drop every ANSI escape entirely. Use
|
|
308
|
+
for content piped into another tool that doesn't care about
|
|
309
|
+
colour.
|
|
310
|
+
- `redactAnsi(value)` — replace the `\x1b` byte with the
|
|
311
|
+
printable form `\\x1b` so the message survives a log write /
|
|
312
|
+
telemetry upload without injecting control codes downstream.
|
|
313
|
+
Use for content whose *bytes* must be preserved (file paths,
|
|
314
|
+
error messages).
|
|
315
|
+
- `scrubForLlm(value)` — strip ANSI *and* replace every
|
|
316
|
+
recognised secret pattern with `[REDACTED]`. This is the
|
|
317
|
+
only safe redaction for content heading back into a model
|
|
318
|
+
prompt.
|
|
319
|
+
|
|
320
|
+
### Automated background garbage collection
|
|
321
|
+
|
|
322
|
+
The staging pipeline writes to `<cwd>/.fixo/staging/<runId>/`
|
|
323
|
+
and would silently bloat the disk if entries were never
|
|
324
|
+
cleaned up. Two sweeps run automatically:
|
|
325
|
+
|
|
326
|
+
- **Per-run GC** — `AtomicStagingManager.gc()` removes entries
|
|
327
|
+
older than `stagingTtlMs` from the current run's directory.
|
|
328
|
+
- **Global GC** — `AtomicStagingManager.garbageCollectAll(cwd, ttlMs)`
|
|
329
|
+
sweeps every `<runId>/` directory. Invoked at the start of
|
|
330
|
+
every `runStreaming` lifecycle (typically < 2 ms) and also
|
|
331
|
+
exposed via the `/fixo gc` slash command for power users.
|
|
332
|
+
|
|
333
|
+
GC is bounded and uses the metadata `createdAt` timestamp
|
|
334
|
+
rather than file mtime, so the TTL is a deterministic policy
|
|
335
|
+
decision rather than a side effect of kernel flush timing.
|
|
336
|
+
|
|
337
|
+
### Troubleshooting runbooks
|
|
338
|
+
|
|
339
|
+
#### "The agent is stuck in a loop"
|
|
340
|
+
|
|
341
|
+
The `[Loop-Trap]` directive is injected into the system prompt
|
|
342
|
+
after 3 consecutive equivalent turns. The model is expected to
|
|
343
|
+
reconsider its strategy. If it doesn't, the detector hard-
|
|
344
|
+
aborts at 6 turns and the session terminates with
|
|
345
|
+
`LoopTrapAbortedError`.
|
|
346
|
+
|
|
347
|
+
**Diagnosis:**
|
|
348
|
+
```bash
|
|
349
|
+
tail -100 ~/.fixocli/telemetry.jsonl | jq 'select(.event == "loopTrap")'
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Fix:**
|
|
353
|
+
1. Read the directive — it tells the LLM *why* the loop is
|
|
354
|
+
happening. Common causes:
|
|
355
|
+
- The test command is failing consistently → fix the test.
|
|
356
|
+
- The patch is being reverted by a pre-commit hook → fix the hook.
|
|
357
|
+
- The agent is confused about file paths → clarify the task.
|
|
358
|
+
2. Force a manual compaction to drop the noise from the
|
|
359
|
+
conversation history:
|
|
360
|
+
```bash
|
|
361
|
+
fixo --compact
|
|
362
|
+
```
|
|
363
|
+
3. To disable the detector for a single session (debugging
|
|
364
|
+
only), set `loopTrap.enabled` to `false` in
|
|
365
|
+
`~/.fixocli/config.json`.
|
|
366
|
+
|
|
367
|
+
#### "LSP gate blocked my write"
|
|
368
|
+
|
|
369
|
+
`write_file` returns
|
|
370
|
+
`Error: Pre-commit hook rejected: LSP pre-save blocked: N
|
|
371
|
+
error(s) in <path> — <line>:<col> <message>; ...`
|
|
372
|
+
|
|
373
|
+
**Diagnosis:**
|
|
374
|
+
1. The first 3 error messages in the string are the gate's
|
|
375
|
+
best guess at the root cause.
|
|
376
|
+
2. Open the file in your editor — the LSP (if installed)
|
|
377
|
+
underlines the offending line.
|
|
378
|
+
3. Common causes:
|
|
379
|
+
- Missing import → add it.
|
|
380
|
+
- Type mismatch → fix the annotation.
|
|
381
|
+
- Reference to an undeclared identifier → typo.
|
|
382
|
+
|
|
383
|
+
**The original file is preserved on every blocked write.**
|
|
384
|
+
Re-run the agent's suggested fix and try again.
|
|
385
|
+
|
|
386
|
+
**If the gate is over-firing** (a known-good write is being
|
|
387
|
+
rejected), lower the mode from `block` to `warn`:
|
|
388
|
+
```json
|
|
389
|
+
{ "preferences": { "safety": { "lspPreSave": "warn" } } }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### "A write silently rolled back"
|
|
393
|
+
|
|
394
|
+
The agent said it wrote a file, but the file is unchanged.
|
|
395
|
+
|
|
396
|
+
**Diagnosis:**
|
|
397
|
+
1. Check the tool result string for
|
|
398
|
+
`PreCommitHookRejectedError` or `StagingPathEscapeError`.
|
|
399
|
+
2. For `PreCommitHookRejectedError`, the `cause` field is the
|
|
400
|
+
underlying error (`LspPreSaveBlockedError`, or a future
|
|
401
|
+
pre-commit hook). Read `cause.message`.
|
|
402
|
+
3. For `StagingPathEscapeError`, the target path escapes the
|
|
403
|
+
workspace root — this is a caller bug. Reject and re-prompt.
|
|
404
|
+
|
|
405
|
+
#### "Staging directory is filling my disk"
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
du -sh ~/.fixo/staging
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Fix:**
|
|
412
|
+
1. The auto-GC runs at the start of every `runStreaming` cycle.
|
|
413
|
+
If the directory is large *during* a session, an LLM is
|
|
414
|
+
staging writes but failing to commit them. Inspect the
|
|
415
|
+
session log for `PreCommitHookRejectedError`.
|
|
416
|
+
2. Manual sweep (safe; staging is ephemeral):
|
|
417
|
+
```bash
|
|
418
|
+
rm -rf ~/.fixo/staging/*
|
|
419
|
+
```
|
|
420
|
+
3. Lower `stagingTtlMs` to expire entries sooner:
|
|
421
|
+
```json
|
|
422
|
+
{ "preferences": { "safety": { "stagingTtlMs": 3600000 } } }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### "A direct-provider API key was rejected"
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
fixo providers list
|
|
429
|
+
fixo providers add openai sk-proj-...
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
The vault is auto-hydrated on the next `getDirectConfig` call,
|
|
433
|
+
so the new key is visible immediately — no restart required.
|
|
434
|
+
|
|
435
|
+
> **Security note:** never paste a real key into a chat
|
|
436
|
+
> message, a GitHub issue, or a tool result. `scrubForLlm`
|
|
437
|
+
> redacts common shapes, but a low-entropy prefix is not
|
|
438
|
+
> guaranteed to match.
|
|
439
|
+
|
|
440
|
+
#### "I need to fully reset the safety layer"
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Drop the staging directory (ephemeral)
|
|
444
|
+
rm -rf ~/.fixo/staging
|
|
445
|
+
|
|
446
|
+
# Drop the cached vault singleton (next call re-hydrates)
|
|
447
|
+
fixo providers reset-vault
|
|
448
|
+
|
|
449
|
+
# Reset to safe production defaults
|
|
450
|
+
fixo config reset --section safety
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
See [`docs/SAFETY.md`](docs/SAFETY.md) for the full threat
|
|
454
|
+
model, design notes, and operator reference.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## 🖥️ Dashboard
|
|
459
|
+
|
|
460
|
+
The `Dashboard` (`src/ui/render.ts`) is a typed event bus that
|
|
461
|
+
the agent and any number of subscribers (UI, telemetry, tests)
|
|
462
|
+
can wire into without coupling:
|
|
463
|
+
|
|
464
|
+
- `Dashboard` is a pure state holder. It never touches stdout.
|
|
465
|
+
- `DashboardSubscriber` is a one-method interface; errors
|
|
466
|
+
thrown by a subscriber are counted in
|
|
467
|
+
`Dashboard.subscriberErrors` and never propagate.
|
|
468
|
+
- The default `AnsiRenderer` paints a double-buffered surface
|
|
469
|
+
to the terminal and chooses one of three render modes:
|
|
470
|
+
- `off` — non-TTY (CI, captured pipes). Nothing is written.
|
|
471
|
+
- `single-line` — terminals below 80×24.
|
|
472
|
+
- `dashboard` — full multi-line surface.
|
|
473
|
+
|
|
474
|
+
Wiring an additional subscriber is one line:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
import { dashboard, AnsiRenderer } from './ui/render.js';
|
|
478
|
+
const renderer = new AnsiRenderer();
|
|
479
|
+
dashboard.subscribe(renderer);
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
The agent emits `turn-start`, `tool-start`, `tool-finish`,
|
|
483
|
+
`tokens`, `status`, `log`, `mode`, and `done` events. Each
|
|
484
|
+
event is a tagged union so refactors stay type-safe.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 🚀 Getting Started
|
|
489
|
+
|
|
490
|
+
### 1. Prerequisites
|
|
491
|
+
Ensure you have **Node.js (v18+)** and **npm** installed. Fixo CLI connects to FreeLLMAPI, so you should have a running FreeLLMAPI server or access to a unified proxy endpoint.
|
|
492
|
+
|
|
493
|
+
### 2. Installation
|
|
494
|
+
Clone the repository and install dependencies:
|
|
495
|
+
```bash
|
|
496
|
+
git clone https://github.com/Abrar-Akhunji/FIXO_CLI.git
|
|
497
|
+
cd FIXO_CLI
|
|
498
|
+
npm install
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### 3. Build the CLI
|
|
502
|
+
Compile the TypeScript code:
|
|
503
|
+
```bash
|
|
504
|
+
npm run build
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 4. Configuration
|
|
508
|
+
Create a `.env` file at the root of your project:
|
|
509
|
+
```env
|
|
510
|
+
# URL of your FreeLLMAPI instance
|
|
511
|
+
FREELLMAPI_URL=http://localhost:3001
|
|
512
|
+
# Your unified API key (retrieve from FreeLLMAPI Dashboard)
|
|
513
|
+
FREELLMAPI_KEY=your-unified-api-key-here
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 5. Run the CLI
|
|
517
|
+
Start Fixo CLI in dev mode or link it globally:
|
|
518
|
+
```bash
|
|
519
|
+
# Run directly
|
|
520
|
+
npm run dev
|
|
521
|
+
|
|
522
|
+
# Or link globally to run 'fixo' from anywhere
|
|
523
|
+
npm link
|
|
524
|
+
fixo
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## 📄 License
|
|
530
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the FreeLLMAPI proxy server.
|
|
3
|
+
* Supports both regular and streaming (SSE) chat completions.
|
|
4
|
+
* Includes retry with exponential backoff for transient errors.
|
|
5
|
+
*/
|
|
6
|
+
import type { ChatMessage, ChatToolDefinition, ChatToolChoice, TokenUsage } from '../shared/types.js';
|
|
7
|
+
export interface ChatOptions {
|
|
8
|
+
tools?: ChatToolDefinition[];
|
|
9
|
+
tool_choice?: ChatToolChoice;
|
|
10
|
+
temperature?: number;
|
|
11
|
+
max_tokens?: number;
|
|
12
|
+
agent_task_type?: 'chat' | 'review' | 'mutation' | 'test-fix' | 'refactor' | 'investigation';
|
|
13
|
+
required_capabilities?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface ChatResult {
|
|
16
|
+
content: string | null;
|
|
17
|
+
tool_calls: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
type: 'function';
|
|
20
|
+
function: {
|
|
21
|
+
name: string;
|
|
22
|
+
arguments: string;
|
|
23
|
+
};
|
|
24
|
+
}> | null;
|
|
25
|
+
usage: TokenUsage;
|
|
26
|
+
model: string;
|
|
27
|
+
finish_reason: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface StreamChunk {
|
|
30
|
+
type: 'content' | 'thinking' | 'tool_call_start' | 'tool_call_delta' | 'done';
|
|
31
|
+
content?: string;
|
|
32
|
+
thinking?: string;
|
|
33
|
+
tool_call?: {
|
|
34
|
+
index: number;
|
|
35
|
+
id?: string;
|
|
36
|
+
function?: {
|
|
37
|
+
name?: string;
|
|
38
|
+
arguments?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
usage?: TokenUsage;
|
|
42
|
+
model?: string;
|
|
43
|
+
finish_reason?: string | null;
|
|
44
|
+
}
|
|
45
|
+
export declare enum ContentType {
|
|
46
|
+
TEXT = "text",
|
|
47
|
+
THINKING = "thinking"
|
|
48
|
+
}
|
|
49
|
+
export interface ContentChunk {
|
|
50
|
+
type: ContentType;
|
|
51
|
+
content: string;
|
|
52
|
+
}
|
|
53
|
+
export declare class ThinkTagParser {
|
|
54
|
+
private OPEN_TAG;
|
|
55
|
+
private CLOSE_TAG;
|
|
56
|
+
private _buffer;
|
|
57
|
+
private _in_think_tag;
|
|
58
|
+
get in_think_mode(): boolean;
|
|
59
|
+
feed(content: string): Generator<ContentChunk>;
|
|
60
|
+
private _parse_outside_think;
|
|
61
|
+
private _parse_inside_think;
|
|
62
|
+
flush(): ContentChunk | null;
|
|
63
|
+
}
|
|
64
|
+
export declare class HttpError extends Error {
|
|
65
|
+
status: number;
|
|
66
|
+
constructor(status: number, message: string);
|
|
67
|
+
}
|
|
68
|
+
export declare class AgentClient {
|
|
69
|
+
private baseUrl;
|
|
70
|
+
private apiKey;
|
|
71
|
+
private verbose;
|
|
72
|
+
constructor(apiKey: string, apiUrl?: string, verbose?: boolean);
|
|
73
|
+
private resolveDirectConfig;
|
|
74
|
+
/**
|
|
75
|
+
* Maps a model id to the provider that will actually serve the
|
|
76
|
+
* request — used as the key for `providerCooldown` tracking. The
|
|
77
|
+
* `freellmapi` sentinel covers the proxy path; everything else
|
|
78
|
+
* routes through a direct provider.
|
|
79
|
+
*/
|
|
80
|
+
private getProviderId;
|
|
81
|
+
chat(messages: ChatMessage[], model: string, options?: ChatOptions): Promise<ChatResult>;
|
|
82
|
+
private executeSingleChatStreamAttempt;
|
|
83
|
+
chatStream(messages: ChatMessage[], model: string, options?: ChatOptions): AsyncGenerator<StreamChunk>;
|
|
84
|
+
/**
|
|
85
|
+
* Streaming chat with autonomous mid-stream resume.
|
|
86
|
+
*
|
|
87
|
+
* If the underlying `chatStream` throws *after* at least one chunk
|
|
88
|
+
* has been yielded, the resume engine inspects the partial response,
|
|
89
|
+
* appends a "continue from here" payload to the working message list,
|
|
90
|
+
* and starts a fresh streaming attempt. The consumer sees a single
|
|
91
|
+
* continuous `AsyncGenerator<StreamChunk>` — the resume is invisible.
|
|
92
|
+
*
|
|
93
|
+
* The engine respects:
|
|
94
|
+
* - `maxResumeAttempts` (default 3) — additional attempts beyond
|
|
95
|
+
* this throw `StreamResumeExhaustedError`.
|
|
96
|
+
* - `isMidStreamResumable` — user aborts and 4xx are never resumed.
|
|
97
|
+
* - Cuts inside a tool call — the partial text up to the tool call
|
|
98
|
+
* boundary is preserved, but the call itself cannot be resumed.
|
|
99
|
+
*
|
|
100
|
+
* The method is *additive* and does not change the existing
|
|
101
|
+
* `chatStream` contract. Callers opt in by switching to this entry
|
|
102
|
+
* point (see `SingleAgent.streamResponse`).
|
|
103
|
+
*/
|
|
104
|
+
chatStreamWithResume(messages: ChatMessage[], model: string, options?: ChatOptions, maxResumeAttempts?: number): AsyncGenerator<StreamChunk, void, void>;
|
|
105
|
+
getEmbedding(text: string, model?: string): Promise<number[]>;
|
|
106
|
+
ping(): Promise<boolean>;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=agent-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-client.d.ts","sourceRoot":"","sources":["../../src/agent/agent-client.ts"],"names":[],"mappings":"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"}
|