nebula-notebook 0.1.1 → 0.2.1
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 +110 -16
- package/dist/assets/errorwidget-Wy5ajPiM.js +5 -0
- package/dist/assets/fa-brands-400-CEJbCg16.woff +0 -0
- package/dist/assets/fa-brands-400-CSYNqBb_.ttf +0 -0
- package/dist/assets/fa-brands-400-DnkPfk3o.eot +0 -0
- package/dist/assets/fa-brands-400-UxlILjvJ.woff2 +0 -0
- package/dist/assets/fa-brands-400-cH1MgKbP.svg +3717 -0
- package/dist/assets/fa-regular-400-BhTwtT8w.eot +0 -0
- package/dist/assets/fa-regular-400-D1vz6WBx.ttf +0 -0
- package/dist/assets/fa-regular-400-DFnMcJPd.woff +0 -0
- package/dist/assets/fa-regular-400-DGzu1beS.woff2 +0 -0
- package/dist/assets/fa-regular-400-gwj8Pxq-.svg +801 -0
- package/dist/assets/fa-solid-900-B4ZZ7kfP.svg +5034 -0
- package/dist/assets/fa-solid-900-B6Axprfb.eot +0 -0
- package/dist/assets/fa-solid-900-BUswJgRo.woff2 +0 -0
- package/dist/assets/fa-solid-900-DOXgCApm.woff +0 -0
- package/dist/assets/fa-solid-900-mxuxnBEa.ttf +0 -0
- package/dist/assets/index-B_Bs5koo.js +1 -0
- package/dist/assets/index-BtWv4MIT.css +7 -0
- package/dist/assets/index-DK4nS74r.js +829 -0
- package/dist/assets/index-DeQN03nW.js +81 -0
- package/dist/assets/index-DzjpmUak.css +32 -0
- package/dist/assets/index-g6MjT-VG.js +1 -0
- package/dist/assets/services-shim-BirHVsih.js +33 -0
- package/dist/assets/viewlist-uomDf7I7.js +1 -0
- package/dist/assets/widgets-X7J3NxEn.css +1 -0
- package/dist/index.html +2 -2
- package/node-server/dist/auth/auth-middleware.js +22 -4
- package/node-server/dist/auth/auth-service.js +37 -7
- package/node-server/dist/cluster/client-registration.d.ts +12 -1
- package/node-server/dist/cluster/client-registration.js +38 -2
- package/node-server/dist/cluster/kernel-proxy.js +43 -12
- package/node-server/dist/cluster/server-registry.d.ts +17 -0
- package/node-server/dist/cluster/server-registry.js +57 -7
- package/node-server/dist/discovery/conda-locations.d.ts +73 -0
- package/node-server/dist/discovery/conda-locations.js +427 -0
- package/node-server/dist/discovery/discovery-service.d.ts +73 -4
- package/node-server/dist/discovery/discovery-service.js +211 -127
- package/node-server/dist/discovery/types.d.ts +0 -1
- package/node-server/dist/discovery/types.js +1 -2
- package/node-server/dist/environment.d.ts +12 -0
- package/node-server/dist/environment.js +104 -0
- package/node-server/dist/fs/fs-service.d.ts +86 -8
- package/node-server/dist/fs/fs-service.js +640 -99
- package/node-server/dist/fs/notebook-formats/percent.d.ts +25 -0
- package/node-server/dist/fs/notebook-formats/percent.js +286 -0
- package/node-server/dist/fs/notebook-formats/qmd.d.ts +29 -0
- package/node-server/dist/fs/notebook-formats/qmd.js +307 -0
- package/node-server/dist/fs/notebook-formats/registry.d.ts +12 -0
- package/node-server/dist/fs/notebook-formats/registry.js +77 -0
- package/node-server/dist/fs/notebook-formats/types.d.ts +37 -0
- package/node-server/dist/fs/notebook-formats/types.js +13 -0
- package/node-server/dist/fs/types.d.ts +9 -0
- package/node-server/dist/fs/types.js +8 -0
- package/node-server/dist/idle-exit.d.ts +52 -0
- package/node-server/dist/idle-exit.js +83 -0
- package/node-server/dist/index.js +198 -8
- package/node-server/dist/kernel/kernel-service.d.ts +130 -2
- package/node-server/dist/kernel/kernel-service.js +834 -64
- package/node-server/dist/kernel/kernelspec.d.ts +27 -0
- package/node-server/dist/kernel/kernelspec.js +75 -4
- package/node-server/dist/notebook/headless-handler.d.ts +12 -0
- package/node-server/dist/notebook/headless-handler.js +125 -29
- package/node-server/dist/notebook/operation-router.js +7 -2
- package/node-server/dist/notebook/undoRedoManager.d.ts +12 -1
- package/node-server/dist/notebook/undoRedoManager.js +49 -18
- package/node-server/dist/output/display-data.js +2 -0
- package/node-server/dist/private-tmp.d.ts +14 -0
- package/node-server/dist/private-tmp.js +70 -0
- package/node-server/dist/routes/autocomplete.d.ts +19 -0
- package/node-server/dist/routes/autocomplete.js +294 -0
- package/node-server/dist/routes/cluster.js +2 -2
- package/node-server/dist/routes/compute.d.ts +8 -0
- package/node-server/dist/routes/compute.js +136 -0
- package/node-server/dist/routes/fs.js +23 -6
- package/node-server/dist/routes/kernel.js +156 -3
- package/node-server/dist/routes/notebook.js +37 -3
- package/node-server/dist/routes/python.d.ts +1 -2
- package/node-server/dist/routes/python.js +139 -3
- package/node-server/dist/scheduler/allocation-service.d.ts +63 -0
- package/node-server/dist/scheduler/allocation-service.js +285 -0
- package/node-server/dist/scheduler/job-template.d.ts +30 -0
- package/node-server/dist/scheduler/job-template.js +85 -0
- package/node-server/dist/scheduler/mock-scheduler.d.ts +30 -0
- package/node-server/dist/scheduler/mock-scheduler.js +125 -0
- package/node-server/dist/scheduler/slurm-scheduler.d.ts +31 -0
- package/node-server/dist/scheduler/slurm-scheduler.js +406 -0
- package/node-server/dist/scheduler/types.d.ts +118 -0
- package/node-server/dist/scheduler/types.js +8 -0
- package/node-server/dist/scheduler/util.d.ts +7 -0
- package/node-server/dist/scheduler/util.js +20 -0
- package/node-server/dist/terminal/agent-registry.d.ts +87 -0
- package/node-server/dist/terminal/agent-registry.js +246 -0
- package/node-server/dist/terminal/binding-store.d.ts +57 -0
- package/node-server/dist/terminal/binding-store.js +0 -0
- package/node-server/dist/terminal/pty-manager.d.ts +23 -6
- package/node-server/dist/terminal/pty-manager.js +138 -19
- package/node-server/dist/terminal/server.js +155 -23
- package/node-server/dist/terminal/types.d.ts +2 -0
- package/node-server/dist/update-check.d.ts +20 -0
- package/node-server/dist/update-check.js +114 -0
- package/node-server/package.json +2 -0
- package/node_modules/nebula-autocomplete/README.md +145 -0
- package/node_modules/nebula-autocomplete/dist/backends/claude.d.ts +69 -0
- package/node_modules/nebula-autocomplete/dist/backends/claude.js +449 -0
- package/node_modules/nebula-autocomplete/dist/backends/codex.d.ts +45 -0
- package/node_modules/nebula-autocomplete/dist/backends/codex.js +108 -0
- package/node_modules/nebula-autocomplete/dist/client/fetcher.d.ts +16 -0
- package/node_modules/nebula-autocomplete/dist/client/fetcher.js +41 -0
- package/node_modules/nebula-autocomplete/dist/codemirror/ghostText.d.ts +43 -0
- package/node_modules/nebula-autocomplete/dist/codemirror/ghostText.js +175 -0
- package/node_modules/nebula-autocomplete/dist/core/engine.d.ts +17 -0
- package/node_modules/nebula-autocomplete/dist/core/engine.js +135 -0
- package/node_modules/nebula-autocomplete/dist/core/lru.d.ts +7 -0
- package/node_modules/nebula-autocomplete/dist/core/lru.js +26 -0
- package/node_modules/nebula-autocomplete/dist/core/prompt.d.ts +12 -0
- package/node_modules/nebula-autocomplete/dist/core/prompt.js +0 -0
- package/node_modules/nebula-autocomplete/dist/core/text.d.ts +53 -0
- package/node_modules/nebula-autocomplete/dist/core/text.js +169 -0
- package/node_modules/nebula-autocomplete/dist/index.d.ts +7 -0
- package/node_modules/nebula-autocomplete/dist/index.js +5 -0
- package/node_modules/nebula-autocomplete/dist/server/fastify.d.ts +40 -0
- package/node_modules/nebula-autocomplete/dist/server/fastify.js +52 -0
- package/node_modules/nebula-autocomplete/dist/transport.d.ts +55 -0
- package/node_modules/nebula-autocomplete/dist/transport.js +66 -0
- package/node_modules/nebula-autocomplete/dist/types.d.ts +103 -0
- package/node_modules/nebula-autocomplete/dist/types.js +1 -0
- package/node_modules/nebula-autocomplete/package.json +58 -0
- package/node_modules/nebula-autocomplete/server.d.ts +3 -0
- package/node_modules/nebula-autocomplete/server.js +3 -0
- package/package.json +18 -5
- package/scripts/postinstall.cjs +25 -5
- package/dist/assets/index-BvrHu37J.js +0 -648
- package/dist/assets/index-Dfj_xsKU.css +0 -32
- package/node-server/dist/llm/index.d.ts +0 -5
- package/node-server/dist/llm/index.js +0 -21
- package/node-server/dist/llm/llm-service.d.ts +0 -77
- package/node-server/dist/llm/llm-service.js +0 -454
- package/node-server/dist/llm/types.d.ts +0 -40
- package/node-server/dist/llm/types.js +0 -15
- package/node-server/dist/routes/llm.d.ts +0 -8
- package/node-server/dist/routes/llm.js +0 -105
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AutocompleteEngine } from "../core/engine.js";
|
|
2
|
+
import type { CompletionRequest } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural types so this plugin works with Fastify 4/5 without a
|
|
5
|
+
* hard dependency on the fastify package.
|
|
6
|
+
*/
|
|
7
|
+
interface FastifyLikeRequest {
|
|
8
|
+
body: unknown;
|
|
9
|
+
}
|
|
10
|
+
interface FastifyLikeReply {
|
|
11
|
+
raw: {
|
|
12
|
+
writeHead(status: number, headers: Record<string, string>): void;
|
|
13
|
+
write(chunk: string): void;
|
|
14
|
+
end(): void;
|
|
15
|
+
on(event: "close", cb: () => void): void;
|
|
16
|
+
writableEnded: boolean;
|
|
17
|
+
};
|
|
18
|
+
hijack?: () => void;
|
|
19
|
+
}
|
|
20
|
+
interface FastifyLikeInstance {
|
|
21
|
+
post(path: string, handler: (req: FastifyLikeRequest, reply: FastifyLikeReply) => Promise<void>): void;
|
|
22
|
+
}
|
|
23
|
+
export interface AutocompleteRouteOptions {
|
|
24
|
+
/** Route path. Default "/autocomplete". */
|
|
25
|
+
path?: string;
|
|
26
|
+
}
|
|
27
|
+
export type EngineResolver = (req: CompletionRequest) => AutocompleteEngine;
|
|
28
|
+
/**
|
|
29
|
+
* Register `POST <path>` streaming completions as SSE.
|
|
30
|
+
*
|
|
31
|
+
* Request body: CompletionRequest (JSON).
|
|
32
|
+
* Response events: {type:"chunk",text} then {type:"done",...CompletionResult},
|
|
33
|
+
* or {type:"error",message}.
|
|
34
|
+
*
|
|
35
|
+
* Usage in nebula-notebook's node-server:
|
|
36
|
+
* const engine = new AutocompleteEngine({ backend: new ClaudeBackend() });
|
|
37
|
+
* app.register(async (f) => registerAutocompleteRoute(f, engine), { prefix: "/api" });
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerAutocompleteRoute(fastify: FastifyLikeInstance, engine: AutocompleteEngine | EngineResolver, options?: AutocompleteRouteOptions): void;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register `POST <path>` streaming completions as SSE.
|
|
3
|
+
*
|
|
4
|
+
* Request body: CompletionRequest (JSON).
|
|
5
|
+
* Response events: {type:"chunk",text} then {type:"done",...CompletionResult},
|
|
6
|
+
* or {type:"error",message}.
|
|
7
|
+
*
|
|
8
|
+
* Usage in nebula-notebook's node-server:
|
|
9
|
+
* const engine = new AutocompleteEngine({ backend: new ClaudeBackend() });
|
|
10
|
+
* app.register(async (f) => registerAutocompleteRoute(f, engine), { prefix: "/api" });
|
|
11
|
+
*/
|
|
12
|
+
export function registerAutocompleteRoute(fastify, engine, options = {}) {
|
|
13
|
+
const path = options.path ?? "/autocomplete";
|
|
14
|
+
const resolve = typeof engine === "function" ? engine : () => engine;
|
|
15
|
+
fastify.post(path, async (req, reply) => {
|
|
16
|
+
reply.hijack?.(); // take over the raw socket for SSE (Fastify 4/5)
|
|
17
|
+
const res = reply.raw;
|
|
18
|
+
res.writeHead(200, {
|
|
19
|
+
"content-type": "text/event-stream",
|
|
20
|
+
"cache-control": "no-cache",
|
|
21
|
+
connection: "keep-alive",
|
|
22
|
+
});
|
|
23
|
+
const send = (obj) => res.write(`data: ${JSON.stringify(obj)}\n\n`);
|
|
24
|
+
const abort = new AbortController();
|
|
25
|
+
// Detect client disconnect via the RESPONSE stream closing before we
|
|
26
|
+
// ended it. (The request's "close" fires as soon as its body is fully
|
|
27
|
+
// consumed on modern Node — listening there aborts every request.)
|
|
28
|
+
res.on("close", () => {
|
|
29
|
+
if (!res.writableEnded)
|
|
30
|
+
abort.abort(new Error("client disconnected"));
|
|
31
|
+
});
|
|
32
|
+
try {
|
|
33
|
+
const body = req.body;
|
|
34
|
+
if (!body || typeof body.prefix !== "string") {
|
|
35
|
+
send({ type: "error", message: "prefix (string) is required" });
|
|
36
|
+
res.end();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const result = await resolve(body).complete(body, {
|
|
40
|
+
signal: abort.signal,
|
|
41
|
+
onChunk: (text) => send({ type: "chunk", text }),
|
|
42
|
+
});
|
|
43
|
+
send({ type: "done", ...result });
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
if (!abort.signal.aborted) {
|
|
47
|
+
send({ type: "error", message: e instanceof Error ? e.message : String(e) });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
res.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process transport for backends: run a provider CLI either LOCALLY (same host
|
|
3
|
+
* as this process) or OVER SSH on a remote host.
|
|
4
|
+
*
|
|
5
|
+
* The over-ssh case powers "remote Nebula, local agent": the Nebula server runs
|
|
6
|
+
* on a cluster, but autocomplete should drive the user's OWN claude/codex on
|
|
7
|
+
* their laptop — reached over the reverse SSH tunnel the user already has. The
|
|
8
|
+
* CLI's stream-json stdio flows transparently over the ssh channel; a persistent
|
|
9
|
+
* warm worker keeps one ssh connection open (handshake paid once, not per turn),
|
|
10
|
+
* and ControlMaster makes any reconnect ~free. See the validated latency probe:
|
|
11
|
+
* per-turn overhead ≈ one network RTT (~50ms), negligible vs ~2s model latency.
|
|
12
|
+
*/
|
|
13
|
+
export interface SshTransport {
|
|
14
|
+
kind: 'ssh';
|
|
15
|
+
/** Host to ssh to — typically 'localhost' (the reverse-tunnel endpoint). */
|
|
16
|
+
host: string;
|
|
17
|
+
/** Reverse-tunnel port on this (server) host that forwards to the user's sshd. */
|
|
18
|
+
port: number;
|
|
19
|
+
/** Username on the user's machine. */
|
|
20
|
+
user: string;
|
|
21
|
+
/** Absolute path to the provider binary ON THE USER'S machine (PATH isn't set
|
|
22
|
+
* for a non-interactive ssh, so this must be absolute). */
|
|
23
|
+
remoteBin: string;
|
|
24
|
+
/** ControlMaster socket path, so warm workers + reconnects reuse one connection. */
|
|
25
|
+
controlPath?: string;
|
|
26
|
+
/** Working dir on the remote side (created if absent). Keep it empty of any
|
|
27
|
+
* CLAUDE.md so the agent harness stays minimal. */
|
|
28
|
+
remoteCwd?: string;
|
|
29
|
+
}
|
|
30
|
+
export type Transport = {
|
|
31
|
+
kind: 'local';
|
|
32
|
+
} | SshTransport;
|
|
33
|
+
export interface SpawnPlan {
|
|
34
|
+
command: string;
|
|
35
|
+
args: string[];
|
|
36
|
+
/** Options to pass to child_process.spawn (env/cwd). For ssh these are empty
|
|
37
|
+
* because env/cwd are embedded in the remote command line. */
|
|
38
|
+
options: {
|
|
39
|
+
env?: NodeJS.ProcessEnv;
|
|
40
|
+
cwd?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** POSIX single-quote a string so it survives the remote shell verbatim. */
|
|
44
|
+
export declare function shellQuote(s: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Plan how to spawn `bin argv` with `env` in `cwd`, honoring the transport.
|
|
47
|
+
* - local: spawn the binary directly; env/cwd go to spawn().
|
|
48
|
+
* - ssh: spawn `ssh …host <remoteCmd>` where remoteCmd re-establishes cwd + env
|
|
49
|
+
* on the far side (via `cd` and `env K=V …`) and execs the binary. `cwd`/`env`
|
|
50
|
+
* passed here are interpreted on the REMOTE side.
|
|
51
|
+
*/
|
|
52
|
+
export declare function planSpawn(transport: Transport, bin: string, argv: string[], env: Record<string, string>, cwd: string): SpawnPlan;
|
|
53
|
+
/** Best-effort remote cleanup (e.g. wipe an ephemeral config dir) over the same
|
|
54
|
+
* ssh path. Returns the command+args for a fire-and-forget spawn. */
|
|
55
|
+
export declare function planRemoteCleanup(transport: SshTransport, remotePath: string): SpawnPlan;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process transport for backends: run a provider CLI either LOCALLY (same host
|
|
3
|
+
* as this process) or OVER SSH on a remote host.
|
|
4
|
+
*
|
|
5
|
+
* The over-ssh case powers "remote Nebula, local agent": the Nebula server runs
|
|
6
|
+
* on a cluster, but autocomplete should drive the user's OWN claude/codex on
|
|
7
|
+
* their laptop — reached over the reverse SSH tunnel the user already has. The
|
|
8
|
+
* CLI's stream-json stdio flows transparently over the ssh channel; a persistent
|
|
9
|
+
* warm worker keeps one ssh connection open (handshake paid once, not per turn),
|
|
10
|
+
* and ControlMaster makes any reconnect ~free. See the validated latency probe:
|
|
11
|
+
* per-turn overhead ≈ one network RTT (~50ms), negligible vs ~2s model latency.
|
|
12
|
+
*/
|
|
13
|
+
/** POSIX single-quote a string so it survives the remote shell verbatim. */
|
|
14
|
+
export function shellQuote(s) {
|
|
15
|
+
return `'${String(s).replace(/'/g, `'\\''`)}'`;
|
|
16
|
+
}
|
|
17
|
+
const SSH_BASE_OPTS = [
|
|
18
|
+
'-o', 'ProxyCommand=none',
|
|
19
|
+
'-o', 'StrictHostKeyChecking=accept-new',
|
|
20
|
+
'-o', 'BatchMode=yes',
|
|
21
|
+
'-o', 'ServerAliveInterval=30',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Plan how to spawn `bin argv` with `env` in `cwd`, honoring the transport.
|
|
25
|
+
* - local: spawn the binary directly; env/cwd go to spawn().
|
|
26
|
+
* - ssh: spawn `ssh …host <remoteCmd>` where remoteCmd re-establishes cwd + env
|
|
27
|
+
* on the far side (via `cd` and `env K=V …`) and execs the binary. `cwd`/`env`
|
|
28
|
+
* passed here are interpreted on the REMOTE side.
|
|
29
|
+
*/
|
|
30
|
+
export function planSpawn(transport, bin, argv, env, cwd) {
|
|
31
|
+
if (transport.kind === 'local') {
|
|
32
|
+
return { command: bin, args: argv, options: { env: { ...process.env, ...env }, cwd } };
|
|
33
|
+
}
|
|
34
|
+
const sshOpts = [...SSH_BASE_OPTS, '-p', String(transport.port)];
|
|
35
|
+
if (transport.controlPath) {
|
|
36
|
+
// ControlPersist 1h (was 120s): the master handshake is the expensive part
|
|
37
|
+
// over the reverse tunnel, and a 2-minute persist meant any idle gap made
|
|
38
|
+
// the next worker spawn pay it again — part of the "autocomplete lags
|
|
39
|
+
// after I come back" cold start. An idle master on a localhost tunnel
|
|
40
|
+
// costs one file descriptor; keep it warm for the whole work session.
|
|
41
|
+
sshOpts.push('-o', 'ControlMaster=auto', '-o', `ControlPath=${transport.controlPath}`, '-o', 'ControlPersist=3600');
|
|
42
|
+
}
|
|
43
|
+
const remoteCwd = transport.remoteCwd ?? cwd;
|
|
44
|
+
const envPrefix = Object.entries(env).map(([k, v]) => `${k}=${shellQuote(v)}`).join(' ');
|
|
45
|
+
// mkdir+cd so the remote agent runs in a clean, CLAUDE.md-free dir; `exec env …`
|
|
46
|
+
// sets the child env on the far side (the local process.env doesn't reach it).
|
|
47
|
+
const remoteCmd = `mkdir -p ${shellQuote(remoteCwd)} 2>/dev/null; cd ${shellQuote(remoteCwd)} 2>/dev/null; ` +
|
|
48
|
+
`exec env ${envPrefix} ${shellQuote(transport.remoteBin)} ${argv.map(shellQuote).join(' ')}`;
|
|
49
|
+
return {
|
|
50
|
+
command: 'ssh',
|
|
51
|
+
args: [...sshOpts, `${transport.user}@${transport.host}`, remoteCmd],
|
|
52
|
+
options: {},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Best-effort remote cleanup (e.g. wipe an ephemeral config dir) over the same
|
|
56
|
+
* ssh path. Returns the command+args for a fire-and-forget spawn. */
|
|
57
|
+
export function planRemoteCleanup(transport, remotePath) {
|
|
58
|
+
const sshOpts = [...SSH_BASE_OPTS, '-p', String(transport.port)];
|
|
59
|
+
if (transport.controlPath)
|
|
60
|
+
sshOpts.push('-o', `ControlPath=${transport.controlPath}`);
|
|
61
|
+
return {
|
|
62
|
+
command: 'ssh',
|
|
63
|
+
args: [...sshOpts, `${transport.user}@${transport.host}`, `rm -rf ${shellQuote(remotePath)}`],
|
|
64
|
+
options: {},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface NotebookCellContext {
|
|
2
|
+
type: "code" | "markdown";
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface CompletionRequest {
|
|
6
|
+
/** Text before the cursor in the active cell. Required. */
|
|
7
|
+
prefix: string;
|
|
8
|
+
/** Text after the cursor in the active cell. */
|
|
9
|
+
suffix?: string;
|
|
10
|
+
/** Explicit language, if known. Usually omitted — prefer the hints below and
|
|
11
|
+
* let the model infer the language from the code + kernel/filename. */
|
|
12
|
+
language?: string;
|
|
13
|
+
/** Active kernel name/spec (e.g. "python3", "ir", "julia-1.9"). A hint. */
|
|
14
|
+
kernelName?: string;
|
|
15
|
+
/** Notebook filename (e.g. "analysis.ipynb"). A hint. */
|
|
16
|
+
filename?: string;
|
|
17
|
+
/** All notebook cells, in order, for cross-cell context. */
|
|
18
|
+
cells?: NotebookCellContext[];
|
|
19
|
+
/** Index of the active cell within `cells`. */
|
|
20
|
+
activeCellIndex?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Requests sharing a sessionKey supersede each other: issuing a new one
|
|
23
|
+
* aborts the previous in-flight request. Use the cell id.
|
|
24
|
+
*/
|
|
25
|
+
sessionKey?: string;
|
|
26
|
+
/** Model override (e.g. "haiku" | "sonnet"). Engines are pooled per model. */
|
|
27
|
+
model?: string;
|
|
28
|
+
/** Per-request cross-cell context budget in chars (clamped server-side).
|
|
29
|
+
* More context grounds suggestions in the notebook (fewer hallucinated
|
|
30
|
+
* names) at the cost of a bigger prompt. */
|
|
31
|
+
contextBudget?: number;
|
|
32
|
+
/** Per-request cap on suggested lines (clamped server-side). */
|
|
33
|
+
maxLines?: number;
|
|
34
|
+
/** Backend selector, for servers hosting more than one engine. */
|
|
35
|
+
backend?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface CompletionResult {
|
|
38
|
+
text: string;
|
|
39
|
+
backend: string;
|
|
40
|
+
fromCache: boolean;
|
|
41
|
+
ttfbMs: number;
|
|
42
|
+
totalMs: number;
|
|
43
|
+
/** Per-request engine/worker diagnostics (see CompletionDiag). */
|
|
44
|
+
diag?: CompletionDiag;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Where a completion's latency went — filled by the backend, forwarded in the
|
|
48
|
+
* SSE done event so the browser console can show the breakdown.
|
|
49
|
+
*/
|
|
50
|
+
export interface CompletionDiag {
|
|
51
|
+
/** Transport the worker ran on. */
|
|
52
|
+
transport?: string;
|
|
53
|
+
/** ms spent queued behind an earlier in-flight turn for the same session
|
|
54
|
+
* (single-flight per cell — typing bursts wait for the previous turn
|
|
55
|
+
* instead of fanning out across workers; see engine.complete). */
|
|
56
|
+
queueWaitMs?: number;
|
|
57
|
+
/** ms from request start until the turn was dispatched to a warm worker
|
|
58
|
+
* (pool scan + possible spawn + warmup wait). The silent part of TTFB. */
|
|
59
|
+
workerWaitMs?: number;
|
|
60
|
+
/** A fresh worker process had to be spawned for this request. */
|
|
61
|
+
coldSpawn?: boolean;
|
|
62
|
+
/** The turn was retried on a fresh worker after the first one died. */
|
|
63
|
+
retried?: boolean;
|
|
64
|
+
/** How many turns this worker had already served (conversation history
|
|
65
|
+
* grows with each turn, inflating time-to-first-byte). */
|
|
66
|
+
workerTurn?: number;
|
|
67
|
+
/** Accumulated prompt chars in this worker's history before this turn —
|
|
68
|
+
* the actual TTFB-drag driver (recycled past maxHistoryCharsPerWorker). */
|
|
69
|
+
workerHistoryChars?: number;
|
|
70
|
+
/** Busy workers / pool size at request time (busy=size → cold spawn). */
|
|
71
|
+
poolBusy?: number;
|
|
72
|
+
poolSize?: number;
|
|
73
|
+
/** Characters in the assembled prompt. */
|
|
74
|
+
promptChars?: number;
|
|
75
|
+
/** Characters in the RAW model reply, before tag/fence/overlap trimming —
|
|
76
|
+
* rawChars>0 with empty final text means post-processing removed it. */
|
|
77
|
+
rawChars?: number;
|
|
78
|
+
}
|
|
79
|
+
export interface CompleteOptions {
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
/** Called with each streamed text chunk (claude backend streams; codex emits once). */
|
|
82
|
+
onChunk?: (text: string) => void;
|
|
83
|
+
}
|
|
84
|
+
/** A completion backend: turns a prompt into text. */
|
|
85
|
+
export interface CompletionBackend {
|
|
86
|
+
readonly name: string;
|
|
87
|
+
complete(prompt: string, opts: {
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
onChunk?: (text: string) => void;
|
|
90
|
+
diag?: CompletionDiag;
|
|
91
|
+
}): Promise<string>;
|
|
92
|
+
/** Release child processes. */
|
|
93
|
+
dispose(): void;
|
|
94
|
+
}
|
|
95
|
+
export interface EngineOptions {
|
|
96
|
+
backend: CompletionBackend;
|
|
97
|
+
/** Max completions cached (keyed on context). Default 128. 0 disables. */
|
|
98
|
+
cacheSize?: number;
|
|
99
|
+
/** Character budget for cross-cell context in the prompt. Default 6000. */
|
|
100
|
+
contextBudget?: number;
|
|
101
|
+
/** Max lines the model is asked to produce. Default 5. */
|
|
102
|
+
maxLines?: number;
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nebula-autocomplete",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LLM code autocomplete engine backed by Claude Code / Codex CLI subscriptions, with a Fastify plugin and CodeMirror 6 ghost-text extension.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./server": {
|
|
15
|
+
"types": "./dist/server/fastify.d.ts",
|
|
16
|
+
"default": "./dist/server/fastify.js"
|
|
17
|
+
},
|
|
18
|
+
"./client": {
|
|
19
|
+
"types": "./dist/client/fetcher.d.ts",
|
|
20
|
+
"default": "./dist/client/fetcher.js"
|
|
21
|
+
},
|
|
22
|
+
"./codemirror": {
|
|
23
|
+
"types": "./dist/codemirror/ghostText.d.ts",
|
|
24
|
+
"default": "./dist/codemirror/ghostText.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"server.js",
|
|
30
|
+
"server.d.ts",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -p tsconfig.json",
|
|
35
|
+
"prepare": "node scripts/prepare.cjs",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"smoke": "node scripts/smoke.mjs"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@codemirror/state": "^6.0.0",
|
|
41
|
+
"@codemirror/view": "^6.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"@codemirror/state": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"@codemirror/view": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@codemirror/state": "^6.5.0",
|
|
53
|
+
"@codemirror/view": "^6.36.0",
|
|
54
|
+
"@types/node": "^22.0.0",
|
|
55
|
+
"typescript": "^5.8.0",
|
|
56
|
+
"vitest": "^3.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nebula-notebook",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI-native notebook computing environment
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "AI-native notebook computing environment — real Jupyter kernels, real filesystem, built to be driven by agents (Claude Code / Codex) via MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "https://github.com/jzhoulab/nebula-notebook.git"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
12
|
"notebook",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"node-server/package.json",
|
|
28
28
|
"scripts/postinstall.cjs"
|
|
29
29
|
],
|
|
30
|
+
"bundledDependencies": [
|
|
31
|
+
"nebula-autocomplete"
|
|
32
|
+
],
|
|
30
33
|
"engines": {
|
|
31
34
|
"node": ">=20"
|
|
32
35
|
},
|
|
@@ -42,10 +45,12 @@
|
|
|
42
45
|
"mcp": "npm --prefix packages/mcp run mcp",
|
|
43
46
|
"mcp:build": "npm --prefix packages/mcp run build",
|
|
44
47
|
"mcp:test": "npm --prefix packages/mcp test",
|
|
48
|
+
"autocomplete:test": "npm --prefix packages/autocomplete test",
|
|
45
49
|
"setup-mcp": "npm --prefix packages/mcp run setup-mcp",
|
|
46
50
|
"setup": "node setup.cjs",
|
|
47
51
|
"postinstall": "node scripts/postinstall.cjs",
|
|
48
|
-
"
|
|
52
|
+
"autocomplete:build": "npm --prefix packages/autocomplete run build",
|
|
53
|
+
"prepack": "npm --prefix packages/autocomplete install && npm run autocomplete:build && npm run build && npm run node-server:build",
|
|
49
54
|
"stop": "lsof -ti:3000,3001,8000 | xargs kill -9 2>/dev/null; echo 'Servers stopped'",
|
|
50
55
|
"test": "vitest",
|
|
51
56
|
"test:run": "vitest run"
|
|
@@ -56,18 +61,23 @@
|
|
|
56
61
|
"@fastify/static": "^9.0.0",
|
|
57
62
|
"@fastify/websocket": "^11.2.0",
|
|
58
63
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.14.0",
|
|
64
|
+
"@xterm/addon-clipboard": "^0.2.0",
|
|
65
|
+
"@xterm/addon-webgl": "^0.19.0",
|
|
59
66
|
"better-sqlite3": "^11.0.0",
|
|
60
67
|
"fastify": "^5.8.2",
|
|
61
68
|
"jsonwebtoken": "^9.0.2",
|
|
69
|
+
"nebula-autocomplete": "file:packages/autocomplete",
|
|
62
70
|
"otplib": "^12.0.1",
|
|
63
71
|
"qrcode-terminal": "^0.12.0",
|
|
64
72
|
"uuid": "^9.0.1",
|
|
65
73
|
"ws": "^8.16.0",
|
|
74
|
+
"yaml": "^2.9.0",
|
|
66
75
|
"zeromq": "^6.4.2"
|
|
67
76
|
},
|
|
68
77
|
"devDependencies": {
|
|
69
78
|
"@codemirror/lang-markdown": "^6.5.0",
|
|
70
79
|
"@codemirror/lang-python": "^6.2.1",
|
|
80
|
+
"@jupyter-widgets/html-manager": "^1.0.14",
|
|
71
81
|
"@playwright/test": "^1.57.0",
|
|
72
82
|
"@tailwindcss/vite": "^4.2.1",
|
|
73
83
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -92,5 +102,8 @@
|
|
|
92
102
|
"typescript": "~5.8.2",
|
|
93
103
|
"vite": "^6.2.0",
|
|
94
104
|
"vitest": "^4.0.16"
|
|
95
|
-
}
|
|
105
|
+
},
|
|
106
|
+
"bundleDependencies": [
|
|
107
|
+
"nebula-autocomplete"
|
|
108
|
+
]
|
|
96
109
|
}
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Repo-checkout convenience: install the node-server
|
|
3
|
-
* subpackage dependencies after a root `npm install`.
|
|
2
|
+
* Repo-checkout convenience: install the packages/autocomplete, node-server,
|
|
3
|
+
* and packages/mcp subpackage dependencies after a root `npm install`.
|
|
4
|
+
* packages/autocomplete is listed first so its `prepare` step builds dist/
|
|
5
|
+
* before node-server (which links to it via file:) is set up.
|
|
4
6
|
*
|
|
5
7
|
* Skipped entirely when nebula-notebook is installed from npm as a
|
|
6
8
|
* dependency — the published package is self-contained (server runtime
|
|
7
|
-
* deps live in the root package.json;
|
|
9
|
+
* deps live in the root package.json; nebula-autocomplete is a bundled
|
|
10
|
+
* dependency; dist artifacts are prebuilt).
|
|
8
11
|
*/
|
|
9
12
|
const path = require('path');
|
|
10
13
|
const fs = require('fs');
|
|
@@ -15,12 +18,29 @@ if (__dirname.split(path.sep).includes('node_modules')) {
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const root = path.join(__dirname, '..');
|
|
18
|
-
for (const dir of ['node-server', path.join('packages', 'mcp')]) {
|
|
21
|
+
for (const dir of [path.join('packages', 'autocomplete'), 'node-server', path.join('packages', 'mcp')]) {
|
|
19
22
|
const pkg = path.join(root, dir, 'package.json');
|
|
20
23
|
if (!fs.existsSync(pkg)) continue;
|
|
21
24
|
try {
|
|
22
|
-
|
|
25
|
+
// --include=dev: the subpackage builds (tsc) need their devDependencies
|
|
26
|
+
// even when the environment forces production installs (NODE_ENV=production
|
|
27
|
+
// in server shells, npm production config) — without it, tsc is missing
|
|
28
|
+
// and the prepare builds are skipped.
|
|
29
|
+
execSync('npm install --include=dev', { cwd: path.join(root, dir), stdio: 'inherit' });
|
|
23
30
|
} catch {
|
|
24
31
|
console.warn(`⚠️ postinstall: npm install failed in ${dir}`);
|
|
25
32
|
}
|
|
26
33
|
}
|
|
34
|
+
|
|
35
|
+
// The autocomplete build is load-bearing (the root depends on its dist/ via
|
|
36
|
+
// file:). Its prepare skips quietly when typescript is absent, so verify the
|
|
37
|
+
// artifact exists and fall back to an explicit build.
|
|
38
|
+
const autocompleteDir = path.join(root, 'packages', 'autocomplete');
|
|
39
|
+
if (fs.existsSync(path.join(autocompleteDir, 'package.json')) &&
|
|
40
|
+
!fs.existsSync(path.join(autocompleteDir, 'dist', 'index.js'))) {
|
|
41
|
+
try {
|
|
42
|
+
execSync('npm run build', { cwd: autocompleteDir, stdio: 'inherit' });
|
|
43
|
+
} catch {
|
|
44
|
+
console.warn('⚠️ postinstall: nebula-autocomplete build failed — run "npm install --include=dev && npm run build" in packages/autocomplete');
|
|
45
|
+
}
|
|
46
|
+
}
|