@xingjisoft/neutrino 0.1.3 → 0.1.5

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