nebula-notebook 0.1.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 +222 -0
- package/bin/nebula-notebook.js +33 -0
- package/dist/assets/index-C1h_sArD.css +32 -0
- package/dist/assets/index-CDSTBon8.js +658 -0
- package/dist/favicon.svg +11 -0
- package/dist/index.html +73 -0
- package/node-server/dist/app.d.ts +5 -0
- package/node-server/dist/app.js +38 -0
- package/node-server/dist/auth/auth-middleware.d.ts +24 -0
- package/node-server/dist/auth/auth-middleware.js +276 -0
- package/node-server/dist/auth/auth-service.d.ts +84 -0
- package/node-server/dist/auth/auth-service.js +265 -0
- package/node-server/dist/auth/index.d.ts +3 -0
- package/node-server/dist/auth/index.js +8 -0
- package/node-server/dist/cluster/client-registration.d.ts +43 -0
- package/node-server/dist/cluster/client-registration.js +217 -0
- package/node-server/dist/cluster/cluster-secret.d.ts +11 -0
- package/node-server/dist/cluster/cluster-secret.js +90 -0
- package/node-server/dist/cluster/kernel-proxy.d.ts +100 -0
- package/node-server/dist/cluster/kernel-proxy.js +361 -0
- package/node-server/dist/cluster/server-registry.d.ts +109 -0
- package/node-server/dist/cluster/server-registry.js +217 -0
- package/node-server/dist/config/output-limits.d.ts +7 -0
- package/node-server/dist/config/output-limits.js +10 -0
- package/node-server/dist/discovery/discovery-service.d.ts +198 -0
- package/node-server/dist/discovery/discovery-service.js +811 -0
- package/node-server/dist/discovery/index.d.ts +5 -0
- package/node-server/dist/discovery/index.js +21 -0
- package/node-server/dist/discovery/types.d.ts +48 -0
- package/node-server/dist/discovery/types.js +24 -0
- package/node-server/dist/fs/fs-service.d.ts +218 -0
- package/node-server/dist/fs/fs-service.js +1422 -0
- package/node-server/dist/fs/index.d.ts +5 -0
- package/node-server/dist/fs/index.js +21 -0
- package/node-server/dist/fs/types.d.ts +132 -0
- package/node-server/dist/fs/types.js +5 -0
- package/node-server/dist/index.d.ts +13 -0
- package/node-server/dist/index.js +556 -0
- package/node-server/dist/kernel/default-kernel.d.ts +5 -0
- package/node-server/dist/kernel/default-kernel.js +138 -0
- package/node-server/dist/kernel/index.d.ts +7 -0
- package/node-server/dist/kernel/index.js +23 -0
- package/node-server/dist/kernel/kernel-service.d.ts +290 -0
- package/node-server/dist/kernel/kernel-service.js +1714 -0
- package/node-server/dist/kernel/kernelspec.d.ts +29 -0
- package/node-server/dist/kernel/kernelspec.js +210 -0
- package/node-server/dist/kernel/session-store.d.ts +87 -0
- package/node-server/dist/kernel/session-store.js +303 -0
- package/node-server/dist/kernel/types.d.ts +143 -0
- package/node-server/dist/kernel/types.js +17 -0
- package/node-server/dist/llm/index.d.ts +5 -0
- package/node-server/dist/llm/index.js +21 -0
- package/node-server/dist/llm/llm-service.d.ts +77 -0
- package/node-server/dist/llm/llm-service.js +454 -0
- package/node-server/dist/llm/types.d.ts +40 -0
- package/node-server/dist/llm/types.js +15 -0
- package/node-server/dist/notebook/cell-metadata.d.ts +27 -0
- package/node-server/dist/notebook/cell-metadata.js +76 -0
- package/node-server/dist/notebook/headless-handler.d.ts +127 -0
- package/node-server/dist/notebook/headless-handler.js +1530 -0
- package/node-server/dist/notebook/notebook-websocket.d.ts +12 -0
- package/node-server/dist/notebook/notebook-websocket.js +103 -0
- package/node-server/dist/notebook/operation-router.d.ts +115 -0
- package/node-server/dist/notebook/operation-router.js +641 -0
- package/node-server/dist/notebook/undoRedoManager.d.ts +194 -0
- package/node-server/dist/notebook/undoRedoManager.js +558 -0
- package/node-server/dist/output/display-data.d.ts +14 -0
- package/node-server/dist/output/display-data.js +134 -0
- package/node-server/dist/resources/resource-service.d.ts +69 -0
- package/node-server/dist/resources/resource-service.js +363 -0
- package/node-server/dist/routes/auth.d.ts +5 -0
- package/node-server/dist/routes/auth.js +61 -0
- package/node-server/dist/routes/cluster.d.ts +7 -0
- package/node-server/dist/routes/cluster.js +94 -0
- package/node-server/dist/routes/fs.d.ts +7 -0
- package/node-server/dist/routes/fs.js +392 -0
- package/node-server/dist/routes/kernel.d.ts +13 -0
- package/node-server/dist/routes/kernel.js +637 -0
- package/node-server/dist/routes/llm.d.ts +8 -0
- package/node-server/dist/routes/llm.js +105 -0
- package/node-server/dist/routes/notebook.d.ts +10 -0
- package/node-server/dist/routes/notebook.js +335 -0
- package/node-server/dist/routes/python.d.ts +8 -0
- package/node-server/dist/routes/python.js +187 -0
- package/node-server/dist/routes/resources.d.ts +7 -0
- package/node-server/dist/routes/resources.js +77 -0
- package/node-server/dist/scripts/show-auth-qr.d.ts +1 -0
- package/node-server/dist/scripts/show-auth-qr.js +81 -0
- package/node-server/dist/terminal/pty-manager.d.ts +100 -0
- package/node-server/dist/terminal/pty-manager.js +246 -0
- package/node-server/dist/terminal/server.d.ts +19 -0
- package/node-server/dist/terminal/server.js +254 -0
- package/node-server/dist/terminal/types.d.ts +50 -0
- package/node-server/dist/terminal/types.js +9 -0
- package/node-server/package.json +45 -0
- package/package.json +99 -0
- package/scripts/postinstall.cjs +26 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Server Registry
|
|
4
|
+
*
|
|
5
|
+
* Manages registration of peer servers in a Nebula cluster.
|
|
6
|
+
* Peer servers can register with a main server to enable
|
|
7
|
+
* cross-server kernel access.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.serverRegistry = void 0;
|
|
11
|
+
const cluster_secret_1 = require("./cluster-secret");
|
|
12
|
+
const HEARTBEAT_TIMEOUT_MS = 90_000; // 90 seconds (3 missed heartbeats at 30s interval)
|
|
13
|
+
const CLEANUP_INTERVAL_MS = 30_000; // Check for stale servers every 30s
|
|
14
|
+
class ServerRegistry {
|
|
15
|
+
servers = new Map();
|
|
16
|
+
clusterSecret = null;
|
|
17
|
+
cleanupInterval = null;
|
|
18
|
+
localServerId = 'local';
|
|
19
|
+
constructor() {
|
|
20
|
+
// Load initial cluster secret from environment or disk
|
|
21
|
+
this.clusterSecret = process.env.NEBULA_CLUSTER_SECRET || (0, cluster_secret_1.readClusterSecret)() || null;
|
|
22
|
+
// Start cleanup interval
|
|
23
|
+
this.startCleanupInterval();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set the local server's ID (called on startup)
|
|
27
|
+
*/
|
|
28
|
+
setLocalServerId(id) {
|
|
29
|
+
this.localServerId = id;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the local server's ID
|
|
33
|
+
*/
|
|
34
|
+
getLocalServerId() {
|
|
35
|
+
return this.localServerId;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate registration secret
|
|
39
|
+
*/
|
|
40
|
+
getClusterSecret() {
|
|
41
|
+
const envSecret = process.env.NEBULA_CLUSTER_SECRET;
|
|
42
|
+
// If the env var is explicitly set (even to an empty string), treat it as the
|
|
43
|
+
// source of truth. This allows users/tests to intentionally disable a disk secret
|
|
44
|
+
// without relying on filesystem state.
|
|
45
|
+
if (envSecret !== undefined) {
|
|
46
|
+
this.clusterSecret = envSecret || null;
|
|
47
|
+
return this.clusterSecret;
|
|
48
|
+
}
|
|
49
|
+
// Disk secret is cached defensively: if the file can't be read, don't clear an
|
|
50
|
+
// existing secret to avoid accidentally dropping auth due to transient errors.
|
|
51
|
+
const diskSecret = (0, cluster_secret_1.readClusterSecret)() || null;
|
|
52
|
+
if (diskSecret && diskSecret !== this.clusterSecret) {
|
|
53
|
+
this.clusterSecret = diskSecret;
|
|
54
|
+
}
|
|
55
|
+
return this.clusterSecret;
|
|
56
|
+
}
|
|
57
|
+
validateSecret(providedSecret) {
|
|
58
|
+
const clusterSecret = this.getClusterSecret();
|
|
59
|
+
// If no cluster secret configured, allow all registrations (dev mode)
|
|
60
|
+
if (!clusterSecret) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return providedSecret === clusterSecret;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate server ID from host and port
|
|
67
|
+
*/
|
|
68
|
+
generateServerId(host, port) {
|
|
69
|
+
return `${host}:${port}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Register a peer server
|
|
73
|
+
*/
|
|
74
|
+
register(request) {
|
|
75
|
+
// Validate secret
|
|
76
|
+
if (!this.validateSecret(request.secret)) {
|
|
77
|
+
return { success: false, error: 'Invalid cluster secret' };
|
|
78
|
+
}
|
|
79
|
+
const serverId = this.generateServerId(request.host, request.port);
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
// Check if already registered
|
|
82
|
+
const existing = this.servers.get(serverId);
|
|
83
|
+
if (existing) {
|
|
84
|
+
// Update existing registration
|
|
85
|
+
existing.lastHeartbeat = now;
|
|
86
|
+
existing.status = 'online';
|
|
87
|
+
existing.name = request.name || existing.name;
|
|
88
|
+
if (request.resources) {
|
|
89
|
+
existing.resources = request.resources;
|
|
90
|
+
}
|
|
91
|
+
console.log(`[ServerRegistry] Updated registration for ${serverId}`);
|
|
92
|
+
return { success: true, serverId };
|
|
93
|
+
}
|
|
94
|
+
// New registration
|
|
95
|
+
const server = {
|
|
96
|
+
id: serverId,
|
|
97
|
+
host: request.host,
|
|
98
|
+
port: request.port,
|
|
99
|
+
name: request.name,
|
|
100
|
+
url: `http://${request.host}:${request.port}`,
|
|
101
|
+
registeredAt: now,
|
|
102
|
+
lastHeartbeat: now,
|
|
103
|
+
status: 'online',
|
|
104
|
+
resources: request.resources,
|
|
105
|
+
};
|
|
106
|
+
this.servers.set(serverId, server);
|
|
107
|
+
console.log(`[ServerRegistry] Registered new server: ${serverId} (${request.name || 'unnamed'})`);
|
|
108
|
+
return { success: true, serverId };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Unregister a peer server
|
|
112
|
+
*/
|
|
113
|
+
unregister(serverId) {
|
|
114
|
+
const removed = this.servers.delete(serverId);
|
|
115
|
+
if (removed) {
|
|
116
|
+
console.log(`[ServerRegistry] Unregistered server: ${serverId}`);
|
|
117
|
+
}
|
|
118
|
+
return removed;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Record heartbeat from a peer server
|
|
122
|
+
*/
|
|
123
|
+
heartbeat(serverId, resources) {
|
|
124
|
+
const server = this.servers.get(serverId);
|
|
125
|
+
if (!server) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
server.lastHeartbeat = Date.now();
|
|
129
|
+
server.status = 'online';
|
|
130
|
+
if (resources) {
|
|
131
|
+
server.resources = resources;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get a specific peer server
|
|
137
|
+
*/
|
|
138
|
+
getServer(serverId) {
|
|
139
|
+
return this.servers.get(serverId);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get all registered peer servers
|
|
143
|
+
*/
|
|
144
|
+
getAllServers() {
|
|
145
|
+
return Array.from(this.servers.values());
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get all online peer servers
|
|
149
|
+
*/
|
|
150
|
+
getOnlineServers() {
|
|
151
|
+
return this.getAllServers().filter(s => s.status === 'online');
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Check server health and update status
|
|
155
|
+
*/
|
|
156
|
+
checkServerHealth() {
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
for (const server of this.servers.values()) {
|
|
159
|
+
if (now - server.lastHeartbeat > HEARTBEAT_TIMEOUT_MS) {
|
|
160
|
+
if (server.status === 'online') {
|
|
161
|
+
console.log(`[ServerRegistry] Server ${server.id} marked offline (heartbeat timeout)`);
|
|
162
|
+
server.status = 'offline';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Start the cleanup interval
|
|
169
|
+
*/
|
|
170
|
+
startCleanupInterval() {
|
|
171
|
+
if (this.cleanupInterval) {
|
|
172
|
+
clearInterval(this.cleanupInterval);
|
|
173
|
+
}
|
|
174
|
+
this.cleanupInterval = setInterval(() => {
|
|
175
|
+
this.checkServerHealth();
|
|
176
|
+
}, CLEANUP_INTERVAL_MS);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Stop the cleanup interval (for shutdown)
|
|
180
|
+
*/
|
|
181
|
+
shutdown() {
|
|
182
|
+
if (this.cleanupInterval) {
|
|
183
|
+
clearInterval(this.cleanupInterval);
|
|
184
|
+
this.cleanupInterval = null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get cluster info for API response
|
|
189
|
+
*/
|
|
190
|
+
getClusterInfo(localResources) {
|
|
191
|
+
const servers = this.getAllServers().map(s => ({
|
|
192
|
+
id: s.id,
|
|
193
|
+
name: s.name,
|
|
194
|
+
url: s.url,
|
|
195
|
+
status: s.status,
|
|
196
|
+
isLocal: false,
|
|
197
|
+
resources: s.resources,
|
|
198
|
+
}));
|
|
199
|
+
// Add local server at the beginning
|
|
200
|
+
servers.unshift({
|
|
201
|
+
id: this.localServerId,
|
|
202
|
+
name: 'Local',
|
|
203
|
+
url: '', // Local doesn't need URL
|
|
204
|
+
status: 'online',
|
|
205
|
+
isLocal: true,
|
|
206
|
+
resources: localResources,
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
localServerId: this.localServerId,
|
|
210
|
+
peerCount: this.servers.size,
|
|
211
|
+
onlineCount: this.getOnlineServers().length,
|
|
212
|
+
servers,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Global singleton
|
|
217
|
+
exports.serverRegistry = new ServerRegistry();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel output buffer limits (keep in sync with frontend display.config.ts)
|
|
3
|
+
*/
|
|
4
|
+
export declare const MAX_OUTPUT_LINES = 10000;
|
|
5
|
+
export declare const MAX_OUTPUT_CHARS = 100000000;
|
|
6
|
+
export declare const MAX_OUTPUT_LINES_ERROR = 10000;
|
|
7
|
+
export declare const MAX_OUTPUT_CHARS_ERROR = 100000000;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Kernel output buffer limits (keep in sync with frontend display.config.ts)
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MAX_OUTPUT_CHARS_ERROR = exports.MAX_OUTPUT_LINES_ERROR = exports.MAX_OUTPUT_CHARS = exports.MAX_OUTPUT_LINES = void 0;
|
|
7
|
+
exports.MAX_OUTPUT_LINES = 10000;
|
|
8
|
+
exports.MAX_OUTPUT_CHARS = 100_000_000;
|
|
9
|
+
exports.MAX_OUTPUT_LINES_ERROR = 10000;
|
|
10
|
+
exports.MAX_OUTPUT_CHARS_ERROR = 100_000_000;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Discovers Python environments on the system (conda, pyenv, venv, system).
|
|
5
|
+
* Node.js port of the Python PythonDiscoveryService.
|
|
6
|
+
*/
|
|
7
|
+
import { PythonEnvironment, PythonEnvType, CacheInfo, InstallKernelResult } from './types';
|
|
8
|
+
export interface DiscoveryServiceOptions {
|
|
9
|
+
cacheFile?: string;
|
|
10
|
+
cacheTtlHours?: number;
|
|
11
|
+
versionCheckTimeoutMs?: number;
|
|
12
|
+
ipykernelCheckTimeoutMs?: number;
|
|
13
|
+
condaListTimeoutMs?: number;
|
|
14
|
+
kernelInstallTimeoutMs?: number;
|
|
15
|
+
registrationTimeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class PythonDiscoveryService {
|
|
18
|
+
private cache;
|
|
19
|
+
private cacheTimestamp;
|
|
20
|
+
private cacheFile;
|
|
21
|
+
private cacheTtlHours;
|
|
22
|
+
private versionCheckTimeoutMs;
|
|
23
|
+
private ipykernelCheckTimeoutMs;
|
|
24
|
+
private condaListTimeoutMs;
|
|
25
|
+
private kernelInstallTimeoutMs;
|
|
26
|
+
private registrationTimeoutMs;
|
|
27
|
+
private backgroundRefreshInProgress;
|
|
28
|
+
constructor(options?: DiscoveryServiceOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Load cache from disk
|
|
31
|
+
*/
|
|
32
|
+
private loadCache;
|
|
33
|
+
/**
|
|
34
|
+
* Save environments to cache
|
|
35
|
+
*/
|
|
36
|
+
saveToCache(environments: Record<string, PythonEnvironment>): void;
|
|
37
|
+
/**
|
|
38
|
+
* Get environments from cache (returns null if empty)
|
|
39
|
+
*/
|
|
40
|
+
getFromCache(): Record<string, PythonEnvironment> | null;
|
|
41
|
+
/**
|
|
42
|
+
* Check if cache is still valid
|
|
43
|
+
*/
|
|
44
|
+
isCacheValid(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Get cache info
|
|
47
|
+
*/
|
|
48
|
+
getCacheInfo(): CacheInfo & {
|
|
49
|
+
refreshing: boolean;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Run a command asynchronously with timeout
|
|
53
|
+
*/
|
|
54
|
+
private runCommand;
|
|
55
|
+
/**
|
|
56
|
+
* Generate display name for an environment
|
|
57
|
+
*/
|
|
58
|
+
generateDisplayName(version: string, envType: PythonEnvType, envName: string | null): string;
|
|
59
|
+
/**
|
|
60
|
+
* Sort environments by type priority and name
|
|
61
|
+
*/
|
|
62
|
+
sortEnvironments(envs: PythonEnvironment[]): PythonEnvironment[];
|
|
63
|
+
/**
|
|
64
|
+
* Check if a python path exists
|
|
65
|
+
*/
|
|
66
|
+
pythonExists(pythonPath: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get common conda base paths to search
|
|
69
|
+
*/
|
|
70
|
+
getCondaBasePaths(): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Get common system python paths
|
|
73
|
+
*/
|
|
74
|
+
getSystemPythonPaths(): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Get pyenv versions path
|
|
77
|
+
*/
|
|
78
|
+
getPyenvVersionsPath(): string;
|
|
79
|
+
/**
|
|
80
|
+
* Get common virtualenv paths
|
|
81
|
+
*/
|
|
82
|
+
getVirtualenvPaths(): string[];
|
|
83
|
+
/**
|
|
84
|
+
* Generate a unique kernel name from path
|
|
85
|
+
*/
|
|
86
|
+
generateKernelName(pythonPath: string, version: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Simple string hash function
|
|
89
|
+
*/
|
|
90
|
+
private hashString;
|
|
91
|
+
/**
|
|
92
|
+
* Parse Python version from version string
|
|
93
|
+
*/
|
|
94
|
+
parseVersionString(versionOutput: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Get Python version from executable
|
|
97
|
+
*/
|
|
98
|
+
private getPythonVersion;
|
|
99
|
+
/**
|
|
100
|
+
* Check if ipykernel is installed
|
|
101
|
+
*/
|
|
102
|
+
private checkIpykernel;
|
|
103
|
+
/**
|
|
104
|
+
* Probe an interpreter for the two facts that drive kernel provisioning, in a
|
|
105
|
+
* single Python invocation (keeps discovery cheap — one spawn per env):
|
|
106
|
+
* - whether `ipykernel` is importable
|
|
107
|
+
* - whether the interpreter is PEP 668 "externally managed" (an
|
|
108
|
+
* `EXTERNALLY-MANAGED` marker beside the stdlib), which blocks pip installs.
|
|
109
|
+
*
|
|
110
|
+
* Detecting the marker file is capability-based and stable: we ask Python
|
|
111
|
+
* itself rather than fingerprinting tool names by path, so it keeps working as
|
|
112
|
+
* new package managers (uv, pixi, …) appear.
|
|
113
|
+
*/
|
|
114
|
+
private probeEnvironment;
|
|
115
|
+
/**
|
|
116
|
+
* Refine an environment's type using stable path markers that the coarse
|
|
117
|
+
* discovery scans miss. Only used to pick a better label / guidance hint —
|
|
118
|
+
* the safe-vs-unsafe install decision relies on `externallyManaged`, not this.
|
|
119
|
+
*
|
|
120
|
+
* A venv is left as-is: its `bin/python` symlinks to (and `sysconfig` resolves
|
|
121
|
+
* to) its base interpreter, so following the symlink to e.g. a uv-managed build
|
|
122
|
+
* must NOT relabel the venv itself as "uv".
|
|
123
|
+
*/
|
|
124
|
+
private classifyEnvType;
|
|
125
|
+
/**
|
|
126
|
+
* Build a copy-pasteable command that makes `ipykernel` available for an env,
|
|
127
|
+
* tailored to the detected ecosystem. After the user runs it (and clicks
|
|
128
|
+
* Refresh), the env shows up with ipykernel and Nebula offers one-click
|
|
129
|
+
* Register — Nebula never installs packages itself.
|
|
130
|
+
*
|
|
131
|
+
* Returns null when ipykernel is already present (nothing to do but Register).
|
|
132
|
+
*/
|
|
133
|
+
private buildInstallHint;
|
|
134
|
+
/**
|
|
135
|
+
* Find conda environments
|
|
136
|
+
*/
|
|
137
|
+
private findCondaEnvs;
|
|
138
|
+
/**
|
|
139
|
+
* Find pyenv Python versions
|
|
140
|
+
*/
|
|
141
|
+
private findPyenvVersions;
|
|
142
|
+
/**
|
|
143
|
+
* Find virtualenvs in common locations
|
|
144
|
+
*/
|
|
145
|
+
private findVirtualenvs;
|
|
146
|
+
/**
|
|
147
|
+
* Find system Python installations
|
|
148
|
+
*/
|
|
149
|
+
private findSystemPythons;
|
|
150
|
+
/**
|
|
151
|
+
* Enrich a candidate with version and ipykernel info
|
|
152
|
+
*/
|
|
153
|
+
private enrichEnvironment;
|
|
154
|
+
/**
|
|
155
|
+
* Perform the actual discovery (internal, always runs full scan)
|
|
156
|
+
*/
|
|
157
|
+
private performDiscovery;
|
|
158
|
+
/**
|
|
159
|
+
* Trigger background refresh (non-blocking)
|
|
160
|
+
*/
|
|
161
|
+
private triggerBackgroundRefresh;
|
|
162
|
+
/**
|
|
163
|
+
* Discover all Python environments
|
|
164
|
+
*
|
|
165
|
+
* Uses stale-while-revalidate pattern:
|
|
166
|
+
* - If cache exists (even stale): return immediately, refresh in background if stale
|
|
167
|
+
* - If no cache: block and perform full discovery
|
|
168
|
+
* - If forceRefresh: block and perform full discovery
|
|
169
|
+
*/
|
|
170
|
+
discover(options?: {
|
|
171
|
+
forceRefresh?: boolean;
|
|
172
|
+
}): Promise<PythonEnvironment[]>;
|
|
173
|
+
/**
|
|
174
|
+
* Check if background refresh is in progress
|
|
175
|
+
*/
|
|
176
|
+
isRefreshing(): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Register a Python environment as a Jupyter kernel.
|
|
179
|
+
*
|
|
180
|
+
* Policy (capability-based, see ADR in the kernel onboarding flow):
|
|
181
|
+
* - If `ipykernel` is already importable → just register the kernelspec.
|
|
182
|
+
* This is the safe, universal path and works for every ecosystem.
|
|
183
|
+
* - If `ipykernel` is missing and the interpreter is PEP 668 externally
|
|
184
|
+
* managed → refuse, with a `needs_ipykernel`/`externally_managed` code so
|
|
185
|
+
* the UI shows guidance instead of a raw pip traceback. Nebula does not
|
|
186
|
+
* install into managed interpreters.
|
|
187
|
+
* - If `ipykernel` is missing but the interpreter is writable (a plain venv,
|
|
188
|
+
* conda env, …) → install it, then register. This keeps the API usable for
|
|
189
|
+
* deliberate callers (MCP, scripts); the UI funnels everything through the
|
|
190
|
+
* register-only path.
|
|
191
|
+
*/
|
|
192
|
+
installKernel(pythonPath: string, kernelName?: string): Promise<InstallKernelResult>;
|
|
193
|
+
/** Extract combined stdout+stderr text from a child_process error. */
|
|
194
|
+
private errorOutput;
|
|
195
|
+
/** Detect PEP 668 "externally managed" refusals generically (not tool-specific). */
|
|
196
|
+
private isExternallyManagedError;
|
|
197
|
+
}
|
|
198
|
+
export declare const pythonDiscovery: PythonDiscoveryService;
|