@filsilva/helios-cli 0.10.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 +171 -0
- package/bin/helios.js +34 -0
- package/dist/client/assets/HeliosSessionWorker.browser-BYYjDIKH.js +3 -0
- package/dist/client/assets/HeliosSessionWorker.browser-BYYjDIKH.js.map +1 -0
- package/dist/client/assets/d3force3dWorker-BKANL9of.js +2 -0
- package/dist/client/assets/d3force3dWorker-BKANL9of.js.map +1 -0
- package/dist/client/assets/index-CP7mSmLx.js +9530 -0
- package/dist/client/assets/index-CP7mSmLx.js.map +1 -0
- package/dist/client/assets/layoutWorker-Lc8iIdmf.js +2 -0
- package/dist/client/assets/layoutWorker-Lc8iIdmf.js.map +1 -0
- package/dist/client/index.html +27 -0
- package/package.json +40 -0
- package/skills/helios-cli/SKILL.md +118 -0
- package/skills/helios-cli/references/behaviors.md +47 -0
- package/skills/helios-cli/references/layouts.md +77 -0
- package/skills/helios-cli/references/mappers.md +119 -0
- package/skills/helios-cli/references/metrics.md +83 -0
- package/skills/helios-cli/references/networks.md +53 -0
- package/skills/helios-cli/references/persistence.md +136 -0
- package/skills/helios-cli/references/positions.md +63 -0
- package/skills/helios-cli/references/rendering-export.md +56 -0
- package/skills/helios-cli/references/rpc-methods.md +83 -0
- package/src/cli.js +488 -0
- package/src/client/index.html +27 -0
- package/src/client/main.js +2210 -0
- package/src/daemon/SessionDaemon.js +1065 -0
- package/src/daemon/entry.js +36 -0
- package/src/protocol/jsonl.js +88 -0
- package/src/shared/cliConfig.js +52 -0
- package/src/shared/fileSessionStore.js +202 -0
- package/src/shared/fs.js +59 -0
- package/src/shared/networkFormats.js +55 -0
- package/src/shared/networkInspect.js +81 -0
- package/src/shared/paths.js +43 -0
- package/src/shared/sessionClient.js +88 -0
- package/src/shared/sessionId.js +5 -0
- package/src/shared/sessionRegistry.js +53 -0
- package/src/shared/sessionSurfaces.js +199 -0
- package/vite.config.js +47 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { createJsonLineParser, JsonRpcResponseReader, callJsonRpc } from './protocol/jsonl.js';
|
|
6
|
+
import { inspectNetworkFile } from './shared/networkInspect.js';
|
|
7
|
+
import { inferNetworkFormat } from './shared/networkFormats.js';
|
|
8
|
+
import { packageRoot } from './shared/paths.js';
|
|
9
|
+
import {
|
|
10
|
+
cliConfigPath,
|
|
11
|
+
getConfiguredDesktopAppPath,
|
|
12
|
+
loadCliConfig,
|
|
13
|
+
setConfiguredDesktopAppPath,
|
|
14
|
+
} from './shared/cliConfig.js';
|
|
15
|
+
import {
|
|
16
|
+
appPathExists,
|
|
17
|
+
findDesktopAppPath,
|
|
18
|
+
findRegisteredMacBundleId,
|
|
19
|
+
HELIOS_MAC_BUNDLE_IDS,
|
|
20
|
+
launchDesktopSession,
|
|
21
|
+
launchMacSession,
|
|
22
|
+
macAppPathExists,
|
|
23
|
+
normalizeSessionSurface,
|
|
24
|
+
} from './shared/sessionSurfaces.js';
|
|
25
|
+
import {
|
|
26
|
+
listSessionMetas,
|
|
27
|
+
loadSessionMeta,
|
|
28
|
+
loadSessionState,
|
|
29
|
+
openRpcSocket,
|
|
30
|
+
rpcCall,
|
|
31
|
+
startSession as startManagedSession,
|
|
32
|
+
} from './shared/sessionClient.js';
|
|
33
|
+
|
|
34
|
+
const require = createRequire(import.meta.url);
|
|
35
|
+
|
|
36
|
+
function popFlag(args, name, { hasValue = false } = {}) {
|
|
37
|
+
const index = args.findIndex((entry) => entry === name || entry.startsWith(`${name}=`));
|
|
38
|
+
if (index === -1) return null;
|
|
39
|
+
const current = args[index];
|
|
40
|
+
args.splice(index, 1);
|
|
41
|
+
if (!hasValue) return true;
|
|
42
|
+
if (current.includes('=')) return current.slice(current.indexOf('=') + 1);
|
|
43
|
+
const next = args[index];
|
|
44
|
+
if (next == null) throw new Error(`Missing value for ${name}`);
|
|
45
|
+
args.splice(index, 1);
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseJson(value, fallback = {}) {
|
|
50
|
+
if (!value) return fallback;
|
|
51
|
+
return JSON.parse(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const PLAYWRIGHT_BROWSER_TARGETS = new Set([
|
|
55
|
+
'chromium',
|
|
56
|
+
'chrome',
|
|
57
|
+
'chrome-beta',
|
|
58
|
+
'msedge',
|
|
59
|
+
'msedge-beta',
|
|
60
|
+
'msedge-dev',
|
|
61
|
+
'firefox',
|
|
62
|
+
'webkit',
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
export function parseBrowserInstallArgs(inputArgs) {
|
|
66
|
+
const args = [...inputArgs];
|
|
67
|
+
const withDeps = popFlag(args, '--with-deps') === true;
|
|
68
|
+
const browsers = args.length > 0 ? args : ['chromium'];
|
|
69
|
+
for (const browser of browsers) {
|
|
70
|
+
if (!PLAYWRIGHT_BROWSER_TARGETS.has(browser)) {
|
|
71
|
+
throw new Error(`Unsupported browser "${browser}". Expected one of: ${Array.from(PLAYWRIGHT_BROWSER_TARGETS).join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { browsers, withDeps };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function readPackageVersion(packageName) {
|
|
78
|
+
try {
|
|
79
|
+
let currentDir = path.dirname(require.resolve(packageName));
|
|
80
|
+
while (currentDir && currentDir !== path.dirname(currentDir)) {
|
|
81
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
82
|
+
try {
|
|
83
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
84
|
+
if (packageJson.name === packageName) return packageJson.version ?? null;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
87
|
+
}
|
|
88
|
+
currentDir = path.dirname(currentDir);
|
|
89
|
+
}
|
|
90
|
+
const packageJson = JSON.parse(await fs.readFile(require.resolve(`${packageName}/package.json`), 'utf8'));
|
|
91
|
+
return packageJson.version ?? null;
|
|
92
|
+
} catch (_) {
|
|
93
|
+
try {
|
|
94
|
+
const packageJson = JSON.parse(
|
|
95
|
+
await fs.readFile(path.join(packageRoot, 'node_modules', packageName, 'package.json'), 'utf8'),
|
|
96
|
+
);
|
|
97
|
+
return packageJson.version ?? null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function showVersion() {
|
|
105
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
106
|
+
const versions = {
|
|
107
|
+
cli: packageJson.version ?? null,
|
|
108
|
+
'helios-network': await readPackageVersion('helios-network'),
|
|
109
|
+
'helios-web': await readPackageVersion('helios-web'),
|
|
110
|
+
};
|
|
111
|
+
process.stdout.write(`${JSON.stringify(versions, null, 2)}\n`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function installBrowser(argv) {
|
|
115
|
+
const options = parseBrowserInstallArgs(argv);
|
|
116
|
+
const playwrightRoot = path.dirname(require.resolve('playwright/package.json'));
|
|
117
|
+
const playwrightCli = path.join(playwrightRoot, 'cli.js');
|
|
118
|
+
const installArgs = [
|
|
119
|
+
playwrightCli,
|
|
120
|
+
'install',
|
|
121
|
+
...(options.withDeps ? ['--with-deps'] : []),
|
|
122
|
+
...options.browsers,
|
|
123
|
+
];
|
|
124
|
+
const child = spawn(process.execPath, installArgs, {
|
|
125
|
+
cwd: packageRoot,
|
|
126
|
+
stdio: 'inherit',
|
|
127
|
+
});
|
|
128
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
129
|
+
child.on('error', reject);
|
|
130
|
+
child.on('close', resolve);
|
|
131
|
+
});
|
|
132
|
+
if (exitCode !== 0) {
|
|
133
|
+
throw new Error(`Playwright browser install failed with exit code ${exitCode}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function parseStartArgs(inputArgs) {
|
|
138
|
+
const args = [...inputArgs];
|
|
139
|
+
const browserChannel = popFlag(args, '--browser-channel', { hasValue: true });
|
|
140
|
+
const storageDir = popFlag(args, '--storage-dir', { hasValue: true });
|
|
141
|
+
const mode = popFlag(args, '--mode', { hasValue: true });
|
|
142
|
+
const surface = normalizeSessionSurface(popFlag(args, '--surface', { hasValue: true }));
|
|
143
|
+
const appPath = popFlag(args, '--app-path', { hasValue: true });
|
|
144
|
+
const openFlag = popFlag(args, '--open') === true;
|
|
145
|
+
const noOpen = popFlag(args, '--no-open') === true;
|
|
146
|
+
const resolvedMode = surface === 'managed'
|
|
147
|
+
? (mode ?? 'headed')
|
|
148
|
+
: (surface == null ? (mode ?? 'server') : 'server');
|
|
149
|
+
const resolvedOpen = noOpen
|
|
150
|
+
? false
|
|
151
|
+
: (surface === 'server' || surface === 'managed' || surface === 'mac' || surface === 'desktop')
|
|
152
|
+
? false
|
|
153
|
+
: (surface === 'browser' ? true : (openFlag || mode == null));
|
|
154
|
+
return {
|
|
155
|
+
mode: resolvedMode,
|
|
156
|
+
open: resolvedOpen,
|
|
157
|
+
surface,
|
|
158
|
+
appPath: appPath == null ? null : path.resolve(String(appPath)),
|
|
159
|
+
renderer: popFlag(args, '--renderer', { hasValue: true }) ?? 'webgpu',
|
|
160
|
+
layout: popFlag(args, '--layout', { hasValue: true }) ?? 'gpu-force',
|
|
161
|
+
browserChannel: browserChannel == null ? null : (String(browserChannel).trim() || null),
|
|
162
|
+
storageDir: storageDir == null ? null : path.resolve(String(storageDir)),
|
|
163
|
+
networkPath: popFlag(args, '--network', { hasValue: true }) ?? null,
|
|
164
|
+
noGpu: popFlag(args, '--no-gpu') === true,
|
|
165
|
+
remaining: args,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function resolveStartSurface(options) {
|
|
170
|
+
if (options.surface !== 'auto') return options.surface;
|
|
171
|
+
if (process.platform === 'darwin' && (options.appPath || await findRegisteredMacBundleId())) return 'mac';
|
|
172
|
+
const configuredDesktopPath = await getConfiguredDesktopAppPath();
|
|
173
|
+
if (configuredDesktopPath && await appPathExists(configuredDesktopPath)) return 'desktop';
|
|
174
|
+
if (await findDesktopAppPath()) return 'desktop';
|
|
175
|
+
return 'browser';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function resolveDesktopAppPath(options) {
|
|
179
|
+
if (options.appPath) {
|
|
180
|
+
if (!(await appPathExists(options.appPath))) {
|
|
181
|
+
throw new Error(`Helios Desktop app was not found at ${options.appPath}`);
|
|
182
|
+
}
|
|
183
|
+
return setConfiguredDesktopAppPath(options.appPath);
|
|
184
|
+
}
|
|
185
|
+
const configuredPath = await getConfiguredDesktopAppPath();
|
|
186
|
+
if (configuredPath) {
|
|
187
|
+
if (await appPathExists(configuredPath)) return configuredPath;
|
|
188
|
+
throw new Error(`Configured Helios Desktop app path does not exist: ${configuredPath}. Pass --app-path <path-to-Helios-Desktop> once, or run "helios config set desktop.appPath <path>".`);
|
|
189
|
+
}
|
|
190
|
+
const discoveredPath = await findDesktopAppPath();
|
|
191
|
+
if (discoveredPath) return setConfiguredDesktopAppPath(discoveredPath);
|
|
192
|
+
throw new Error('Helios Desktop could not be located. Pass --app-path <path-to-Helios-Desktop> once, or run "helios config set desktop.appPath <path>". The path will be saved in the helios-cli config file.');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function startSession(argv) {
|
|
196
|
+
const options = parseStartArgs(argv);
|
|
197
|
+
const surface = await resolveStartSurface(options);
|
|
198
|
+
let macBundleId = null;
|
|
199
|
+
let desktopAppPath = null;
|
|
200
|
+
if (surface === 'mac') {
|
|
201
|
+
if (process.platform !== 'darwin') {
|
|
202
|
+
throw new Error('Helios Mac sessions are only supported on macOS.');
|
|
203
|
+
}
|
|
204
|
+
if (options.appPath) {
|
|
205
|
+
if (!(await macAppPathExists(options.appPath))) {
|
|
206
|
+
throw new Error(`Helios Mac app was not found at ${options.appPath}`);
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
macBundleId = await findRegisteredMacBundleId();
|
|
210
|
+
if (!macBundleId) {
|
|
211
|
+
throw new Error('HeliosWeb is not registered. Build or install HeliosWeb.app, or pass --app-path /path/to/HeliosWeb.app.');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else if (surface === 'desktop') {
|
|
215
|
+
desktopAppPath = await resolveDesktopAppPath(options);
|
|
216
|
+
}
|
|
217
|
+
const runtime = surface === 'mac' ? 'mac' : (surface === 'desktop' ? 'desktop' : 'cli');
|
|
218
|
+
const client = surface === 'mac'
|
|
219
|
+
? {
|
|
220
|
+
kind: 'helios-mac',
|
|
221
|
+
bundleId: macBundleId ?? HELIOS_MAC_BUNDLE_IDS[0],
|
|
222
|
+
appPath: options.appPath,
|
|
223
|
+
}
|
|
224
|
+
: (surface === 'desktop'
|
|
225
|
+
? {
|
|
226
|
+
kind: 'helios-desktop',
|
|
227
|
+
appPath: desktopAppPath,
|
|
228
|
+
}
|
|
229
|
+
: null);
|
|
230
|
+
const meta = await startManagedSession({
|
|
231
|
+
mode: options.mode,
|
|
232
|
+
open: surface === 'browser' ? true : options.open,
|
|
233
|
+
renderer: options.renderer,
|
|
234
|
+
layout: options.layout,
|
|
235
|
+
runtime,
|
|
236
|
+
surface,
|
|
237
|
+
client,
|
|
238
|
+
browserChannel: options.browserChannel,
|
|
239
|
+
storageDir: options.storageDir,
|
|
240
|
+
networkPath: options.networkPath ? path.resolve(options.networkPath) : null,
|
|
241
|
+
noGpu: options.noGpu,
|
|
242
|
+
requireBridge: surface === 'mac' || surface === 'desktop' ? false : undefined,
|
|
243
|
+
});
|
|
244
|
+
if (surface === 'mac') await launchMacSession(meta, { appPath: options.appPath, bundleId: macBundleId });
|
|
245
|
+
if (surface === 'desktop') await launchDesktopSession(meta, { appPath: desktopAppPath });
|
|
246
|
+
process.stdout.write(`${JSON.stringify(meta, null, 2)}\n`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function listSessions() {
|
|
250
|
+
const sessions = await listSessionMetas();
|
|
251
|
+
process.stdout.write(`${JSON.stringify(sessions, null, 2)}\n`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function sessionInfo(sessionId) {
|
|
255
|
+
const meta = await loadSessionMeta(sessionId);
|
|
256
|
+
if (!meta) throw new Error(`Unknown session ${sessionId}`);
|
|
257
|
+
process.stdout.write(`${JSON.stringify(meta, null, 2)}\n`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function sessionState(sessionId) {
|
|
261
|
+
if (!sessionId) throw new Error('Usage: helios session state <sessionId>');
|
|
262
|
+
const state = await loadSessionState(sessionId);
|
|
263
|
+
if (!state) throw new Error(`No saved session state for ${sessionId}`);
|
|
264
|
+
process.stdout.write(`${JSON.stringify(state, null, 2)}\n`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function stopSession(sessionId) {
|
|
268
|
+
const result = await rpcCall(sessionId, 'session.stop', {});
|
|
269
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function callMethod(argv) {
|
|
273
|
+
const [sessionId, method, ...rest] = argv;
|
|
274
|
+
if (!sessionId || !method) {
|
|
275
|
+
throw new Error('Usage: helios call <sessionId> <method> [--json <payload>]');
|
|
276
|
+
}
|
|
277
|
+
const json = popFlag(rest, '--json', { hasValue: true });
|
|
278
|
+
const result = await rpcCall(sessionId, method, parseJson(json, {}));
|
|
279
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function stateGet(argv) {
|
|
283
|
+
const [sessionId, pathArg] = argv;
|
|
284
|
+
if (!sessionId) throw new Error('Usage: helios state get <sessionId> [path]');
|
|
285
|
+
const result = await rpcCall(sessionId, 'state.get', pathArg ? { path: pathArg } : {});
|
|
286
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function stateSet(argv) {
|
|
290
|
+
const [sessionId, pathArg, rawValue, ...rest] = argv;
|
|
291
|
+
if (!sessionId || !pathArg || rawValue == null) {
|
|
292
|
+
throw new Error('Usage: helios state set <sessionId> <path> <json-value> [--scope user|workspace|network|session]');
|
|
293
|
+
}
|
|
294
|
+
const scope = popFlag(rest, '--scope', { hasValue: true });
|
|
295
|
+
const reason = popFlag(rest, '--reason', { hasValue: true }) ?? 'cli-state-set';
|
|
296
|
+
const value = JSON.parse(rawValue);
|
|
297
|
+
const result = await rpcCall(sessionId, 'state.set', {
|
|
298
|
+
path: pathArg,
|
|
299
|
+
value,
|
|
300
|
+
scope: scope ?? undefined,
|
|
301
|
+
reason,
|
|
302
|
+
});
|
|
303
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function stateReset(argv) {
|
|
307
|
+
const [sessionId, pathArg, ...rest] = argv;
|
|
308
|
+
if (!sessionId || !pathArg) throw new Error('Usage: helios state reset <sessionId> <path>');
|
|
309
|
+
const reason = popFlag(rest, '--reason', { hasValue: true }) ?? 'cli-state-reset';
|
|
310
|
+
const result = await rpcCall(sessionId, 'state.reset', { path: pathArg, reason });
|
|
311
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function attachSession(argv) {
|
|
315
|
+
const [sessionId, ...rest] = argv;
|
|
316
|
+
if (!sessionId) throw new Error('Usage: helios session attach <sessionId> --stdio');
|
|
317
|
+
const stdio = popFlag(rest, '--stdio');
|
|
318
|
+
if (!stdio) throw new Error('Only --stdio attach is supported');
|
|
319
|
+
const { socket } = await openRpcSocket(sessionId);
|
|
320
|
+
process.stdin.setEncoding('utf8');
|
|
321
|
+
process.stdin.pipe(socket);
|
|
322
|
+
socket.pipe(process.stdout);
|
|
323
|
+
await new Promise((resolve, reject) => {
|
|
324
|
+
socket.on('close', resolve);
|
|
325
|
+
socket.on('error', reject);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function streamEvents(argv) {
|
|
330
|
+
const [sessionId] = argv;
|
|
331
|
+
if (!sessionId) throw new Error('Usage: helios events <sessionId>');
|
|
332
|
+
const { socket } = await openRpcSocket(sessionId);
|
|
333
|
+
const reader = new JsonRpcResponseReader();
|
|
334
|
+
const parser = createJsonLineParser((message) => {
|
|
335
|
+
reader.handleMessage(message);
|
|
336
|
+
});
|
|
337
|
+
socket.setEncoding('utf8');
|
|
338
|
+
socket.on('data', parser);
|
|
339
|
+
reader.onNotification((message) => {
|
|
340
|
+
if (message?.method === 'events.notification') {
|
|
341
|
+
process.stdout.write(`${JSON.stringify(message.params)}\n`);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
await callJsonRpc(socket, reader, {
|
|
345
|
+
jsonrpc: '2.0',
|
|
346
|
+
id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
347
|
+
method: 'events.subscribe',
|
|
348
|
+
params: {},
|
|
349
|
+
});
|
|
350
|
+
await new Promise((resolve, reject) => {
|
|
351
|
+
socket.on('close', resolve);
|
|
352
|
+
socket.on('error', reject);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function parseInspectArgs(inputArgs) {
|
|
357
|
+
const args = [...inputArgs];
|
|
358
|
+
const json = popFlag(args, '--json') === true;
|
|
359
|
+
const format = popFlag(args, '--format', { hasValue: true });
|
|
360
|
+
const [filePath, ...remaining] = args;
|
|
361
|
+
if (!filePath || remaining.length > 0) {
|
|
362
|
+
throw new Error('Usage: helios inspect <network-path> [--json] [--format bxnet|zxnet|xnet|gt]');
|
|
363
|
+
}
|
|
364
|
+
const normalizedFormat = format == null ? null : String(format).trim().toLowerCase();
|
|
365
|
+
if (normalizedFormat && !['bxnet', 'zxnet', 'xnet', 'gt'].includes(normalizedFormat)) {
|
|
366
|
+
throw new Error(`Unsupported inspect format "${format}"`);
|
|
367
|
+
}
|
|
368
|
+
return { filePath, json, format: normalizedFormat };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function renderInspectionSummary(inspection) {
|
|
372
|
+
const lines = [
|
|
373
|
+
`${inspection.name} (${inspection.format})`,
|
|
374
|
+
`Path: ${inspection.path}`,
|
|
375
|
+
`Nodes: ${inspection.nodeCount}`,
|
|
376
|
+
`Edges: ${inspection.edgeCount}`,
|
|
377
|
+
`Directed: ${inspection.directed ? 'yes' : 'no'}`,
|
|
378
|
+
`File size: ${inspection.fileSize} bytes`,
|
|
379
|
+
`Node attributes: ${inspection.attributes.node.map((entry) => `${entry.name}:${entry.typeName}[${entry.dimension}]`).join(', ') || 'none'}`,
|
|
380
|
+
`Edge attributes: ${inspection.attributes.edge.map((entry) => `${entry.name}:${entry.typeName}[${entry.dimension}]`).join(', ') || 'none'}`,
|
|
381
|
+
`Network attributes: ${inspection.attributes.network.map((entry) => `${entry.name}:${entry.typeName}[${entry.dimension}]`).join(', ') || 'none'}`,
|
|
382
|
+
];
|
|
383
|
+
if (inspection.warnings.length > 0) lines.push(`Warnings: ${inspection.warnings.join(' | ')}`);
|
|
384
|
+
return `${lines.join('\n')}\n`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function inspectNetwork(argv) {
|
|
388
|
+
const options = parseInspectArgs(argv);
|
|
389
|
+
const inspection = await inspectNetworkFile(options.filePath, {
|
|
390
|
+
format: options.format ?? inferNetworkFormat(options.filePath, null),
|
|
391
|
+
});
|
|
392
|
+
process.stdout.write(options.json ? `${JSON.stringify(inspection, null, 2)}\n` : renderInspectionSummary(inspection));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function parseDesktopOpenArgs(inputArgs) {
|
|
396
|
+
const args = [...inputArgs];
|
|
397
|
+
const app = popFlag(args, '--app', { hasValue: true });
|
|
398
|
+
const [filePath, ...remaining] = args;
|
|
399
|
+
if (!filePath || remaining.length > 0) {
|
|
400
|
+
throw new Error('Usage: helios desktop open <network-path> [--app <app-name-or-path>]');
|
|
401
|
+
}
|
|
402
|
+
return { filePath: path.resolve(filePath), app: app == null ? null : String(app).trim() || null };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function spawnAndWait(command, args) {
|
|
406
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: false });
|
|
407
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
408
|
+
child.on('error', reject);
|
|
409
|
+
child.on('close', resolve);
|
|
410
|
+
});
|
|
411
|
+
if (exitCode !== 0) throw new Error(`${command} failed with exit code ${exitCode}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function openDesktop(argv) {
|
|
415
|
+
const options = parseDesktopOpenArgs(argv);
|
|
416
|
+
if (process.platform === 'darwin') {
|
|
417
|
+
const args = options.app ? ['-a', options.app, options.filePath] : [options.filePath];
|
|
418
|
+
await spawnAndWait('open', args);
|
|
419
|
+
} else if (process.platform === 'win32') {
|
|
420
|
+
await spawnAndWait('cmd', ['/c', 'start', '', options.filePath]);
|
|
421
|
+
} else {
|
|
422
|
+
await spawnAndWait('xdg-open', [options.filePath]);
|
|
423
|
+
}
|
|
424
|
+
process.stdout.write(`${JSON.stringify({ opened: true, path: options.filePath, app: options.app }, null, 2)}\n`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async function showConfig(argv) {
|
|
428
|
+
if (argv.length > 0) throw new Error('Usage: helios config get');
|
|
429
|
+
const config = await loadCliConfig();
|
|
430
|
+
process.stdout.write(`${JSON.stringify({ path: cliConfigPath, config }, null, 2)}\n`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function setConfig(argv) {
|
|
434
|
+
const [key, value, ...remaining] = argv;
|
|
435
|
+
if (!key || value == null || remaining.length > 0) {
|
|
436
|
+
throw new Error('Usage: helios config set desktop.appPath <path>');
|
|
437
|
+
}
|
|
438
|
+
if (key !== 'desktop.appPath') {
|
|
439
|
+
throw new Error(`Unsupported config key "${key}". Supported keys: desktop.appPath`);
|
|
440
|
+
}
|
|
441
|
+
const resolvedPath = path.resolve(value);
|
|
442
|
+
if (!(await appPathExists(resolvedPath))) {
|
|
443
|
+
throw new Error(`Helios Desktop app was not found at ${resolvedPath}`);
|
|
444
|
+
}
|
|
445
|
+
const appPath = await setConfiguredDesktopAppPath(resolvedPath);
|
|
446
|
+
process.stdout.write(`${JSON.stringify({ path: cliConfigPath, key, value: appPath }, null, 2)}\n`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export async function runCli(argv) {
|
|
450
|
+
const [command, subcommand, ...rest] = argv;
|
|
451
|
+
if (command === '--version' || command === '-v' || command === 'version') return showVersion();
|
|
452
|
+
if (command === 'browser' && subcommand === 'install') return installBrowser(rest);
|
|
453
|
+
if (command === 'config' && subcommand === 'get') return showConfig(rest);
|
|
454
|
+
if (command === 'config' && subcommand === 'set') return setConfig(rest);
|
|
455
|
+
if (command === 'inspect') return inspectNetwork([subcommand, ...rest].filter((entry) => entry != null));
|
|
456
|
+
if (command === 'desktop' && subcommand === 'open') return openDesktop(rest);
|
|
457
|
+
if (command === 'session' && subcommand === 'start') return startSession(rest);
|
|
458
|
+
if (command === 'session' && subcommand === 'list') return listSessions();
|
|
459
|
+
if (command === 'session' && subcommand === 'info') return sessionInfo(rest[0]);
|
|
460
|
+
if (command === 'session' && subcommand === 'state') return sessionState(rest[0]);
|
|
461
|
+
if (command === 'session' && subcommand === 'stop') return stopSession(rest[0]);
|
|
462
|
+
if (command === 'session' && subcommand === 'attach') return attachSession(rest);
|
|
463
|
+
if (command === 'state' && subcommand === 'get') return stateGet(rest);
|
|
464
|
+
if (command === 'state' && subcommand === 'set') return stateSet(rest);
|
|
465
|
+
if (command === 'state' && subcommand === 'reset') return stateReset(rest);
|
|
466
|
+
if (command === 'call') return callMethod([subcommand, ...rest]);
|
|
467
|
+
if (command === 'events') return streamEvents([subcommand, ...rest]);
|
|
468
|
+
throw new Error(
|
|
469
|
+
'Usage:\n'
|
|
470
|
+
+ ' helios version\n'
|
|
471
|
+
+ ' helios browser install [chromium|chrome|firefox|webkit] [--with-deps]\n'
|
|
472
|
+
+ ' helios config get\n'
|
|
473
|
+
+ ' helios config set desktop.appPath <path>\n'
|
|
474
|
+
+ ' helios inspect <network-path> [--json] [--format bxnet|zxnet|xnet|gt]\n'
|
|
475
|
+
+ ' helios desktop open <network-path> [--app <app-name-or-path>]\n'
|
|
476
|
+
+ ' helios session start [--surface server|browser|managed|desktop|mac|auto] [--app-path <Helios.app|HeliosWeb.app>] [--mode headed|headless|server] [--open|--no-open] [--renderer auto|webgl|webgpu] [--layout <name>] [--browser-channel <channel>] [--storage-dir <path>] [--network <path>] [--no-gpu]\n'
|
|
477
|
+
+ ' helios session list\n'
|
|
478
|
+
+ ' helios session info <sessionId>\n'
|
|
479
|
+
+ ' helios session state <sessionId>\n'
|
|
480
|
+
+ ' helios session stop <sessionId>\n'
|
|
481
|
+
+ ' helios state get <sessionId> [path]\n'
|
|
482
|
+
+ ' helios state set <sessionId> <path> <json-value> [--scope user|workspace|network|session]\n'
|
|
483
|
+
+ ' helios state reset <sessionId> <path>\n'
|
|
484
|
+
+ ' helios session attach <sessionId> --stdio\n'
|
|
485
|
+
+ ' helios call <sessionId> <method> [--json <payload>]\n'
|
|
486
|
+
+ ' helios events <sessionId>\n',
|
|
487
|
+
);
|
|
488
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Helios CLI Session</title>
|
|
7
|
+
<style>
|
|
8
|
+
html, body {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
background: #050505;
|
|
15
|
+
color: #f6f4ef;
|
|
16
|
+
}
|
|
17
|
+
#app {
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 100%;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<div id="app"></div>
|
|
25
|
+
<script type="module" src="./main.js"></script>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|