demian-cli 1.0.3
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.md +374 -0
- package/architecture-tui.md +468 -0
- package/architecture.md +2773 -0
- package/bin/demian +12 -0
- package/bin/demian-cli.js +12 -0
- package/bin/demian-plain.js +12 -0
- package/bin/demian.js +12 -0
- package/dist/cli.mjs +30346 -0
- package/dist/index.mjs +28826 -0
- package/dist/tui.mjs +33873 -0
- package/dist/vscode-worker.mjs +32479 -0
- package/docs/ko/README.md +158 -0
- package/media/demian.svg +9 -0
- package/package.json +67 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
# demian TUI Architecture
|
|
2
|
+
|
|
3
|
+
> Status: canonical TUI design
|
|
4
|
+
> Depends on: `nodejs/architecture.md`
|
|
5
|
+
> Scope: terminal UI layer for the integrated `demian` package
|
|
6
|
+
|
|
7
|
+
`demian` has two terminal experiences:
|
|
8
|
+
|
|
9
|
+
- `demian-plain`: plain CLI. It preserves raw Markdown output and uses an interactive startup flow when a TTY is available.
|
|
10
|
+
- `demian` and `demian-cli`: rich terminal UI powered by Ink.
|
|
11
|
+
|
|
12
|
+
The TUI is a presentation layer over the existing `SessionRunner` and `EventBus`. It must not change provider behavior, tool policy, permission ordering, transcript format, or safety rules.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Goals
|
|
17
|
+
|
|
18
|
+
- Provide a high-quality terminal experience for interactive coding-agent sessions.
|
|
19
|
+
- Render model Markdown richly inside the terminal.
|
|
20
|
+
- Keep plain CLI output raw and automation-friendly.
|
|
21
|
+
- Preserve the existing `SessionRunner` and `EventBus` contracts.
|
|
22
|
+
- Make Markdown rendering an independent module so it can be tested and improved without touching the session loop.
|
|
23
|
+
- Keep streaming responsive while still producing high-quality final output.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Command Split
|
|
28
|
+
|
|
29
|
+
| Command | Experience | Output contract |
|
|
30
|
+
|---------|------------|-----------------|
|
|
31
|
+
| `demian` | TUI | Interactive Ink UI |
|
|
32
|
+
| `demian-cli` | TUI alias | Interactive Ink UI |
|
|
33
|
+
| `demian-plain` | Plain CLI | Raw Markdown final answer on stdout |
|
|
34
|
+
|
|
35
|
+
Rationale:
|
|
36
|
+
|
|
37
|
+
- `demian` should be the best human-facing experience.
|
|
38
|
+
- `demian` and `demian-cli` open the TUI first, show the resolved defaults, and collect the first message inside the interface.
|
|
39
|
+
- Prompt arguments may remain as a compatibility shortcut, but the canonical UX is not shell-argument driven.
|
|
40
|
+
- `demian-plain` keeps raw Markdown output, but it also has an interactive settings/message flow when run in a TTY.
|
|
41
|
+
- The command split avoids adding mode ambiguity to the session runtime.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Dependencies
|
|
46
|
+
|
|
47
|
+
TUI dependencies:
|
|
48
|
+
|
|
49
|
+
- `ink`: terminal UI renderer
|
|
50
|
+
- `react`: Ink component runtime
|
|
51
|
+
- `marked`: Markdown parser
|
|
52
|
+
- `highlight.js`: code fence syntax highlighting
|
|
53
|
+
- `chalk`: terminal styling
|
|
54
|
+
- `string-width`, `wrap-ansi`, `slice-ansi`: terminal width and wrapping helpers
|
|
55
|
+
|
|
56
|
+
Dependency policy:
|
|
57
|
+
|
|
58
|
+
- These dependencies belong to the UI layer.
|
|
59
|
+
- Provider, tools, hooks, permissions, and transcript modules must not import Ink or Markdown renderer code.
|
|
60
|
+
- TUI modules may be dynamically imported by the TUI entrypoint so plain CLI and unit tests remain lightweight.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 4. Module Layout
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
src/
|
|
68
|
+
cli.ts # existing plain CLI main
|
|
69
|
+
tui.ts # TUI main
|
|
70
|
+
ui/
|
|
71
|
+
settings.ts # shared provider/model selection rules
|
|
72
|
+
plain-renderer.ts # current stderr/stdout renderer extracted later
|
|
73
|
+
plain/
|
|
74
|
+
interactive.ts # plain CLI settings/message startup flow
|
|
75
|
+
tui/
|
|
76
|
+
app.ts # Ink root component
|
|
77
|
+
controller.ts # config/session setup and event bridge
|
|
78
|
+
store.ts # event reducer, settings state, and view model
|
|
79
|
+
input.ts # initial prompt input helpers, extracted as needed
|
|
80
|
+
permission.ts # TUI permission prompt adapter
|
|
81
|
+
theme.ts # colors and semantic styles
|
|
82
|
+
markdown/
|
|
83
|
+
render.ts # Markdown -> render blocks
|
|
84
|
+
component.ts # Ink component for rendered blocks
|
|
85
|
+
highlight.ts # code fence highlighting
|
|
86
|
+
tables.ts # v1 simple table alignment
|
|
87
|
+
wrap.ts # terminal wrapping helpers
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Initial implementation may keep `plain-renderer.ts` implicit inside `cli.ts`. TUI modules should still be separate from CLI rendering.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 5. Rendering Model
|
|
95
|
+
|
|
96
|
+
The Markdown renderer is independent from Ink session orchestration.
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
markdown string
|
|
100
|
+
-> marked tokens
|
|
101
|
+
-> RenderBlock[]
|
|
102
|
+
-> Ink component rendering
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Render blocks:
|
|
106
|
+
|
|
107
|
+
- paragraph
|
|
108
|
+
- heading
|
|
109
|
+
- list
|
|
110
|
+
- blockquote
|
|
111
|
+
- code fence
|
|
112
|
+
- table
|
|
113
|
+
- horizontal rule
|
|
114
|
+
- plain fallback
|
|
115
|
+
|
|
116
|
+
Rendering policy:
|
|
117
|
+
|
|
118
|
+
- Plain CLI never renders Markdown. It prints raw Markdown.
|
|
119
|
+
- TUI renders Markdown for final assistant messages.
|
|
120
|
+
- Streaming uses an incremental fallback render.
|
|
121
|
+
- Once the assistant message is complete, the TUI re-renders with the full Markdown parser.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 6. Streaming Policy
|
|
126
|
+
|
|
127
|
+
During streaming:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
model.text.delta events
|
|
131
|
+
-> append to active assistant buffer
|
|
132
|
+
-> display incremental fallback render
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The incremental fallback renderer:
|
|
136
|
+
|
|
137
|
+
- preserves responsiveness
|
|
138
|
+
- does not require balanced Markdown syntax
|
|
139
|
+
- treats unclosed code fences as plain preformatted text
|
|
140
|
+
- avoids expensive full parse on every token
|
|
141
|
+
|
|
142
|
+
After completion:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
model.text event or session.ended
|
|
146
|
+
-> parse full assistant Markdown
|
|
147
|
+
-> replace fallback output with final rich render
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Rationale:
|
|
151
|
+
|
|
152
|
+
- Markdown streams often contain temporarily invalid syntax.
|
|
153
|
+
- Full parsing every delta is expensive and can cause visual churn.
|
|
154
|
+
- Final render should prioritize quality and correct layout.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 7. Markdown Quality
|
|
159
|
+
|
|
160
|
+
### Code Fences
|
|
161
|
+
|
|
162
|
+
Code fences support syntax highlighting with `highlight.js`.
|
|
163
|
+
|
|
164
|
+
Policy:
|
|
165
|
+
|
|
166
|
+
- If language is known, use language-specific highlighting.
|
|
167
|
+
- If language is unknown, use auto-detect only when cheap enough; otherwise render as plain code.
|
|
168
|
+
- Preserve indentation.
|
|
169
|
+
- Wrap long lines only when the pane width requires it.
|
|
170
|
+
- Show the language label when present.
|
|
171
|
+
|
|
172
|
+
### Tables
|
|
173
|
+
|
|
174
|
+
v1 table support is simple alignment:
|
|
175
|
+
|
|
176
|
+
- parse Markdown table rows
|
|
177
|
+
- calculate display width per column
|
|
178
|
+
- align with spaces
|
|
179
|
+
- wrap very long cells conservatively
|
|
180
|
+
|
|
181
|
+
v1.x improvements:
|
|
182
|
+
|
|
183
|
+
- column truncation
|
|
184
|
+
- horizontal scrolling
|
|
185
|
+
- border styles
|
|
186
|
+
- alignment markers (`:---`, `---:`, `:---:`)
|
|
187
|
+
|
|
188
|
+
### Links
|
|
189
|
+
|
|
190
|
+
Links render as `label (url)` initially. v1.x can add terminal hyperlink escape sequences where supported.
|
|
191
|
+
|
|
192
|
+
### Width
|
|
193
|
+
|
|
194
|
+
All layout must use terminal display width, not string length. CJK and ANSI styling require `string-width`, `wrap-ansi`, and `slice-ansi`.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 8. TUI Layout
|
|
199
|
+
|
|
200
|
+
Default layout:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
┌ status ─ provider / model / agent / cwd / sandbox ───────────┐
|
|
204
|
+
│ settings ─ p provider · m model · enter submit · ctrl+c exit ─│
|
|
205
|
+
│ transcript │
|
|
206
|
+
│ user prompt │
|
|
207
|
+
│ assistant markdown │
|
|
208
|
+
│ tool activity summaries │
|
|
209
|
+
│ │
|
|
210
|
+
├ activity / diagnostics ─ tool, retry, usage, transcript path ─┤
|
|
211
|
+
│ permission prompt or current action │
|
|
212
|
+
└ composer ─ first message input or permission keys ────────────┘
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The first implementation can be a single-column layout with:
|
|
216
|
+
|
|
217
|
+
- status bar
|
|
218
|
+
- settings/help bar
|
|
219
|
+
- scroll-like transcript area using recent events
|
|
220
|
+
- activity/status line
|
|
221
|
+
- initial prompt input line
|
|
222
|
+
- permission prompt line
|
|
223
|
+
|
|
224
|
+
The architecture should not require a full-screen widget system before the core TUI is useful.
|
|
225
|
+
|
|
226
|
+
### Interactive Message Loop
|
|
227
|
+
|
|
228
|
+
TUI prompt handling is split from the runtime session. The TUI process outlives individual task runs:
|
|
229
|
+
|
|
230
|
+
```text
|
|
231
|
+
demian
|
|
232
|
+
-> render TUI
|
|
233
|
+
-> show resolved provider/model defaults
|
|
234
|
+
-> allow provider/model edits before first submit
|
|
235
|
+
-> wait for first message in input line
|
|
236
|
+
-> submit prompt to SessionRunner
|
|
237
|
+
-> render assistant answer
|
|
238
|
+
-> return to standby composer
|
|
239
|
+
-> repeat until /exit or /quit
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The controller keeps prior non-system messages in memory and passes them as `SessionRunner.history` on the next run. The system prompt is rebuilt for each run so environment notes stay fresh.
|
|
243
|
+
|
|
244
|
+
If a prompt argument is supplied as a compatibility shortcut:
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
demian [flags] "prompt"
|
|
248
|
+
-> render TUI
|
|
249
|
+
-> show selected provider/model briefly
|
|
250
|
+
-> start SessionRunner with that prompt
|
|
251
|
+
-> return to standby composer after the answer
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Keys in composer mode:
|
|
255
|
+
|
|
256
|
+
- `Enter`: submit non-empty prompt
|
|
257
|
+
- `Ctrl+U`: clear input
|
|
258
|
+
- `Up`: recall the previous submitted prompt
|
|
259
|
+
- `Down`: move toward newer submitted prompts, then restore the current draft
|
|
260
|
+
- `p`: open provider selector when the composer is empty
|
|
261
|
+
- `m`: open model editor when the composer is empty
|
|
262
|
+
- `/exit` or `/quit`: exit the TUI
|
|
263
|
+
- `/stop`: report that no task is running while standby
|
|
264
|
+
|
|
265
|
+
Policy:
|
|
266
|
+
|
|
267
|
+
- The TUI owns initial settings selection and message collection.
|
|
268
|
+
- Submitted prompt history is stored only in the in-memory TUI store. It is capped and is not written to transcript or grants storage.
|
|
269
|
+
- Prompt history stores user prompts only. `/stop`, `/exit`, and `/quit` are control commands and are not recall entries.
|
|
270
|
+
- `SessionRunner` receives a normal `prompt: string` plus optional prior `history`, and remains unaware of TUI input state.
|
|
271
|
+
- Provider/model changes are allowed only before a run starts. A running tool loop has a stable provider and model.
|
|
272
|
+
|
|
273
|
+
During a running task, the bottom composer switches to command mode:
|
|
274
|
+
|
|
275
|
+
- `/stop`: abort the active run and return to standby.
|
|
276
|
+
- `/exit` or `/quit`: abort the active run and exit the TUI.
|
|
277
|
+
- Other text is rejected until the current run finishes.
|
|
278
|
+
|
|
279
|
+
This keeps the UI available for control commands while avoiding concurrent task runs in v1.
|
|
280
|
+
|
|
281
|
+
### Provider And Model Selection
|
|
282
|
+
|
|
283
|
+
The TUI must make configured defaults visible before the first prompt.
|
|
284
|
+
|
|
285
|
+
Status/settings display:
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
provider openai model gpt-model-name agent build cwd ./repo
|
|
289
|
+
[p] provider [m] model [enter] send [ctrl+c] exit
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Provider selector:
|
|
293
|
+
|
|
294
|
+
- Opens with `p` while the composer is empty and no session is running.
|
|
295
|
+
- Lists configured provider keys from resolved config.
|
|
296
|
+
- Highlights the current provider and marks the source as `config`, `saved`, `flag`, or `interactive`.
|
|
297
|
+
- `Up`/`Down` moves selection.
|
|
298
|
+
- `Enter` selects provider.
|
|
299
|
+
- `Esc` returns to composer without changes.
|
|
300
|
+
|
|
301
|
+
Model editor:
|
|
302
|
+
|
|
303
|
+
- Opens with `m` while the composer is empty and no session is running.
|
|
304
|
+
- Shows the selected provider's configured model as the default.
|
|
305
|
+
- Accepts free-form model or deployment name text.
|
|
306
|
+
- `Enter` accepts the typed model or keeps the default when blank.
|
|
307
|
+
- `Esc` returns to composer without changes.
|
|
308
|
+
|
|
309
|
+
Provider/model state:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
interface InteractiveModelSelection {
|
|
313
|
+
providerName: string
|
|
314
|
+
providerSource: "config" | "saved" | "flag" | "interactive"
|
|
315
|
+
model: string
|
|
316
|
+
modelSource: "config" | "saved" | "flag" | "interactive"
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Rules:
|
|
321
|
+
|
|
322
|
+
- Saved UI preferences from `.demian/preferences.json` override config defaults.
|
|
323
|
+
- CLI flags preselect provider/model, override saved preferences, and mark values as flag-sourced.
|
|
324
|
+
- `config` means the resolved value after built-in defaults, user config, workspace config, and `--config` merge. The current config loader does not expose finer provenance.
|
|
325
|
+
- `saved` means the last explicit provider/model selection from this workspace. Only provider key and model name are stored; API keys and provider config are never copied into preferences.
|
|
326
|
+
- If the user changes provider, the model resets to that provider's configured default.
|
|
327
|
+
- If the new provider has no configured model, the TUI opens model editor immediately.
|
|
328
|
+
- Provider/model selection is committed into the `SessionRunner` options only when the message is submitted.
|
|
329
|
+
- The selector must not import provider SDKs or make network calls. It only reads resolved local config.
|
|
330
|
+
- The selected provider/model is saved after an interactive or flag-sourced selection.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 9. Event Mapping
|
|
335
|
+
|
|
336
|
+
TUI subscribes to `RuntimeEvent`:
|
|
337
|
+
|
|
338
|
+
| Event | TUI behavior |
|
|
339
|
+
|-------|--------------|
|
|
340
|
+
| `session.started` | initialize status |
|
|
341
|
+
| `user.message` | append user block |
|
|
342
|
+
| `model.request` | show model activity |
|
|
343
|
+
| `model.text.delta` | update streaming fallback buffer |
|
|
344
|
+
| `model.text` | finalize rich Markdown block |
|
|
345
|
+
| `model.usage` | update usage summary |
|
|
346
|
+
| `model.content_filter` | show warning block |
|
|
347
|
+
| `provider.retry` | show retry diagnostics |
|
|
348
|
+
| `tool.requested` | append pending tool item |
|
|
349
|
+
| `tool.started` | mark tool running |
|
|
350
|
+
| `tool.completed` | mark tool done and preview |
|
|
351
|
+
| `tool.failed` | mark tool failed |
|
|
352
|
+
| `permission.requested` | show permission prompt and make it the bottom bar priority |
|
|
353
|
+
| `permission.granted` | show grant key |
|
|
354
|
+
| `permission.denied` | show denial |
|
|
355
|
+
| `session.ended` | show completion state, then return to standby composer |
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 10. Permission UX
|
|
360
|
+
|
|
361
|
+
TUI provides `SessionRunner.permissionPrompt`.
|
|
362
|
+
|
|
363
|
+
Permission prompts are modal inside the TUI. When a tool is waiting for approval, the bottom bar must show the permission prompt instead of the generic running command bar. This is important because keypresses are handled as permission answers while the session is otherwise still in `running` mode.
|
|
364
|
+
|
|
365
|
+
The prompt displays a human-readable summary of the tool input before shortcuts. For `bash`, the command is shown explicitly; for file tools, the path is shown explicitly; for text-heavy inputs, content is summarized by character count and a short preview. Raw JSON is only a fallback for unknown shapes.
|
|
366
|
+
|
|
367
|
+
Keys:
|
|
368
|
+
|
|
369
|
+
- `y`: allow once
|
|
370
|
+
- `a`: allow and persist according to configured grant scope
|
|
371
|
+
- `n`: deny
|
|
372
|
+
- `Enter`: deny by default
|
|
373
|
+
- `Ctrl+C`: emergency exit from the TUI
|
|
374
|
+
|
|
375
|
+
Policy:
|
|
376
|
+
|
|
377
|
+
- TUI permission answers must call the same permission engine path as plain CLI.
|
|
378
|
+
- Deny and hook blocks still win over `y`, `a`, and `--yes`.
|
|
379
|
+
- When stdin is not a TTY, TUI should refuse to start and suggest `demian-plain`.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 11. Failure And Fallback
|
|
384
|
+
|
|
385
|
+
If TUI dependencies are missing:
|
|
386
|
+
|
|
387
|
+
- `demian` and `demian-cli` should print a clear install hint.
|
|
388
|
+
- `demian-plain` should still work.
|
|
389
|
+
|
|
390
|
+
If terminal is not interactive:
|
|
391
|
+
|
|
392
|
+
- TUI should fail fast with a message.
|
|
393
|
+
- Plain CLI remains the supported non-interactive path only when an explicit automation input path is provided.
|
|
394
|
+
- Interactive provider/model prompts require a TTY.
|
|
395
|
+
|
|
396
|
+
If Markdown rendering fails:
|
|
397
|
+
|
|
398
|
+
- render raw Markdown in the TUI block
|
|
399
|
+
- show a low-noise warning in diagnostics
|
|
400
|
+
- do not fail the session
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 12. Implementation Sequence
|
|
405
|
+
|
|
406
|
+
1. Add `architecture-tui.md`.
|
|
407
|
+
2. Add TUI dependencies to `package.json`.
|
|
408
|
+
3. Add `bin/demian-plain.js` for existing plain CLI.
|
|
409
|
+
4. Change `bin/demian.js` to TUI entrypoint.
|
|
410
|
+
5. Add `bin/demian-cli.js` as TUI alias.
|
|
411
|
+
6. Implement `src/tui.ts`.
|
|
412
|
+
7. Implement Ink app/controller/state.
|
|
413
|
+
8. Implement initial TUI prompt input for promptless startup.
|
|
414
|
+
9. Implement provider selector and model editor before session start.
|
|
415
|
+
10. Implement plain CLI interactive provider/model/message flow.
|
|
416
|
+
11. Keep CLI/TUI alive after each run and support `/stop`, `/exit`, and `/quit`.
|
|
417
|
+
12. Implement independent Markdown renderer with parser and syntax highlighting.
|
|
418
|
+
13. Wire TUI permission prompt.
|
|
419
|
+
14. Add smoke tests:
|
|
420
|
+
- `demian-plain --help`
|
|
421
|
+
- `demian --help`
|
|
422
|
+
- TUI promptless startup in a pseudo-TTY
|
|
423
|
+
- plain CLI promptless startup in a pseudo-TTY
|
|
424
|
+
- Markdown renderer fixture tests without running an interactive TUI.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## 13. Non-goals
|
|
429
|
+
|
|
430
|
+
- Replacing JSONL transcripts.
|
|
431
|
+
- Changing provider/tool/session contracts.
|
|
432
|
+
- Implementing a full editor inside the TUI beyond the initial single-line prompt input.
|
|
433
|
+
- Building an IDE-like file explorer in v1.
|
|
434
|
+
- Perfect Markdown/GFM rendering in the first implementation.
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 14. Decision Log
|
|
439
|
+
|
|
440
|
+
| Decision | Choice | Reason |
|
|
441
|
+
|----------|--------|--------|
|
|
442
|
+
| Default command | `demian` opens TUI | Human-facing default should be high quality |
|
|
443
|
+
| Plain command | `demian-plain` | Preserve raw Markdown and automation |
|
|
444
|
+
| TUI alias | `demian-cli` | Explicit terminal UI command requested |
|
|
445
|
+
| Promptless start | all commands enter UI/CLI first | Interactive use should not require long shell-argument prompts |
|
|
446
|
+
| TUI provider selector | `p` before run | Defaults are visible and can be corrected without editing config |
|
|
447
|
+
| TUI model editor | `m` before run | Model names and Azure deployment names are often deployment-specific |
|
|
448
|
+
| Provider/model source labels | `config`, `saved`, `flag`, `interactive` | Shows whether the value came from config, remembered UI preferences, flags, or current UI edits |
|
|
449
|
+
| Provider/model lock | locked during a run | Keeps one tool loop deterministic and transcript-readable |
|
|
450
|
+
| Persistent UI loop | return to standby after each run | The command should feel like an interactive session, not a one-shot wrapper |
|
|
451
|
+
| Stop command | `/stop` | User needs a clear in-UI way to abort active work without closing the whole UI |
|
|
452
|
+
| Exit command | `/exit` and `/quit` | User needs an explicit way to close the interactive CLI/TUI |
|
|
453
|
+
| Prompt recall | `Up`/`Down` over in-memory submitted prompts | Fast prompt reuse without adding persistence or leaking commands into history |
|
|
454
|
+
| Provider/model preferences | project-local `.demian/preferences.json` | Reuse the last explicit selection without mutating user config or storing secrets |
|
|
455
|
+
| Framework | Ink + React | Best balance of UI quality and implementation speed |
|
|
456
|
+
| Markdown parser | `marked` | Practical parser with simple token API |
|
|
457
|
+
| Syntax highlight | `highlight.js` | Good quality with reasonable weight |
|
|
458
|
+
| Streaming render | incremental fallback | Streaming Markdown is often incomplete |
|
|
459
|
+
| Final render | full Markdown parse | Quality matters once message is complete |
|
|
460
|
+
| Tables | v1 simple alignment | Useful baseline, advanced layout later |
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## 15. Summary
|
|
465
|
+
|
|
466
|
+
The TUI is a rich presentation layer over the existing agent runtime. It shows provider/model defaults before the first message and lets the user change them with focused shortcuts. Plain CLI remains raw and scriptable, but also offers a TTY-based provider/model/message flow. Markdown rendering is independent, high-quality final rendering is prioritized, and streaming stays responsive through a fallback renderer.
|
|
467
|
+
|
|
468
|
+
`demian` and `demian-cli` are interactive TUI commands. `demian-plain` is the stable plain CLI with interactive startup when a TTY is available.
|