deel-local-cli 0.9.0 → 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/README.en.md CHANGED
@@ -23,7 +23,7 @@ Zero dependencies · Node 20+ · Exactly one place your source can go
23
23
  │ This PC 337 skills · 127 commands · 42 plugins │
24
24
  ╰──────────────────────────────────────────────────────────────╯
25
25
 
26
- ▏myproject qwen2.5-coder:7b ▏▰▰▱▱▱▱▱▱▱▱ 22% 28k/128k ▏◇ medium·save auto
26
+ ▏myproject · qwen2.5-coder:7b ▰▰▱▱▱▱▱▱▱▱ 22% 28k/128k ◎ 종합 · ◇ medium·절약 · auto
27
27
  ❯ unify the logging style
28
28
 
29
29
  ❊ Grep(console.log)
@@ -33,7 +33,7 @@ Zero dependencies · Node 20+ · Exactly one place your source can go
33
33
  ◈ Edit(src/runner.js)
34
34
  └ 1 spot
35
35
 
36
- Unified log calls to the logger format. One change in runner.js.
36
+ Unified log calls to the logger format. One change in runner.js.
37
37
 
38
38
  ── 4.2s · 3 tools · ↑3,900 ↓180
39
39
  ```
@@ -55,6 +55,7 @@ Zero dependencies · Node 20+ · Exactly one place your source can go
55
55
  - [Reasoning effort](#reasoning-effort)
56
56
  - [Auto-compaction](#auto-compaction)
57
57
  - [Resuming a conversation](#resuming-a-conversation)
58
+ - [Attaching tools from outside (MCP)](#attaching-tools-from-outside-mcp)
58
59
  - [Safety](#safety)
59
60
  - [Corporate review package](#corporate-review-package)
60
61
  - [Configuration](#configuration)
@@ -211,16 +212,20 @@ Names follow Claude Code / Codex conventions.
211
212
  | `/help` | Command list |
212
213
  | `/context` | What is consuming the context window |
213
214
  | `/ctx [auto\|number]` | Context **length** — re-read it off the model, or set it yourself |
215
+ | `/out [number\|auto]` | Cap on a **single reply** — raise it when large files get cut |
214
216
  | `/compact` | Summarise and fold older turns |
215
217
  | `/clear` | Clear the conversation (keeps link and rules) |
216
218
  | `/model` | Switch connection / model |
217
- | `/think <level\|profile>` | `off·low·medium·high·max` or `even·save·deep` |
219
+ | `/think <level>` | Reasoning level (`off·low·medium·high·max`) |
220
+ | `/think 배분 <profile>` | Per-stage profile (`even·save·deep`) |
221
+ | `/think 자세히` | Stage table — which stage runs at which level and cap |
218
222
  | `/mode <mode>` | Approval policy — how much it asks (`auto` · `confirm` · `strict`) |
219
223
  | `/work [mode]` | Work mode — what kind of work you are doing |
220
224
  | `/auto` | Hand the wheel back — it picks the mode from what you type |
221
225
  | `/code` `/plan` `/architect` `/debug` `/ask` `/orchestrator` | Switch work mode directly (pins it) |
222
226
  | `/level [level]` | How much to show (`쉬움` simple · `개발자` developer) |
223
227
  | `/undo [turns]` | Revert file changes |
228
+ | `/diff [file]` | Files changed this session, and the changed lines |
224
229
  | `/tools` | Available tools |
225
230
  | `/skills [query\|all\|off]` | Browse, search, load skills |
226
231
  | `/plugin [install\|remove\|pack]` | Manage plugins |
@@ -228,6 +233,9 @@ Names follow Claude Code / Codex conventions.
228
233
  | `/status` | Connection status |
229
234
  | `/scan [save]` | Sweep this machine for local model servers (`save` registers them) |
230
235
  | `/sessions` | Past conversations in this folder |
236
+ | `/recall <text>` | Search past conversations **by content** |
237
+ | `/memory` | What persists across sessions — view, add, delete |
238
+ | `/mcp` | Externally attached tools (MCP servers) |
231
239
  | `/init` | Create a `DEEL.md` rules file |
232
240
  | `/exit` | Quit |
233
241
 
@@ -237,6 +245,43 @@ Discovered plugin commands are invoked as `/<plugin>:<name>`, with `$ARGUMENTS`
237
245
  server or loaded a different model, `/scan save` then `/model` switches over without losing
238
246
  the conversation.
239
247
 
248
+ ### Attaching a file with `@`
249
+
250
+ Write `@` followed by a path and that file is sent along with your message.
251
+
252
+ ```
253
+ ❯ @src/a.js why is this slow?
254
+ ◧ attached src/a.js
255
+ ```
256
+
257
+ That removes **one round-trip** — the one where the model has to call `Read` itself. Local
258
+ models are weak at tool calling and that round-trip often misfires: it calls the wrong path,
259
+ or skips the call and invents the contents. You already know which file it is; there is no
260
+ reason to make the model go looking.
261
+
262
+ The hard part is not attaching. It is **not mistaking something else for a file.**
263
+ Things starting with `@` are everywhere.
264
+
265
+ | What you type | What happens |
266
+ |---|---|
267
+ | `@src/a.js` | Attached — only when the path actually exists |
268
+ | `hong@example.com` | Not a mention at all. A letter before the `@` means it is an address |
269
+ | `@media` · `@dataclass` · `@scope/pkg` | Left alone — no such path exists |
270
+ | `@src/` (a directory) | A listing of what is inside is attached |
271
+ | `@"draft report.txt"` | Quote names containing spaces |
272
+ | Outside the working scope | Refused, and the refusal is printed |
273
+ | A CP949 corporate document | Decoded by content and attached correctly |
274
+
275
+ There is one rule: **attach only when the path really exists.** Otherwise the text is left
276
+ exactly as typed, silently — it probably wasn't a mention.
277
+
278
+ Attachments are capped at **25% of the context length** (20,000 tokens at most). Anything larger is attached from the
279
+ top only, and a truncated file is **not marked as already read.** Marking it read would let the
280
+ model edit a part it never saw. Only a fully attached file lets it skip `Read`.
281
+
282
+ Every attachment is announced on screen. Text the user did not type is now in the conversation;
283
+ not showing it would also leave them wondering where the context went.
284
+
240
285
  ### Interrupting
241
286
 
242
287
  Press **Ctrl+C** to stop the model mid-answer when it is heading the wrong way.
@@ -349,6 +394,52 @@ Two things matter here:
349
394
  - **Beginners do not get fewer safeguards.** Undo, workspace scope and dangerous-command
350
395
  blocking are identical. A beginner needs the undo more, not less.
351
396
 
397
+ ### Two screens
398
+
399
+ Launched in a terminal, deel runs **full-screen**: conversation on the left, files changed
400
+ this session at the top right, todos below them, with a status line and an input box pinned
401
+ at the bottom.
402
+
403
+ ```
404
+ ┌ 대화 ─────────────────────────────────────────────┬ 바뀐 파일 ───────────────┐
405
+ │ ❊ Grep(console.log) │ src/runner.js +3-1 │
406
+ │ └ 3 files · 11 hits │ src/index.js +1-1 │
407
+ │ │ src/ui/log.js +12-0 │
408
+ │ ◈ Edit(src/runner.js) ├ 할 일 ───────────────────│
409
+ │ └ 1 spot +3-1 │ ☑ find log calls │
410
+ │ - 12 console.log('시작', 이름) │ ☑ unify runner.js │
411
+ │ + 12 logger.info({ 단계: '시작', 이름 }) │ ▶ unify index.js │
412
+ │ │ ☐ update docs │
413
+ │ ▌ Unified log calls to the logger format. │ │
414
+ │ │ │
415
+ │ ── 4.2s · 3 tools · ↑3,900 ↓180 │ │
416
+ └───────────────────────────────────────────────────┴──────────────────────────┘
417
+ ▏myproject · qwen2.5-coder:7b ▏ ▰▰▱▱▱▱▱▱ 22% ▏ ◎ 종합 · ◇ medium · auto ▏ ↑3.8k ↓180
418
+ ╭─────────────────────────────────────────────────────────────────────────────╮
419
+ │ ❯ │
420
+ ╰─────────────────────────────────────────────────────────────────────────────╯
421
+ ```
422
+
423
+ **It switches itself off where it would do harm.** Piped or redirected output, `CI` set,
424
+ `TERM=dumb`, or a window under 60×16 all fall back to the **scrolling view** without asking —
425
+ `deel … | tee log.txt` must not become a pile of escape codes. Passing `--tui` does not
426
+ override a pipe.
427
+
428
+ | | Full-screen | Scrolling (`--no-tui`) |
429
+ |---|---|---|
430
+ | When | Launched in a terminal | Pipes, CI, small windows, `--no-tui` |
431
+ | Files changed / todos | Always visible on the right (≥96 cols) | Printed inline as they happen |
432
+ | Earlier output | Wrapped and paged inside the frame | Your terminal's own scrollback |
433
+ | On exit | **Replays the conversation into scrollback** | Already there |
434
+ | For keeping a log | Not this one | This one |
435
+
436
+ Full-screen uses the alternate screen buffer, like vim. Exiting would otherwise erase the whole
437
+ conversation, so the recent lines are reprinted into the real screen on the way out. Whether it
438
+ exits on Ctrl+C or dies on an uncaught error, **the terminal is always restored.**
439
+
440
+ If the full-screen view cannot be set up — an unusual terminal, anything at all — it falls back
441
+ to the scrolling view and says why in one line. **No program should fail to start over a screen.**
442
+
352
443
  ---
353
444
 
354
445
  ## Tools
@@ -359,14 +450,95 @@ Names and arguments match Claude Code, so skills written for that convention wor
359
450
  |---|---|
360
451
  | `Read` | Read a file (line numbers, `offset`/`limit`, **Excel as CSV**) |
361
452
  | `Write` | Write / overwrite a file |
453
+ | `Append` | Append to the end of a file — **how large files get written in pieces** |
362
454
  | `Edit` | Replace an exact string (`replace_all` supported) |
363
455
  | `Glob` | Find files by name pattern |
364
456
  | `Grep` | Regex search file contents |
365
457
  | `Bash` | Run a command |
366
458
  | `Skill` | Expand a skill body (shown to the model only when skills exist) |
367
459
  | `WebFetch` | Read a web page (read-only; hidden under `--offline`) |
460
+ | `Recall` | Search **past conversations** — the model digs up "that thing last time" itself |
461
+ | `Remember` | One line that outlives the session — known from the start next time |
368
462
  | `TodoWrite` | Checklist — breaks long work into steps and shows progress |
369
463
 
464
+ Only **three** tools here are not in Claude Code — `Append`, `Recall`, `Remember`. Each
465
+ tool costs roughly 150 tokens of schema on **every request**, so a test stops you every
466
+ time the list grows (`test/loop.test.js`).
467
+
468
+ ### Finding past conversations, and remembering decisions
469
+
470
+ deel writes every conversation to `.deel/sessions/*.jsonl`. Until now all you could do was
471
+ list them — **a record you cannot search is the same as no record.**
472
+
473
+ ```
474
+ $ /recall 인코딩을 어떻게
475
+
476
+ 2026-08-01 10:15 모델 20260801-101500
477
+ CP949 인코딩 문제입니다. 읽을 때 인코딩을 재서 그대로 되돌려 쓰도록…
478
+ ```
479
+
480
+ Korean particles are handled: `인코딩을` also matches `인코딩`. A morphological analyser is
481
+ out of the question (zero dependencies), so particle-looking tails are stripped and **both**
482
+ forms are searched. No index is built — an index inevitably goes stale, and **a stale index is
483
+ worse than none** ("not found" reads as "never happened"). Instead every search reports how
484
+ much it read and what it could not.
485
+
486
+ `Recall` is also a **tool**. Left as a human-only command, "do it the way we decided last
487
+ time" leaves the model nothing to do but ask again.
488
+
489
+ **Memory (`/memory`) is a different thing.** Recall has to be *searched*; memory is *already
490
+ there*. Things you cannot re-explain every session go here.
491
+
492
+ ```
493
+ $ /memory
494
+
495
+ 1 사내 문서는 CP949 로 읽고 CP949 로 되돌려 쓴다
496
+ 2 검증할 때 7080 포트는 쓰지 않는다
497
+
498
+ 2줄 · 약 30토큰이 매 요청마다 함께 나갑니다
499
+ 파일 .deel/memory.md — 직접 고치셔도 됩니다
500
+ ```
501
+
502
+ `.deel/memory.md` is **prose a human edits**, not a database. That matters: a line the model
503
+ got wrong ships on every request and keeps being wrong. **A wrong memory is worse than none.**
504
+ So `/memory 지우기 2` deletes one.
505
+
506
+ Because it ships on every request it is bounded: 400 chars per line, 60 lines, 6,000 chars
507
+ total. Overflow drops the oldest and says so. `/context` shows its line count and tokens.
508
+
509
+ ### Repeatable procedures become skills
510
+
511
+ When a multi-step job finishes and it is something that will come up again, the model writes
512
+ the procedure to `.deel/skills/<name>/SKILL.md`. Next session it appears in the skill list;
513
+ when it turns out to be wrong somewhere, the model edits that file.
514
+
515
+ No new tool needed — the existing `Write` writes it and the existing skill sweep reads it.
516
+
517
+ ### Large files are written in pieces — `Append`
518
+
519
+ A model with a 4k output cap still has to be able to write a 2,000-line file, eight pieces at
520
+ a time. Stitching with `Edit` does not work in practice — HTML repeats anchors like `</div>`,
521
+ so the match comes back as "found in several places", and a longer anchor eats the tokens that
522
+ should have gone into the body.
523
+
524
+ `Write` to create, `Append` to continue. Encoding follows `Write` (CP949 for corporate
525
+ documents, the BOM on a `.csv` is preserved). The undo snapshot is taken **only on the first
526
+ `Append`** — eight appends must not leave eight copies in the history, or there is no single
527
+ point to revert to.
528
+
529
+ ```
530
+ ⏺ Write(dashboard.html)
531
+ └ ⚠ wrote only as far as it arrived — 632 lines
532
+ ↻ the reply hit the cap — retrying with 9,984 → 16,384
533
+ ⏺ Append(dashboard.html)
534
+ └ +567 lines · 1,199 total
535
+
536
+ ✓ dashboard.html · 1,199 lines · 97.7KB
537
+ ```
538
+
539
+ That last line matters. If the file does not exist and the model says "created it", you would
540
+ believe it. **The real file is measured at the end of the turn.**
541
+
370
542
  ### Checklists
371
543
 
372
544
  Keeps the model from losing its place on multi-step work. The list is redrawn whenever the
@@ -417,6 +589,65 @@ Measured with `npm run bench`:
417
589
 
418
590
  On failure it points at the closest line in the file.
419
591
 
592
+ ### It shows you what it changed
593
+
594
+ `auto` mode edits without asking. That is the speed of this tool — but if all that reaches the
595
+ screen is `1 spot`, you move on without knowing what happened. Undo is the safety net, and
596
+ **you cannot decide whether to undo something you never saw.** So every `Edit` and `Write` is
597
+ followed by the changed lines themselves.
598
+
599
+ ```
600
+ ◈ Edit(src/runner.js)
601
+ └ 1 spot +1 −2
602
+
603
+ 11 const id = job.id;
604
+ - console.log("start: " + id);
605
+ - console.log(" opts " + JSON.stringify(opts));
606
+ + 12 logger.info('start', { id, opts });
607
+ 13 return run(job);
608
+ ```
609
+
610
+ The `+1 −2` next to the summary is how many lines were added and removed.
611
+
612
+ - **Removed lines carry no line number.** They no longer exist in the file. Printing the old
613
+ number put it directly under a context line's new number — two different files' numbering in
614
+ one column. Line 8 really did appear twice on screen.
615
+ - If only the line endings changed (CRLF/LF), it says so. Otherwise every visually identical
616
+ line shows as changed and the real edit is impossible to find.
617
+ - Large files are compared after trimming the identical head and tail. If it is still too big,
618
+ exact matching is abandoned for "this whole block changed" — a rough answer now beats an
619
+ exact one later.
620
+
621
+ How many lines are shown depends on the level. Forty lines at someone's first launch means none get read.
622
+
623
+ | | Simple | Developer |
624
+ |---|---|---|
625
+ | After a tool call | 14 lines | 40 lines |
626
+ | `/diff <file>` | 60 lines | 200 lines |
627
+
628
+ ### `/diff` — everything changed this session
629
+
630
+ Those lines scroll away. `/diff` collects every file touched this session onto one page.
631
+
632
+ ```
633
+ $ /diff
634
+
635
+ ── files changed this session ─────────────────────────────
636
+ src/runner.js +12 −7 3×
637
+ src/logger.js +40 −0
638
+ ──────────────────────────────────────────────────────────
639
+ 2 files +52 −7
640
+
641
+ /diff <file> for detail, /undo to revert
642
+ ```
643
+
644
+ `/diff <file>` compares **the state at the start of the session against now.** Even after
645
+ three edits, what you want to know is "what is different from before I asked", not what the
646
+ last edit did. That original state comes from the earliest undo snapshot.
647
+
648
+ `/diff` is **in the simple level's command list.** As long as `auto` edits without asking,
649
+ a beginner needs a way to see what changed more than anyone.
650
+
420
651
  ---
421
652
 
422
653
  ## Korean text and Excel
@@ -453,6 +684,12 @@ Newly created files are UTF-8.
453
684
  Command output is handled the same way. A Windows console is not UTF-8, so taking `Bash`
454
685
  output as utf8 garbles non-ASCII text. It is collected as bytes and decoded afterwards.
455
686
 
687
+ **Undo snapshots are stored as bytes too.** They used to be stored as UTF-8 text, so undoing
688
+ a CP949 file brought back `가나다` (bytes `b0a1 b3aa b4d9`) as six U+FFFD characters — **the
689
+ safety net itself destroyed the original bytes.** Now every snapshot is round-tripped through
690
+ UTF-8 first; anything that does not come back identical is stored as base64 and restored
691
+ byte-exact.
692
+
456
693
  ### Excel — read as CSV
457
694
 
458
695
  An Excel file is a compressed archive, not text, so normally you get "this is a binary file"
@@ -539,15 +776,14 @@ with a licence table — ready to hand to a security reviewer.
539
776
  One answer means several model calls, and **each needs a different amount of thinking.**
540
777
  All-high is slow; all-low wanders off.
541
778
 
779
+ The default is **one line**. What you want to know is how hard it is thinking right now,
780
+ not a stage table.
781
+
542
782
  ```
543
783
  $ /think
544
784
 
545
- Base medium Profile save Hard on the first decision, light while continuing
546
-
547
- Stage Effort Cap When
548
- first call · medium 4,096 deciding what to do
549
- continuing ↓ low 2,048 reading a tool result, picking the next step
550
- stuck ↑ high 4,096 the previous tool errored
785
+ 추론 강도 medium (첫 판단 medium · 이어가기 low · 막혔을 때 high)
786
+ 더 세게 /think high 더 빠르게 /think low
551
787
  ```
552
788
 
553
789
  | Profile | Character |
@@ -556,6 +792,31 @@ $ /think
556
792
  | `save` (default) | Hard on the first decision only |
557
793
  | `deep` | Everything one notch up — for hard work |
558
794
 
795
+ Set the profile with `/think 배분 절약`. **Level and profile are different axes, so the
796
+ commands were split** — `/think high` and `/think save` used to set different things under
797
+ one name, which made the screen unreadable.
798
+
799
+ The stage table moved to `/think 자세히` (the default at developer level).
800
+
801
+ ```
802
+ $ /think 자세히
803
+
804
+ 추론 강도 medium (첫 판단 medium · 이어가기 low · 막혔을 때 high)
805
+ 배분 절약 첫 판단만 세게, 이어가기는 얕게 — 대개 이게 낫습니다
806
+
807
+ 단계 강도 출력상한 언제
808
+ 첫 판단 · medium 15,549 무엇을 할지 정하는 자리
809
+ 이어가기 ↓ low 13,605 도구 결과를 읽고 다음 한 수
810
+ 막혔을 때 ↑ high 16,384 직전 도구가 오류를 냄
811
+
812
+ 출력 상한은 16,384 (모르는 값이라 기본값) 안에서 나눕니다 — /out
813
+ 컨텍스트 40,960 · 지금 찬 양 2,087
814
+ ```
815
+
816
+ That second-to-last line exists for a reason: **when all three caps are equal, it is the
817
+ only thing that says whether that is correct.** A low known cap makes them equal, and that
818
+ is fine. For a while all three read `16,384` always — which meant the table said nothing.
819
+
559
820
  ### Context length is read off the model
560
821
 
561
822
  This one number sizes the whole program: how many files fit in one read, when the
@@ -595,29 +856,99 @@ the **loaded length is what deel uses**, and the maximum is reported separately.
595
856
  | `/ctx` | Current length and remaining room |
596
857
  | `/ctx auto` | Ask the server again and match the model |
597
858
  | `/ctx 655360` | Set it yourself (`640k`, `128k`, `1m` also work) |
598
- | `/ctx out 32k` | Cap on a **single reply**a different axis from context |
859
+ | `/ctx 자세히` | Which endpoints were probed and what each returned how to see why a lookup failed |
599
860
  | `deel --ctx 655360` | Start at this value (skips the lookup) |
600
861
 
601
862
  **`k` means 1024 here.** Context lengths are all powers of two, so that is the only base
602
863
  that lines up: 655,360 is `640k`, not `655k`; 131,072 is `128k`, not `131k`. The display and
603
864
  `/ctx` use the same unit, so typing back what you see gives you the same number.
604
865
 
866
+ ### Reply length cap — `/out`
867
+
868
+ Context (how much can be held) and the **output cap** (how much can come back at once) are
869
+ different numbers. Treating them as one makes it impossible to understand why a large file
870
+ never gets written — the context is roomy while the reply is being cut.
871
+
872
+ | Command | What it does |
873
+ |---|---|
874
+ | `/out` | Current cap and **where it came from** (set by you / discovered / default) |
875
+ | `/out 32k` | Set it yourself (`k` is 1024). Saved to the profile |
876
+ | `/out auto` | Drop your value and go back to the discovered one, or the default |
877
+ | `deel --max-tokens 65536` | Start at this value |
878
+
879
+ The old name `/ctx out 32k` still works.
880
+
605
881
  **Caps are not fixed numbers.** They are computed from the model's context window and how
606
882
  much of it is currently used — the profile decides what share of the remaining room a stage gets.
607
883
 
608
- | Model | First call | Continuing | Stuck |
609
- |---|---|---|---|
610
- | 2k local | 554 | 512 | 554 |
611
- | 8k local | 2,007 | 1,003 | 2,007 |
612
- | 40k (qwen3) | 11,688 | 5,844 | 11,688 |
613
- | 128k gateway | 16,384 | 16,384 | 16,384 |
614
- | 128k, 80% full | 7,680 | 3,840 | 7,680 |
884
+ | Model | First call | Continuing | Stuck | Retry after truncation |
885
+ |---|---|---|---|---|
886
+ | 2k local | 819 | 716 | 921 | 1,638 |
887
+ | 8k local | 3,276 | 2,867 | 3,686 | 6,553 |
888
+ | 40k (qwen3) | 16,384 | 14,336 | 16,384 | 16,384 |
889
+ | 128k gateway | 16,384 | 16,384 | 16,384 | 16,384 |
890
+ | 128k, 80% full | 10,485 | 9,174 | 11,796 | 16,384 |
891
+ | 640k with `/out 65536` | 65,536 | 65,536 | 65,536 | 65,536 |
615
892
 
616
893
  Caps shrink as the context fills. Handing a 4k model a 4,096-token cap would leave no room for input.
617
- Raise the ceiling with `maxTokens` in the profile if you need more.
618
894
 
619
- If a saved cap truncates a reply, **that step alone is retried with the cap lifted.**
620
- A truncated reply means a half-written tool call, which fails silently.
895
+ The last row is the point: **a known cap overrides the 16,384 default.** For a while it did
896
+ not the third argument of `Math.min(cap, max ?? 16384, 16384)` clamped it right back, so a
897
+ configured cap could only be lowered, never raised. Meanwhile the comment, the README, and the
898
+ on-screen help all said it could be raised. A documented escape hatch that is welded shut is
899
+ the worst kind.
900
+
901
+ If a cap truncates a reply, **the call is retried with the cap lifted** — and the thinking
902
+ level drops one notch, because reasoning tokens eat the same budget first. Without that, more
903
+ headroom just buys more thinking. A truncated reply means a half-written tool call, which fails silently.
904
+
905
+ **When the server refuses, it is read for the answer.**
906
+
907
+ ```
908
+ This model's maximum context length is 8192 tokens, however you requested 41003
909
+ ```
910
+
911
+ The number is extracted, applied, and the call is retried. You never see the failure.
912
+ No spec knowledge is needed, so **this works against servers we have never seen.**
913
+
914
+ ### Truncated tool calls
915
+
916
+ This actually happened. A user asked for a dashboard; the model tried to put an entire HTML
917
+ document into `Write`'s arguments, hit the output token limit, and the arguments JSON arrived
918
+ cut off mid-string.
919
+
920
+ The old code quietly turned that unparseable JSON into `{_raw: "..."}` and handed it to the
921
+ tool. The tool answered `path is empty` — **a message with nothing to do with the real cause.**
922
+ The model had not omitted the path, so there was nothing to fix; it retried identically, and
923
+ was truncated again.
924
+
925
+ ```
926
+ ◆ Write(dashboard.html)
927
+ └ path is empty ← nine identical times
928
+
929
+ ── 71s · 13 tool calls · context filled and auto-compacted · no file produced
930
+ ```
931
+
932
+ One silently swallowed value produced all of that. What happens now:
933
+
934
+ | | Now |
935
+ |---|---|
936
+ | Unparseable arguments | **Marked as truncated**, not swallowed. Never passed to the tool |
937
+ | The model is told | Exactly what happened, and to stop resending the whole thing — write a short skeleton first, then **build it up with `Edit`** |
938
+ | The truncated payload | Never re-injected into the conversation — it is half a payload and it costs context |
939
+ | Detecting truncation | Even when the gateway reports `finish_reason: "stop"`, **broken arguments are themselves proof it was cut.** No model writes half a JSON object on purpose |
940
+ | Three identical failures | The turn stops, with a suggestion to split the request |
941
+
942
+ ```
943
+ ⊘ Stopped — it is spinning in the same place.
944
+ the same tool call keeps getting truncated
945
+ What you asked for in one go is larger than the model's output cap. Try splitting it —
946
+ e.g. "just the skeleton first" → "now add the table" → "now add the chart"
947
+ ```
948
+
949
+ **A step limit (`maxSteps`) cannot catch this.** It cannot tell a long healthy task from a
950
+ spinning one. What is counted here is not steps but **how many times the same tool failed for
951
+ the same reason.**
621
952
 
622
953
  ---
623
954
 
@@ -670,6 +1001,43 @@ covers `.deel/` so it never reaches a repository.
670
1001
 
671
1002
  ---
672
1003
 
1004
+ ## Attaching tools from outside (MCP)
1005
+
1006
+ A corporate wiki search, an issue tracker, a DB query tool — if a team publishes one as an MCP
1007
+ server, deel uses it as a tool **without a code change**.
1008
+
1009
+ Configure in `.deel/mcp.json`. A Claude Code config can be copied over verbatim:
1010
+
1011
+ ```json
1012
+ { "mcpServers": { "wiki": { "command": "node", "args": ["wiki-mcp.js"] } } }
1013
+ ```
1014
+
1015
+ The model sees it as `mcp__wiki__search`. `/mcp` shows what is attached.
1016
+
1017
+ **Dependencies stay at zero.** The stdio transport is nothing but newline-delimited JSON-RPC
1018
+ 2.0 over a child process's stdin/stdout, so `child_process` and `JSON` cover it. No SDK.
1019
+
1020
+ ### But this is somebody else's program
1021
+
1022
+ This project exists because unapproved software is blocked. Turning on MCP carelessly would
1023
+ tear down that line with our own hands. So:
1024
+
1025
+ | | |
1026
+ |---|---|
1027
+ | **Off by default** | Nothing runs unless it is in `.deel/mcp.json` |
1028
+ | **Never under `--offline`** | We cannot police where a child process connects. **We do not claim to block what we cannot block** |
1029
+ | **Outside the working scope** | MCP servers do not honour our fence. The `/mcp` screen says so |
1030
+ | **Audited** | What was launched and what was called, in `.deel/audit.jsonl` |
1031
+ | **No key passthrough** | Our environment is not forwarded wholesale — a `DEEL_*` gateway key in someone else's process goes somewhere we cannot see |
1032
+ | **Not in read-only modes** | A tool named "search" can still write files. Handing an unknown to plan/architect mode would make that promise meaningless |
1033
+ | **24 tools per server** | Schemas ship on every request. Past that they are dropped, and **the drop is reported** |
1034
+
1035
+ One server crashing, hanging, or talking nonsense does not affect the others. Failures are not
1036
+ swallowed — the reason appears in the header, because a silent drop leaves "why is that tool
1037
+ missing?" unanswerable.
1038
+
1039
+ ---
1040
+
673
1041
  ## Safety
674
1042
 
675
1043
  Instead of approval prompts, the design makes things **reversible**. The default `auto` mode
@@ -678,10 +1046,13 @@ does not ask.
678
1046
  | Mechanism | Detail |
679
1047
  |---|---|
680
1048
  | **Undo** | Snapshot before every write. `/undo` restores per turn |
1049
+ | **Change display** | The changed lines are shown on every edit; `/diff` for the whole session |
681
1050
  | **Scope** | Outside the starting folder is refused, even if the model insists |
682
1051
  | **Blocked commands** | Only irreversible ones (disk format, recursive delete, `--force` push) |
683
1052
  | **No re-run** | A mutating command is never retried after failure |
684
1053
  | **Interrupt** | Ctrl+C stops mid-answer and leaves the conversation valid |
1054
+ | **Spin guard** | Three identical failures stop the turn, with the reason |
1055
+ | **Not read** | Other tools' private stores, and deel's own logs and config (the key), are refused |
685
1056
  | **Audit log** | Everything recorded in `.deel/audit.jsonl` |
686
1057
 
687
1058
  | Mode | Asks when |
@@ -694,6 +1065,43 @@ Undo history stores whole file contents, so repeated edits to large files add up
694
1065
  it keeps the **most recent 50 turns** and drops the rest. What you just did is always
695
1066
  undoable; `/status` shows how large the history currently is.
696
1067
 
1068
+ ### What it will not read
1069
+
1070
+ Walking a folder turns up things that are not project files: the private stores other coding
1071
+ tools keep — past conversations, command history, caches, and keys. They have nothing to do
1072
+ with the task, but once they appear in a listing the model reads them first.
1073
+
1074
+ ```
1075
+ ◧ Read(~/.deel/audit.jsonl) 77 lines
1076
+ ◧ Read(~/.claude/history.jsonl) 35 lines
1077
+ ```
1078
+
1079
+ The audit log is **this program's own record of what it just did.** Reading it back into the
1080
+ conversation makes the model chase its own shadow. It has nothing to do with what was asked,
1081
+ and it fills the context.
1082
+
1083
+ The config file is worse. `.deel/config.json` holds the gateway **API key.** Reading it puts
1084
+ that key into the conversation, sends it to the model, and writes it into the on-disk session
1085
+ log. It hands the key to the very service the key is for.
1086
+
1087
+ | Refused | Why |
1088
+ |---|---|
1089
+ | `.deel/config.json` | Contains the gateway key |
1090
+ | `.deel/audit.jsonl` · `.deel/sessions` · `.deel/history` | deel's own records — chasing its own shadow |
1091
+ | `.claude` `.codex` `.cursor` `.gemini` `.aider` `.continue` `.cline` `.roo` `.kilocode` `.windsurf` `.opencode` `.zed` `.trae` `.augment` `.qodo` `.tabnine` `.cody` `.sourcegraph` `.copilot` `.amazonq` `.junie` `.codeium` `.goose` `.crush` `.gptme` `.openhands` `.devin` | Other tools' private stores |
1092
+ | File-style leftovers like `.aider.chat.history.md` | Same reason |
1093
+
1094
+ Writing is blocked too, not just reading. Blocking only reads would still let the agent
1095
+ overwrite another tool's settings, and overwriting `.deel/config.json` destroys the connection.
1096
+
1097
+ **It blocks, it does not hide** — the refusal says exactly why. New tools keep appearing;
1098
+ when a name is missing from the list, **adding it is one line.**
1099
+
1100
+ That list lives in **exactly one place in the source.** The directory walker (`SKIP_DIRS`) and
1101
+ the read guard look at the same set. They used to be two copies, and two copies means the day
1102
+ comes when only one of them learns a new name — a folder that is skipped while walking but
1103
+ readable if you name it directly, which is very hard to explain.
1104
+
697
1105
  ---
698
1106
 
699
1107
  ## Corporate review package
@@ -780,11 +1188,14 @@ deel --root <folder> Working scope. Defaults to the current folder
780
1188
  deel --mode <mode> auto (default) / confirm / strict
781
1189
  deel --work <mode> auto (default) / code / plan / architect / debug / ask / orchestrator
782
1190
  deel --level <level> 쉬움 (simple) / 개발자 (developer)
1191
+ deel --ctx <length> Set the context length yourself (655360 · 640k · 128k)
1192
+ deel --max-tokens <len> Cap on a single reply (32k) — same value as /out
783
1193
  deel --think <level> off / low / medium (default) / high / max
784
1194
  deel --effort <profile> even / save (default) / deep
785
1195
  deel --offline Nothing leaves this machine
786
1196
  deel --continue Resume the most recent conversation
787
1197
  deel --resume <id> Resume a specific one
1198
+ deel --no-tui Turn the full-screen view off; use the scrolling view (see below)
788
1199
  ```
789
1200
 
790
1201
  ### Project rules
@@ -805,7 +1216,9 @@ If the working folder has `DEEL.md`, `CLAUDE.md` or `AGENTS.md`, it is loaded as
805
1216
  | 401 / 403 | Wrong key or auth header style (four are tried automatically) |
806
1217
  | `address not permitted` | The lock did its job — pick a connection with `/model` |
807
1218
  | Tool calls don't work | Run `deel diagnose`. Small models (1B–3B) often can't |
808
- | Empty replies | A heavy-reasoning model try `/think low` |
1219
+ | Empty replies | The server ignores streaming. deel retries once, then turns streaming off for the session |
1220
+ | Large files cut off mid-write | Check `/out` and raise it — the cap may be sitting at the 16,384 default because it could not be discovered |
1221
+ | Only `HTTP 400` shows | The server's own message is shown verbatim. If it is a length problem the number is read and applied automatically |
809
1222
  | `deel scan` finds nothing | Server is off or on another port — use `--ports` |
810
1223
 
811
1224
  ---
@@ -813,11 +1226,12 @@ If the working folder has `DEEL.md`, `CLAUDE.md` or `AGENTS.md`, it is loaded as
813
1226
  ## Development
814
1227
 
815
1228
  ```bash
816
- npm test Full suite (254 checks)
817
- npm run verify Import + network checks only
818
- npm run bench Edit success rate
819
- npm run demo See what the UI actually looks like
820
- npm run check Syntax check every file
1229
+ npm test Full suite (1,745 checks)
1230
+ npm run coverage Which lines the tests actually execute
1231
+ npm run verify Import + network checks only
1232
+ npm run bench Edit success rate
1233
+ npm run demo See what the UI actually looks like
1234
+ npm run check Syntax check every file
821
1235
  ```
822
1236
 
823
1237
  Tests run against a **fake gateway**, so the loop, streaming, tool execution, undo and compaction
@@ -834,17 +1248,46 @@ so one run tells you everything.
834
1248
  |---|---|---|
835
1249
  | `smoke` | 20 | Tools, scope, undo, audit log |
836
1250
  | `loop` | 16 | Agent loop, streaming, tool calls |
1251
+ | `guard` | 24 | **What it refuses to do** — denied edits, unknown tools, repeated mutations, out-of-scope writes |
837
1252
  | `network` | 30 | Nothing escapes the configured address |
838
1253
  | `web` | 25 | Web reads stay read-only |
839
1254
  | `abort` | 16 | Ctrl+C leaves the conversation valid |
840
1255
  | `parallel` | 23 | Read-only tools run together; checklists |
1256
+ | `cli` | 75 | **Spawns the real `deel`** and drives it to completion |
1257
+ | `setup` | 42 | First-run wizard, driven through a fake TTY |
1258
+ | `detect` | 66 | Identifying shape and auth from one address |
1259
+ | `modes` · `route` | 89 · 33 | Work modes; auto-switching from Auto |
1260
+ | `ctxsize` | 43 | Reading context length off the model |
1261
+ | `commands` · `commands-more` | 128 · 62 | Every slash command |
1262
+ | `ui` · `ui2` | 60 · 40 | Password masking, CJK width, status line, session list, Excel→text |
1263
+ | `encoding` · `xlsx` | 68 · 72 | Legacy-encoding detection; Excel reading |
841
1264
  | `compact` | 21 | Summary folding, pairing intact, graceful fallback |
842
1265
  | `store` | 34 | Session persistence, resume, crash recovery |
843
- | `scan` | 19 | Distinguishing multiple runtimes |
1266
+ | `scan` | 29 | Distinguishing multiple runtimes |
844
1267
  | `plugins` | 38 | Plugin fetch/pack, ZIP/TAR |
845
1268
  | `no-bundle` | 12 | Nothing foreign in the published package; test-file hygiene |
846
1269
  | `edit-bench` | 20 cases | Edit success rate |
847
1270
 
1271
+ ### Coverage
1272
+
1273
+ ```bash
1274
+ npm run coverage Summary
1275
+ node test/coverage.mjs --file src/repl.js One file in detail
1276
+ node test/coverage.mjs --json Machine-readable
1277
+ ```
1278
+
1279
+ Zero dependencies rules out c8 and nyc, so this reads Node's own
1280
+ `NODE_V8_COVERAGE` instead — nothing new to get through an import review. It picks up
1281
+ child processes too, so the `cli` suite that spawns `deel` counts like everything else.
1282
+
1283
+ Currently **92% overall** (6,960 of 7,570 lines). Three files are deliberately left short.
1284
+
1285
+ | File | Now | Why it stops there |
1286
+ |---|---|---|
1287
+ | `tools/excel.js` | 67% | The password path needs Excel installed and a genuinely encrypted file. Faking it would produce a test that only *looks* like it passes |
1288
+ | `repl.js` | 77% | The keypress paths — Shift+Tab, Ctrl+C, password entry, paste. Reaching them needs a pty, and a pty is a dependency. What the screen *prints* is measured instead, as a value (`ui` and `tui` suites) |
1289
+ | `plugins/manage.js` | 79% | The GitHub download path. **Tests not reaching the network** matters more. Folder installs are covered |
1290
+
848
1291
  ---
849
1292
 
850
1293
  ## Licence