klyro 0.1.14 → 0.1.16

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/READ.md ADDED
@@ -0,0 +1,467 @@
1
+ # Klyro — Complete Build Documentation
2
+
3
+ **For any coding agent:** This file is the single source of truth for what has been built till now (v0.1.15, Levels 1-5 complete, Level 6-8 partial, TUI full-screen). After reading, you have the complete picture.
4
+
5
+ ---
6
+
7
+ ## 1. What is Klyro?
8
+
9
+ **Klyro** is an autonomous AI coding harness — a terminal-native agent that understands repositories, executes tools, verifies its work, and repairs failures.
10
+
11
+ > **Vision:** *Build a highly autonomous software engineering harness capable of understanding complex codebases, executing long-running tasks, dynamically coordinating workflows, verifying its own work, and continuously improving through evaluation.* — `PRD.md:3`
12
+
13
+ **Not a chatbot.** The harness wraps the model: `CLI → Session → Context → Agent Runtime → Model → Tool → Observation → Verification → Repair → Persistence`.
14
+
15
+ **Current version:** `0.1.15` (`package.json:3`, `npm view klyro dist-tags → latest:0.1.15`, `43 files 309/309 tests`)
16
+
17
+ ---
18
+
19
+ ## 2. Tech Stack
20
+
21
+ | Layer | Choice | Why |
22
+ |-------|--------|-----|
23
+ | Language | TypeScript 5.5, Node 20+ | Strict, `NodeNext`, `tsc` → `dist/` |
24
+ | CLI | `commander 12.1` | Stable, `InvalidArgumentError` for `exit 2` |
25
+ | TUI | `ink 7.1` + `react 19` + `ink-spinner 5` | React for terminal, proven by Claude Code/Gemini |
26
+ | Schema | `zod 4.5` | Tool input validation + config schema |
27
+ | Test | `vitest 4.1` `fileParallelism:false` `10s timeout` | `node` env, deterministic mocks |
28
+ | Build | `tsc` (not `tsup`) | `tsc --noEmit` `typecheck`, `tsc` `build` |
29
+ | Providers | Native `fetch` (Node 20) | No SDK lock-in, 3 adapters |
30
+ | Workspace | `pnpm-workspace.yaml` `packages/*` | `shared` `KlyroError` |
31
+ | CI | `.github/workflows/ci.yml` `ubuntu/macos/windows × 20/22` | `pnpm install` `typecheck` `test` `build` `pack` |
32
+
33
+ **No Docker, no MCP, no browser in MVP** — deferred to post-1.0.
34
+
35
+ ---
36
+
37
+ ## 3. Architecture (Full)
38
+
39
+ ```
40
+ USER
41
+
42
+
43
+ CLI / TUI (ink) ←→ Global flags (--cwd/--config/--debug/--json) + completion + update + login
44
+
45
+
46
+ Session Manager (SessionStore + TraceWriter + AuditLog)
47
+
48
+
49
+ Task Analyzer
50
+
51
+
52
+ Context Engine (L6 ProjectMap + L7 Telemetry + L8 Accounting)
53
+
54
+
55
+ Agent Runtime (ProviderAdapter → Policy → Registry → Tool → Observation → Repair)
56
+
57
+ ├─ Model Layer (OpenAI httpChatAdapter / Anthropic anthropicAdapter / retryingAdapter)
58
+ ├─ Tool Registry (18 tools: fs/search/shell/git/verify/plan)
59
+ ├─ Policy Engine (modes default|plan|accept-edits|auto + .env guard)
60
+ ├─ Memory Engine (history 40 turns / 80k chars, pair-preserving)
61
+ └─ Verification Engine (verify + detect + auto + repair loop ≤3)
62
+
63
+
64
+ Repair Loop (verify → classify → diagnosis → repair → re-verify)
65
+
66
+
67
+ Completion Engine (diff + report + status)
68
+
69
+
70
+ Observability ( Ink transcript + status line + trace JSONL)
71
+
72
+
73
+ Evals (harness + fixtures + compare)
74
+ ```
75
+
76
+ **Data Flow (one `klyro run`):**
77
+ ```
78
+ Task → makeRunSystemPrompt() injects L6 (12k) + KLYRO.md (4k) + telemetry
79
+ → CallRequest{system, transcript, tools} → ProviderAdapter.stream()
80
+ → StreamEvent{ text_delta | tool_call_start/delta/end | message_end | error }
81
+ → finalize ToolUseBlock → PolicyEngine.evaluate() → ApprovalPrompt → registry.execute()
82
+ → redactOutput() deep → transcript.push(tool_result) → telemetry.record*()
83
+ → hasEdits? → verify() → detect() → diagnosticForModel() → repair (up to 3)
84
+ → checkpoint (SessionStore + TraceWriter) per step
85
+ → onEvent → stdout/stderr (human) | JSONL | TUI
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 4. 20-Level Plan — Status
91
+
92
+ | Level | Title | Spec `plan.md` | Status | Key Files |
93
+ |-------|-------|----------------|--------|-----------|
94
+ | **1** | Professional CLI Foundation | `1.1` Skeleton `1.2` Parser `1.3` Config `1.4` REPL `1.5` Output | **5/5 PASS** | `src/index.ts:50` `src/cli/config.ts:10` `src/cli/doctor.ts:1` `src/tui/app.tsx:14` |
95
+ | **2** | AI Chat Core | `2.1` Provider `2.2` OpenAI `2.3` Messages `2.4` Streaming `2.5` Cost/headless | **5/5 PASS** | `src/providers/model-info.ts:1` `src/cli/auth.ts:1` `src/context/system-prompt.ts:1` `src/tui/status.tsx:30` |
96
+ | **3** | Tool Runtime | `3.1` Events `3.2` read/write `3.3` shell `3.4` Permissions `3.5` Loop | **5/5 PASS** | `src/events/catalog.ts:1` `src/tools/fs/read-file.ts:39` `src/tools/shell/shell-exec.ts:20` `src/policy/engine.ts:34` `src/agent/runtime.ts:375` |
97
+ | **4** | Repository Coding Agent | `4.1` Edit core `4.2` Fuzzy `4.3` Navigation `4.4` KLYRO.md `4.5` Checkpoints | **5/5 PASS** | `src/tools/fs/edit-file.ts:26` `src/tools/fs/multi-edit.ts:1` `src/context/klyro-md.ts:1` `src/checkpoints/store.ts:1` |
98
+ | **5** | Autonomous Task Loop + Eval | `5.1` Limits `5.2` Stuck `5.3` Plan `5.4` Harness `5.5` Suite | **5/5 PASS** | `src/agent/runtime.ts:152` `phases` `src/tools/plan/todo-write.ts:1` `evals/fixtures/*` `evals/results/baseline.json` |
99
+ | **6** | Verification + Repair | `6.1` Verifiers `6.2` Parsers `6.3` Scoped `6.4` Repair `6.5` Contract | **PARTIAL** (engine exists, but harness needs `check.sh` from 5.5) | `src/verification/engine.ts:28` `auto.ts:10` |
100
+ | **7** | Codebase Intelligence | `7.1` Scanner `7.2` RepoMap `7.3` Search `7.4` Symbols | **PARTIAL** (heuristic, no tree-sitter) | `src/context/project-map.ts:352` `repo-map.ts:33` |
101
+ | **8** | Context Intelligence | `8.1` Accounting `8.2` Lifecycle `8.3` Compaction | **STUB** (tokenizer exists, not wired) | `src/context/tokenizer.ts:35` |
102
+ | **9** | Sessions, Persistence | `9.1` Store `9.2` Continue | **PARTIAL** (JSON store, no SQLite) | `src/persistence/store.ts:52` `session.ts:10` |
103
+ | **10** | Professional Harness | MCP, Hooks, SDK | **NOT STARTED** | — |
104
+ | **11-20** | Multi-Agent → Adaptive Platform | | **NOT STARTED** | — |
105
+
106
+ **Graduation for L1-L5 — PASS** (verified `vitest 309/309`, `klyro --version --help config list doctor -p "hi"`, `20-turn`, `cancel <1s`, `10/10 smoke` `evals/fixtures`).
107
+
108
+ ---
109
+
110
+ ## 5. Detailed Flows (Per Level)
111
+
112
+ ### L1 — CLI
113
+ ```
114
+ klyro --version → readVersion() tries 3 candidates src/index.ts:28 → 0.1.15
115
+ klyro --help → commander tree src/index.ts:50 + showSuggestionAfterError
116
+ klyro → isTTY ? startRepl() : repl() src/index.ts:84 + --tui/--chat + -p headless src/index.ts:60
117
+ klyro config → 5-layer loadMergedConfig() src/cli/config.ts:80 → Zod validate → JSONC stripJsonComments()
118
+ klyro doctor → 7 checks src/cli/doctor.ts:20 (Node/config/Provider/Sessions/git/Tools/Platform)
119
+ ```
120
+
121
+ ### L2 — AI Chat
122
+ ```
123
+ Provider resolution: flags → env KLYRO_PROVIDER → inferProviderFromBaseURL() src/agent/registry.ts:40 (9router→openai alias) → httpChatAdapter / anthropicAdapter
124
+ Streaming: httpChatAdapter.stream() src/agent/provider-adapter.ts:202 → fetch POST /chat/completions stream:true → SSE \n\n → toolIds map + pendingUsage + redacted error
125
+ Retry: retryingAdapter src/agent/retry.ts:46 streamWithAbort() + it.return() + combined signal (req.signal + opts.signal)
126
+ Cost: getModelInfo() src/providers/model-info.ts:1 estimateCost() → StatusLine src/tui/status.tsx:30 $0.043 · ctx 6%
127
+ Headless: klyro -p "hi" argument('[prompt]') src/index.ts:60 → runOnce output json|stream-json
128
+ ```
129
+
130
+ ### L3 — Tool Runtime
131
+ ```
132
+ KlyroEvent bus src/events/bus.ts:1 globalBus.emit() + TraceWriter src/trace/writer.ts:1 JSONL fsync on tool.result
133
+ Tool<TInput,TOutput> src/tools/types.ts:1 {name, description, inputSchema, permission, isConcurrencySafe, renderCall}
134
+ read_file: 2000 window src/tools/fs/read-file.ts:70, 10MB refusal src/tools/fs/read-file.ts:49, binary null-byte src/tools/fs/read-file.ts:60, 8k tokens hint
135
+ write_file: wasRead guard src/tools/fs/read-history.ts:1, diff, 0600, atomic tmp+fsync
136
+ shell: 120s max 600s src/tools/shell/shell-exec.ts:20, persistentCwd, filteredEnv(), 30k head+tail → ~/.klyro/tool-output/<id>.txt, tree-kill taskkill/SIGKILL, interactive block
137
+ Policy: mode default|plan|accept-edits|auto src/policy/engine.ts:34, glob allow/deny/ask matchesGlobRule() src/policy/engine.ts:120, .env deny src/policy/engine.ts:99, sandbox --add-dir
138
+ Loop: stream→collect → no tools? return → policy → parallel if all isConcurrencySafe src/agent/runtime.ts:386 → Promise.all else sequential
139
+ ```
140
+
141
+ ### L4 — Repository Coding
142
+ ```
143
+ edit_file: EOL CRLF/LF src/tools/fs/edit-file.ts:40, BOM src/tools/fs/edit-file.ts:40, trailing newline, staleness mtime+hash src/tools/fs/edit-file.ts:26, actionable findClosestMatch() src/tools/fs/edit-file.ts:70
144
+ Fuzzy tiers after exact fails: trailing-whitespace → indent → unicode quotes → line-window 0.95 src/tools/fs/edit-file.ts:40
145
+ multi_edit atomic src/tools/fs/multi-edit.ts:10, apply_patch Codex tolerant src/tools/fs/apply-patch.ts:1
146
+ Navigation: list_dir/glob/grep respect .klyroignore, git_log/status/diff src/tools/git/git-log.ts:1, background shell src/tools/shell/background.ts:1 1MB ring
147
+ KLYRO.md: loadKlyroMd() ~/.klyro/KLYRO.md → KLYRO.local.md → subdir lazy src/context/klyro-md.ts:1 @import depth5
148
+ Checkpoints: snapshot() src/checkpoints/store.ts:13 .klyro/checkpoints/<id> → diff vs HEAD → undo/rewind
149
+ ```
150
+
151
+ ### L5 — Autonomous Loop + Eval
152
+ ```
153
+ Loop controller: maxSteps alias maxTurns src/agent/runtime.ts:152, maxCost/maxTimeMs src/agent/runtime.ts:180 → status:limit phase:limit exit 7
154
+ Phases: understanding→exploring→planning→implementing→verifying setPhase() src/agent/runtime.ts:180 phase.changed
155
+ Stuck: identical call×3 src/agent/runtime.ts:180, same file >8× fileEditCounts, ≥5 fails → [system note]
156
+ Planning: todo_write src/tools/plan/todo-write.ts:1 → .klyro/plans/todos.json, plan mode blocks writes, ask_user src/tools/plan/ask-user.ts:1 HEADLESS → KLYRO_AUTO_ANSWER
157
+ Eval: FileFixture {dir, task.md, check.sh, meta.json} loadFileFixture() src/eval/harness.ts:130, runFileFixture() tmp cp + bash -c check.sh, harness 10 fixtures evals/fixtures/*, baseline evals/results/baseline.json 8/10, compareReports() src/eval/harness.ts:180, klyro eval --suite smoke src/cli/eval.ts:84
158
+ ```
159
+
160
+ ---
161
+
162
+ ## 6. File Structure (Purpose)
163
+
164
+ ```
165
+ klyro/
166
+ ├── package.json # klyro 0.1.15, bin klyro/ky, files [dist], commander/ink/zod
167
+ ├── pnpm-workspace.yaml # packages/*
168
+ ├── tsconfig.json # ES2022, NodeNext, strict, noUncheckedIndexedAccess
169
+ ├── vitest.config.ts # include src/**/*.test, fileParallelism:false
170
+ ├── .github/workflows/ci.yml # ubuntu/macos/windows × 20/22 → typecheck/test/build/eval smoke
171
+ ├── src/
172
+ │ ├── index.ts # commander entry: tui/run/chat/eval/session/resume + global flags --cwd/--config/-p
173
+ │ ├── chat.ts # one-shot chat, normalizeBaseURL, assertSafeBaseURL (loopback+private+ALLOW_INSECURE), streamToStdout \n\n, writeWithBackpressure, readBoundedText
174
+ │ ├── repl.ts # legacy readline REPL, history 40/80k, trimHistory pair-preserving
175
+ │ ├── providers.ts # resolveProvider() probe Ollama 11434 etc 600ms, providerHelp()
176
+ │ ├── agent/
177
+ │ │ ├── runtime.ts # run() loop: CallRequest → ProviderAdapter → policy → registry → checkpoint → verify→repair (≤3) → trace
178
+ │ │ ├── message.ts # Message{role, content: TextBlock|ToolUseBlock|ToolResultBlock}
179
+ │ │ ├── provider-adapter.ts # httpChatAdapter, buildChatCompletionsBody, streamChatCompletions \n\n + pendingUsage + redact + isConcurrencySafe
180
+ │ │ ├── anthropic-adapter.ts # anthropicAdapter, x-api-key, indexToToolId map, assertSafeBaseURL
181
+ │ │ ├── registry.ts # buildProvider(), inferProviderFromBaseURL() 9router→openai, isLoopback, retryingAdapter
182
+ │ │ ├── retry.ts # retryingAdapter, computeBackoff, streamWithAbort it.return()
183
+ │ │ ├── worker-spawner.ts # stub for future sub-agents
184
+ │ │ └── observation.ts # ObservationStore (placeholder)
185
+ │ ├── tools/
186
+ │ │ ├── types.ts # Tool<TIn,TOut> + ToolContext{sessionId,permissions,logger,emit}
187
+ │ │ ├── registry.ts # ToolRegistry 18 tools: fs/search/shell/git/verify/plan
188
+ │ │ ├── schema.ts # zodToJsonSchema (Zod3/4)
189
+ │ │ ├── normalize.ts # safe(), TOOL_ERROR_CODES
190
+ │ │ ├── fs/read-file.ts # 2000 window, 10MB, binary, 8k hint, wasRead
191
+ │ │ ├── fs/write-file.ts # 0600 tmp+fsync, diff, needsApproval
192
+ │ │ ├── fs/edit-file.ts # EOL/BOM/trailing, staleness, findClosestMatch, fuzzy tiers
193
+ │ │ ├── fs/multi-edit.ts # atomic sequential
194
+ │ │ ├── fs/apply-patch.ts # Codex tolerant
195
+ │ │ ├── fs/read-history.ts # wasRead Set
196
+ │ │ ├── fs/list-dir.ts # depth, .klyroignore
197
+ │ │ ├── search/glob.ts # globToRegex **, SKIP_DIRS
198
+ │ │ ├── search/grep.ts # RegExp g + lastIndex, binary skip, 500 cap, ReDoS risk noted
199
+ │ │ ├── search/search-files.ts# fzf query, firstParty+recency ranking
200
+ │ │ ├── search/recent-files.ts# mtime >= since, .map skip
201
+ │ │ ├── search/dependencies.ts# npm/py/go/rust/Cargo parsers
202
+ │ │ ├── shell/shell-exec.ts # 120s, persistentCwd, filteredEnv, 30k head+tail, tool-output file, DANGEROUS_PATTERNS + $HOME
203
+ │ │ ├── shell/background.ts # 1MB ring, startBackground/getOutput/killJob
204
+ │ │ ├── git/git-status.ts # porcelain + branch + log
205
+ │ │ ├── git/git-log.ts # read-only
206
+ │ │ └── verify/run-verify.ts # 5m, 256k cap
207
+ │ ├── context/
208
+ │ │ ├── repo-map.ts # regex symbols ts/js/py/go/rs, 500 files
209
+ │ │ ├── project-map.ts # language/framework/PM/test detection
210
+ │ │ ├── level6.ts # buildLevel6Context 12k = project+repo(40)+recent(15)+deps
211
+ │ │ ├── level7.ts # RuntimeTelemetry step/tools/errors/tokens
212
+ │ │ ├── tokenizer.ts # estimateTokens chars/4, compressTranscript deep-copy
213
+ │ │ ├── selector.ts # selectFiles (dead, not wired)
214
+ │ │ ├── snippets.ts # readSnippet windowed
215
+ │ │ └── system-prompt.ts # buildSystemPrompt layered identity→env→global
216
+ │ ├── policy/
217
+ │ │ ├── engine.ts # PolicyEngine 4 rules + mode/ glob/.env/add-dir
218
+ │ │ ├── approval.ts # StdinApprovalPrompt/DenyAll/InMemoryAllowlist + TuiApprovalBridge
219
+ │ │ ├── path-guard.ts # resolveWithinCwd + resolveAndFollowSymlinks (TOCTOU noted)
220
+ │ │ └── secret-redactor.ts # redact() 7 patterns + createRedactor 1KiB tail
221
+ │ ├── persistence/
222
+ │ │ ├── store.ts # SessionStore JSON per session + sessions.json index (atomic tmp→fsync→rename, retry)
223
+ │ │ ├── session.ts # getDefaultSessionsDir ~/.klyro/sessions, formatSession, resolveSessionId prefix
224
+ │ │ └── audit.ts # AuditLog JSONL
225
+ │ ├── verification/
226
+ │ │ ├── engine.ts # verify() spawn shell, timeout SIGKILL, detect(), diagnosticForModel()
227
+ │ │ ├── detect.ts # heuristic type/test/lint/build/runtime + summarize()
228
+ │ │ └── auto.ts # detectVerifyCommand() npm test / tsc / make
229
+ │ ├── checkpoints/
230
+ │ │ └── store.ts # snapshot/.klyro/checkpoints/<id> diff/undo/rewind
231
+ │ ├── events/
232
+ │ │ ├── catalog.ts # KlyroEvent 15 types
233
+ │ │ └── bus.ts # EventBus + globalBus
234
+ │ ├── trace/
235
+ │ │ └── writer.ts # TraceWriter JSONL appendFile + fsync on tool.result
236
+ │ ├── renderers/
237
+ │ │ ├── terminal.ts # TerminalRenderer
238
+ │ │ └── json.ts # JsonRenderer
239
+ │ ├── tui/
240
+ │ │ ├── app.tsx # Ink full-screen: Static history + live region batched 30fps + single useInput
241
+ │ │ ├── tokens.ts # accent #8B7CF6, glyphs › ● ⎿ ✔, spacing
242
+ │ │ ├── banner.tsx # 5.1 Banner + resume
243
+ │ │ ├── input-box.tsx # 5.2 InputBox queued
244
+ │ │ ├── thinking-block.tsx # 5.5 Thinking
245
+ │ │ ├── activity-line.tsx # 5.6 Spinner + verb·elapsed
246
+ │ │ ├── transcript.tsx # 11 kinds: text/tool/policy/error/file_changed/diff
247
+ │ │ ├── status.tsx # model step/max repairs tokens cost ctx%
248
+ │ │ ├── header.tsx # abbrevPath
249
+ │ │ ├── diff.tsx # DiffView per-file hunk
250
+ │ │ ├── approval.tsx # TuiApprovalBridge y/a/A/n/e/?
251
+ │ │ ├── plan.tsx # PlanView glyphs ◯●✓✗⊘
252
+ │ │ └── diff-parser.ts # parseUnifiedDiff
253
+ │ ├── cli/
254
+ │ │ ├── repl.ts # startRepl() Ink TUI: resolveProvider probe, httpChat/anthropic, queued bridge, verify repair loop
255
+ │ │ ├── run.ts # runOnce() one-shot: verify/persist, session create/resume, headless -p
256
+ │ │ ├── eval.ts # runEval() JSONL suite + --suite smoke file fixtures
257
+ │ │ ├── config.ts # 5-layer loadMergedConfig, Zod, JSONC stripJsonComments, get|set|list|path
258
+ │ │ ├── doctor.ts # 7 checks Node/config/Provider/Sessions/git/Tools/Platform
259
+ │ │ ├── completion.ts # bash|zsh|fish|powershell
260
+ │ │ ├── update.ts # 24h cache, KLYRO_NO_UPDATE_CHECK
261
+ │ │ ├── auth.ts # login 0600 credentials.json, MODEL_ALIASES sonnet→claude
262
+ │ │ ├── markdown.ts # renderMarkdown incremental, stripMarkdown
263
+ │ │ ├── errors.ts # handleFatal ✖/hint, setupGlobalHandlers
264
+ │ │ └── slash/parser.ts # /clear/compact/model/diff/plan/status/quit/help/config/doctor/cost/thinking
265
+ │ ├── shared/
266
+ │ │ ├── errors.ts # KlyroError{code,exitCode}
267
+ │ │ └── types.ts # Role, Message, ExitCode
268
+ │ └── util/log.ts # pino JSON ~/.klyro/logs/klyro-YYYY-MM-DD.log 14d rotation redact
269
+ ├── packages/shared # workspace: @klyro/shared (re-export)
270
+ ├── evals/
271
+ │ ├── fixtures/ # 10 smoke fixtures: read-answer, add-fn-test, fix-failing-test…
272
+ │ └── results/baseline.json # 8/10 80% + markdown compareReports()
273
+ ├── .klyro/ # runtime: sessions.json, traces/*.jsonl, checkpoints/<id>, logs/, history, memory/
274
+ └── dist/ # tsc output, bin klyro
275
+ ```
276
+
277
+ ---
278
+
279
+ ## 7. CLI Reference
280
+
281
+ ```
282
+ klyro # TUI REPL (TTY) or legacy pipe REPL
283
+ klyro --tui / --no-tui # force TUI / legacy
284
+ klyro -p "prompt" # headless one-shot (stdin piped + --output-format json)
285
+ klyro tui [-m model] [--max-steps n]
286
+ klyro run <prompt> [-m model] [--max-steps 30] [--max-cost $] [--max-time ms]
287
+ [--verify/--no-verify --verify-command <cmd> --max-repairs 3]
288
+ [--persist/--no-persist --resume-session <id>] [--output human|json|silent]
289
+ klyro chat [prompt] [-s system] # legacy streamed chat
290
+ klyro eval <input.jsonl> | --suite smoke [--filter str] [--runs 1] [--parallel 1] [--output json]
291
+ klyro eval:compare <a.json> <b.json>
292
+ klyro config [list|get <key>|set <key> <value>|unset <key>|path|edit]
293
+ klyro doctor [--json]
294
+ klyro completion <bash|zsh|fish|powershell>
295
+ klyro update
296
+ klyro login / logout [provider] # 0600 credentials.json, MODEL_ALIASES
297
+ klyro session list|show <id>|resume <id> [--json]
298
+ klyro resume <id> # alias
299
+ klyro trace <id> [--stats --json]
300
+ klyro --version / --help
301
+ ```
302
+
303
+ Global flags (preAction `src/index.ts:60`): `--cwd <path>` `--config <path>` `--debug` `--verbose` `--quiet` `--json` `--yes` `--no-color` (respects `NO_COLOR/FORCE_COLOR`)
304
+
305
+ ---
306
+
307
+ ## 8. Provider System
308
+
309
+ - **Resolution:** `flags` → `KLYRO_PROVIDER` (aliases `9router/openrouter/groq→openai`) → `inferProviderFromBaseURL()` `anthropic.com` exact → `openai` default `src/agent/registry.ts:30`
310
+ - **Adapters:** `httpChatAdapter` `src/agent/provider-adapter.ts:190` (`/chat/completions` SSE `\n\n`, `toolIds` map + `pendingUsage`, `redact` error) + `anthropicAdapter` `src/agent/anthropic-adapter.ts:76` (`/v1/messages` `x-api-key`, `indexToToolId` `Map<number,string>` `src/agent/anthropic-adapter.ts:169` fixes `findToolIdByIndex` interleaving) + `retryingAdapter` `src/agent/retry.ts:46` `computeBackoff` `base*2^attempt ±25%` `3 attempts 500ms/8s`, `streamWithAbort` `it.return()` `src/agent/retry.ts:46`
311
+ - **Security:** `assertSafeBaseURL()` `src/chat.ts:38` `https:` OK, `http:` only `localhost/127.*` `10/8` `192.168/16` `172.16/12` or `KLYRO_ALLOW_INSECURE=1` with warning.
312
+
313
+ ---
314
+
315
+ ## 9. Tool System (18 tools)
316
+
317
+ `ToolRegistry` `src/tools/registry.ts:81` `register/get/list/toOpenAITools/execute` (`safeParse` → `INVALID_INPUT`).
318
+
319
+ | Tool | Input | Output | Notes |
320
+ |------|-------|--------|-------|
321
+ | `read_file` | `path, startLine?, endLine?, maxBytes?` | `lines, totalLines, bytesRead, truncated` | `cat -n` 2000 window, 10MB `src/tools/fs/read-file.ts:49`, binary `null-byte` `src/tools/fs/read-file.ts:60`, `wasRead` `src/tools/fs/read-history.ts:1` |
322
+ | `write_file` | `path, content` | `bytesWritten, diff` | `0600` tmp `crypto.randomBytes` + `fsync` + `rename` `src/tools/fs/write-file.ts:36`, `wasRead` guard `>200` lines `needsApproval` |
323
+ | `edit_file` | `path, find, replace, replaceAll?` | `replacements, diff` | `EOL/BOM/trailing` `src/tools/fs/edit-file.ts:40`, staleness `mtime+hash` `src/tools/fs/edit-file.ts:26`, `findClosestMatch` `src/tools/fs/edit-file.ts:70`, fuzzy 4 tiers `src/tools/fs/edit-file.ts:40` |
324
+ | `multi_edit` | `path, edits[{find,replace}]` | `edits, diff` | atomic `src/tools/fs/multi-edit.ts:10` |
325
+ | `apply_patch` | `patch` | `patchedFiles` | Codex tolerant `src/tools/fs/apply-patch.ts:1` |
326
+ | `list_directory` | `path, maxDepth?` | `entries` | skips `DEFAULT_SKIP` + dotfiles `src/tools/fs/list-dir.ts:67`, depth `maxDepth` |
327
+ | `glob` | `pattern, cwd?` | `paths` | `globToRegex` `**/` `src/tools/search/glob.ts:50` |
328
+ | `grep` | `pattern, path?, include?` | `matches` | `RegExp g` `lastIndex=0` `src/tools/search/grep.ts:49` `500` cap, binary skip |
329
+ | `search_files` | `query` | `paths` | ranking `exact basename 200` `src/tools/search/search-files.ts:1` |
330
+ | `recent_files` | `sinceHours?, glob?` | `paths` | `mtime >= sinceMs` `src/tools/search/recent-files.ts:1` |
331
+ | `dependencies` | `manager?` | `deps` | `npm/py/go/rust` parsers `src/tools/search/dependencies.ts:1` |
332
+ | `shell_exec` | `command, cwd?, timeoutMs?, env?` | `exitCode, stdout, truncated` | `120s max 600s` `src/tools/shell/shell-exec.ts:20` `persistentCwd` `filteredEnv()` `30k head+tail` `~/.klyro/tool-output/<id>.txt` `taskkill /F /T` `src/tools/shell/shell-exec.ts:182`, `DANGEROUS_PATTERNS` `rm -rf` `curl|sh` `src/tools/shell/shell-exec.ts:30` |
333
+ | `git_status` | — | `porcelain, branch, log` | `spawn git` `shell:false` `15s` `src/tools/git/git-status.ts:1` |
334
+ | `git_diff` | `cached?` | `diff, stat, patchedFiles` | `diff --git` regex `src/tools/git/git-diff.ts:61` |
335
+ | `git_log` | `limit?, path?` | `log` | read-only `src/tools/git/git-log.ts:1` |
336
+ | `run_verify` | `command` | `ok, exitCode` | `5m 256k` `src/tools/verify/run-verify.ts:1` `taskkill` |
337
+ | `todo_write` | `todos[{id,title,status}]` | `updated` | `src/tools/plan/todo-write.ts:1` `.klyro/plans/todos.json` |
338
+ | `ask_user` | `question, options?` | `answer` | `src/tools/plan/ask-user.ts:1` `HEADLESS` `KLYRO_AUTO_ANSWER` |
339
+
340
+ ---
341
+
342
+ ## 10. Context Engine
343
+
344
+ - **L6 static:** `buildLevel6Context()` `src/context/level6.ts:131` `12k` = `projectMap` `src/context/project-map.ts:352` (`language/framework/PM` `statSync`) + `repoMap` `src/context/repo-map.ts:33` `regex symbols` 500 files + `recentFiles` `src/**` 15 + `deps` `package.json` → `truncated at last \n`
345
+ - **L7 live:** `RuntimeTelemetry` `src/context/level7.ts:60` `step/maxSteps/toolCallCount/inputTokens` `record*()` `format()` `1200` chars `emptyTelemetryBlock()` `summarize()` — injected via `systemPrompt(cwd,telemetry)` `src/agent/runtime.ts:240` each step
346
+ - **Tokenizer:** `estimateTokens(s)=ceil(len/4)` `src/context/tokenizer.ts:35` `compressTranscript()` `src/context/tokenizer.ts:76` deep-copy (was shallow mutate) + `consumed` set + `tool_result` 400 chars + `drop oldest` — **not wired into runtime** (dead code, transcript unbounded)
347
+ - **Selector:** `selectFiles()` `src/context/selector.ts:22` `+100 exact path` `+20 basename` — **not wired**
348
+
349
+ ---
350
+
351
+ ## 11. Verification & Repair (L6/L8)
352
+
353
+ - **auto:** `detectVerifyCommand(cwd)` `src/verification/auto.ts:10` `package.json test`→`npm test` / `tsconfig.json`→`tsc` / `Makefile` / `pytest` / `go test`
354
+ - **engine:** `verify({cwd,command,timeoutMs=5m})` `src/verification/engine.ts:28` `spawn shell true` `stdout+stderr` `timeout kill` `detect()` `diagnosticForModel()` `src/verification/engine.ts:67`
355
+ - **detect:** `detect(stdout,stderr,exitCode)` `src/verification/detect.ts:36` `type` `type|test|lint|build|runtime|unknown` `TS_LINE` `TEST_FAIL` `LINT_LINE` `RUNTIME_LINE` + `summarize()` 8 files
356
+ - **Runtime wiring:** `hasEdits` `src/agent/runtime.ts:175` `write_file/edit_file` → after `no tools` `finalText` → `verifyEnabled && hasEdits && verifyCmd` `src/agent/runtime.ts:331` → `verify()` → `verification_succeeded` → `complete` else `diagnosticForModel()` → `transcript.push(user repair)` `src/agent/runtime.ts:357` → `continue` ≤3 `maxRepairs` `src/agent/runtime.ts:330` → `verify_failed` `src/agent/runtime.ts:366` + `repair_started` event
357
+
358
+ ---
359
+
360
+ ## 12. Persistence (L9)
361
+
362
+ - **Store:** `SessionStore` `src/persistence/store.ts:52` `dir .sessions` `sessions.json` index + `{id}.json` `{record,messages,observations}` `randomUUID` `ensureDir` `readIndex` `writeIndex` `tmp→fsync→rename` `src/persistence/store.ts:74` (was non-atomic) + 3-attempt `writeSession` `src/persistence/store.ts:100`
363
+ - **Session:** `SessionRecord{id,cwd,task,status,createdAt,updatedAt,config,finalText}` `SessionStatus open|complete|verify_failed|aborted|max_steps` `src/persistence/store.ts:17` + `getDefaultSessionsDir()` `~/.klyro/sessions` `src/persistence/session.ts:10` `formatSession()` `resolveSessionId(prefix)` `src/persistence/session.ts:30`
364
+ - **Audit:** `AuditLog` `src/persistence/audit.ts:23` `AuditEvent` 11 kinds `JSONL appendFile`
365
+ - **Trace:** `TraceWriter` `src/trace/writer.ts:1` `JSONL .klyro/traces/<id>.jsonl` `appendFile + open/sync/close` on `tool.result` (was persistent `FileHandle` leak `src/trace/writer.ts:20` fixed)
366
+ - **Checkpoints:** `src/checkpoints/store.ts:1` `snapshot(cwd,files)` `.klyro/checkpoints/<id>` `listCheckpoints` `diff` `git diff --stat` `undo` `rewind` — **not wired to runtime snapshots** (runtime uses `SessionStore` checkpoint, not `checkpoints/store.ts`)
367
+
368
+ ---
369
+
370
+ ## 13. TUI (Professional, Full-Screen)
371
+
372
+ - **Stack:** `ink 7.1` `react 19` `ink-spinner 5` `react/jsx` `target ES2022` `module NodeNext`
373
+ - **Tokens** `src/tui/tokens.ts:1` `accent #8B7CF6/magenta` `muted #7A7A7A` `success #4ADE80` `error #F87171` `glyphs › ● ⎿ ✔ ✘ ⚠` + ASCII fallback `isAsciiMode()`
374
+ - **App** `src/tui/app.tsx:14` inline/scrollback per `TUI_DESIGN.md` `Static` history `src/tui/app.tsx:325` + batched `30fps` `src/tui/app.tsx:117` + single `useInput` `src/tui/app.tsx:185`
375
+ - `Banner` `src/tui/banner.tsx:1` `◆ Klyro v0.1.11` `cwd (branch ✎3)` `KLYRO.md` + resume `↻`
376
+ - `InputBox` `src/tui/input-box.tsx:1` `accent` `queued:` `shell` `!` `note` `#`
377
+ - `ThinkingBlock` `src/tui/thinking-block.tsx:1` `∴ Thinking… ctrl+t`
378
+ - `ActivityLine` `src/tui/activity-line.tsx:1` `✻ verb (4s · ↑1.2k)` + `CompactionDivider` `⟲`
379
+ - `Transcript` `src/tui/transcript.tsx:37` 6 kinds `ToolCard` `round` `cyan running` `green ✓`
380
+ - `StatusLine` `src/tui/status.tsx:23` `model step/max repairs tokens $0.043 ctx 6%`
381
+ - `Header` `src/tui/header.tsx:31` `abbrevPath`
382
+ - `DiffView` `src/tui/diff.tsx:57` `DiffHunk` `+ green` `- red` `context gray`
383
+ - `ApprovalModal` `src/tui/approval.tsx:60` `[y] once [a] session [A] always [n] [e] [?]` `TuiApprovalBridge` `src/tui/approval.tsx:31` promise `PendingPrompt`
384
+ - `PlanView` `src/tui/plan.tsx:44` `◯●✓✗⊘` `expanded`
385
+ - `DiffParser` `src/tui/diff-parser.ts:20` `parseUnifiedDiff`
386
+ - **Full-screen alt:** `src/tui/app.fullscreen.tsx:1` `width={width} height={height-1}` `Header` `KLYRO v0.1.9` `Conversation` `flexGrow` `Input` `StatusBar` (kept as `app.inline.tsx` backup)
387
+ - **History:** `~/.klyro/history` JSONL per project `src/tui/app.tsx:50` `loadHistory/appendHistory` `↑/↓` `Ctrl+R`
388
+
389
+ ---
390
+
391
+ ## 14. Policy & Safety
392
+
393
+ - **PathGuard** `src/policy/path-guard.ts:46` `resolveWithinCwd` `path.relative` + drive `PathGuardError`, `resolveAndFollowSymlinks` `realpath` `realParent` parent-symlink defense `src/policy/path-guard.ts:106`
394
+ - **Engine** `src/policy/engine.ts:84` `Decision allow|ask|deny` `PolicyConfig mode default|plan|accept-edits|auto + allow/deny/ask glob` `src/policy/engine.ts:34` `matchesGlobRule()` `src/policy/engine.ts:120` `.env` deny `src/policy/engine.ts:99` `additionalDirs` `src/policy/engine.ts:176` `shellDeny` `curl|sh` `src/policy/engine.ts:141`
395
+ - **Approval** `StdinApprovalPrompt` `DenyAll` `InMemoryAllowlist` `src/policy/approval.ts:21` + `TuiApprovalBridge`
396
+ - **Redactor** `src/policy/secret-redactor.ts:14` 7 patterns `aws-key` `aws-secret+b64` `pem-block` `github-token` `slack-token` `bearer` `jwt` `redact()` `createRedactor()` 1KiB tail
397
+
398
+ ---
399
+
400
+ ## 15. Eval Harness
401
+
402
+ - **Harness** `src/eval/harness.ts:70` `runTask()` `tmp klyro-eval-<id>` `ToolRegistry 9 tools` `verify()` + `runHarness()` `formatReport()` `compareReports()` `src/eval/harness.ts:180`
403
+ - **Tasks** `src/eval/tasks.ts:1` `MVP_TASKS 5` `t1 direct-answer` `t2 write-then-answer` `t3 policy-deny` `t4 max_steps` `t6 multitool`
404
+ - **File fixtures** `evals/fixtures/*` 10 smoke `read-answer` `add-fn-test` `fix-failing-test` … `task.md` `check.sh` `meta.json` `src/eval/harness.ts:130` `loadFileFixture` `runFileFixture` `bash -c`
405
+ - **Results** `evals/results/baseline.json` `8/10 80%` `src/eval/harness.ts:130`, `compareReports` `src/eval/harness.ts:180`, `klyro eval --suite smoke` `src/cli/eval.ts:84`
406
+
407
+ ---
408
+
409
+ ## 16. CLI Surface
410
+
411
+ ```
412
+ klyro # TUI REPL (isTTY ? TUI : legacy pipe)
413
+ klyro --tui / --no-tui / --chat # force
414
+ klyro -p "prompt" # headless positional (2.5)
415
+ klyro tui [-m model] [--max-steps n]
416
+ klyro run <prompt> [-m model] [--max-steps 30] [--max-cost $] [--max-time ms]
417
+ [--verify/--no-verify --verify-command <cmd> --max-repairs 3]
418
+ [--persist/--no-persist --resume-session <id>] [--output human|json|silent]
419
+ [--provider openai|anthropic] [--dry-run] [--resume <file>]
420
+ klyro chat [prompt] [-s system] # legacy
421
+ klyro eval [input] [--suite smoke --filter str --runs 1 --parallel 1 --output json]
422
+ klyro eval:compare <a> <b>
423
+ klyro config [list|get <key>|set <key> <value>|unset <key>|path|edit] # 5-layer Zod JSONC
424
+ klyro doctor [--json] # 7 checks
425
+ klyro completion <bash|zsh|fish|powershell>
426
+ klyro update # 24h cache
427
+ klyro login / logout [provider] # 0600 credentials.json, MODEL_ALIASES
428
+ klyro session list|show <id>|resume <id> [--json]
429
+ klyro resume <id> # alias
430
+ klyro trace <id> [--stats --json]
431
+ klyro --version / --help # global --cwd/--config/--debug/--json/--yes/--no-color
432
+ ```
433
+
434
+ Global flags `src/index.ts:60` `--cwd/--config/--debug/--verbose/--quiet/--json/--yes/--no-color` via `preAction` hook, `showSuggestionAfterError`.
435
+
436
+ ---
437
+
438
+ ## 17. Testing
439
+
440
+ - **Config:** `vitest.config.ts:1` `include src/**/*.test` `environment node` `fileParallelism:false` `testTimeout 10_000`
441
+ - **Results:** `43 files 309/309` (was 36/288, +7 P0 L4)
442
+ - **Coverage:** No `--coverage` threshold; L4 new tests: `edit-file.test.ts:1` 10 tests (CRLF/BOM/trailing), `multi-edit` 2, `apply-patch` 2, `background` 2, `klyro-md` 2, `checkpoints` 1, `git` 2
443
+ - **Security gate:** `src/security.test.ts:1` 16 tests (cwd jail, policy, redaction, hostile)
444
+ - **Flaky:** `fileParallelism:false` hides concurrency bugs (`plan.md:199` parallel tools)
445
+
446
+ ---
447
+
448
+ ## 18. Known Gaps (Post-MVP)
449
+
450
+ - `compressTranscript()` `src/context/tokenizer.ts:76` deep-copy fixed but **not wired** into `runtime` (transcript unbounded)
451
+ - `verify` unbounded `stdout` `src/verification/engine.ts:31` no cap (vs `run_verify` 256k) — repair OOM
452
+ - `AuditLog` `src/persistence/audit.ts:23` defined but never written (dead code)
453
+ - `L6 repo-map` heuristic, no `tree-sitter`/`LSP` (plan L7)
454
+ - `L8+L9` wiring: `compressTranscript` + `checkpoints/store.ts` not used by `SessionStore`
455
+
456
+ ---
457
+
458
+ ## 19. For a New Agent — Where to Start
459
+
460
+ 1. **Runtime is king:** `src/agent/runtime.ts:150` `run()` — get this loop right, everything else is leaf.
461
+ 2. **Provider contract:** `src/agent/provider-adapter.ts:42` `ProviderAdapter` + `src/tools/schema.ts:63` Zod→JSON — defines `multi-model`.
462
+ 3. **Edit tool:** `src/tools/fs/edit-file.ts:26` — most used, diff-return shapes model reasoning.
463
+ 4. **Verification:** `src/verification/engine.ts:28` + `detect.ts:36` — the “more reliable than prompting” claim.
464
+ 5. **Config:** `src/cli/config.ts:10` Zod-derived types constrain every module.
465
+
466
+ Run: `pnpm build && pnpm test && node dist/index.js --help` → `klyro -p "fix login test" --output json` → `klyro doctor`.
467
+
@@ -264,7 +264,7 @@ export async function run(opts, deps) {
264
264
  }
265
265
  if (finalizedCalls.length === 0) {
266
266
  finalText = textBuf;
267
- // Level 8 — Verification + Autonomous Repair
267
+ // Level 8 — Verification + Autonomous Repair (gated on hasEdits below — pure analysis skips verify)
268
268
  const verifyEnabled = opts.verify?.enabled !== false;
269
269
  const verifyCmd = opts.verify?.command ?? detectVerifyCommand(opts.cwd);
270
270
  const maxRepairs = opts.verify?.maxRepairAttempts ?? 3;
package/dist/cli/repl.js CHANGED
@@ -330,17 +330,10 @@ export async function startRepl(opts = {}) {
330
330
  queuedStatus({ status: 'done', repairs: result.repairs ?? 0 });
331
331
  }
332
332
  else {
333
- // For simple chat, no_final with empty text is often a provider quirk (e.g. gemini via openai compat)
334
- // Show a helpful message but don't mark as error in header for chat
335
- const isSimpleChat = taskText.trim().split(/\s+/).length <= 6;
336
- if (isSimpleChat) {
337
- queuedStatus({ status: 'done', repairs: result.repairs ?? 0 });
338
- queuedAppend({ id: `no_final-${Date.now()}`, kind: 'text', text: `(no response — try /model ${model} or check provider logs)`, role: 'assistant' });
339
- }
340
- else {
341
- queuedStatus({ status: 'error', errorMessage: 'no final text' });
342
- queuedAppend({ id: `no_final-${Date.now()}`, kind: 'error', message: 'Provider returned no final text — check model/provider (try /model or /doctor)' });
343
- }
333
+ // Genuine provider error: stream ended without a final answer. Surface as a loud
334
+ // error header with /doctor guidance rather than hiding it behind a polite hint.
335
+ queuedStatus({ status: 'error', errorMessage: 'no final text' });
336
+ queuedAppend({ id: `no_final-${Date.now()}`, kind: 'error', message: 'Provider returned no final text — check model/provider (try /model or /doctor)' });
344
337
  }
345
338
  }
346
339
  else {
package/dist/tui/app.js CHANGED
@@ -160,5 +160,5 @@ export function App(props) {
160
160
  const width = stdout?.columns ?? 100;
161
161
  const height = stdout?.rows ?? 30;
162
162
  const isSmall = width < 80;
163
- return (_jsxs(Box, { flexDirection: "column", width: width, height: height - 1, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: "KLYRO v0.1.14" }), _jsxs(Text, { color: tokens.ansi.muted, children: [status.model, " \u00B7 API Usage Billing \u00B7 step ", status.step, "/", status.maxSteps, " \u00B7 ", status.status, " \u00B7 repairs ", status.repairs] }), _jsx(Text, { color: tokens.ansi.muted, children: props.cwd })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingX: 1, paddingY: 1, children: [transcript.length === 0 ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: tokens.ansi.muted, children: "No conversation yet. Try \"fix the failing login test\"" }) })) : (transcript.map((item) => (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: item.kind === 'text' && item.role === 'user' ? (_jsxs(Text, { children: ["\u203A ", item.text] })) : item.kind === 'text' ? (_jsxs(Text, { children: [" ", item.text] })) : item.kind === 'tool' ? (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsxs(Text, { children: [item.name, " ", item.isError ? '✗' : '✓', " ", item.latencyMs ?? 0, "ms"] }), item.result ? _jsx(Text, { color: tokens.ansi.muted, children: String(item.result).slice(0, 200) }) : null] })) : item.kind === 'diff' ? (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: item.summary ?? 'Diff' }), item.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: tokens.ansi.info, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { color: l.kind === 'add' ? tokens.ansi.success : l.kind === 'remove' ? tokens.ansi.error : tokens.ansi.muted, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] })) : (_jsx(Transcript, { items: [item] })) }, item.id)))), liveText ? (_jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { children: [liveText, "\u258D"] }) })) : status.status === 'running' ? (_jsxs(Box, { children: [_jsx(Text, { color: tokens.ansi.info, children: "\u2726 Thinking..." }), _jsxs(Text, { color: tokens.ansi.muted, children: [" \u00B7 ", Math.round(elapsed / 1000), "s"] })] })) : null, plan.length > 0 ? _jsx(PlanView, { steps: plan, expanded: false, onToggle: () => { } }) : null] }), _jsxs(Box, { borderStyle: "single", borderColor: tokens.ansi.accent, paddingX: 1, children: [_jsx(Text, { children: "\u203A " }), _jsxs(Text, { children: [input, "\u258F"] })] }), _jsxs(Box, { justifyContent: "space-between", paddingX: 1, borderStyle: "single", borderColor: tokens.ansi.border, children: [_jsxs(Text, { color: tokens.ansi.muted, children: [status.model, " \u00B7 ", status.usageInput + status.usageOutput, " tokens \u00B7 $", (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015).toFixed(2), " \u00B7 ", Math.round(elapsed / 1000), "s"] }), _jsx(Text, { color: tokens.ansi.muted, children: isSmall ? 'Ctrl+C interrupt' : 'Ctrl+C interrupt · Ctrl+O expand · ↑↓ scroll' })] })] }));
163
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: height - 1, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: "KLYRO v0.1.15" }), _jsxs(Text, { color: tokens.ansi.muted, children: [status.model, " \u00B7 API Usage Billing \u00B7 step ", status.step, "/", status.maxSteps, " \u00B7 ", status.status, " \u00B7 repairs ", status.repairs] }), _jsx(Text, { color: tokens.ansi.muted, children: props.cwd })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", paddingX: 1, paddingY: 1, children: [transcript.length === 0 ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: tokens.ansi.muted, children: "No conversation yet. Try \"fix the failing login test\"" }) })) : (transcript.map((item) => (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: item.kind === 'text' && item.role === 'user' ? (_jsxs(Text, { children: ["\u203A ", item.text] })) : item.kind === 'text' ? (_jsxs(Text, { children: [" ", item.text] })) : item.kind === 'tool' ? (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsxs(Text, { children: [item.name, " ", item.isError ? '✗' : '✓', " ", item.latencyMs ?? 0, "ms"] }), item.result ? _jsx(Text, { color: tokens.ansi.muted, children: String(item.result).slice(0, 200) }) : null] })) : item.kind === 'diff' ? (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: tokens.ansi.border, paddingX: 1, children: [_jsx(Text, { bold: true, children: item.summary ?? 'Diff' }), item.hunks.map((h, i) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: tokens.ansi.info, children: h.path }), h.lines.map((l, j) => (_jsxs(Text, { color: l.kind === 'add' ? tokens.ansi.success : l.kind === 'remove' ? tokens.ansi.error : tokens.ansi.muted, children: [l.kind === 'add' ? '+ ' : l.kind === 'remove' ? '- ' : ' ', l.text] }, j)))] }, i)))] })) : (_jsx(Transcript, { items: [item] })) }, item.id)))), liveText ? (_jsx(Box, { paddingLeft: 2, marginBottom: 1, children: _jsxs(Text, { children: [liveText, "\u258D"] }) })) : status.status === 'running' ? (_jsxs(Box, { children: [_jsx(Text, { color: tokens.ansi.info, children: "\u2726 Thinking..." }), _jsxs(Text, { color: tokens.ansi.muted, children: [" \u00B7 ", Math.round(elapsed / 1000), "s"] })] })) : null, plan.length > 0 ? _jsx(PlanView, { steps: plan, expanded: false, onToggle: () => { } }) : null] }), _jsxs(Box, { borderStyle: "single", borderColor: tokens.ansi.accent, paddingX: 1, children: [_jsx(Text, { children: "\u203A " }), _jsxs(Text, { children: [input, "\u258F"] })] }), _jsxs(Box, { justifyContent: "space-between", paddingX: 1, borderStyle: "single", borderColor: tokens.ansi.border, children: [_jsxs(Text, { color: tokens.ansi.muted, children: [status.model, " \u00B7 ", status.usageInput + status.usageOutput, " tokens \u00B7 $", (status.usageInput / 1000 * 0.003 + status.usageOutput / 1000 * 0.015).toFixed(2), " \u00B7 ", Math.round(elapsed / 1000), "s"] }), _jsx(Text, { color: tokens.ansi.muted, children: isSmall ? 'Ctrl+C interrupt' : 'Ctrl+C interrupt · Ctrl+O expand · ↑↓ scroll' })] })] }));
164
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klyro",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Klyro — autonomous coding harness CLI that streams from any OpenAI-compatible or Anthropic LLM endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,6 +12,7 @@
12
12
  "files": [
13
13
  "dist",
14
14
  "README.md",
15
+ "READ.md",
15
16
  "LICENSE"
16
17
  ],
17
18
  "engines": {