@xingjisoft/neutrino 0.6.2 → 0.7.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.md +6 -1
- package/assets/fast-planner.txt +57 -0
- package/assets/fast-replanner.txt +28 -0
- package/dist/agents.d.ts +25 -0
- package/dist/app.d.ts +206 -0
- package/dist/app.js +15 -0
- package/dist/background.d.ts +96 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +40 -3
- package/dist/browser.d.ts +18 -0
- package/dist/clipboard.d.ts +16 -0
- package/dist/colors.d.ts +29 -0
- package/dist/compact.d.ts +52 -0
- package/dist/config.d.ts +221 -0
- package/dist/config.js +19 -7
- package/dist/cwd.d.ts +19 -0
- package/dist/fast/artifacts.d.ts +26 -0
- package/dist/fast/artifacts.js +109 -0
- package/dist/fast/engine.d.ts +78 -0
- package/dist/fast/engine.js +657 -0
- package/dist/fast/evidence.d.ts +21 -0
- package/dist/fast/evidence.js +68 -0
- package/dist/fast/planner.d.ts +10 -0
- package/dist/fast/planner.js +51 -0
- package/dist/fast/pool.d.ts +17 -0
- package/dist/fast/pool.js +78 -0
- package/dist/fast/scheduler.d.ts +54 -0
- package/dist/fast/scheduler.js +383 -0
- package/dist/fast/types.d.ts +100 -0
- package/dist/fast/types.js +21 -0
- package/dist/fast/worker.d.ts +23 -0
- package/dist/fast/worker.js +185 -0
- package/dist/header.d.ts +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/llm/approval.d.ts +204 -0
- package/dist/llm/approval.js +14 -3
- package/dist/llm/client.d.ts +118 -0
- package/dist/llm/client.js +57 -8
- package/dist/llm/codeExecutor.d.ts +14 -0
- package/dist/llm/codemode.d.ts +15 -0
- package/dist/llm/context.d.ts +29 -0
- package/dist/llm/diagnostics.d.ts +91 -0
- package/dist/llm/diagnostics.js +242 -0
- package/dist/llm/plan.d.ts +148 -0
- package/dist/llm/retry.d.ts +80 -0
- package/dist/llm/risk.d.ts +21 -0
- package/dist/llm/risk.js +7 -0
- package/dist/llm/runtime.d.ts +189 -0
- package/dist/llm/runtime.js +77 -4
- package/dist/llm/search.d.ts +2 -0
- package/dist/llm/searchNetwork.d.ts +8 -0
- package/dist/llm/subagent.d.ts +112 -0
- package/dist/llm/subagent.js +4 -2
- package/dist/llm/tools.d.ts +20 -0
- package/dist/llm/tools.js +4 -1
- package/dist/llm/workspace.d.ts +6 -0
- package/dist/llm/workspace.js +271 -0
- package/dist/markdown.d.ts +58 -0
- package/dist/migration.d.ts +8 -0
- package/dist/persistence.d.ts +14 -0
- package/dist/plugins/catalog.d.ts +20 -0
- package/dist/plugins/catalog.js +45 -0
- package/dist/plugins/cli.d.ts +1 -0
- package/dist/plugins/cli.js +106 -0
- package/dist/plugins/host.d.ts +121 -0
- package/dist/plugins/host.js +450 -0
- package/dist/plugins/manager.d.ts +24 -0
- package/dist/plugins/manager.js +288 -0
- package/dist/plugins/package.d.ts +5 -0
- package/dist/plugins/package.js +53 -0
- package/dist/plugins/runner.d.ts +1 -0
- package/dist/plugins/runner.js +227 -0
- package/dist/plugins/session.d.ts +12 -0
- package/dist/plugins/session.js +97 -0
- package/dist/plugins/terminal.d.ts +22 -0
- package/dist/plugins/terminal.js +33 -0
- package/dist/plugins/types.d.ts +178 -0
- package/dist/plugins/types.js +6 -0
- package/dist/plugins/validation.d.ts +14 -0
- package/dist/plugins/validation.js +174 -0
- package/dist/plugins/web-sdk.d.ts +58 -0
- package/dist/plugins/web-sdk.js +1 -0
- package/dist/pro/agents.d.ts +34 -0
- package/dist/pro/config.d.ts +75 -0
- package/dist/pro/config.js +3 -2
- package/dist/pro/preflight.d.ts +21 -0
- package/dist/sanitize.d.ts +34 -0
- package/dist/screens/agentConversation.d.ts +28 -0
- package/dist/screens/agentHistory.d.ts +25 -0
- package/dist/screens/agentSelect.d.ts +16 -0
- package/dist/screens/chat.d.ts +292 -0
- package/dist/screens/chat.js +139 -21
- package/dist/screens/history.d.ts +23 -0
- package/dist/screens/modelSelect.d.ts +29 -0
- package/dist/screens/plugins.d.ts +29 -0
- package/dist/screens/plugins.js +196 -0
- package/dist/screens/settings.d.ts +42 -0
- package/dist/screens/start.d.ts +43 -0
- package/dist/screens/start.js +25 -17
- package/dist/screens/updatePrompt.d.ts +41 -0
- package/dist/selection.d.ts +75 -0
- package/dist/state.d.ts +26 -0
- package/dist/terminal/buffer.d.ts +47 -0
- package/dist/terminal/input.d.ts +70 -0
- package/dist/terminal/renderer.d.ts +43 -0
- package/dist/terminal/restore.d.ts +41 -0
- package/dist/terminal/screen.d.ts +57 -0
- package/dist/tray.d.ts +14 -0
- package/dist/ui.d.ts +123 -0
- package/dist/update.d.ts +71 -0
- package/dist/updateWatch.d.ts +43 -0
- package/dist/web/approvalQueue.d.ts +8 -0
- package/dist/web/approvalQueue.js +59 -0
- package/dist/web/auth.d.ts +14 -0
- package/dist/web/clientState.d.ts +16 -0
- package/dist/web/folderPicker.d.ts +34 -0
- package/dist/web/folderPicker.js +544 -0
- package/dist/web/hub.d.ts +213 -0
- package/dist/web/hub.js +14 -2
- package/dist/web/localApi.d.ts +11 -0
- package/dist/web/localApi.js +260 -0
- package/dist/web/plugins.d.ts +8 -0
- package/dist/web/plugins.js +64 -0
- package/dist/web/server.d.ts +81 -0
- package/dist/web/server.js +127 -19
- package/dist/web/service.d.ts +7 -0
- package/dist/web/service.js +1 -0
- package/dist/web/session.d.ts +176 -0
- package/dist/web/session.js +153 -19
- package/dist/web/settings.d.ts +65 -0
- package/dist/web/terminalBridge.d.ts +15 -0
- package/dist/web/terminalBridge.js +8 -2
- package/dist/width.d.ts +6 -0
- package/docs/plugins/README.md +126 -0
- package/docs/plugins/agent-sdk.md +92 -0
- package/docs/plugins/architecture.md +64 -0
- package/docs/plugins/compatibility.md +34 -0
- package/docs/plugins/development.md +55 -0
- package/docs/plugins/manifest.md +59 -0
- package/docs/plugins/manifest.schema.json +286 -0
- package/docs/plugins/security.md +53 -0
- package/docs/plugins/skills-prompts.md +69 -0
- package/docs/plugins/ui-sdk.md +109 -0
- package/examples/local-api.py +105 -0
- package/examples/plugins/cards/card.html +6 -0
- package/examples/plugins/cards/neutrino.plugin.json +42 -0
- package/examples/plugins/composer/editor.html +10 -0
- package/examples/plugins/composer/neutrino.plugin.json +10 -0
- package/examples/plugins/function-tools/agent.js +32 -0
- package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
- package/examples/plugins/menus-tabs/panel.html +6 -0
- package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
- package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
- package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
- package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
- package/examples/plugins/theme/neutrino.plugin.json +12 -0
- package/package.json +66 -42
- package/web/LOCAL-API.md +65 -0
- package/web/README.md +27 -6
- package/web/app.js +201 -32
- package/web/i18n.js +15 -0
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +11 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Buffer } from "../terminal/buffer.js";
|
|
2
|
+
import { Screen } from "../terminal/screen.js";
|
|
3
|
+
import type { Key, MouseEvent } from "../terminal/input.js";
|
|
4
|
+
export declare class SettingsScreen extends Screen {
|
|
5
|
+
private intensityIdx;
|
|
6
|
+
/** Levels the current chat model accepts, in canonical order. */
|
|
7
|
+
private intensityLevels;
|
|
8
|
+
private contextTokens;
|
|
9
|
+
private contextChanged;
|
|
10
|
+
/** 0 = same model as the chat, otherwise index + 1 into `models`. */
|
|
11
|
+
private approvalModelIdx;
|
|
12
|
+
/** 0 = off (human only), 1 = on (two independent assessments). */
|
|
13
|
+
private extremeIdx;
|
|
14
|
+
/** 0 = silent, 1 = audible cue on turn end and on approval requests. */
|
|
15
|
+
private soundIdx;
|
|
16
|
+
/** 0 = web UI off, 1 = mounted alongside the terminal. */
|
|
17
|
+
private webIdx;
|
|
18
|
+
/** 0 = terminal only, 1 = the Web console is run by a detached service. */
|
|
19
|
+
private backgroundIdx;
|
|
20
|
+
/** Only a row the user actually touched rewrites the saved default. */
|
|
21
|
+
private webChanged;
|
|
22
|
+
private backgroundPending;
|
|
23
|
+
private models;
|
|
24
|
+
private rowIdx;
|
|
25
|
+
private lastHoverX;
|
|
26
|
+
private lastHoverY;
|
|
27
|
+
private editingPassword;
|
|
28
|
+
private passwordDraft;
|
|
29
|
+
private passwordMessage;
|
|
30
|
+
onActivate(): void;
|
|
31
|
+
/** Change the value of a row by `delta` steps and persist it immediately. */
|
|
32
|
+
private cycle;
|
|
33
|
+
private applyBackground;
|
|
34
|
+
onKey(key: Key): void;
|
|
35
|
+
onMouse(ev: MouseEvent): void;
|
|
36
|
+
onPaste(text: string): void;
|
|
37
|
+
onDeactivate(): void;
|
|
38
|
+
private rowTexts;
|
|
39
|
+
private layout;
|
|
40
|
+
private save;
|
|
41
|
+
render(buf: Buffer): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Buffer } from "../terminal/buffer.js";
|
|
2
|
+
import { Screen } from "../terminal/screen.js";
|
|
3
|
+
import type { Key, MouseEvent } from "../terminal/input.js";
|
|
4
|
+
export declare class StartScreen extends Screen {
|
|
5
|
+
private extensions;
|
|
6
|
+
private get tabs();
|
|
7
|
+
private get focusCount();
|
|
8
|
+
onActivate(): void;
|
|
9
|
+
private get approvalMode();
|
|
10
|
+
private toggleApproval;
|
|
11
|
+
private logoLines;
|
|
12
|
+
private inputValue;
|
|
13
|
+
private cursor;
|
|
14
|
+
private focusIdx;
|
|
15
|
+
private hoverIdx;
|
|
16
|
+
private proHover;
|
|
17
|
+
private webHover;
|
|
18
|
+
private lastHoverX;
|
|
19
|
+
private lastHoverY;
|
|
20
|
+
showsCursor(): boolean;
|
|
21
|
+
onPaste(text: string): void;
|
|
22
|
+
onKey(key: Key): void;
|
|
23
|
+
onMouse(ev: MouseEvent): void;
|
|
24
|
+
/**
|
|
25
|
+
* The message box, the tabs below it and the hint row are persistent chrome;
|
|
26
|
+
* repainting them every frame keeps them self-healing against terminal-side
|
|
27
|
+
* drift (see Screen.alwaysRepaintRows).
|
|
28
|
+
*/
|
|
29
|
+
alwaysRepaintRows(): number[];
|
|
30
|
+
private layout;
|
|
31
|
+
/**
|
|
32
|
+
* First content row. The centered block normally floats in the middle, but
|
|
33
|
+
* it must start below the top-left buttons when they are visible: the boxes
|
|
34
|
+
* are opaque chrome and would otherwise clip the top of the logo.
|
|
35
|
+
*/
|
|
36
|
+
private contentTop;
|
|
37
|
+
private move;
|
|
38
|
+
/** Move the text cursor by one character, staying within bounds and on
|
|
39
|
+
* surrogate-pair boundaries. */
|
|
40
|
+
private moveCursor;
|
|
41
|
+
private enterTab;
|
|
42
|
+
render(buf: Buffer): void;
|
|
43
|
+
}
|
package/dist/screens/start.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { terminalUI } from "../plugins/terminal.js";
|
|
1
2
|
import { Screen } from "../terminal/screen.js";
|
|
2
3
|
import { C } from "../colors.js";
|
|
3
4
|
import { loadLogo, loadVersion, loadConfig, saveConfig, normalizeApprovalMode } from "../config.js";
|
|
4
5
|
import { drawHeader } from "../header.js";
|
|
5
6
|
import { centerVertically, computeInputWindow, cursorFromColumn, drawHint, drawInputBox, drawTabButton, inRect, approvalToggle, drawApprovalToggle, drawHeaderButton, proModeButton, webModeButton, } from "../ui.js";
|
|
6
|
-
const TABS = ["Model", "History", "Settings"];
|
|
7
|
+
const TABS = ["Model", "History", "Settings", "Plugins"];
|
|
7
8
|
const LOGO = loadLogo();
|
|
8
|
-
const FOCUS_COUNT = TABS.length + 2; // input box + 3 tabs + approval mode
|
|
9
9
|
const INPUT_H = 3;
|
|
10
10
|
const TAB_H = 3;
|
|
11
11
|
function isLowSurrogate(code) {
|
|
12
12
|
return code >= 0xdc00 && code <= 0xdfff;
|
|
13
13
|
}
|
|
14
14
|
export class StartScreen extends Screen {
|
|
15
|
+
extensions = [];
|
|
16
|
+
get tabs() { return [...TABS, ...this.extensions.slice(0, Math.max(0, Math.floor(((this.app?.width ?? 80) - 2) / 10) - TABS.length)).map(item => item.title)]; }
|
|
17
|
+
get focusCount() { return this.tabs.length + 2; }
|
|
18
|
+
onActivate() { this.extensions = terminalUI().filter(item => item.slot === "app.menu" || item.slot === "workspace.tabs"); }
|
|
15
19
|
get approvalMode() {
|
|
16
20
|
return normalizeApprovalMode(loadConfig().approval_mode);
|
|
17
21
|
}
|
|
@@ -81,7 +85,7 @@ export class StartScreen extends Screen {
|
|
|
81
85
|
if (text)
|
|
82
86
|
this.app.startNewChat(text);
|
|
83
87
|
}
|
|
84
|
-
else if (this.focusIdx ===
|
|
88
|
+
else if (this.focusIdx === this.focusCount - 1) {
|
|
85
89
|
this.toggleApproval();
|
|
86
90
|
}
|
|
87
91
|
else {
|
|
@@ -149,7 +153,7 @@ export class StartScreen extends Screen {
|
|
|
149
153
|
// must not light up.
|
|
150
154
|
if (!proHover && !webHover) {
|
|
151
155
|
if (inRect(ev.x, ev.y, approvalToggle(this.app.width, this.app.height - 4, this.approvalMode))) {
|
|
152
|
-
hover =
|
|
156
|
+
hover = this.focusCount - 1;
|
|
153
157
|
}
|
|
154
158
|
else if (layout && inRect(ev.x, ev.y, { x: layout.inputX, y: layout.inputY, w: layout.inputW, h: INPUT_H })) {
|
|
155
159
|
hover = 0;
|
|
@@ -191,7 +195,7 @@ export class StartScreen extends Screen {
|
|
|
191
195
|
return;
|
|
192
196
|
}
|
|
193
197
|
if (ev.y >= 1 && inRect(ev.x, ev.y, approvalToggle(this.app.width, this.app.height - 4, this.approvalMode))) {
|
|
194
|
-
this.focusIdx =
|
|
198
|
+
this.focusIdx = this.focusCount - 1;
|
|
195
199
|
this.toggleApproval();
|
|
196
200
|
return;
|
|
197
201
|
}
|
|
@@ -256,12 +260,12 @@ export class StartScreen extends Screen {
|
|
|
256
260
|
const inputY = this.contentTop(h, contentH) + logoLines.length + logoGap;
|
|
257
261
|
const tabY = inputY + INPUT_H + gap;
|
|
258
262
|
const tabGap = 2;
|
|
259
|
-
const tabInner = inputW - tabGap * (
|
|
260
|
-
const baseW = Math.floor(tabInner /
|
|
261
|
-
const extra = tabInner - baseW *
|
|
263
|
+
const tabInner = inputW - tabGap * (this.tabs.length - 1);
|
|
264
|
+
const baseW = Math.floor(tabInner / this.tabs.length);
|
|
265
|
+
const extra = tabInner - baseW * this.tabs.length;
|
|
262
266
|
const tabs = [];
|
|
263
267
|
let tx = inputX;
|
|
264
|
-
for (let i = 0; i <
|
|
268
|
+
for (let i = 0; i < this.tabs.length; i++) {
|
|
265
269
|
const tw = baseW + (i < extra ? 1 : 0);
|
|
266
270
|
tabs.push({ x: tx, w: tw });
|
|
267
271
|
tx += tw + tabGap;
|
|
@@ -279,7 +283,7 @@ export class StartScreen extends Screen {
|
|
|
279
283
|
return pro ? Math.max(top, pro.y + pro.h) : top;
|
|
280
284
|
}
|
|
281
285
|
move(delta) {
|
|
282
|
-
this.focusIdx = (this.focusIdx + delta +
|
|
286
|
+
this.focusIdx = (this.focusIdx + delta + this.focusCount) % this.focusCount;
|
|
283
287
|
this.hoverIdx = -1;
|
|
284
288
|
this.app.render();
|
|
285
289
|
}
|
|
@@ -306,8 +310,12 @@ export class StartScreen extends Screen {
|
|
|
306
310
|
this.app.openModelConfig();
|
|
307
311
|
else if (this.focusIdx === 2)
|
|
308
312
|
this.app.openHistory();
|
|
309
|
-
else
|
|
313
|
+
else if (this.focusIdx === 3)
|
|
310
314
|
this.app.openSettings();
|
|
315
|
+
else if (this.focusIdx === 4)
|
|
316
|
+
this.app.openPlugins();
|
|
317
|
+
else if (this.extensions[this.focusIdx - 5])
|
|
318
|
+
this.app.openPluginContribution(this.extensions[this.focusIdx - 5]);
|
|
311
319
|
}
|
|
312
320
|
render(buf) {
|
|
313
321
|
drawHeader(buf, this.app.state);
|
|
@@ -335,14 +343,14 @@ export class StartScreen extends Screen {
|
|
|
335
343
|
});
|
|
336
344
|
y += INPUT_H + gap;
|
|
337
345
|
const tabGap = 2;
|
|
338
|
-
const tabInner = inputW - tabGap * (
|
|
339
|
-
const baseW = Math.floor(tabInner /
|
|
340
|
-
const extra = tabInner - baseW *
|
|
346
|
+
const tabInner = inputW - tabGap * (this.tabs.length - 1);
|
|
347
|
+
const baseW = Math.floor(tabInner / this.tabs.length);
|
|
348
|
+
const extra = tabInner - baseW * this.tabs.length;
|
|
341
349
|
let tx = inputX;
|
|
342
|
-
for (let i = 0; i <
|
|
350
|
+
for (let i = 0; i < this.tabs.length; i++) {
|
|
343
351
|
const tw = baseW + (i < extra ? 1 : 0);
|
|
344
352
|
const selected = i === this.focusIdx - 1 || i === this.hoverIdx - 1;
|
|
345
|
-
drawTabButton(buf, tx, y, tw,
|
|
353
|
+
drawTabButton(buf, tx, y, tw, this.tabs[i], selected);
|
|
346
354
|
tx += tw + tabGap;
|
|
347
355
|
}
|
|
348
356
|
// The version label is pinned one row above the very bottom of the screen,
|
|
@@ -363,7 +371,7 @@ export class StartScreen extends Screen {
|
|
|
363
371
|
if (showVersion) {
|
|
364
372
|
buf.centerText(versionY, `v${version}`, { fg: C.dim });
|
|
365
373
|
}
|
|
366
|
-
drawApprovalToggle(buf, approvalY, this.approvalMode, this.focusIdx ===
|
|
374
|
+
drawApprovalToggle(buf, approvalY, this.approvalMode, this.focusIdx === this.focusCount - 1 || this.hoverIdx === this.focusCount - 1);
|
|
367
375
|
// Painted last so their opaque footprints are never clipped by the
|
|
368
376
|
// decorative content drawn before them. Hit testing shares the geometry.
|
|
369
377
|
drawHeaderButton(buf, proModeButton(w, h), this.proHover);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Buffer } from "../terminal/buffer.js";
|
|
2
|
+
import { Screen } from "../terminal/screen.js";
|
|
3
|
+
import type { Key, MouseEvent } from "../terminal/input.js";
|
|
4
|
+
import { type UpdateStatus } from "../update.js";
|
|
5
|
+
/**
|
|
6
|
+
* The "a newer Neutrino is published" notice.
|
|
7
|
+
*
|
|
8
|
+
* It is pushed over the start screen only, never over a conversation: the
|
|
9
|
+
* answer arrives a moment after launch, and stealing a chat the user has
|
|
10
|
+
* already opened would be worse than a missed notice.
|
|
11
|
+
*
|
|
12
|
+
* Confirming runs npm here, in this process; the running code is not replaced,
|
|
13
|
+
* so a successful update ends by telling the user to restart. A failed one
|
|
14
|
+
* shows the command to run by hand, because npm usually fails for a reason the
|
|
15
|
+
* user has to fix (permissions, a proxy, an offline machine).
|
|
16
|
+
*/
|
|
17
|
+
export declare class UpdatePromptScreen extends Screen {
|
|
18
|
+
private phase;
|
|
19
|
+
/** Why the install failed, or which version landed. */
|
|
20
|
+
private detail;
|
|
21
|
+
private selectedIdx;
|
|
22
|
+
private lastHoverX;
|
|
23
|
+
private lastHoverY;
|
|
24
|
+
private readonly current;
|
|
25
|
+
private readonly latest;
|
|
26
|
+
constructor(status: UpdateStatus);
|
|
27
|
+
/** The buttons drawn right now; a click always lands on one of these. */
|
|
28
|
+
private buttons;
|
|
29
|
+
private buttonRow;
|
|
30
|
+
/** Columns the body paragraphs may use before wrapping. */
|
|
31
|
+
private bodyWidth;
|
|
32
|
+
private start;
|
|
33
|
+
private close;
|
|
34
|
+
private activate;
|
|
35
|
+
private move;
|
|
36
|
+
onKey(key: Key): void;
|
|
37
|
+
onMouse(ev: MouseEvent): void;
|
|
38
|
+
/** What the dialog says, in the order it is drawn. */
|
|
39
|
+
private body;
|
|
40
|
+
render(buf: Buffer): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Buffer } from "./terminal/buffer.js";
|
|
2
|
+
export interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
/** A point in *content* space: a line of the scrollable log, plus a column. */
|
|
7
|
+
export interface LinePoint {
|
|
8
|
+
line: number;
|
|
9
|
+
x: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A mouse text selection. `anchor` is the cell where the drag started and
|
|
13
|
+
* `cursor` is the cell currently under the pointer; either may be above/left of
|
|
14
|
+
* the other, so consumers normalise before use.
|
|
15
|
+
*/
|
|
16
|
+
export interface Selection {
|
|
17
|
+
anchor: Point;
|
|
18
|
+
cursor: Point;
|
|
19
|
+
}
|
|
20
|
+
/** Order the two ends of a selection into reading order (top-left first). */
|
|
21
|
+
export declare function normalizeSelection(sel: Selection): {
|
|
22
|
+
start: Point;
|
|
23
|
+
end: Point;
|
|
24
|
+
};
|
|
25
|
+
/** Order the two ends of a line-space selection into reading order. */
|
|
26
|
+
export declare function orderLinePoints(a: LinePoint, b: LinePoint): {
|
|
27
|
+
start: LinePoint;
|
|
28
|
+
end: LinePoint;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Paint the selection highlight directly onto the rendered buffer. Working on
|
|
32
|
+
* the buffer (rather than on each screen) means the highlight automatically
|
|
33
|
+
* follows whatever is on screen — chat output, the input box, menus, …
|
|
34
|
+
*
|
|
35
|
+
* Intermediate lines stop at the last non-blank cell so a multi-line selection
|
|
36
|
+
* doesn't paint a block out to the right edge of the terminal.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applySelection(buf: Buffer, sel: Selection): void;
|
|
39
|
+
/**
|
|
40
|
+
* Extract the plain text covered by a selection.
|
|
41
|
+
*
|
|
42
|
+
* Three kinds of cell are skipped so the result is clean, pasteable text:
|
|
43
|
+
* - UI chrome (the scrollbar), which is never content;
|
|
44
|
+
* - wide-character continuation cells (`""`), which would duplicate a CJK
|
|
45
|
+
* glyph;
|
|
46
|
+
* - trailing blanks, trimmed per line (shell-style).
|
|
47
|
+
*
|
|
48
|
+
* Intermediate lines are copied out to the buffer's full width and only then
|
|
49
|
+
* trimmed, which keeps indentation intact while dropping the padding on the
|
|
50
|
+
* right. Skipping the scrollbar *before* trimming is what lets that trim
|
|
51
|
+
* actually reach the end of the text instead of stopping at the bar.
|
|
52
|
+
*/
|
|
53
|
+
export declare function selectionText(buf: Buffer, sel: Selection): string;
|
|
54
|
+
/**
|
|
55
|
+
* One buffer row as column-indexed text: `cells[x]` is what column `x` holds,
|
|
56
|
+
* or `""` for a cell that never copies (UI chrome, and the tail of a
|
|
57
|
+
* double-width glyph).
|
|
58
|
+
*
|
|
59
|
+
* Keeping the column index is the point: it lets a row be sliced by column
|
|
60
|
+
* later, after the frame it came from is long gone. A drag that scrolls the log
|
|
61
|
+
* needs exactly that, because the rows it started on leave the screen while the
|
|
62
|
+
* selection keeps growing.
|
|
63
|
+
*/
|
|
64
|
+
export declare function rowCells(buf: Buffer, y: number): string[];
|
|
65
|
+
/**
|
|
66
|
+
* Extract the text of a selection expressed in content lines (`rowSelectionText`
|
|
67
|
+
* is to `selectionText` what `LinePoint` is to `Point`).
|
|
68
|
+
*
|
|
69
|
+
* `getRow` supplies the cells of a line — from the current frame while the line
|
|
70
|
+
* is on screen, from the snapshot taken during the drag once it has scrolled
|
|
71
|
+
* away, or `null` for a line that was never captured. Trailing blanks and edge
|
|
72
|
+
* blank lines are trimmed exactly like `selectionText`, so both paths produce
|
|
73
|
+
* the same text for a selection that fits on one screen.
|
|
74
|
+
*/
|
|
75
|
+
export declare function rowSelectionText(getRow: (line: number) => readonly string[] | null, start: LinePoint, end: LinePoint): string;
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Selection } from "./selection.js";
|
|
2
|
+
export interface AppState {
|
|
3
|
+
selectedProvider: string;
|
|
4
|
+
selectedModelName: string;
|
|
5
|
+
selectedModelUrl: string;
|
|
6
|
+
selectedModelApiKey: string;
|
|
7
|
+
selectedModelStreaming: boolean;
|
|
8
|
+
selectedIntensity: string;
|
|
9
|
+
isThinking: boolean;
|
|
10
|
+
/** Blink phase for the "thinking" indicator in the top status bar. */
|
|
11
|
+
blinkOn: boolean;
|
|
12
|
+
/** Blink phase for the text cursors (true = lit, false = hidden). */
|
|
13
|
+
cursorOn: boolean;
|
|
14
|
+
/** Pending human approval; while set the top bar turns orange. */
|
|
15
|
+
approval: {
|
|
16
|
+
tool: string;
|
|
17
|
+
level: "high" | "extreme";
|
|
18
|
+
} | null;
|
|
19
|
+
/** URL of the running browser UI, or null when it is not mounted. */
|
|
20
|
+
webUrl: string | null;
|
|
21
|
+
/** True when that console is owned by the detached background service. */
|
|
22
|
+
webBackground: boolean;
|
|
23
|
+
/** Active mouse text selection, or null when nothing is selected. */
|
|
24
|
+
selection: Selection | null;
|
|
25
|
+
}
|
|
26
|
+
export declare function defaultState(): AppState;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Style } from "../colors.js";
|
|
2
|
+
export interface Cell {
|
|
3
|
+
ch: string;
|
|
4
|
+
style: Style;
|
|
5
|
+
/**
|
|
6
|
+
* True for UI chrome (scrollbars, borders, …). Chrome is drawn like any
|
|
7
|
+
* other cell, but is deliberately *not* copied: extracting a selection
|
|
8
|
+
* skips it, so a copied line never ends up with a scrollbar glued to its
|
|
9
|
+
* right edge.
|
|
10
|
+
*/
|
|
11
|
+
chrome?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class Buffer {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
cells: Cell[][];
|
|
17
|
+
constructor(width: number, height: number);
|
|
18
|
+
/**
|
|
19
|
+
* Write a single cell, keeping the wide-glyph invariant intact. The renderer
|
|
20
|
+
* (and the selection logic) rely on the rule that a `""` cell is *always* the
|
|
21
|
+
* right half of a double-width glyph at `x - 1`. If the invariant is broken an
|
|
22
|
+
* orphan tail is left behind: the renderer skips it, so that column is never
|
|
23
|
+
* repainted and a stale glyph from the previous frame stays on screen (the
|
|
24
|
+
* "residual line" bug). To preserve it we blank the *other* half whenever we
|
|
25
|
+
* overwrite one half of a wide glyph.
|
|
26
|
+
*/
|
|
27
|
+
private overwriteCell;
|
|
28
|
+
/**
|
|
29
|
+
* Mark `x` as the tail (`""`) of the wide glyph at `x - 1`. Unlike
|
|
30
|
+
* `overwriteCell` this never blanks the cell to the left, because that cell is
|
|
31
|
+
* the glyph we are currently writing.
|
|
32
|
+
*/
|
|
33
|
+
private writeTail;
|
|
34
|
+
private writeChar;
|
|
35
|
+
set(x: number, y: number, ch: string, style?: Style): void;
|
|
36
|
+
writeText(x: number, y: number, text: string, style?: Style): void;
|
|
37
|
+
centerText(y: number, text: string, style?: Style): void;
|
|
38
|
+
fillRect(x: number, y: number, w: number, h: number, style?: Style): void;
|
|
39
|
+
drawBox(x: number, y: number, w: number, h: number, style?: Style): void;
|
|
40
|
+
/**
|
|
41
|
+
* Flag a cell as non-copyable chrome (e.g. a scrollbar). The flag lives on
|
|
42
|
+
* the cell and is cleared automatically whenever the cell is written again,
|
|
43
|
+
* since every frame starts from a fresh buffer.
|
|
44
|
+
*/
|
|
45
|
+
markChrome(x: number, y: number): void;
|
|
46
|
+
clear(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface Key {
|
|
2
|
+
name: string;
|
|
3
|
+
ctrl: boolean;
|
|
4
|
+
meta: boolean;
|
|
5
|
+
shift: boolean;
|
|
6
|
+
char: string;
|
|
7
|
+
sequence: string;
|
|
8
|
+
}
|
|
9
|
+
export type MouseEvent = {
|
|
10
|
+
type: "wheel";
|
|
11
|
+
dir: -1 | 1;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
} | {
|
|
15
|
+
type: "click";
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
button: number;
|
|
19
|
+
} | {
|
|
20
|
+
type: "move";
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
drag: boolean;
|
|
24
|
+
} | {
|
|
25
|
+
type: "release";
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
button: number;
|
|
29
|
+
};
|
|
30
|
+
export type InputEvent = {
|
|
31
|
+
type: "key";
|
|
32
|
+
key: Key;
|
|
33
|
+
} | {
|
|
34
|
+
type: "paste";
|
|
35
|
+
text: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: "mouse";
|
|
38
|
+
event: MouseEvent;
|
|
39
|
+
};
|
|
40
|
+
export declare class InputReader {
|
|
41
|
+
private decoder;
|
|
42
|
+
private pending;
|
|
43
|
+
private state;
|
|
44
|
+
private csiBuf;
|
|
45
|
+
private escTimer;
|
|
46
|
+
private inPaste;
|
|
47
|
+
private pasteBuf;
|
|
48
|
+
private handler;
|
|
49
|
+
private dataHandler;
|
|
50
|
+
start(handler: (ev: InputEvent) => void): void;
|
|
51
|
+
stop(): void;
|
|
52
|
+
private onData;
|
|
53
|
+
private process;
|
|
54
|
+
private scheduleEscTimeout;
|
|
55
|
+
private cancelEscTimeout;
|
|
56
|
+
private finishCsi;
|
|
57
|
+
/**
|
|
58
|
+
* Parse SGR (extended) mouse sequences: ESC [ < b ; x ; y M/m
|
|
59
|
+
*
|
|
60
|
+
* `b` is a bit field: the low two bits are the button (0 left, 1 middle,
|
|
61
|
+
* 2 right, 3 none/release), bit 5 (32) marks a motion event and bit 6 (64)
|
|
62
|
+
* marks a wheel event. A trailing `M` is a press/motion while `m` is a
|
|
63
|
+
* release. So motion with the left button held (a drag) is `b = 32`, whereas
|
|
64
|
+
* plain hover is `b = 35` (32 | 3).
|
|
65
|
+
*/
|
|
66
|
+
private handleMouse;
|
|
67
|
+
private emitKey;
|
|
68
|
+
private emitPaste;
|
|
69
|
+
private emitMouse;
|
|
70
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Buffer } from "./buffer.js";
|
|
2
|
+
export declare class Renderer {
|
|
3
|
+
private prevSig;
|
|
4
|
+
private disposed;
|
|
5
|
+
private prevW;
|
|
6
|
+
private prevH;
|
|
7
|
+
private out;
|
|
8
|
+
/**
|
|
9
|
+
* The most recent frame, kept so `repaint()` can re-emit it without re-running the
|
|
10
|
+
* screen's (potentially expensive) render — to heal any drift the terminal
|
|
11
|
+
* introduced behind our back.
|
|
12
|
+
*/
|
|
13
|
+
private lastBuf;
|
|
14
|
+
private blocked;
|
|
15
|
+
private pendingForce;
|
|
16
|
+
private drainHandler;
|
|
17
|
+
init(): void;
|
|
18
|
+
dispose(): void;
|
|
19
|
+
/**
|
|
20
|
+
* @param forceRows rows to repaint unconditionally, even when their contents
|
|
21
|
+
* are unchanged. Screens use this for persistent chrome (input box, hints)
|
|
22
|
+
* so that terminal-side drift can never leave it stale (see
|
|
23
|
+
* `Screen.alwaysRepaintRows`).
|
|
24
|
+
*/
|
|
25
|
+
render(buf: Buffer, forceRows?: readonly number[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Re-emit the last frame in full. Cheap (no screen re-render) and visually a
|
|
28
|
+
* no-op when nothing drifted, but it repairs rows the terminal moved on its
|
|
29
|
+
* own (resize reflow, stray scroll). Called periodically as a safety net.
|
|
30
|
+
*/
|
|
31
|
+
repaint(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Ring the terminal bell.
|
|
34
|
+
*
|
|
35
|
+
* Written straight to the stream rather than into a frame: BEL has no visual
|
|
36
|
+
* effect, so it cannot desync the diffed rows, and it must never end up in
|
|
37
|
+
* `lastBuf` where repaint() would replay it.
|
|
38
|
+
*/
|
|
39
|
+
bell(times?: number): void;
|
|
40
|
+
private draw;
|
|
41
|
+
private renderRow;
|
|
42
|
+
private write;
|
|
43
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape sequences that undo everything the TUI switches on.
|
|
3
|
+
*
|
|
4
|
+
* Mouse tracking is disabled before the alternate screen is left, and the
|
|
5
|
+
* cursor is restored before the screen swap, so the shell never inherits a
|
|
6
|
+
* half-configured terminal. Order matters: `?1049l` must come last or the
|
|
7
|
+
* remaining writes would land on the main screen.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TUI_TEARDOWN: string;
|
|
10
|
+
/** Called once the renderer has entered the alternate screen and raw mode. */
|
|
11
|
+
export declare function markTuiActive(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Puts the terminal back into a usable state, synchronously and at most once.
|
|
14
|
+
*
|
|
15
|
+
* Everything here has to work while the process is dying: no promises, no
|
|
16
|
+
* asynchronous writes, and every step individually guarded so one failure
|
|
17
|
+
* cannot skip the rest (a broken `setRawMode` must not leave the cursor
|
|
18
|
+
* hidden, for example).
|
|
19
|
+
*/
|
|
20
|
+
export declare function restoreTerminalSync(): boolean;
|
|
21
|
+
/** Appends a crash entry to `~/.neutrino/crash.log` and returns the path. */
|
|
22
|
+
export declare function writeCrashLog(kind: string, err: unknown): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Installs process-wide handlers that restore the terminal before the process
|
|
25
|
+
* dies.
|
|
26
|
+
*
|
|
27
|
+
* Without this, any throw that escapes a timer, a stream callback or a floating
|
|
28
|
+
* promise kills the process with raw mode still on and the alternate screen
|
|
29
|
+
* still active. The visible result is a terminal that works but spews
|
|
30
|
+
* `[<b>;<x>;<y>M` mouse reports at the shell prompt, because the shell now
|
|
31
|
+
* receives the mouse events the TUI asked the terminal to send.
|
|
32
|
+
*/
|
|
33
|
+
export interface CrashGuardOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Keep SIGHUP for the caller. A detached background service must survive the
|
|
36
|
+
* terminal that started it closing (on POSIX that arrives as SIGHUP), while
|
|
37
|
+
* the terminal UI still wants a hangup to end the process.
|
|
38
|
+
*/
|
|
39
|
+
surviveHangup?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function installCrashGuard(options?: CrashGuardOptions): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Buffer } from "./buffer.js";
|
|
2
|
+
import type { Key, MouseEvent } from "./input.js";
|
|
3
|
+
import type { App } from "../app.js";
|
|
4
|
+
/**
|
|
5
|
+
* A screen's scrollbar, as seen by the app. Supplying this lets the app turn a
|
|
6
|
+
* press/drag on the scrollbar column into scrolling, the same way a GUI does.
|
|
7
|
+
*/
|
|
8
|
+
export interface ScrollbarHandle {
|
|
9
|
+
/** Column the bar is drawn in. */
|
|
10
|
+
x: number;
|
|
11
|
+
/** Inclusive row range of the groove. */
|
|
12
|
+
top: number;
|
|
13
|
+
bottom: number;
|
|
14
|
+
/** Total scrollable lines and how many fit on screen. */
|
|
15
|
+
total: number;
|
|
16
|
+
viewport: number;
|
|
17
|
+
/** Current topmost visible line. */
|
|
18
|
+
offset: number;
|
|
19
|
+
/** Scroll to the given offset (clamped by the screen). */
|
|
20
|
+
scrollTo(offset: number): void;
|
|
21
|
+
}
|
|
22
|
+
export declare abstract class Screen {
|
|
23
|
+
app: App;
|
|
24
|
+
abstract onKey(key: Key): void;
|
|
25
|
+
onMouse(_ev: MouseEvent): void;
|
|
26
|
+
/**
|
|
27
|
+
* Describe the on-screen scrollbar so the app can drag it, or null when this
|
|
28
|
+
* screen has none / its content fits. Geometry is only valid right after a
|
|
29
|
+
* render, which is exactly when the app needs it.
|
|
30
|
+
*/
|
|
31
|
+
scrollbar(): ScrollbarHandle | null;
|
|
32
|
+
/**
|
|
33
|
+
* Rows this screen draws as persistent "chrome" (an input box, key hints, …)
|
|
34
|
+
* that must be repainted on *every* frame, even when their contents are
|
|
35
|
+
* unchanged.
|
|
36
|
+
*
|
|
37
|
+
* The renderer normally emits only rows whose cells changed, which is what
|
|
38
|
+
* keeps redraws cheap. But the terminal can shift its own contents behind our
|
|
39
|
+
* back — ConPTY reflowing the buffer mid-resize, a size-race repaint, a stray
|
|
40
|
+
* scroll — and a diffed row would then stay stale forever (the classic
|
|
41
|
+
* "input box swallowed by the log" artefact). Redrawing this small strip by
|
|
42
|
+
* absolute position every frame makes it self-healing.
|
|
43
|
+
*/
|
|
44
|
+
alwaysRepaintRows(): number[];
|
|
45
|
+
/**
|
|
46
|
+
* Handle a bracketed (or detected) paste. `text` already has its newlines
|
|
47
|
+
* flattened to spaces, so it can be inserted as a single line. Screens that
|
|
48
|
+
* have no text input can leave this as a no-op.
|
|
49
|
+
*/
|
|
50
|
+
onPaste(_text: string): void;
|
|
51
|
+
abstract render(buf: Buffer): void;
|
|
52
|
+
/** Whether this screen currently shows a blinking text cursor. Used by the
|
|
53
|
+
* app to run (or stop) the cursor blink timer. */
|
|
54
|
+
showsCursor(): boolean;
|
|
55
|
+
onActivate(): void;
|
|
56
|
+
onDeactivate(): void;
|
|
57
|
+
}
|
package/dist/tray.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface TrayOptions {
|
|
2
|
+
/** Console address opened by a click. */
|
|
3
|
+
url: string;
|
|
4
|
+
/** Management endpoint that stops the service. */
|
|
5
|
+
stopUrl: string;
|
|
6
|
+
/** Bearer token for `stopUrl`. */
|
|
7
|
+
token: string;
|
|
8
|
+
/** Process id the icon belongs to; the icon exits with it. */
|
|
9
|
+
pid: number;
|
|
10
|
+
}
|
|
11
|
+
/** The icon itself. Only Windows has a notification area, and only it can run this. */
|
|
12
|
+
export declare function startBackgroundTray(options: TrayOptions): {
|
|
13
|
+
close(): void;
|
|
14
|
+
} | null;
|