deel-local-cli 0.8.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
  ```
@@ -47,11 +47,15 @@ Zero dependencies · Node 20+ · Exactly one place your source can go
47
47
  - [Where your data can go](#where-your-data-can-go)
48
48
  - [Multiple local runtimes](#multiple-local-runtimes)
49
49
  - [Slash commands](#slash-commands)
50
+ - [Work modes](#work-modes)
51
+ - [Simple vs developer](#simple-vs-developer)
50
52
  - [Tools](#tools)
53
+ - [Korean text and Excel](#korean-text-and-excel)
51
54
  - [Skills and plugins](#skills-and-plugins)
52
55
  - [Reasoning effort](#reasoning-effort)
53
56
  - [Auto-compaction](#auto-compaction)
54
57
  - [Resuming a conversation](#resuming-a-conversation)
58
+ - [Attaching tools from outside (MCP)](#attaching-tools-from-outside-mcp)
55
59
  - [Safety](#safety)
56
60
  - [Corporate review package](#corporate-review-package)
57
61
  - [Configuration](#configuration)
@@ -207,12 +211,21 @@ Names follow Claude Code / Codex conventions.
207
211
  |---|---|
208
212
  | `/help` | Command list |
209
213
  | `/context` | What is consuming the context window |
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 |
210
216
  | `/compact` | Summarise and fold older turns |
211
217
  | `/clear` | Clear the conversation (keeps link and rules) |
212
218
  | `/model` | Switch connection / model |
213
- | `/think <level\|profile>` | `off·low·medium·high·max` or `even·save·deep` |
214
- | `/mode <mode>` | `auto` · `confirm` · `strict` |
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 |
222
+ | `/mode <mode>` | Approval policy — how much it asks (`auto` · `confirm` · `strict`) |
223
+ | `/work [mode]` | Work mode — what kind of work you are doing |
224
+ | `/auto` | Hand the wheel back — it picks the mode from what you type |
225
+ | `/code` `/plan` `/architect` `/debug` `/ask` `/orchestrator` | Switch work mode directly (pins it) |
226
+ | `/level [level]` | How much to show (`쉬움` simple · `개발자` developer) |
215
227
  | `/undo [turns]` | Revert file changes |
228
+ | `/diff [file]` | Files changed this session, and the changed lines |
216
229
  | `/tools` | Available tools |
217
230
  | `/skills [query\|all\|off]` | Browse, search, load skills |
218
231
  | `/plugin [install\|remove\|pack]` | Manage plugins |
@@ -220,6 +233,9 @@ Names follow Claude Code / Codex conventions.
220
233
  | `/status` | Connection status |
221
234
  | `/scan [save]` | Sweep this machine for local model servers (`save` registers them) |
222
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) |
223
239
  | `/init` | Create a `DEEL.md` rules file |
224
240
  | `/exit` | Quit |
225
241
 
@@ -229,6 +245,43 @@ Discovered plugin commands are invoked as `/<plugin>:<name>`, with `$ARGUMENTS`
229
245
  server or loaded a different model, `/scan save` then `/model` switches over without losing
230
246
  the conversation.
231
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
+
232
285
  ### Interrupting
233
286
 
234
287
  Press **Ctrl+C** to stop the model mid-answer when it is heading the wrong way.
@@ -252,22 +305,240 @@ Pressing Ctrl+C again on an empty line quits.
252
305
 
253
306
  ---
254
307
 
308
+ ## Work modes
309
+
310
+ What you are working on changes **which tools the model is given and how hard it thinks.**
311
+ Cycle with `Shift+Tab`, or type the name.
312
+
313
+ | Mode | For | Can edit files | Reasoning |
314
+ |---|---|---|---|
315
+ | `/auto` ◎ Auto | **Default.** Reads your message and switches for you | Yes | Normal (`save`) |
316
+ | `/code` ◆ Code | Writing and fixing | Yes | Normal (`save`) |
317
+ | `/plan` ☰ Plan | Planning first | **No** | Deep (`deep`·high) |
318
+ | `/architect` ◈ Architect | Shaping structure | **No** | Deep (`deep`·high) |
319
+ | `/debug` ◉ Debug | Finding causes | Yes | Deep, more steps (32) |
320
+ | `/ask` ◇ Ask | Explaining only | **No** | Shallow (`low`) |
321
+ | `/orchestrator` ❋ Orchestrator | Breaking up large work | Yes | Many steps (40) |
322
+
323
+ In read-only modes, `Write`, `Edit` and `Bash` are **never sent to the model at all.**
324
+ It is not asked politely not to edit — models forget requests. A tool that isn't there can't be used.
325
+
326
+ Don't confuse this with `/mode`. They are separate axes:
327
+
328
+ - `/mode` — **how much it asks you** (auto · confirm · strict)
329
+ - `/work` — **what kind of work you are doing** (the seven above)
330
+
331
+ If you have explicitly set `/think` or `/mode`, your choice wins. A work mode never
332
+ overrides something a person chose.
333
+
334
+ ### Switching by itself (Auto mode)
335
+
336
+ You start in **Auto**. Nothing has been decided about what kind of work is coming.
337
+ Every turn, deel reads what you typed, picks the mode that fits, and works in it.
338
+
339
+ ```
340
+ ❯ why won't the login go through?
341
+
342
+ ◉ Debug because your message contained "why won't", "won't"
343
+ Not what you wanted? Type /code to pin a mode yourself.
344
+ ```
345
+
346
+ Switching brings **the whole mode** with it — its working protocol, its tool set, its
347
+ reasoning settings. It isn't a label saying "debug mode": the model is actually walked
348
+ through symptom → reproduce → hypothesis → evidence, and in Plan mode `Write` and `Edit`
349
+ are not handed over at all.
350
+
351
+ | When you say | It goes to |
352
+ |---|---|
353
+ | why won't · error · fails · crashes · what's causing | ◉ Debug |
354
+ | plan · roadmap · what order · let's map it out first | ☰ Plan |
355
+ | design · architecture · how should this be structured · how to split | ◈ Architect |
356
+ | what is · explain · how does it work · difference between | ◇ Ask |
357
+ | all of · everything · one by one · to the end · unify | ❋ Orchestrator |
358
+ | fix · add · implement · rename · delete | ◆ Code |
359
+
360
+ **When it's close, it doesn't switch.** "ok", "go on", "that thing from earlier" leave you
361
+ in Auto. So does a near-tie between first and second place — a wrong switch into a read-only
362
+ mode leaves you blocked without knowing *why*. Read-only modes (Plan, Architect, Ask)
363
+ therefore carry a higher bar: "explain this and fix it" routes to Code, not Ask.
364
+
365
+ A switch lasts **one turn only.** The next message is judged fresh.
366
+ A `~` in the status line means it switched by itself; no `~` means you chose it.
367
+
368
+ ```
369
+ ◎ Auto ← waiting
370
+ ~◉ Debug ← this turn only, chosen for you
371
+ ◉ Debug ← you typed /debug. It stays.
372
+ ```
373
+
374
+ Choosing a mode yourself **pins** it. `/auto` (or `/work auto`) hands the wheel back.
375
+
376
+ ---
377
+
378
+ ## Simple vs developer
379
+
380
+ Twenty commands on first launch means nothing gets chosen. Locking features away means
381
+ hitting a wall later. So only **what is shown** differs.
382
+
383
+ | | Simple (`쉬움`, default) | Developer (`개발자`) |
384
+ |---|---|---|
385
+ | `/help` listing | Common commands only | Everything |
386
+ | Error messages | What to do about it | The original text |
387
+ | Safety | **Identical** | **Identical** |
388
+
389
+ `/level 개발자` is saved to config and persists across sessions.
390
+
391
+ Two things matter here:
392
+
393
+ - **Hidden commands still work.** `/think high` works in simple mode. It just isn't listed.
394
+ - **Beginners do not get fewer safeguards.** Undo, workspace scope and dangerous-command
395
+ blocking are identical. A beginner needs the undo more, not less.
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
+
443
+ ---
444
+
255
445
  ## Tools
256
446
 
257
447
  Names and arguments match Claude Code, so skills written for that convention work unchanged.
258
448
 
259
449
  | Tool | What it does |
260
450
  |---|---|
261
- | `Read` | Read a file (line numbers, `offset`/`limit`) |
451
+ | `Read` | Read a file (line numbers, `offset`/`limit`, **Excel as CSV**) |
262
452
  | `Write` | Write / overwrite a file |
453
+ | `Append` | Append to the end of a file — **how large files get written in pieces** |
263
454
  | `Edit` | Replace an exact string (`replace_all` supported) |
264
455
  | `Glob` | Find files by name pattern |
265
456
  | `Grep` | Regex search file contents |
266
457
  | `Bash` | Run a command |
267
458
  | `Skill` | Expand a skill body (shown to the model only when skills exist) |
268
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 |
269
462
  | `TodoWrite` | Checklist — breaks long work into steps and shows progress |
270
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
+
271
542
  ### Checklists
272
543
 
273
544
  Keeps the model from losing its place on multi-step work. The list is redrawn whenever the
@@ -318,6 +589,139 @@ Measured with `npm run bench`:
318
589
 
319
590
  On failure it points at the closest line in the file.
320
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
+
651
+ ---
652
+
653
+ ## Korean text and Excel
654
+
655
+ ### Encoding — written back the way it was read
656
+
657
+ Corporate documents are often not UTF-8. Files saved by old Windows Notepad in a legacy
658
+ codepage (CP949 in Korea, CP932 in Japan, GBK in China) are still around. Reading one as
659
+ UTF-8 garbles it completely: `한글` becomes `�ѱ�`.
660
+
661
+ Writing is the dangerous part. Read it garbled, save it as UTF-8, and the original is gone.
662
+ So there is one rule: **write it back in the encoding it was read in.**
663
+
664
+ Which encoding that is comes from **the file's contents, not the machine's settings.**
665
+ Each candidate is decoded strictly, then scored on whether the result looks like real text
666
+ written in that encoding. So the same CP949 document reads identically on Ubuntu, on a US
667
+ Windows machine, and on a Korean one.
668
+
669
+ ```
670
+ › Read report.txt
671
+ └ 4 lines · CP949
672
+ ```
673
+
674
+ If you try to insert a character that encoding **cannot hold**, it refuses instead of saving.
675
+
676
+ ```
677
+ › Edit report.txt note → note 🚀
678
+ └ This file is CP949, and you are inserting a character that encoding does not have: 🚀
679
+ ```
680
+
681
+ Silently substituting question marks would be worse than not writing at all.
682
+ Newly created files are UTF-8.
683
+
684
+ Command output is handled the same way. A Windows console is not UTF-8, so taking `Bash`
685
+ output as utf8 garbles non-ASCII text. It is collected as bytes and decoded afterwards.
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
+
693
+ ### Excel — read as CSV
694
+
695
+ An Excel file is a compressed archive, not text, so normally you get "this is a binary file"
696
+ and somebody has to export a CSV by hand. `Read` just does it.
697
+
698
+ ```
699
+ › Read report.xlsx
700
+ └ 3 sheets · 128 rows · unpacked directly
701
+ ```
702
+
703
+ - **Still zero dependencies.** An xlsx is a zip full of XML, so Node's built-in `zlib` is enough.
704
+ - Every sheet is returned. Hidden sheets too, marked as hidden.
705
+ - Dates come back as dates, not serial numbers — the cell format is read to decide.
706
+ - Formulas come back as **computed values**, and error values like `#REF!` are not dropped.
707
+
708
+ **Password-protected files and legacy `.xls`** are handed to Excel itself; those cannot be
709
+ unpacked directly. You are asked for the password at that point.
710
+
711
+ The password is **not stored anywhere**:
712
+
713
+ - not in the config file
714
+ - not in the session log
715
+ - not in the audit log
716
+ - not as a command-line argument (other people can see your command lines)
717
+
718
+ The only path out is the child process's stdin, and a test asserts that this stays true.
719
+ Extracted intermediate files are deleted after use.
720
+
721
+ > **Excel files are read-only here.** `Edit` and `Write` refuse them, and say why and what
722
+ > to do instead. Round-tripping a file with formatting, formulas and charts through CSV
723
+ > always loses something. Better not to write than to write knowing you'll lose data.
724
+
321
725
  ---
322
726
 
323
727
  ## Skills and plugins
@@ -372,15 +776,14 @@ with a licence table — ready to hand to a security reviewer.
372
776
  One answer means several model calls, and **each needs a different amount of thinking.**
373
777
  All-high is slow; all-low wanders off.
374
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
+
375
782
  ```
376
783
  $ /think
377
784
 
378
- Base medium Profile save Hard on the first decision, light while continuing
379
-
380
- Stage Effort Cap When
381
- first call · medium 4,096 deciding what to do
382
- continuing ↓ low 2,048 reading a tool result, picking the next step
383
- stuck ↑ high 4,096 the previous tool errored
785
+ 추론 강도 medium (첫 판단 medium · 이어가기 low · 막혔을 때 high)
786
+ 더 세게 /think high 더 빠르게 /think low
384
787
  ```
385
788
 
386
789
  | Profile | Character |
@@ -389,22 +792,163 @@ $ /think
389
792
  | `save` (default) | Hard on the first decision only |
390
793
  | `deep` | Everything one notch up — for hard work |
391
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
+
820
+ ### Context length is read off the model
821
+
822
+ This one number sizes the whole program: how many files fit in one read, when the
823
+ conversation gets folded, how long a single reply may be — **all of it comes from here.**
824
+
825
+ So deel asks the server on every launch rather than trusting the saved value. The same
826
+ model name can be loaded at a different length each time, and if that difference never
827
+ reaches the screen there is no way to notice. **It just quietly gets smaller.**
828
+
829
+ ```
830
+ │ Model qwen3-coder (640k tokens) │
831
+ ╰─────────────────────────────────────────────────────────╯
832
+ ✓ Context adjusted 32,768 → 655,360 (read from LM Studio)
833
+ ```
834
+
835
+ Every server puts this number under a different name in a different place, so deel checks
836
+ all of them.
837
+
838
+ | Server | Where it reads |
839
+ |---|---|
840
+ | LM Studio | `/api/v0/models` — `max_context_length`, `loaded_context_length` |
841
+ | llama.cpp | `/props` — `n_ctx` |
842
+ | vLLM | `/v1/models` — `max_model_len` |
843
+ | Ollama | `/api/show` — `<model>.context_length` |
844
+ | Other OpenAI-compatible | `/v1/models/<model>` — `context_window`, `context_length`, `max_input_tokens`, `max_position_embeddings` (found even when nested) |
845
+
846
+ **Model maximum and loaded length are not the same thing.** LM Studio will happily load a
847
+ 655,360-capable model at 8,192. Trusting the maximum there gets your requests rejected. So
848
+ the **loaded length is what deel uses**, and the maximum is reported separately.
849
+
850
+ ```
851
+ ⚠ This model goes up to 655,360 — raise it on the server, then /ctx auto
852
+ ```
853
+
854
+ | Command | What it does |
855
+ |---|---|
856
+ | `/ctx` | Current length and remaining room |
857
+ | `/ctx auto` | Ask the server again and match the model |
858
+ | `/ctx 655360` | Set it yourself (`640k`, `128k`, `1m` also work) |
859
+ | `/ctx 자세히` | Which endpoints were probed and what each returned — how to see why a lookup failed |
860
+ | `deel --ctx 655360` | Start at this value (skips the lookup) |
861
+
862
+ **`k` means 1024 here.** Context lengths are all powers of two, so that is the only base
863
+ that lines up: 655,360 is `640k`, not `655k`; 131,072 is `128k`, not `131k`. The display and
864
+ `/ctx` use the same unit, so typing back what you see gives you the same number.
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
+
392
881
  **Caps are not fixed numbers.** They are computed from the model's context window and how
393
882
  much of it is currently used — the profile decides what share of the remaining room a stage gets.
394
883
 
395
- | Model | First call | Continuing | Stuck |
396
- |---|---|---|---|
397
- | 2k local | 554 | 512 | 554 |
398
- | 8k local | 2,007 | 1,003 | 2,007 |
399
- | 40k (qwen3) | 11,688 | 5,844 | 11,688 |
400
- | 128k gateway | 16,384 | 16,384 | 16,384 |
401
- | 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 |
402
892
 
403
893
  Caps shrink as the context fills. Handing a 4k model a 4,096-token cap would leave no room for input.
404
- Raise the ceiling with `maxTokens` in the profile if you need more.
405
894
 
406
- If a saved cap truncates a reply, **that step alone is retried with the cap lifted.**
407
- 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.**
408
952
 
409
953
  ---
410
954
 
@@ -457,6 +1001,43 @@ covers `.deel/` so it never reaches a repository.
457
1001
 
458
1002
  ---
459
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
+
460
1041
  ## Safety
461
1042
 
462
1043
  Instead of approval prompts, the design makes things **reversible**. The default `auto` mode
@@ -465,10 +1046,13 @@ does not ask.
465
1046
  | Mechanism | Detail |
466
1047
  |---|---|
467
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 |
468
1050
  | **Scope** | Outside the starting folder is refused, even if the model insists |
469
1051
  | **Blocked commands** | Only irreversible ones (disk format, recursive delete, `--force` push) |
470
1052
  | **No re-run** | A mutating command is never retried after failure |
471
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 |
472
1056
  | **Audit log** | Everything recorded in `.deel/audit.jsonl` |
473
1057
 
474
1058
  | Mode | Asks when |
@@ -481,6 +1065,43 @@ Undo history stores whole file contents, so repeated edits to large files add up
481
1065
  it keeps the **most recent 50 turns** and drops the rest. What you just did is always
482
1066
  undoable; `/status` shows how large the history currently is.
483
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
+
484
1105
  ---
485
1106
 
486
1107
  ## Corporate review package
@@ -565,11 +1186,16 @@ Auth style is detected automatically: `Authorization: Bearer` → `x-api-key`
565
1186
  ```bash
566
1187
  deel --root <folder> Working scope. Defaults to the current folder
567
1188
  deel --mode <mode> auto (default) / confirm / strict
1189
+ deel --work <mode> auto (default) / code / plan / architect / debug / ask / orchestrator
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
568
1193
  deel --think <level> off / low / medium (default) / high / max
569
1194
  deel --effort <profile> even / save (default) / deep
570
1195
  deel --offline Nothing leaves this machine
571
1196
  deel --continue Resume the most recent conversation
572
1197
  deel --resume <id> Resume a specific one
1198
+ deel --no-tui Turn the full-screen view off; use the scrolling view (see below)
573
1199
  ```
574
1200
 
575
1201
  ### Project rules
@@ -590,7 +1216,9 @@ If the working folder has `DEEL.md`, `CLAUDE.md` or `AGENTS.md`, it is loaded as
590
1216
  | 401 / 403 | Wrong key or auth header style (four are tried automatically) |
591
1217
  | `address not permitted` | The lock did its job — pick a connection with `/model` |
592
1218
  | Tool calls don't work | Run `deel diagnose`. Small models (1B–3B) often can't |
593
- | 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 |
594
1222
  | `deel scan` finds nothing | Server is off or on another port — use `--ports` |
595
1223
 
596
1224
  ---
@@ -598,11 +1226,12 @@ If the working folder has `DEEL.md`, `CLAUDE.md` or `AGENTS.md`, it is loaded as
598
1226
  ## Development
599
1227
 
600
1228
  ```bash
601
- npm test Full suite (254 checks)
602
- npm run verify Import + network checks only
603
- npm run bench Edit success rate
604
- npm run demo See what the UI actually looks like
605
- 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
606
1235
  ```
607
1236
 
608
1237
  Tests run against a **fake gateway**, so the loop, streaming, tool execution, undo and compaction
@@ -619,17 +1248,46 @@ so one run tells you everything.
619
1248
  |---|---|---|
620
1249
  | `smoke` | 20 | Tools, scope, undo, audit log |
621
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 |
622
1252
  | `network` | 30 | Nothing escapes the configured address |
623
1253
  | `web` | 25 | Web reads stay read-only |
624
1254
  | `abort` | 16 | Ctrl+C leaves the conversation valid |
625
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 |
626
1264
  | `compact` | 21 | Summary folding, pairing intact, graceful fallback |
627
1265
  | `store` | 34 | Session persistence, resume, crash recovery |
628
- | `scan` | 19 | Distinguishing multiple runtimes |
1266
+ | `scan` | 29 | Distinguishing multiple runtimes |
629
1267
  | `plugins` | 38 | Plugin fetch/pack, ZIP/TAR |
630
1268
  | `no-bundle` | 12 | Nothing foreign in the published package; test-file hygiene |
631
1269
  | `edit-bench` | 20 cases | Edit success rate |
632
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
+
633
1291
  ---
634
1292
 
635
1293
  ## Licence