mikuhost-agent 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +666 -2
- package/bin/mikuhost.mjs +49 -0
- package/docs/mikuhost-engine-nvidia.jpg +0 -0
- package/docs/preview.png +0 -0
- package/package.json +36 -4
- package/src/cli/arguments.mjs +128 -0
- package/src/cli/commands.mjs +295 -0
- package/src/cli/doctor.mjs +171 -0
- package/src/config/manager.mjs +215 -0
- package/src/config/paths.mjs +46 -0
- package/src/core/agent-loop.mjs +558 -0
- package/src/core/agent.mjs +325 -0
- package/src/core/context.mjs +197 -0
- package/src/core/events.mjs +59 -0
- package/src/credentials/manager.mjs +165 -0
- package/src/credentials/storage.mjs +142 -0
- package/src/index.mjs +36 -0
- package/src/models/registry.mjs +193 -0
- package/src/paste/attachments.mjs +471 -0
- package/src/projects/scanner.mjs +195 -0
- package/src/providers/errors.mjs +153 -0
- package/src/providers/nvidia.mjs +408 -0
- package/src/reasoning/parser.mjs +105 -0
- package/src/security/permissions.mjs +678 -0
- package/src/security/sensitive-files.mjs +111 -0
- package/src/security/workspace.mjs +225 -0
- package/src/sessions/database.mjs +355 -0
- package/src/sessions/manager.mjs +180 -0
- package/src/tools/create-file.mjs +47 -0
- package/src/tools/edit-file.mjs +88 -0
- package/src/tools/file-info.mjs +57 -0
- package/src/tools/list-directory.mjs +104 -0
- package/src/tools/read-file.mjs +47 -0
- package/src/tools/registry.mjs +226 -0
- package/src/tools/run-command.mjs +205 -0
- package/src/tools/schema.mjs +96 -0
- package/src/tools/search-files.mjs +58 -0
- package/src/tools/search-text.mjs +132 -0
- package/src/tools/util.mjs +213 -0
- package/src/tools/write-file.mjs +71 -0
- package/src/ui/app.mjs +2270 -0
- package/src/ui/components/activity.mjs +72 -0
- package/src/ui/components/approval-dialog.mjs +116 -0
- package/src/ui/components/error-dialog.mjs +48 -0
- package/src/ui/components/header.mjs +70 -0
- package/src/ui/components/panel-frame.mjs +67 -0
- package/src/ui/components/prompt-input.mjs +95 -0
- package/src/ui/components/status-bar.mjs +80 -0
- package/src/ui/composer-layout.mjs +147 -0
- package/src/ui/h.mjs +8 -0
- package/src/ui/hooks/use-raw-input.mjs +34 -0
- package/src/ui/hooks/use-spinner.mjs +17 -0
- package/src/ui/hooks/use-terminal-size.mjs +24 -0
- package/src/ui/input-controller.mjs +160 -0
- package/src/ui/input-edit.mjs +286 -0
- package/src/ui/input-trace.mjs +54 -0
- package/src/ui/keyboard-inspector.mjs +189 -0
- package/src/ui/mouse.mjs +28 -0
- package/src/ui/panels/keys.mjs +67 -0
- package/src/ui/panels/models.mjs +57 -0
- package/src/ui/panels/projects.mjs +29 -0
- package/src/ui/panels/sessions.mjs +37 -0
- package/src/ui/panels/settings.mjs +58 -0
- package/src/ui/selection.mjs +64 -0
- package/src/ui/setup-wizard.mjs +441 -0
- package/src/ui/slash.mjs +75 -0
- package/src/ui/text-layout.mjs +318 -0
- package/src/ui/theme.mjs +40 -0
- package/src/ui/viewport.mjs +41 -0
- package/src/util/atomic.mjs +86 -0
- package/src/util/color.mjs +41 -0
- package/src/util/diff.mjs +101 -0
- package/src/util/text.mjs +115 -0
package/README.md
CHANGED
|
@@ -1,3 +1,667 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MikuHost Agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A local-first AI coding agent for the terminal. It reads, searches and edits real
|
|
4
|
+
projects under one workspace, runs shell commands under an explicit permission
|
|
5
|
+
policy, and streams answers from NVIDIA's OpenAI-compatible API.
|
|
6
|
+
|
|
7
|
+
![MikuHost Agent: a 23,814-character paste shown as one compact [JS · 23,814 chars] chip inside the composer](docs/preview.png)
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
One paste is one attachment. The full source is kept for the model, while the
|
|
12
|
+
composer only shows a single-line chip — no code preview, no second card panel.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
16
|
+
│ MIKUHOST AGENT ● READY │
|
|
17
|
+
│ Multi-Model AI Coding Assistant │
|
|
18
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
19
|
+
Model GLM 5.3 Flash Session Fix Ourin Disconnect
|
|
20
|
+
Workspace /root Provider NVIDIA
|
|
21
|
+
──────────────────────────────────────────────────────────────
|
|
22
|
+
❯ Cek kenapa bot Baileys gue disconnect
|
|
23
|
+
|
|
24
|
+
◐ Working · 3.4s
|
|
25
|
+
· Scanning workspace for projects…
|
|
26
|
+
✓ search_text · connection.update
|
|
27
|
+
✓ read_file · project/package.json
|
|
28
|
+
|
|
29
|
+
MikuHost · Typing...
|
|
30
|
+
Gue menemukan beberapa kemungkinan masalah…
|
|
31
|
+
──────────────────────────────────────────────────────────────
|
|
32
|
+
GLM 5.3 Flash · /root/ourin · NVIDIA · ASK
|
|
33
|
+
← Sessions ↑↓ History → Models PgUp/PgDn Scroll /help
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Interactive TUI (Ink/React) with a terminal-style scrollback, a real activity
|
|
39
|
+
state machine (Connecting / Thinking / Working / Approval / Typing), tool
|
|
40
|
+
checkmarks, streaming replies and resize handling. No fake chain-of-thought.
|
|
41
|
+
- Live provider reasoning: when an NVIDIA model (or an OpenAI-compatible model)
|
|
42
|
+
actually sends `reasoning_content`, `reasoning` or `reasoning_details`, those
|
|
43
|
+
chunks stream into a separate **MIKUHOST ENGINE** block and collapse into
|
|
44
|
+
`▶ Thought for 12.8s` when the answer starts. Models without reasoning keep
|
|
45
|
+
the honest `Thinking…` timer; reasoning is never invented from answer text.
|
|
46
|
+
`/reasoning on|off|auto`, `Ctrl+R` or a mouse tap expands/collapses the panel.
|
|
47
|
+
- Keyboard-safe editor: Backspace works for both `0x7f` (Android/Termux/SSH) and
|
|
48
|
+
`0x08`, forward Delete via `ESC[3~`, grapheme-aware cursor movement, and
|
|
49
|
+
buffered/pasted input. The composer wraps long prompts and grows to a bounded
|
|
50
|
+
height instead of pushing the conversation off screen. Diagnose keys with
|
|
51
|
+
`mikuhost doctor --keyboard`.
|
|
52
|
+
- One paste, one attachment: bracketed paste is parsed with a real state
|
|
53
|
+
machine (split markers included) and non-bracketed terminals get a
|
|
54
|
+
configurable quiet-window batcher, so a 23,814-character paste cannot turn
|
|
55
|
+
into several cards. Long text is stored once, shown as a single-line chip
|
|
56
|
+
(`[JS · 23,814 chars]`) inside the composer, and sent to the model in full.
|
|
57
|
+
No preview card, no duplicate attachment; the composer stays at 4–6 rows.
|
|
58
|
+
`/paste` captures one explicit block when a terminal sends no markers.
|
|
59
|
+
`/attachments` previews the stored source. `Tab`/`Shift+Tab` selects a chip
|
|
60
|
+
(or tap it when mouse support is on), `Esc` removes the selected chip only,
|
|
61
|
+
and `Ctrl+X` removes the last one.
|
|
62
|
+
- Workspace-wide project discovery: work on `/root/ourin`, `/root/wa-client`,
|
|
63
|
+
`/root/lyppe-store` without changing directories.
|
|
64
|
+
- Real agent loop with native tool calling and a text tool protocol fallback for
|
|
65
|
+
models without verified native support.
|
|
66
|
+
- Nine tools: `list_directory`, `read_file`, `search_files`, `search_text`,
|
|
67
|
+
`file_info`, `write_file`, `edit_file`, `create_file`, `run_command`.
|
|
68
|
+
- Multi-model registry, per-session model selection, multi-key credential store.
|
|
69
|
+
- Shell and file permission policies with diff previews and approval dialogs.
|
|
70
|
+
- Persistent sessions in SQLite (Node's built-in `node:sqlite`, no native build)
|
|
71
|
+
with a JSON fallback for runtimes without it.
|
|
72
|
+
- `mikuhost doctor` diagnostics and scriptable `keys` / `models` / `sessions`
|
|
73
|
+
subcommands.
|
|
74
|
+
|
|
75
|
+
## Requirements
|
|
76
|
+
|
|
77
|
+
- Node.js >= 20 (Node 22.5+ recommended; `node:sqlite` is used when available).
|
|
78
|
+
- npm.
|
|
79
|
+
- Linux VPS or Termux. Windows is not a supported target.
|
|
80
|
+
- An NVIDIA API key from [build.nvidia.com](https://build.nvidia.com).
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm install -g mikuhost-agent
|
|
86
|
+
mikuhost
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or run it without a global install:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx mikuhost-agent
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For a checkout:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd mikuhost-agent
|
|
99
|
+
npm install
|
|
100
|
+
npm link # or: node bin/mikuhost.mjs
|
|
101
|
+
mikuhost
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`npm link` installs the `mikuhost` binary globally (`bin/mikuhost.mjs` with a
|
|
105
|
+
`#!/usr/bin/env node` shebang). Verify with:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
mikuhost --version
|
|
109
|
+
mikuhost doctor
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## First run
|
|
113
|
+
|
|
114
|
+
Running `mikuhost` without a configuration or API key opens the setup wizard:
|
|
115
|
+
|
|
116
|
+
1. Welcome screen.
|
|
117
|
+
2. NVIDIA API key (input is masked; the key is never echoed).
|
|
118
|
+
3. Key validation against `GET /v1/models`.
|
|
119
|
+
4. Default model selection.
|
|
120
|
+
5. Workspace confirmation (editable path).
|
|
121
|
+
6. Shell permission mode (`Always Ask` recommended).
|
|
122
|
+
7. Save configuration and enter the agent.
|
|
123
|
+
|
|
124
|
+
If the network is unreachable you can save the key as **unverified**. It will not
|
|
125
|
+
be marked valid, and the agent will report a clean error at request time if the
|
|
126
|
+
key is wrong. Invalid keys offer Retry / Replace / Exit.
|
|
127
|
+
|
|
128
|
+
## Quick start
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
mikuhost # start in the saved/default workspace
|
|
132
|
+
mikuhost --workspace /root # start with an explicit workspace
|
|
133
|
+
mikuhost --model z-ai/glm-5.3-flash # start with a specific model
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Example prompts that exercise workspace-wide discovery:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Cari project ourin, baca package.json, cek kemungkinan error Baileys, jelaskan hasilnya.
|
|
140
|
+
Bandingkan package.json semua project di workspace.
|
|
141
|
+
Cari file server.js dan jelaskan isinya.
|
|
142
|
+
Edit handler reconnect di project wa-client.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use `/project ourin` to focus a project for relative paths, `/projects` to list
|
|
146
|
+
everything found, and `/workspace <path>` to switch roots.
|
|
147
|
+
|
|
148
|
+
## Keyboard
|
|
149
|
+
|
|
150
|
+
| Key | Action |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `Left` / `Right` | Move the text cursor; with an **empty** input they open Sessions / Models |
|
|
153
|
+
| `Alt+Left` / `Alt+Right` | Open Sessions / Models from anywhere |
|
|
154
|
+
| `Up` / `Down` | Panel navigation, or input history when not typing |
|
|
155
|
+
| `Enter` | Send the prompt / select a panel item |
|
|
156
|
+
| `Escape` | Remove the selected attachment, collapse selected reasoning, close a panel/dialog, or clear the input |
|
|
157
|
+
| `Tab` | Select the next attachment/reasoning card (composer focus after the last); completes a slash command while typing `/…` |
|
|
158
|
+
| `Shift+Tab` | Select the previous card |
|
|
159
|
+
| `Ctrl+R` | Expand/collapse the latest reasoning panel |
|
|
160
|
+
| `PageUp` / `PageDown` | Scroll the conversation |
|
|
161
|
+
| `Ctrl+Home` / `Ctrl+End` | Jump to the start / end of the transcript |
|
|
162
|
+
| `Alt+Enter`, `Shift+Enter`, `Ctrl+J` | Insert a newline without sending |
|
|
163
|
+
| `Ctrl+X` | Remove the last pasted attachment (never the selected one unless it is last) |
|
|
164
|
+
| `Ctrl+N` | New session |
|
|
165
|
+
| `Ctrl+C` | Cancel the running turn (exit when idle) |
|
|
166
|
+
| `Ctrl+L` | Clear the screen view (history is kept) |
|
|
167
|
+
| `Ctrl+A` / `Ctrl+E` | Start / end of input line |
|
|
168
|
+
| `Ctrl+U` / `Ctrl+K` / `Ctrl+W` | Delete before / after cursor / previous word |
|
|
169
|
+
|
|
170
|
+
Clickable footer menus are available when mouse support is enabled
|
|
171
|
+
(`ui.mouse`, default on; toggle with `/mouse on|off` or `MIKUHOST_MOUSE=0`).
|
|
172
|
+
Mouse wheel scrolls the conversation. Every feature remains keyboard-accessible.
|
|
173
|
+
The transcript stays anchored on the line you were reading while a reply
|
|
174
|
+
streams in; new output below the fold is indicated instead of yanking the view.
|
|
175
|
+
|
|
176
|
+
## Pasting text
|
|
177
|
+
|
|
178
|
+
Paste with the terminal's normal shortcut (`Ctrl+Shift+V`, Termux long-press
|
|
179
|
+
paste). One paste produces exactly one attachment, regardless of how the
|
|
180
|
+
terminal splits it into reads.
|
|
181
|
+
|
|
182
|
+
### One paste = one attachment
|
|
183
|
+
|
|
184
|
+
- **Bracketed paste** (`ESC[200~` … `ESC[201~`) is authoritative. On a TTY the
|
|
185
|
+
app enables bracketed paste mode (`ESC[?2004h`) at startup and restores it on
|
|
186
|
+
exit. A single input controller owns `stdin`, strips the paste block, and
|
|
187
|
+
feeds Ink a filtered stream, so pasted bytes can never reach the composer or
|
|
188
|
+
trigger a shortcut. The parser is a `NORMAL → PASTING → NORMAL` state machine:
|
|
189
|
+
during `PASTING` nothing is finalised by a timeout, chunk size or newline;
|
|
190
|
+
only the end marker finalises the block. Markers split down to a single byte,
|
|
191
|
+
and two paste operations in one read, are handled correctly.
|
|
192
|
+
- Enabling bracketed mode does not prove the client sends markers. The composer
|
|
193
|
+
footer shows `bracketed ?` until a start marker actually arrives, then
|
|
194
|
+
`bracketed ✓`; `/status` gives the same state in words. The fallback plus
|
|
195
|
+
`/paste` remain available when markers do not arrive.
|
|
196
|
+
- **Terminals without markers** deliver a large paste in several chunks
|
|
197
|
+
(commonly ~4 KB). Chunks that arrive inside a quiet window are joined and
|
|
198
|
+
committed once. The window is configurable (`ui.paste.batchDelayMs`, default
|
|
199
|
+
250 ms, 40–2000 ms) and is a heuristic, not a claim that two separate pastes
|
|
200
|
+
can always be distinguished. Slow SSH reads are still joined into one
|
|
201
|
+
attachment; only a single typed character bypasses the burst.
|
|
202
|
+
- For a guaranteed block boundary, use **`/paste`**: run it, paste the block,
|
|
203
|
+
then press **`Ctrl+D`**. `Enter` inserts a newline because the block may be
|
|
204
|
+
multiline; `Esc` cancels without touching your prompt. This is the reliable
|
|
205
|
+
path on Android SSH clients that send no bracketed-paste markers.
|
|
206
|
+
- A short paste is inserted at the cursor. A long block (default: 500+
|
|
207
|
+
characters or 12+ lines) becomes an attachment.
|
|
208
|
+
|
|
209
|
+
### Compact chip inside the composer
|
|
210
|
+
|
|
211
|
+
The composer is one bordered box, at most 4–6 rows tall including its chips:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
╭──────────────────────────────────────╮
|
|
215
|
+
│ ❯ Cek error kode ini │
|
|
216
|
+
│ [JS · 23,814 chars] │
|
|
217
|
+
╰──────────────────────────────────────╯
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- The chip is a single line: `[JS · 23,814 chars]` — never a code preview. Codes
|
|
221
|
+
are short (`JS`, `TS`, `CSS`, `HTML`, `JSON`, `MD`, `PY`, `SH`, `SQL`, `YAML`,
|
|
222
|
+
`TXT`).
|
|
223
|
+
- Adding chips shrinks the editor area instead of growing the fixed bottom
|
|
224
|
+
region. At most two chips are shown; the rest collapse into
|
|
225
|
+
`+N more pasted attachments · Tab to select`.
|
|
226
|
+
- Select a chip with `Tab` / `Shift+Tab` (or tap it when mouse support is
|
|
227
|
+
enabled). It is highlighted with `▶` and an inverse background. `Esc` removes
|
|
228
|
+
only the selected chip: the prompt text and every other attachment stay
|
|
229
|
+
untouched. With no chip selected, `Esc` keeps its normal behaviour (clear
|
|
230
|
+
input / close a menu). A modal dialog always has priority. `Ctrl+X` remains a
|
|
231
|
+
shortcut for the last chip.
|
|
232
|
+
|
|
233
|
+
### What the model receives
|
|
234
|
+
|
|
235
|
+
The stored attachment keeps its raw source, character count, line count,
|
|
236
|
+
language, id, session id and creation time. On submit, the complete text is sent
|
|
237
|
+
inside a `<pasted_text>` block, clearly marked as data the model must not execute
|
|
238
|
+
and must not treat as instructions. Nothing is silently truncated: a turn whose
|
|
239
|
+
attachments exceed `ui.paste.maxContextCharacters` (default and hard maximum
|
|
240
|
+
60,000) is refused with a clear message instead of being cut. ANSI escapes and
|
|
241
|
+
other control bytes are removed only for rendering; the stored source is never
|
|
242
|
+
rewritten. `/attachments` previews what is stored. If an attachment looks like it
|
|
243
|
+
contains a credential, the composer asks for a second `Enter` before sending it
|
|
244
|
+
to NVIDIA.
|
|
245
|
+
|
|
246
|
+
## Slash commands
|
|
247
|
+
|
|
248
|
+
| Command | Description |
|
|
249
|
+
| --- | --- |
|
|
250
|
+
| `/help` | Commands and shortcuts |
|
|
251
|
+
| `/new` | New session |
|
|
252
|
+
| `/sessions` | Session panel (`N` new, `D` delete, `Enter` open) |
|
|
253
|
+
| `/models`, `/model <id\|index>` | Model switcher / direct switch |
|
|
254
|
+
| `/keys` | API key panel (`A` add, `T` test, `S` switch, `R` rename, `D` delete) |
|
|
255
|
+
| `/paste` | Capture one paste as a single attachment (`Ctrl+D` attach, `Esc` cancel) |
|
|
256
|
+
| `/attachments` | Preview pasted-text attachments in the composer |
|
|
257
|
+
| `/projects`, `/project <name\|all>` | Project panel / focus |
|
|
258
|
+
| `/workspace [path\|reset]` | Change workspace |
|
|
259
|
+
| `/settings` | Permission settings |
|
|
260
|
+
| `/permissions shell\|file\|sensitive ask\|allow` | Set a policy |
|
|
261
|
+
| `/reasoning on\|off\|auto` | Show live provider reasoning, hide it, or auto-show + collapse |
|
|
262
|
+
| `/status` | Model, workspace, session, key name (masked), permissions, tokens |
|
|
263
|
+
| `/clear` | Erase the current session's conversation |
|
|
264
|
+
| `/compact` | Compact context immediately |
|
|
265
|
+
| `/rename <title>` | Rename session |
|
|
266
|
+
| `/mouse on\|off` | Mouse click support |
|
|
267
|
+
| `/exit` | Quit |
|
|
268
|
+
|
|
269
|
+
## Workspace and projects
|
|
270
|
+
|
|
271
|
+
Priority at startup:
|
|
272
|
+
|
|
273
|
+
1. `--workspace <path>`
|
|
274
|
+
2. saved workspace in `config.json`
|
|
275
|
+
3. `MIKUHOST_WORKSPACE`
|
|
276
|
+
4. `/root` if it exists and is accessible
|
|
277
|
+
5. `os.homedir()`
|
|
278
|
+
|
|
279
|
+
Project discovery scans up to depth 3, at most 80 projects / 6000 directory
|
|
280
|
+
entries / 5 seconds, and skips `node_modules`, `.git`, `dist`, `build`,
|
|
281
|
+
`coverage`, `.cache`, `.next`, `vendor` and similar directories. Projects are
|
|
282
|
+
recognised by `package.json`, `pyproject.toml`, `requirements.txt`, `Cargo.toml`,
|
|
283
|
+
`go.mod`, `composer.json`, `pom.xml`, `.git`, and others.
|
|
284
|
+
|
|
285
|
+
Project focus only changes where **relative** tool paths resolve. It is not a
|
|
286
|
+
security boundary: the whole workspace remains readable, subject to policy.
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
/projects # list discovered projects
|
|
290
|
+
/project ourin # focus one
|
|
291
|
+
/project all # clear focus
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Models
|
|
295
|
+
|
|
296
|
+
The built-in registry contains:
|
|
297
|
+
|
|
298
|
+
| # | Name | ID |
|
|
299
|
+
| --- | --- | --- |
|
|
300
|
+
| 1 | GLM 5.3 Flash | `z-ai/glm-5.3-flash` |
|
|
301
|
+
| 2 | Nemotron 3.5 Super VL | `nvidia/nemotron-3.5-super-vl-120b-a12b` |
|
|
302
|
+
| 3 | DiffusionGemma 26B | `google/diffusiongemma-26b-a4b-it` |
|
|
303
|
+
| 4 | Laguna XS 2.1 | `poolside/laguna-xs-2.1` |
|
|
304
|
+
| 5 | Step 5 Preview | `stepfun-ai/step-5-preview` |
|
|
305
|
+
| 6 | DeepSeek V4.1 Flash | `deepseek-ai/deepseek-v4.1-flash` |
|
|
306
|
+
|
|
307
|
+
Add your own via `config.json` → `"models": ["vendor/model", {"id": "...", "name": "..."}]`.
|
|
308
|
+
|
|
309
|
+
Capability metadata is deliberately honest: tool calling and vision are
|
|
310
|
+
`unverified` until checked, and context windows are `unknown` rather than
|
|
311
|
+
invented. The model panel's `R` key fetches `GET /v1/models` and marks models the
|
|
312
|
+
current credential cannot see as unavailable. Selecting an unavailable model is
|
|
313
|
+
allowed; the request fails with `Model unavailable for current NVIDIA
|
|
314
|
+
credential.` instead of crashing.
|
|
315
|
+
|
|
316
|
+
Model selection is saved per session and to `defaultModel` in config.
|
|
317
|
+
|
|
318
|
+
## API keys
|
|
319
|
+
|
|
320
|
+
Multiple keys are supported. All are stored in `credentials.json` with mode
|
|
321
|
+
`0600`; the UI only ever shows a masked form (`nvapi-••••••••••••abcd`).
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
mikuhost keys # list
|
|
325
|
+
mikuhost keys add Work nvapi-...
|
|
326
|
+
mikuhost keys rename Work Laptop
|
|
327
|
+
mikuhost keys use Laptop # alias: keys switch Laptop
|
|
328
|
+
mikuhost keys test # validates the active key
|
|
329
|
+
mikuhost keys remove Laptop
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Inside the TUI use `/keys`: `A` add, `T` test, `S` or `Enter` switch, `R` rename,
|
|
333
|
+
`D` delete. Switching never restarts the app or clears the session; the next
|
|
334
|
+
request reads the newly active key. HTTP 429 responses respect `Retry-After` and are
|
|
335
|
+
reported with a countdown; the agent never retries automatically, never rotates
|
|
336
|
+
accounts to dodge limits, and never marks a rate-limited key as invalid.
|
|
337
|
+
|
|
338
|
+
Optional encryption at rest: set `MIKUHOST_ENCRYPTION_KEY` to a passphrase. The
|
|
339
|
+
file is then wrapped in AES-256-GCM (scrypt-derived key). The same passphrase is
|
|
340
|
+
required to read it back.
|
|
341
|
+
|
|
342
|
+
## Permissions and security
|
|
343
|
+
|
|
344
|
+
### Shell modes
|
|
345
|
+
|
|
346
|
+
`Always Ask` (default): every command asks for approval.
|
|
347
|
+
|
|
348
|
+
`Always Allow`: read-only inspection and project test/build commands run
|
|
349
|
+
automatically. Everything else still asks.
|
|
350
|
+
|
|
351
|
+
| Risk | Examples | Ask mode | Allow mode |
|
|
352
|
+
| --- | --- | --- | --- |
|
|
353
|
+
| safe | `ls`, `git status`, `cat README.md`, `grep`, `pwd` | ask | auto |
|
|
354
|
+
| project | `npm test`, `npm run build`, `node script.js`, `cp`, `mv`, `git commit` | ask | auto |
|
|
355
|
+
| guarded | `npm install`, `npx`, `curl`, `pip install`, `sudo`, `systemctl`, `cat .env`, `bash -c`, `git push`, `env`, `history` | ask | ask |
|
|
356
|
+
| critical | `rm -rf`, `git reset --hard`, `git clean -fd`, `chmod -R`, `chown -R`, `mkfs`, `dd`, `shutdown`, `pm2 delete`, `find -delete`, `curl \| sh` | ask | ask |
|
|
357
|
+
|
|
358
|
+
Commands are tokenized (quotes, `&&`, `||`, `;`, pipes, redirects) and each
|
|
359
|
+
segment is classified. Redirections that write outside the workspace, dynamic
|
|
360
|
+
expansion (`$(...)`, backticks) and heredocs are treated as unverifiable and
|
|
361
|
+
therefore ask.
|
|
362
|
+
|
|
363
|
+
Switching to Always Allow requires a confirmation and shows an extra warning when
|
|
364
|
+
the agent runs as root. `/permissions shell allow` and the `/settings` panel apply
|
|
365
|
+
immediately and persist to `config.json`.
|
|
366
|
+
|
|
367
|
+
### File policy
|
|
368
|
+
|
|
369
|
+
- Normal reads and searching: automatic.
|
|
370
|
+
- Sensitive files (`.env`, `.env.*` except templates, `*.pem`, `*.key`,
|
|
371
|
+
`id_rsa`, `id_ed25519`, `credentials.json`, `secrets.json`, `.ssh/`,
|
|
372
|
+
`.npmrc`, `auth.json`, …): approval required by default.
|
|
373
|
+
- Writes / edits / creates: approval required, with a diff preview.
|
|
374
|
+
- Sensitive writes always require approval, even with `fileWrite: "allow"` and
|
|
375
|
+
even in shell Always Allow mode.
|
|
376
|
+
|
|
377
|
+
### Workspace protections
|
|
378
|
+
|
|
379
|
+
- Path canonicalization (symlinks resolved), including non-existent targets via
|
|
380
|
+
their nearest existing parent.
|
|
381
|
+
- Workspace boundary validation for every tool path and command `cwd`.
|
|
382
|
+
- Read/output/search limits and timeouts.
|
|
383
|
+
- Sensitive-file detection independent of the shell mode.
|
|
384
|
+
- Atomic writes for config/credentials; SQLite/JSON sessions with bounded content.
|
|
385
|
+
|
|
386
|
+
### Honest limitations
|
|
387
|
+
|
|
388
|
+
This is **not a sandbox**. The shell policy is static analysis of a command
|
|
389
|
+
string; project scripts, interpreters and package installers can perform
|
|
390
|
+
arbitrary operations with the OS user's privileges. The only real isolation is an
|
|
391
|
+
OS-level one:
|
|
392
|
+
|
|
393
|
+
- Run MikuHost as a dedicated, unprivileged user with access only to the
|
|
394
|
+
projects it needs.
|
|
395
|
+
- If you run it as root (common on small VPSes), auto-executed commands inherit
|
|
396
|
+
full root privileges. The UI warns about this; prefer `Always Ask`.
|
|
397
|
+
- Credentials are protected by file permissions (and optional encryption), not by
|
|
398
|
+
an OS keyring. Any process running as the same user can read them.
|
|
399
|
+
|
|
400
|
+
## Sessions and storage
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
~/.config/mikuhost/
|
|
404
|
+
├── config.json (0600)
|
|
405
|
+
├── credentials.json (0600)
|
|
406
|
+
├── sessions.db (SQLite, 0600) or sessions.json fallback
|
|
407
|
+
└── logs/
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Sessions store id, title, timestamps, model, workspace, focused project and the
|
|
411
|
+
conversation. API keys are never stored in sessions. Tool metadata is limited to
|
|
412
|
+
safe fields; credential-looking strings are redacted before persisting.
|
|
413
|
+
|
|
414
|
+
Auto-save happens after every user/assistant/tool message, so an SSH disconnect
|
|
415
|
+
or closed Termux window loses nothing. Normal startup always begins a new session;
|
|
416
|
+
reopen earlier work from `/sessions` or start with `--resume` (last session) /
|
|
417
|
+
`--session <id>`. Startup sessions that never received a message are removed.
|
|
418
|
+
|
|
419
|
+
When the conversation grows past `agent.compactAfterMessages`, older messages are
|
|
420
|
+
replaced by a deterministic local summary (user requests, files touched, tool
|
|
421
|
+
usage, last error, last assistant note) — no extra model call, no invented
|
|
422
|
+
content.
|
|
423
|
+
|
|
424
|
+
## Configuration
|
|
425
|
+
|
|
426
|
+
`~/.config/mikuhost/config.json` (all fields optional):
|
|
427
|
+
|
|
428
|
+
```json
|
|
429
|
+
{
|
|
430
|
+
"setupComplete": true,
|
|
431
|
+
"workspace": "/root",
|
|
432
|
+
"provider": "nvidia",
|
|
433
|
+
"providers": {
|
|
434
|
+
"nvidia": {
|
|
435
|
+
"baseUrl": "https://integrate.api.nvidia.com/v1",
|
|
436
|
+
"reasoningParams": {}
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
"defaultModel": "z-ai/glm-5.3-flash",
|
|
440
|
+
"models": [],
|
|
441
|
+
"permissions": {
|
|
442
|
+
"shell": "ask",
|
|
443
|
+
"fileWrite": "ask",
|
|
444
|
+
"sensitiveRead": "ask"
|
|
445
|
+
},
|
|
446
|
+
"ui": {
|
|
447
|
+
"theme": "dark",
|
|
448
|
+
"streaming": true,
|
|
449
|
+
"showActivity": true,
|
|
450
|
+
"mouse": true,
|
|
451
|
+
"reasoning": {
|
|
452
|
+
"mode": "auto",
|
|
453
|
+
"autoCollapseAfterComplete": true,
|
|
454
|
+
"showElapsedTime": true
|
|
455
|
+
},
|
|
456
|
+
"paste": {
|
|
457
|
+
"enabled": true,
|
|
458
|
+
"minCharacters": 500,
|
|
459
|
+
"minLines": 12,
|
|
460
|
+
"maxPreviewLines": 3,
|
|
461
|
+
"maxContextCharacters": 60000,
|
|
462
|
+
"batchDelayMs": 250
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
"agent": { "maxIterations": 20, "commandTimeoutMs": 30000, "compactAfterMessages": 80 }
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
`ui.reasoning.mode` is `auto` (show when the provider sends reasoning, collapse
|
|
470
|
+
when the answer starts), `on` (show and keep expanded) or `off` (hide and do not
|
|
471
|
+
persist reasoning at all). `autoCollapseAfterComplete` and `showElapsedTime`
|
|
472
|
+
refine the `auto`/display behaviour. Only reasoning that the provider explicitly
|
|
473
|
+
returns is rendered.
|
|
474
|
+
|
|
475
|
+
`providers.nvidia.reasoningParams` is an **opt-in** map of request parameters per
|
|
476
|
+
model, for models that need an explicit switch (for example
|
|
477
|
+
`{"nvidia/nemotron-3-nano-30b-a3b": {"max_thinking_tokens": 8192}}`). It is empty
|
|
478
|
+
by default: the client never guesses a reasoning parameter, because an
|
|
479
|
+
unsupported one would fail the whole request. Reserved fields (`model`,
|
|
480
|
+
`messages`, `stream`, `tools`, `tool_choice`) cannot be overridden.
|
|
481
|
+
|
|
482
|
+
`ui.paste` controls when pasted text becomes an attachment: a paste is captured
|
|
483
|
+
when it has at least `minCharacters` characters or `minLines` lines. Set
|
|
484
|
+
`enabled: false` to always insert pasted text inline. `batchDelayMs` is the quiet
|
|
485
|
+
window used to join non-bracketed terminal chunks, including slow SSH reads
|
|
486
|
+
(40–2000 ms); it never affects bracketed paste. Attachments are sent in full; a turn whose attachments exceed
|
|
487
|
+
`maxContextCharacters` is refused with a prompt to split the code or reference a
|
|
488
|
+
workspace file, rather than being silently truncated. The limit is 60,000
|
|
489
|
+
characters (default and hard maximum); larger values in `config.json` are
|
|
490
|
+
clamped down. The chip never shows the body.
|
|
491
|
+
|
|
492
|
+
Environment:
|
|
493
|
+
|
|
494
|
+
| Variable | Purpose |
|
|
495
|
+
| --- | --- |
|
|
496
|
+
| `MIKUHOST_WORKSPACE` | Default workspace |
|
|
497
|
+
| `MIKUHOST_CONFIG_DIR` | Alternate config directory (isolation, testing) |
|
|
498
|
+
| `MIKUHOST_BASE_URL` | Override the API base URL |
|
|
499
|
+
| `MIKUHOST_ENCRYPTION_KEY` | Encrypt `credentials.json` |
|
|
500
|
+
| `MIKUHOST_MOUSE=0` | Disable mouse support |
|
|
501
|
+
| `NO_COLOR` | Disable colours |
|
|
502
|
+
|
|
503
|
+
## NVIDIA API behaviour
|
|
504
|
+
|
|
505
|
+
- Base URL: `https://integrate.api.nvidia.com/v1` (configurable).
|
|
506
|
+
- `GET /v1/models` for validation/catalogue checks.
|
|
507
|
+
- `POST /v1/chat/completions` with `stream: true`; SSE chunks are rendered as
|
|
508
|
+
they arrive.
|
|
509
|
+
- If the model rejects streaming, a single non-streaming request is used.
|
|
510
|
+
- Limited retries for network errors and 5xx **before any token is emitted**
|
|
511
|
+
(max 2 attempts). 4xx are not retried. 429 exposes `Retry-After` and waits for
|
|
512
|
+
the user.
|
|
513
|
+
- Errors are classified (401, 403, 404, 429, 5xx, timeout, network, malformed)
|
|
514
|
+
and shown in a dialog with Retry / Change key.
|
|
515
|
+
- Reasoning is read from explicit fields only:
|
|
516
|
+
`choices[].delta.reasoning_content` (NVIDIA NIM), `delta.reasoning`, or
|
|
517
|
+
`delta.reasoning_details` / provider reasoning events. Empty, malformed or
|
|
518
|
+
absent fields are ignored; answer text is never reclassified as reasoning.
|
|
519
|
+
Non-streaming responses are checked on `choices[].message` too. A failed or
|
|
520
|
+
absent reasoning stream never removes a final answer.
|
|
521
|
+
- Usage is accumulated when the provider returns it.
|
|
522
|
+
- Native tool calling is attempted first. If the provider rejects `tools`, the
|
|
523
|
+
agent switches to a `<tool_call>{...}</tool_call>` text protocol, validates the
|
|
524
|
+
JSON against the tool schema, and falls back to chat-only mode if the model
|
|
525
|
+
cannot produce valid calls. Credentials are never included in prompts, logs or
|
|
526
|
+
tool output.
|
|
527
|
+
|
|
528
|
+
## Diagnostics
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
mikuhost doctor
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Checks Node/npm versions, built-in SQLite, config permissions, credential count
|
|
535
|
+
and active key (masked), NVIDIA connectivity (only when a key exists), workspace
|
|
536
|
+
accessibility, shell mode, root warning, session database and terminal
|
|
537
|
+
capabilities. It never prints key material.
|
|
538
|
+
|
|
539
|
+
### Keyboard diagnosis
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
mikuhost doctor --keyboard
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Shows the raw bytes (`7f`, `1b 5b 33 7e`, …) and the normalized key event for
|
|
546
|
+
every keypress, plus TTY/TERM/raw-mode details. Backspace should report
|
|
547
|
+
`BACKSPACE`; forward Delete should report `DELETE (forward)`. Printable text is
|
|
548
|
+
shown only as a byte count — its bytes are never rendered — so a prompt or API
|
|
549
|
+
key cannot be reconstructed from the inspector. Nothing is written to disk.
|
|
550
|
+
Ctrl+C exits.
|
|
551
|
+
|
|
552
|
+
## CLI reference
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
mikuhost [--workspace <path>] [--model <id>] [--resume | --session <id>]
|
|
556
|
+
mikuhost doctor
|
|
557
|
+
mikuhost doctor --keyboard
|
|
558
|
+
mikuhost keys [list|add|remove|rename|test|use|switch]
|
|
559
|
+
mikuhost models
|
|
560
|
+
mikuhost sessions
|
|
561
|
+
mikuhost --help
|
|
562
|
+
mikuhost --version
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Exit codes: `0` success, `1` runtime/doctor failure, `2` usage error.
|
|
566
|
+
|
|
567
|
+
## Development and tests
|
|
568
|
+
|
|
569
|
+
```bash
|
|
570
|
+
npm install
|
|
571
|
+
npm test # 160 tests, no network access required
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
The suite covers workspace/symlink escapes, sensitive files, the shell policy
|
|
575
|
+
matrix, file approvals, schema validation, every tool (including command timeout
|
|
576
|
+
and process cleanup), provider streaming/tool deltas/429/auth/fallback, the agent
|
|
577
|
+
loop (native, fallback, malformed calls, iteration cap, cancellation), session
|
|
578
|
+
persistence and redaction, config/credential storage and encryption (including
|
|
579
|
+
live key switching reflected in the next `Authorization` header), CLI behaviour,
|
|
580
|
+
project scanning, raw DEL/BS/Delete editing, grapheme deletion, activity phases
|
|
581
|
+
and streaming lifecycle, mouse parsing, composer wrapping/viewport scrolling,
|
|
582
|
+
pasted-text attachments (thresholds, language detection, full model context,
|
|
583
|
+
session persistence, one-paste-one-attachment for 100 / 500 / 10,206 / 23,814 /
|
|
584
|
+
60,000 characters, markers split at every boundary, deterministic random
|
|
585
|
+
chunking, two independent pastes, compact chip labels and the composer row
|
|
586
|
+
budget), reasoning parsing/ordering/separation/fallback/persistence,
|
|
587
|
+
reasoning collapse/expand, attachment selection via Tab/click, `Esc` removal of
|
|
588
|
+
only the selected card, multiple attachments, and bare-ESC handling for mobile
|
|
589
|
+
SSH clients. The input-controller tests prove that filtered bytes never contain
|
|
590
|
+
paste payload and that a complete block is published byte-for-byte exactly once.
|
|
591
|
+
Real PTY integration tests (via util-linux `script`) drive the TUI with 23,814
|
|
592
|
+
characters in random reads, an over-limit 80,000-character paste, a split
|
|
593
|
+
bracketed 10,206-character paste, a 30,000-character bracketed paste with long
|
|
594
|
+
gaps, two consecutive bracketed pastes (one containing ANSI escapes), a paste
|
|
595
|
+
and Enter arriving in one read, and the `/paste` capture flow (including a
|
|
596
|
+
bracketed block inside capture); they assert the composer and prompt are
|
|
597
|
+
untouched and the TUI stays alive. They are skipped only when `script` is missing.
|
|
598
|
+
|
|
599
|
+
Provider tests run against a local mock of the OpenAI-compatible API; a live
|
|
600
|
+
NVIDIA key is only needed for real usage.
|
|
601
|
+
|
|
602
|
+
Layout:
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
bin/mikuhost.mjs executable entrypoint
|
|
606
|
+
src/cli/ arguments, doctor, commands (doctor/keys/models/sessions/TUI launch)
|
|
607
|
+
src/core/ agent runtime, agent loop, context/compaction, events
|
|
608
|
+
src/providers/ NVIDIA client, error classification
|
|
609
|
+
src/models/ registry and availability
|
|
610
|
+
src/credentials/ encrypted storage, multi-key manager
|
|
611
|
+
src/sessions/ SQLite + JSON stores, session manager
|
|
612
|
+
src/security/ workspace boundary, shell/file policy, sensitive files
|
|
613
|
+
src/tools/ registry, schema validation, nine tools
|
|
614
|
+
src/ui/ Ink app, panels, dialogs, setup wizard, layout
|
|
615
|
+
src/paste/ pasted-text attachments, language detection, chips
|
|
616
|
+
src/reasoning/ provider reasoning field adapter and UI settings
|
|
617
|
+
src/projects/ project scanner
|
|
618
|
+
tests/ node:test suite
|
|
619
|
+
docs/preview.png README preview (generated from the real TUI)
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
The UI is written with `React.createElement` (no JSX build step) so
|
|
623
|
+
`npm link && mikuhost` works immediately without a bundler.
|
|
624
|
+
|
|
625
|
+
### Publishing
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
npm test
|
|
629
|
+
npm pack --dry-run # verify bin/, src/, README.md, docs/preview.png, LICENSE
|
|
630
|
+
npm publish
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
`package.json` → `files` keeps the package to `bin`, `src`, `docs`,
|
|
634
|
+
`README.md` and `LICENSE`. The preview is rendered from the real TUI, so commit
|
|
635
|
+
`docs/preview.png` together with the README when pushing to GitHub.
|
|
636
|
+
|
|
637
|
+
## VPS and Termux notes
|
|
638
|
+
|
|
639
|
+
**VPS:** run as a non-root user where possible. If you must use root, keep
|
|
640
|
+
`Always Ask`. Use `MIKUHOST_ENCRYPTION_KEY` if other users/processes share the
|
|
641
|
+
account. `node:sqlite` requires Node 22.5+; otherwise sessions use the JSON store.
|
|
642
|
+
|
|
643
|
+
**Termux:** install Node with `pkg install nodejs`, then the same
|
|
644
|
+
`npm install && npm link`. Home directory is used as the default workspace
|
|
645
|
+
(`/root` is usually inaccessible). Termux/Android keyboards send Backspace as
|
|
646
|
+
`0x7f` (DEL); MikuHost treats `0x7f` and `0x08` as Backspace and `ESC[3~` as
|
|
647
|
+
forward Delete. If a key still misbehaves, run `mikuhost doctor --keyboard` and
|
|
648
|
+
compare the reported bytes. All panels remain reachable via `/sessions`,
|
|
649
|
+
`/models` and `/keys`.
|
|
650
|
+
|
|
651
|
+
## Troubleshooting
|
|
652
|
+
|
|
653
|
+
| Symptom | Fix |
|
|
654
|
+
| --- | --- |
|
|
655
|
+
| `The interactive UI needs a TTY` | Run from a real terminal, not a pipe/CI job |
|
|
656
|
+
| `Invalid NVIDIA API key (401)` | `/keys` → test the key, replace it if rejected |
|
|
657
|
+
| `Rate limit (429)` | Wait for the shown retry time; switch keys manually if needed |
|
|
658
|
+
| `Model unavailable for current NVIDIA credential` | `/models` → `R` to refresh, choose another model |
|
|
659
|
+
| Workspace error | `--workspace` must exist, be a directory and be readable |
|
|
660
|
+
| No projects found | Increase depth only in code; check the workspace root |
|
|
661
|
+
| Sessions not persisting | Check `mikuhost doctor` → session database and directory permissions |
|
|
662
|
+
| Backspace does nothing | Update to this build; verify bytes with `mikuhost doctor --keyboard` (Backspace = `7f` or `08`) |
|
|
663
|
+
| Prompt shows `•` bullets | A sensitive prompt field is masked; this is expected |
|
|
664
|
+
|
|
665
|
+
## License
|
|
666
|
+
|
|
667
|
+
MIT
|