@xingjisoft/neutrino 0.1.4 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingjisoft/neutrino",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "XINGJI Neutrino - a CLI coding agent in the terminal.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -10,15 +10,17 @@
10
10
  "main": "dist/app.js",
11
11
  "files": [
12
12
  "dist",
13
- "assets"
13
+ "assets",
14
+ "web"
14
15
  ],
15
16
  "engines": {
16
17
  "node": ">=18.0.0"
17
18
  },
18
19
  "scripts": {
19
20
  "build": "tsc -p tsconfig.json",
20
- "test": "npm run build && node scripts/test-paste.mjs && node scripts/test-mouse.mjs && node scripts/test-cursor.mjs && node scripts/test-selection.mjs && node scripts/test-copy.mjs && node scripts/test-scrollbar.mjs && node scripts/test-render.mjs && node scripts/test-drift.mjs && node scripts/test-sanitize.mjs && node scripts/test-markdown.mjs && node scripts/test-wrap.mjs && node scripts/test-fetch.mjs && node scripts/test-compact.mjs && node scripts/test-tools.mjs",
21
- "prepare": "npm run build"
21
+ "test": "npm run build && node scripts/test-paste.mjs && node scripts/test-mouse.mjs && node scripts/test-cursor.mjs && node scripts/test-selection.mjs && node scripts/test-drag-scroll.mjs && node scripts/test-selection-performance.mjs && node scripts/test-copy.mjs && node scripts/test-scrollbar.mjs && node scripts/test-render.mjs && node scripts/test-drift.mjs && node scripts/test-sanitize.mjs && node scripts/test-markdown.mjs && node scripts/test-wrap.mjs && node scripts/test-fetch.mjs && node scripts/test-compact.mjs && node scripts/test-history-fold.mjs && node scripts/test-web-history.mjs && node scripts/test-turn-fold.mjs && node scripts/test-web-settings.mjs && node scripts/test-tools.mjs && node scripts/test-approval.mjs && node scripts/test-approval-review.mjs && node scripts/test-approval-ui.mjs && node scripts/test-web-approval-details.mjs && node scripts/test-approval-mode.mjs && node scripts/test-approval-context.mjs && node scripts/test-settings-render.mjs && node scripts/test-approval-flow.mjs && node scripts/test-web.mjs && node scripts/test-web-settings-http.mjs && node scripts/test-web-auth.mjs && node scripts/test-web-auth-ui.mjs && node scripts/test-interrupt.mjs && node scripts/test-terminal-lifecycle.mjs && node scripts/test-persistence.mjs && node scripts/test-resilience.mjs && node scripts/test-approval-timeout.mjs && node scripts/test-approval-hardening.mjs && node scripts/test-approval-extreme.mjs && node scripts/test-approval-reason.mjs",
22
+ "prepare": "npm run build",
23
+ "web": "node dist/bin.js web"
22
24
  },
23
25
  "keywords": [
24
26
  "cli",
package/web/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # Neutrino Web UI
2
+
3
+ A small, dependency-free single page app that is served by the Neutrino CLI on
4
+ a port you choose. It lets you:
5
+
6
+ - browse every saved conversation grouped by working directory (like Codex),
7
+ with only the five latest transcripts per path shown and the rest collapsed;
8
+ - watch a running terminal conversation stream in real time, with every
9
+ finished turn folded into one summary line that opens on a click (the turn
10
+ being answered stays open, and a paused turn is not folded either);
11
+ - switch the provider, model and intensity for the next turn;
12
+ - edit defaults, context compression and approval settings in a Settings dialog;
13
+ - drive the running conversation: pause / resume the current turn and send new
14
+ messages (they queue while the agent is busy);
15
+ - pick up a saved conversation in the browser and keep chatting, even when no
16
+ terminal is running.
17
+
18
+ Nothing needs to be built — these are plain `.html`, `.css` and `.js` files.
19
+
20
+ ## Running it
21
+
22
+ While using the terminal UI, mount the website on a port:
23
+
24
+ ```sh
25
+ neutrino --web-port 8765
26
+ ```
27
+
28
+ Or serve only the saved history, without a terminal:
29
+
30
+ ```sh
31
+ neutrino web --port 8765
32
+ # or
33
+ npm run web
34
+ ```
35
+
36
+ Then open <http://127.0.0.1:8765>.
37
+
38
+ ## Using it from another device
39
+
40
+ The server binds to loopback by default. To open it from your phone or another
41
+ machine, bind an interface others can reach:
42
+
43
+ ```sh
44
+ neutrino web --port 8765 --lan # every interface (0.0.0.0)
45
+ neutrino web --port 8765 --host 100.64.0.7 # one specific address
46
+ ```
47
+
48
+ Startup prints the addresses to open:
49
+
50
+ ```
51
+ Neutrino web UI listening on 0.0.0.0:8765
52
+ Open the address that matches your device:
53
+ http://192.168.1.211:8765/
54
+ http://127.0.0.1:8765/
55
+ ```
56
+
57
+ Hypervisor and container host-only adapters (`vEthernet`, VMware, VirtualBox,
58
+ Docker), link-local `169.254/16` and loopback addresses other than `127.0.0.1`
59
+ are left out of that list on purpose: they cannot be reached from another
60
+ device. CGNAT ranges such as Tailscale's `100.64/10` are kept, since a VPN is a
61
+ common way to reach a machine.
62
+
63
+ ## Authentication
64
+
65
+ The Web UI uses a permanent password, initially **`114514`**, on both localhost
66
+ and network interfaces. Change it in **Settings → Web access** in the browser
67
+ or **Settings → Web password** in the terminal. In the browser, enter the new
68
+ password twice and save; leaving both fields blank preserves the current one.
69
+ In the terminal, select the password row, press Enter, type or paste a new
70
+ password, and press Enter to save (Escape cancels).
71
+
72
+ Only a salted scrypt hash is stored in `~/.neutrino/config.json`. The password
73
+ does not expire and is not reset when the server restarts. Settings never send
74
+ back the password or its hash.
75
+
76
+ A modal login form stays above the conversation UI. Successful login sets an
77
+ HttpOnly, SameSite=Strict cookie, remembered for up to one year and shared by
78
+ HTTP requests and the SSE stream. Session credentials are never put into URLs
79
+ or browser localStorage. A password change invalidates existing login cookies; the browser
80
+ that saves the password receives a replacement cookie and stays signed in.
81
+
82
+ To sign in directly from a link, open `http://<host>:8765/?pwd=114514`.
83
+ The browser automatically checks the supplied password through the login endpoint
84
+ before loading conversations, remembers the login cookie, and removes `pwd`
85
+ from the address bar while keeping other query parameters and the URL fragment.
86
+ An incorrect password or a failed request shows the normal login dialog with
87
+ an error, so you can enter the password manually. URL-encode passwords that
88
+ contain special characters (for example, `+` becomes `%2B` and `&` becomes `%26`).
89
+
90
+ For scripts, POST `{"password":"114514"}` to `/api/auth/login` and retain the
91
+ returned cookie for subsequent requests. POST `/api/auth/logout` clears the
92
+ browser cookie. Missing or invalid login cookies receive HTTP 401. Legacy token
93
+ headers, token URLs, `--token` and `NEUTRINO_WEB_TOKEN` are no longer used.
94
+
95
+ Static files and the login/logout routes stay public. `--no-auth` explicitly
96
+ disables password login.
97
+
98
+ ## Models and settings
99
+
100
+ Open a conversation to see the **Provider**, **Model** and **Thinking intensity**
101
+ selectors. Choose a configured model and press **Apply to conversation**. The
102
+ next turn uses that selection, and it is saved with the transcript. The same
103
+ controls also update a conversation running in the terminal. Pause an active
104
+ turn before applying a change; a running turn returns a clear conflict message.
105
+
106
+ For saved history, **Continue with model** resumes the transcript with the chosen
107
+ selection. Changing the selectors and then using **Continue here** or sending a
108
+ message also uses the draft selection, so a transcript whose old model was
109
+ removed can still be continued.
110
+
111
+ The **⚙ Settings** button in the sidebar opens defaults for new conversations,
112
+ the context compression threshold, the approval reviewer model, and the
113
+ extreme-risk automatic approval option. Save settings explicitly; cancelling
114
+ leaves the saved values intact. Conversation model changes are separate from
115
+ these defaults. Models come from the same catalog as the terminal, and API keys
116
+ stay on the server. Configure missing keys in the terminal model screen first.
117
+ Intensity uses the same Low–Ultra sampling settings as the terminal.
118
+
119
+ ## History vs. live sessions
120
+
121
+ A saved transcript on disk is a finished record: nothing is running, so the
122
+ page shows it read-only (dashed `read-only` badge) and there is nothing to
123
+ pause.
124
+
125
+ To continue one, either
126
+
127
+ - press **✎ Continue here** in the header, or
128
+ - just start typing in the composer and press Enter.
129
+
130
+ Both create a *browser-owned session*: the server resumes the transcript from
131
+ disk, registers it with the live hub, and runs the agent loop headlessly with
132
+ the model, provider, intensity, working directory and token counters recorded in
133
+ the file. From that moment the page behaves exactly like a live terminal
134
+ conversation — streaming output, tool calls, pause/resume, queued messages — and
135
+ every turn is written back to the same transcript file.
136
+
137
+ If the same transcript is already live (a terminal attached, or another tab
138
+ resumed it), the page attaches to that session instead of starting a second one.
139
+ Browser-owned sessions are dropped after 30 idle minutes, or immediately through
140
+ `POST /api/live/:key/close`. Sessions owned by the terminal cannot be closed from
141
+ the web.
142
+
143
+ ## How real-time works
144
+
145
+ A `ChatScreen` registers itself with the in-process hub when it is opened. Every
146
+ time it changes (a streamed token, a tool call, a pause, a save) it notifies the
147
+ hub, which coalesces updates and pushes a snapshot over
148
+ `GET /api/live/:key/stream` (SSE). The page keeps the open conversation in sync
149
+ and re-renders it; the sidebar polls `/api/overview` for new sessions.
150
+
151
+ Browser-owned sessions publish to the same hub, so the page code does not need
152
+ to know whether a conversation is driven by the terminal or by the server.
153
+
154
+ ## API
155
+
156
+ | Method | Path | Purpose |
157
+ | --- | --- | --- |
158
+ | GET | `/api/overview` | Sessions grouped by cwd + live sessions |
159
+ | GET | `/api/sessions` | Flat list of saved sessions |
160
+ | GET | `/api/settings` | Model catalog and public settings; no credentials |
161
+ | POST | `/api/settings` | Save validated `{ selection?, settings? }` defaults |
162
+ | GET | `/api/session/:id` | One saved transcript |
163
+ | POST | `/api/session/:id/continue` | Resume a transcript with optional `{ selection }`, returns `{ key }` |
164
+ | GET | `/api/live/:key` | Snapshot of a live session |
165
+ | GET | `/api/live/:key/stream` | SSE stream of a live session |
166
+ | POST | `/api/live/:key/pause` | Pause the running turn |
167
+ | POST | `/api/live/:key/resume` | Resume a paused conversation |
168
+ | POST | `/api/live/:key/message` | Send `{ "text": "…" }` |
169
+ | POST | `/api/live/:key/model` | Apply `{ provider, model, intensity }` while idle or paused |
170
+ | POST | `/api/live/:key/close` | Dispose a browser-owned session |
171
+ | GET | `/api/health` | Liveness probe |
172
+ | POST | `/api/auth/login` | Sign in with `{ "password": "…" }` |
173
+ | POST | `/api/auth/logout` | Clear the login cookie |
174
+ | GET / POST | `/api/settings` | Read settings or update them, including `settings.web_password` |
175
+
176
+ Conversation and settings routes require the login cookie; see
177
+ [Authentication](#authentication). `OPTIONS` is answered unauthenticated so
178
+ preflights are not blocked, and it returns no data.
179
+
180
+ ### Approval requests
181
+
182
+ When a running agent wants to do something risky (a delete-class command, an
183
+ install, a path outside the working directory) the browser shows an orange
184
+ **Request Approval** banner above the composer, showing the full command (or
185
+ formatted tool arguments), working directory and risk reasons, plus two buttons.
186
+ Long proposals remain complete and scroll inside the card. Automatic reviews
187
+ show the same proposal details while they run.
188
+ `Approve` runs the call, `Deny` returns a note to the agent.
189
+ The reviewer uses the current turn's captured user request and earlier user
190
+ messages; queued input belongs to the next turn. Expand a denied tool result
191
+ to inspect the user requests that were reviewed.
192
+
193
+ Use the **Request Approval / Auto Approval** switch in each conversation's
194
+ controls to choose human approval or independent AI review. The choice is saved
195
+ with the conversation and synced with the terminal. `自动审批` / `自动批准` in
196
+ prompt text no longer changes this mode. Delete-class automatic review still
197
+ requires **Settings → Extreme-risk auto-approval**. Each pending call receives two
198
+ independent safety assessments. A round passes at an average safety score of
199
+ 60/100; task relevance has no weight or ordinary minimum. Only an extreme,
200
+ obvious task mismatch independently confirmed with evidence by both reviewers
201
+ (relevance at most 5/100 each) can veto on intent. Concrete serious hazards,
202
+ explicit user prohibitions and failed reviews still prevent execution.
203
+
204
+ It is the same gate the terminal uses: the prompt opt-outs `无需审批` and
205
+ `我已知悉风险,同意给予完整权限,无需任何审批` still work, and
206
+ `POST /api/live/:key/approve` with `{"approved": true|false}` answers the request
207
+ programmatically (409 when nothing is pending).
208
+
209
+ `POST /api/live/:key/approval-mode` with `{"mode":"request"|"auto"}` changes
210
+ the conversation mode and returns its current snapshot (400 for invalid modes).