@xingjisoft/neutrino 0.6.0 → 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 +22 -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/context.js +17 -4
- 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 +178 -24
- 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 +25 -12
- 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/agents.js +1 -1
- 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 +179 -65
- 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 +170 -21
- 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 +30 -8
- package/web/app.js +256 -67
- package/web/i18n.js +18 -3
- package/web/index.html +3 -1
- package/web/plugins.css +10 -0
- package/web/plugins.js +230 -0
- package/web/style.css +12 -3
- package/web/turns.js +7 -8
|
@@ -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;
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Buffer } from "./terminal/buffer.js";
|
|
2
|
+
import { type RGB } from "./colors.js";
|
|
3
|
+
import type { ApprovalMode } from "./config.js";
|
|
4
|
+
/** One terminal row; shared geometry keeps rendering and hit testing aligned. */
|
|
5
|
+
export declare function approvalToggle(width: number, y: number, mode: ApprovalMode): {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
w: number;
|
|
9
|
+
h: number;
|
|
10
|
+
text: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function drawApprovalToggle(buf: Buffer, y: number, mode: ApprovalMode, focused?: boolean): void;
|
|
13
|
+
export declare function wrapText(text: string, width: number): string[];
|
|
14
|
+
export interface InputBoxOptions {
|
|
15
|
+
active?: boolean;
|
|
16
|
+
dimmed?: boolean;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
/** Blink phase of the text cursor. When false the cursor is hidden (the
|
|
19
|
+
* character underneath is shown normally). */
|
|
20
|
+
cursorOn?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface InputWindow {
|
|
23
|
+
/** Index (in UTF-16 code points) of the first visible character. */
|
|
24
|
+
startCp: number;
|
|
25
|
+
/** Display columns before the visible window. */
|
|
26
|
+
prefixW: number;
|
|
27
|
+
/** Number of visible characters. */
|
|
28
|
+
visibleCount: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compute which part of the input value is visible inside the box, given the
|
|
32
|
+
* cursor position. Shared by `drawInputBox` and mouse click handling so the
|
|
33
|
+
* rendered text and click-to-position-cursor always agree.
|
|
34
|
+
*/
|
|
35
|
+
export declare function computeInputWindow(value: string, cursorPos: number, innerW: number): InputWindow;
|
|
36
|
+
/**
|
|
37
|
+
* Map a column inside the input box (0-based, relative to the inner area) to a
|
|
38
|
+
* cursor index in the full value.
|
|
39
|
+
*/
|
|
40
|
+
export declare function cursorFromColumn(value: string, col: number, win: InputWindow): number;
|
|
41
|
+
export interface Rect {
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
w: number;
|
|
45
|
+
h: number;
|
|
46
|
+
}
|
|
47
|
+
export declare function inRect(px: number, py: number, r: Rect): boolean;
|
|
48
|
+
export declare function drawHint(buf: Buffer, y: number, text: string): void;
|
|
49
|
+
export interface ScrollbarOptions {
|
|
50
|
+
/** Colour of the groove. Defaults to `C.scrollTrack`. */
|
|
51
|
+
track?: RGB;
|
|
52
|
+
/** Colour of the thumb. Defaults to `C.scrollThumb`. */
|
|
53
|
+
thumb?: RGB;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Draw a vertical scrollbar in column `x`, spanning rows `top`..`bottom`
|
|
57
|
+
* (inclusive). It is a no-op when everything fits, so callers can render it
|
|
58
|
+
* unconditionally.
|
|
59
|
+
*
|
|
60
|
+
* total - total number of scrollable items/lines
|
|
61
|
+
* viewport - how many of them are visible at once
|
|
62
|
+
* offset - index of the topmost visible item (i.e. the current scrollTop)
|
|
63
|
+
*
|
|
64
|
+
* The thumb length is proportional to `viewport / total` (at least one cell)
|
|
65
|
+
* and its position is proportional to `offset / (total - viewport)`, matching
|
|
66
|
+
* the usual GUI scrollbar behaviour.
|
|
67
|
+
*/
|
|
68
|
+
export interface ScrollbarGeometry {
|
|
69
|
+
/** Height of the groove in cells. */
|
|
70
|
+
trackH: number;
|
|
71
|
+
/** Height of the thumb in cells (at least 1). */
|
|
72
|
+
thumbH: number;
|
|
73
|
+
/** Largest allowed top row of the thumb, relative to the groove. */
|
|
74
|
+
maxThumbTop: number;
|
|
75
|
+
/** Largest valid scroll offset (total - viewport). */
|
|
76
|
+
maxOffset: number;
|
|
77
|
+
/** Current top row of the thumb, relative to the groove. */
|
|
78
|
+
thumbTop: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Geometry of a vertical scrollbar, shared by the drawing code and the app's
|
|
82
|
+
* hit-testing / dragging so the two can never disagree.
|
|
83
|
+
*/
|
|
84
|
+
export declare function scrollbarGeometry(top: number, bottom: number, total: number, viewport: number, offset: number): ScrollbarGeometry;
|
|
85
|
+
/**
|
|
86
|
+
* Inverse of the thumb positioning above: the scroll offset that places the
|
|
87
|
+
* thumb's top at 'thumbTop'. Used while dragging the thumb; feeding the result
|
|
88
|
+
* back through scrollbarGeometry reproduces the same 'thumbTop'.
|
|
89
|
+
*/
|
|
90
|
+
export declare function offsetFromThumbTop(g: ScrollbarGeometry, thumbTop: number): number;
|
|
91
|
+
export declare function drawScrollbar(buf: Buffer, x: number, top: number, bottom: number, total: number, viewport: number, offset: number, opts?: ScrollbarOptions): void;
|
|
92
|
+
export declare function drawInputBox(buf: Buffer, x: number, y: number, w: number, value: string, cursorPos: number, opts?: InputBoxOptions): void;
|
|
93
|
+
export declare function drawTabButton(buf: Buffer, x: number, y: number, w: number, label: string, selected: boolean, align?: "center" | "left"): void;
|
|
94
|
+
/** Label of the CLI entry point that opens the browser console's Pro workspace. */
|
|
95
|
+
export declare const PRO_MODE_LABEL = "\u542F\u7528Pro\u6A21\u5F0F";
|
|
96
|
+
/** Label of the CLI entry point that opens the browser console itself. */
|
|
97
|
+
export declare const WEB_MODE_LABEL = "\u6253\u5F00Web\u7AEF";
|
|
98
|
+
/** One of the small entry-point boxes painted in the top-left corner. */
|
|
99
|
+
export interface HeaderButton {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
w: number;
|
|
103
|
+
h: number;
|
|
104
|
+
label: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The Pro-mode button: flush to the left edge with a single blank column, as
|
|
108
|
+
* the start screen expects.
|
|
109
|
+
*/
|
|
110
|
+
export declare function proModeButton(width: number, height: number): HeaderButton | null;
|
|
111
|
+
/**
|
|
112
|
+
* The Web-mode button: glued to the right border of the Pro-mode button so the
|
|
113
|
+
* two read as one row of entry points. It starts where the Pro box ends, which
|
|
114
|
+
* also means it is hidden together with it on terminals too small for the row.
|
|
115
|
+
*/
|
|
116
|
+
export declare function webModeButton(width: number, height: number): HeaderButton | null;
|
|
117
|
+
/**
|
|
118
|
+
* Draw one top-left button. It is painted as opaque chrome so content behind it
|
|
119
|
+
* can never bleed through; the label row turns into the highlight background
|
|
120
|
+
* while the pointer is over it.
|
|
121
|
+
*/
|
|
122
|
+
export declare function drawHeaderButton(buf: Buffer, button: HeaderButton | null, hovered?: boolean): void;
|
|
123
|
+
export declare function centerVertically(totalHeight: number, contentHeight: number): number;
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** The published package whose registry version decides whether an update exists. */
|
|
2
|
+
export declare const UPDATE_PACKAGE = "@xingjisoft/neutrino";
|
|
3
|
+
/** What a user can run by hand when the automatic update fails. */
|
|
4
|
+
export declare const MANUAL_UPDATE_COMMAND = "npm install -g @xingjisoft/neutrino";
|
|
5
|
+
export interface UpdateStatus {
|
|
6
|
+
/** The version this process runs, read from its own package.json. */
|
|
7
|
+
currentVersion: string;
|
|
8
|
+
/** The published version; empty until a registry lookup has succeeded. */
|
|
9
|
+
latestVersion: string;
|
|
10
|
+
/** True only when the registry offers a strictly newer version. */
|
|
11
|
+
hasUpdate: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface UpdateCheck extends UpdateStatus {
|
|
14
|
+
/** Why the registry could not be read; the status then reports no update. */
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface UpdateInstallResult {
|
|
18
|
+
/** Whether npm reported success. */
|
|
19
|
+
ok: boolean;
|
|
20
|
+
/** The version this run was updating to; empty when none was known. */
|
|
21
|
+
version: string;
|
|
22
|
+
/** Why the install failed; empty on success. */
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface UpdateCheckOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Ask the registry again even though an answer is already remembered. The
|
|
28
|
+
* settings page's manual check and the periodic background check need a
|
|
29
|
+
* fresh answer; waiting for the next start-up would tell them nothing new.
|
|
30
|
+
*/
|
|
31
|
+
refresh?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The last known answer, for the terminal screen and the Web console payload.
|
|
35
|
+
* Reading this never touches the network: the lookup runs once per process (see
|
|
36
|
+
* checkForUpdate) and costs nothing afterwards.
|
|
37
|
+
*/
|
|
38
|
+
export declare function updateStatus(): UpdateStatus;
|
|
39
|
+
/**
|
|
40
|
+
* Ask npm which version is published and remember it. This never rejects: an
|
|
41
|
+
* offline machine, a missing npm or a registry hiccup only means "no update to
|
|
42
|
+
* announce", which is what the next start is for. Concurrent callers share the
|
|
43
|
+
* one lookup, and a successful answer is kept for the rest of the process so
|
|
44
|
+
* the terminal and the Web console cannot disagree.
|
|
45
|
+
*
|
|
46
|
+
* Start-up asks once and is served from that answer from then on. A caller that
|
|
47
|
+
* passes `refresh` — the settings button, the background timer — queries the
|
|
48
|
+
* registry again, so an answer from an hour ago cannot make a check look idle.
|
|
49
|
+
* A failed refresh changes nothing: the last good answer stays, and the caller
|
|
50
|
+
* learns what went wrong from `error`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function checkForUpdate(options?: UpdateCheckOptions): Promise<UpdateCheck>;
|
|
53
|
+
/**
|
|
54
|
+
* Install the latest published release with npm.
|
|
55
|
+
*
|
|
56
|
+
* The terminal and both Web workspaces can ask for this, so requests share one
|
|
57
|
+
* install instead of running npm twice over the same files. It never rejects:
|
|
58
|
+
* whoever asked gets a result and, on failure, the command to run by hand.
|
|
59
|
+
*
|
|
60
|
+
* The version this process runs is not replaced — the running code stays on
|
|
61
|
+
* disk until the user restarts — so callers must tell the user to restart.
|
|
62
|
+
*/
|
|
63
|
+
export declare function installUpdate(): Promise<UpdateInstallResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Semantic-version order: returns 1 when `left` is newer, -1 when older and 0
|
|
66
|
+
* when they are the same release. A version that cannot be parsed compares as
|
|
67
|
+
* equal, so a malformed registry answer can never claim an update is due.
|
|
68
|
+
*/
|
|
69
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
70
|
+
/** Whether `candidate` is a strictly newer release than `current`. */
|
|
71
|
+
export declare function isNewerVersion(candidate: string, current: string): boolean;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The periodic update check.
|
|
3
|
+
*
|
|
4
|
+
* A Web console can stay open for days, so the start-up lookup alone would
|
|
5
|
+
* never tell its windows about a release published afterwards. This module asks
|
|
6
|
+
* the registry again on a timer, in the process that serves those windows, and
|
|
7
|
+
* hands a release it has not seen before to whoever is listening: the console
|
|
8
|
+
* forwards it to every open browser (GET /api/update/events) and the first-open
|
|
9
|
+
* dialog reads the same answer.
|
|
10
|
+
*/
|
|
11
|
+
import { type UpdateCheck } from "./update.js";
|
|
12
|
+
/** How often the background service asks the registry again. */
|
|
13
|
+
export declare const UPDATE_CHECK_INTERVAL_MS: number;
|
|
14
|
+
export interface UpdateWatchOptions {
|
|
15
|
+
/** Overridable so a test can watch a timer; the console never changes it. */
|
|
16
|
+
intervalMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Called after every check, including the ones that found nothing or failed,
|
|
19
|
+
* so the background log can record a change. Never allowed to stop the timer.
|
|
20
|
+
*/
|
|
21
|
+
onCheck?: (check: UpdateCheck) => void;
|
|
22
|
+
}
|
|
23
|
+
type Listener = (check: UpdateCheck) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Listen for a release the periodic check finds. Returns the unsubscribe
|
|
26
|
+
* function.
|
|
27
|
+
*
|
|
28
|
+
* Only news is announced: a version this process already knows — from the
|
|
29
|
+
* start-up lookup, the settings button or an earlier tick — is not sent again,
|
|
30
|
+
* so the dialogs cannot reopen every half hour for a release the user has
|
|
31
|
+
* already declined.
|
|
32
|
+
*/
|
|
33
|
+
export declare function subscribeUpdateAnnouncements(listener: Listener): () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Start asking the registry every `intervalMs` (30 minutes by default).
|
|
36
|
+
*
|
|
37
|
+
* Idempotent, and the returned function stops the timer and leaves the module
|
|
38
|
+
* ready to start again. Checks run off the timer rather than at start-up: both
|
|
39
|
+
* the console and the terminal UI already look once when they start, and a
|
|
40
|
+
* second immediate lookup would only spend npm calls on the same answer.
|
|
41
|
+
*/
|
|
42
|
+
export declare function startUpdateWatch(options?: UpdateWatchOptions): () => void;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Serialize human prompts without overwriting an unresolved decision. */
|
|
2
|
+
export declare class ApprovalQueue {
|
|
3
|
+
private active;
|
|
4
|
+
private waiting;
|
|
5
|
+
run(start: () => Promise<boolean | null>, signal?: AbortSignal): Promise<boolean | null>;
|
|
6
|
+
cancelWaiting(): void;
|
|
7
|
+
private next;
|
|
8
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Serialize human prompts without overwriting an unresolved decision. */
|
|
2
|
+
export class ApprovalQueue {
|
|
3
|
+
active = false;
|
|
4
|
+
waiting = [];
|
|
5
|
+
run(start, signal) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
if (signal?.aborted) {
|
|
8
|
+
resolve(false);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const item = {
|
|
12
|
+
cancel: () => {
|
|
13
|
+
const index = this.waiting.indexOf(item);
|
|
14
|
+
if (index >= 0) {
|
|
15
|
+
this.waiting.splice(index, 1);
|
|
16
|
+
signal?.removeEventListener("abort", item.cancel);
|
|
17
|
+
resolve(false);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
run: () => {
|
|
21
|
+
signal?.removeEventListener("abort", item.cancel);
|
|
22
|
+
if (signal?.aborted) {
|
|
23
|
+
this.next();
|
|
24
|
+
resolve(false);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.active = true;
|
|
28
|
+
let result;
|
|
29
|
+
try {
|
|
30
|
+
result = start();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
this.next();
|
|
34
|
+
reject(error);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
result.then(value => { this.next(); resolve(value); }, error => { this.next(); reject(error); });
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
if (this.active) {
|
|
41
|
+
this.waiting.push(item);
|
|
42
|
+
signal?.addEventListener("abort", item.cancel, { once: true });
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
item.run();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
cancelWaiting() {
|
|
49
|
+
for (const item of [...this.waiting])
|
|
50
|
+
item.cancel();
|
|
51
|
+
}
|
|
52
|
+
next() {
|
|
53
|
+
const next = this.waiting.shift();
|
|
54
|
+
if (next)
|
|
55
|
+
next.run();
|
|
56
|
+
else
|
|
57
|
+
this.active = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type http from "node:http";
|
|
2
|
+
export declare const DEFAULT_WEB_PASSWORD = "114514";
|
|
3
|
+
export declare function validateWebPassword(password: unknown): asserts password is string;
|
|
4
|
+
/** Only a salted password hash is persisted; settings never return it. */
|
|
5
|
+
export declare function hashWebPassword(password: unknown): string;
|
|
6
|
+
/** Signed cookies survive restarts, but a password change invalidates all of them. */
|
|
7
|
+
export declare class WebAuth {
|
|
8
|
+
readonly enabled: boolean;
|
|
9
|
+
constructor(enabled: boolean);
|
|
10
|
+
authenticated(req: http.IncomingMessage): boolean;
|
|
11
|
+
login(password: unknown): Promise<boolean>;
|
|
12
|
+
remember(res: http.ServerResponse): void;
|
|
13
|
+
forget(res: http.ServerResponse): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Mode } from "../config.js";
|
|
2
|
+
export declare const WEB_STATE_FILE: string;
|
|
3
|
+
/** How many finished conversations one workspace remembers, newest last. */
|
|
4
|
+
export declare const SEEN_LIMIT = 300;
|
|
5
|
+
/** Whether a value from a request is an identity this file can hold. */
|
|
6
|
+
export declare function isSeenIdentity(value: unknown): value is string;
|
|
7
|
+
/** The conversations this workspace has already shown the reader, oldest first. */
|
|
8
|
+
export declare function loadSeen(mode?: Mode): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Record (`seen: true`) or forget (`seen: false`) the given conversations and
|
|
11
|
+
* write the file back. Returns the workspace's new record.
|
|
12
|
+
*
|
|
13
|
+
* Identities that are not valid are ignored rather than rejected: the route
|
|
14
|
+
* already validated them, so this is only a guard on what reaches the disk.
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateSeen(mode: Mode, identities: string[], seen: boolean): string[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** What the folder window reported. Closing the window is a normal outcome. */
|
|
2
|
+
export type FolderPick = {
|
|
3
|
+
picked: string;
|
|
4
|
+
} | {
|
|
5
|
+
cancelled: true;
|
|
6
|
+
} | {
|
|
7
|
+
unavailable: string;
|
|
8
|
+
};
|
|
9
|
+
/** One finished dialog process. */
|
|
10
|
+
export interface DialogRun {
|
|
11
|
+
/** Exit code, or null when the process never ran to completion. */
|
|
12
|
+
code: number | null;
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
/** The executable itself does not exist here. */
|
|
16
|
+
missing: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs one dialog command. Injectable so the Windows, macOS and Linux paths can
|
|
20
|
+
* be tested on any machine, without a desktop session to draw a window in.
|
|
21
|
+
*/
|
|
22
|
+
export type DialogRunner = (command: string, args: string[], env: NodeJS.ProcessEnv, timeoutMs: number) => Promise<DialogRun>;
|
|
23
|
+
export interface FolderPickRequest {
|
|
24
|
+
/** Folder the window opens in. Ignored when it is not an existing folder. */
|
|
25
|
+
start?: string;
|
|
26
|
+
/** Window title, already translated by the console. */
|
|
27
|
+
title?: string;
|
|
28
|
+
platform?: NodeJS.Platform;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
runner?: DialogRunner;
|
|
31
|
+
env?: NodeJS.ProcessEnv;
|
|
32
|
+
}
|
|
33
|
+
/** Open the operating system's folder window and report what the user chose. */
|
|
34
|
+
export declare function pickFolder(request?: FolderPickRequest): Promise<FolderPick>;
|