@omochi-ui/cli 0.1.2 → 0.2.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/dist/cli.js DELETED
@@ -1,454 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { hostname, networkInterfaces } from 'node:os';
3
- import process from 'node:process';
4
- import { createStaticLocator, gatewayEndpointKey, loadNodeIdentity, omochiConfigRoot, openTrustStore, pairWithGateway, parseGatewayTarget, startGateway, } from '@omochi-ui/discovery';
5
- import { generateCatalog } from './catalog-codegen.js';
6
- import { helpText, helpTopic } from './help.js';
7
- import { startHub } from './hub/hub.js';
8
- import { startSshGateway, validateSshTarget } from './hub/ssh-gateway.js';
9
- import { startStdioWebSocketBridge } from './stdio-websocket-bridge.js';
10
- import { loadComposition, openBrowser, startCompositionServices } from './up.js';
11
- /**
12
- * An address another machine could actually dial.
13
- *
14
- * A wildcard bind is what the gateway listens on, not somewhere anyone can connect to,
15
- * so printing it back would send a person to `0.0.0.0`.
16
- */
17
- function reachableHost(bound) {
18
- if (bound !== '0.0.0.0' && bound !== '::' && bound !== '')
19
- return bound;
20
- for (const addresses of Object.values(networkInterfaces())) {
21
- for (const address of addresses ?? []) {
22
- if (address.family === 'IPv4' && !address.internal)
23
- return address.address;
24
- }
25
- }
26
- return hostname();
27
- }
28
- function usage(message = 'Invalid or incomplete arguments.') {
29
- process.stderr.write(`[omochi-ui] ${message}\n\n${helpText(topic) ?? helpText()}`);
30
- process.exit(2);
31
- }
32
- const args = process.argv.slice(2);
33
- // Stop scanning at -- so e.g. `dev -- python app.py --help` reaches the app.
34
- const separatorIndex = args.indexOf('--');
35
- const ownArgs = separatorIndex < 0 ? args : args.slice(0, separatorIndex);
36
- const explicitHelp = ownArgs[0] === 'help';
37
- const helpArgs = explicitHelp ? ownArgs.slice(1) : ownArgs;
38
- const topic = helpTopic(helpArgs.filter((arg) => arg !== '--help' && arg !== '-h'));
39
- if (!args.length || explicitHelp || ownArgs.some((arg) => arg === '--help' || arg === '-h')) {
40
- const text = helpText(topic);
41
- if (text === undefined || (explicitHelp && helpArgs.length > (topic.includes(' ') ? 2 : 1))) {
42
- usage(`Unknown help topic: ${helpArgs.join(' ')}`);
43
- }
44
- process.stdout.write(text);
45
- process.exit(0);
46
- }
47
- const commandName = args.shift();
48
- if (commandName === 'generate') {
49
- if (args.shift() !== 'catalog')
50
- usage();
51
- const options = { catalog: '' };
52
- while (args.length) {
53
- const option = args.shift();
54
- if (option === '--check') {
55
- options.check = true;
56
- continue;
57
- }
58
- const value = args.shift();
59
- if (!value)
60
- usage();
61
- if (option === '--catalog')
62
- options.catalog = value;
63
- else if (option === '--name')
64
- options.name = value;
65
- else if (option === '--typescript')
66
- options.typescript = value;
67
- else if (option === '--python')
68
- options.python = value;
69
- else if (option === '--rust')
70
- options.rust = value;
71
- else
72
- usage();
73
- }
74
- if (!options.catalog)
75
- usage();
76
- await generateCatalog(options);
77
- process.exit(0);
78
- }
79
- if (commandName === 'id') {
80
- let nodeName;
81
- while (args.length) {
82
- const option = args.shift();
83
- const value = args.shift();
84
- if (!value || option !== '--node-name')
85
- usage();
86
- nodeName = value;
87
- }
88
- const identity = await loadNodeIdentity(nodeName === undefined ? {} : { name: nodeName });
89
- process.stdout.write(`${identity.nodeId}\t${identity.name}\n`);
90
- process.stderr.write(`[omochi-ui] identity stored in ${omochiConfigRoot()}\n`);
91
- process.exit(0);
92
- }
93
- if (commandName === 'pair') {
94
- const target = args.shift();
95
- if (!target)
96
- usage();
97
- const parsed = parseGatewayTarget(target);
98
- if (!parsed || parsed.endpoint.kind !== 'tcp')
99
- usage();
100
- let code;
101
- while (args.length) {
102
- const option = args.shift();
103
- const value = args.shift();
104
- if (!value || option !== '--code')
105
- usage();
106
- code = value;
107
- }
108
- // Reading the code from stdin keeps it out of shell history.
109
- if (code === undefined) {
110
- process.stderr.write('[omochi-ui] pairing code: ');
111
- code = await new Promise((resolvePromise) => {
112
- let typed = '';
113
- process.stdin.setEncoding('utf8');
114
- process.stdin.on('data', (chunk) => {
115
- typed += chunk;
116
- const line = typed.indexOf('\n');
117
- if (line >= 0) {
118
- process.stdin.pause();
119
- resolvePromise(typed.slice(0, line));
120
- }
121
- });
122
- process.stdin.on('end', () => resolvePromise(typed));
123
- });
124
- }
125
- const identity = await loadNodeIdentity();
126
- const trust = await openTrustStore();
127
- try {
128
- const peer = await pairWithGateway(parsed.endpoint, { identity, trust, code });
129
- process.stderr.write(`[omochi-ui] paired with ${peer.nodeId} (${peer.name})\n`);
130
- process.stderr.write(`[omochi-ui] reach it with: omochi-ui hub --gateway ${peer.nodeId}@${peer.addresses[0]}\n`);
131
- process.exit(0);
132
- }
133
- catch (error) {
134
- process.stderr.write(`[omochi-ui] ${error instanceof Error ? error.message : String(error)}\n`);
135
- process.exit(1);
136
- }
137
- }
138
- if (commandName === 'trust') {
139
- const action = args.shift();
140
- if (action !== 'list' && action !== 'forget')
141
- usage('Expected trust list or trust forget <nodeId>.');
142
- if (action === 'list' && args.length !== 0)
143
- usage('trust list takes no arguments.');
144
- if (action === 'forget' && args.length !== 1)
145
- usage('trust forget requires exactly one node ID.');
146
- const store = await openTrustStore();
147
- if (action === 'list') {
148
- const peers = store.list();
149
- if (store.legacy)
150
- process.stderr.write('[omochi-ui] old TLS-PSK pairings are unsupported; pair devices again\n');
151
- if (!peers.length)
152
- process.stderr.write(`[omochi-ui] no paired peers in ${store.file}\n`);
153
- for (const peer of peers) {
154
- const seen = peer.lastSeenAt ? ` last seen ${peer.lastSeenAt}` : '';
155
- process.stdout.write(`${peer.nodeId}\t${peer.role}\t${peer.name}\tpaired ${peer.pairedAt}${seen}\n`);
156
- }
157
- process.exit(0);
158
- }
159
- if (action === 'forget') {
160
- const nodeId = args.shift();
161
- if (!nodeId)
162
- usage();
163
- const forgotten = await store.forget(nodeId);
164
- process.stderr.write(forgotten
165
- ? `[omochi-ui] forgot ${nodeId}\n`
166
- : `[omochi-ui] no paired peer ${nodeId}\n`);
167
- process.exit(forgotten ? 0 : 1);
168
- }
169
- usage();
170
- }
171
- if (commandName === 'gateway') {
172
- let root;
173
- let socketPath;
174
- let nodeName;
175
- let listen;
176
- let pairing;
177
- let advertise = false;
178
- while (args.length) {
179
- const option = args.shift();
180
- // `--lan` is sugar; accepting hubs and advertising stay separate switches.
181
- if (option === '--lan') {
182
- listen = { host: '0.0.0.0', port: 5556 };
183
- advertise = true;
184
- continue;
185
- }
186
- if (option === '--advertise') {
187
- advertise = true;
188
- continue;
189
- }
190
- if (option === '--pair') {
191
- pairing = {};
192
- continue;
193
- }
194
- const value = args.shift();
195
- if (!value)
196
- usage();
197
- if (option === '--root')
198
- root = value;
199
- else if (option === '--socket')
200
- socketPath = value;
201
- else if (option === '--node-name')
202
- nodeName = value;
203
- else if (option === '--listen') {
204
- const target = parseGatewayTarget(value);
205
- if (!target || target.endpoint.kind !== 'tcp')
206
- usage();
207
- listen = { host: target.endpoint.host, port: target.endpoint.port };
208
- }
209
- else
210
- usage();
211
- }
212
- if ((pairing || advertise) && !listen)
213
- usage('--pair and --advertise require --listen HOST:PORT or --lan.');
214
- const gateway = await startGateway({ root, socketPath, name: nodeName, listen, pairing, advertise })
215
- .catch((error) => {
216
- // Two gateways on one machine is the common mistake here, and a stack trace is a
217
- // poor way to say so.
218
- if (error.code === 'EADDRINUSE' && listen) {
219
- process.stderr.write(`[omochi-ui] ${listen.host}:${listen.port} is already in use; another gateway may be running\n`);
220
- process.exit(1);
221
- }
222
- if (error.code === 'EADDRINUSE') {
223
- process.stderr.write('[omochi-ui] a gateway is already listening on this machine\n');
224
- process.exit(1);
225
- }
226
- throw error;
227
- });
228
- process.stderr.write(`[omochi-ui] gateway ${gateway.nodeId} (${gateway.name})\n[omochi-ui] publishing services from ${gateway.root}\n[omochi-ui] listening on ${gateway.socketPath}\n`);
229
- if (gateway.address) {
230
- const peers = (await openTrustStore()).list().filter((peer) => peer.role === 'hub').length;
231
- process.stderr.write(`[omochi-ui] reachable from the local network on ${gateway.address.host}:${gateway.address.port}; ${peers} hub(s) paired\n`);
232
- if (advertise)
233
- process.stderr.write(`[omochi-ui] advertising as _${'omochi-ui'}._tcp\n`);
234
- }
235
- if (gateway.pairing) {
236
- const seconds = Math.round((gateway.pairing.expiresAt - Date.now()) / 1000);
237
- process.stderr.write([
238
- `[omochi-ui] pairing code: ${gateway.pairing.display} (valid ${seconds}s, one use)`,
239
- `[omochi-ui] on the other machine: omochi-ui pair ${reachableHost(gateway.pairingAddress.host)}:${gateway.pairingAddress.port}`,
240
- '',
241
- ].join('\n'));
242
- void gateway.pairing.ended.then((reason) => {
243
- // The gateway keeps running either way; only the window is over.
244
- process.stderr.write(`[omochi-ui] pairing window closed: ${reason}\n`);
245
- });
246
- }
247
- const stop = () => void gateway.close().then(() => process.exit(0));
248
- process.on('SIGINT', stop);
249
- process.on('SIGTERM', stop);
250
- }
251
- else if (commandName === 'up') {
252
- let host = '127.0.0.1';
253
- let port = 5555;
254
- let shouldOpen = false;
255
- const allowOrigins = [];
256
- let file = 'omochi.toml';
257
- let foundFile = false;
258
- while (args.length) {
259
- const option = args.shift();
260
- if (option === '--open') {
261
- shouldOpen = true;
262
- continue;
263
- }
264
- if (!option.startsWith('-')) {
265
- if (foundFile)
266
- usage('up accepts at most one composition file.');
267
- file = option;
268
- foundFile = true;
269
- continue;
270
- }
271
- const value = args.shift();
272
- if (!value)
273
- usage();
274
- if (option === '--host')
275
- host = value;
276
- else if (option === '--port' && Number.isInteger(Number(value)))
277
- port = Number(value);
278
- else if (option === '--allow-origin')
279
- allowOrigins.push(value);
280
- else
281
- usage();
282
- }
283
- if (port < 0 || port > 65535)
284
- usage('--port must be an integer from 0 to 65535.');
285
- let services;
286
- let hub;
287
- let shutdown;
288
- const stop = (code = 0) => shutdown ??= (async () => {
289
- await services?.close();
290
- await hub?.close();
291
- process.exit(code);
292
- })();
293
- process.on('SIGINT', () => { void stop(130); });
294
- process.on('SIGTERM', () => { void stop(143); });
295
- try {
296
- const identity = await loadNodeIdentity();
297
- const composition = await loadComposition(file, identity.nodeId);
298
- services = startCompositionServices(composition.services);
299
- hub = await startHub({ host, port, allowOrigins, presets: [composition.preset] });
300
- const url = new URL(hub.httpUrl);
301
- url.searchParams.set('preset', composition.id);
302
- process.stderr.write(`[omochi-ui] composition ${composition.id}: ${composition.services.length} services\n[omochi-ui] ${url.toString()}\n`);
303
- if (shouldOpen) {
304
- if (['0.0.0.0', '::'].includes(url.hostname))
305
- url.hostname = '127.0.0.1';
306
- openBrowser(url.toString());
307
- }
308
- }
309
- catch (error) {
310
- process.stderr.write(`[omochi-ui] ${error instanceof Error ? error.message : String(error)}\n`);
311
- await services?.close();
312
- await hub?.close();
313
- process.exit(1);
314
- }
315
- }
316
- else if (commandName === 'hub') {
317
- let host = '127.0.0.1';
318
- let port = 5555;
319
- let autoGateway = true;
320
- let discoverLan = false;
321
- let sshHost;
322
- let sshSocket;
323
- const peers = [];
324
- const peerIds = new Map();
325
- const allowOrigins = [];
326
- while (args.length) {
327
- const option = args.shift();
328
- if (option === '--no-auto-gateway') {
329
- autoGateway = false;
330
- continue;
331
- }
332
- if (option === '--discover-lan') {
333
- discoverLan = true;
334
- continue;
335
- }
336
- const value = args.shift();
337
- if (!value)
338
- usage();
339
- if (option === '--host')
340
- host = value;
341
- else if (option === '--port' && Number.isInteger(Number(value)))
342
- port = Number(value);
343
- else if (option === '--ssh') {
344
- if (sshHost !== undefined)
345
- usage();
346
- sshHost = value;
347
- }
348
- else if (option === '--ssh-socket') {
349
- if (sshSocket !== undefined)
350
- usage();
351
- sshSocket = value;
352
- }
353
- else if (option === '--gateway') {
354
- const target = parseGatewayTarget(value);
355
- if (!target)
356
- usage();
357
- peers.push(target.endpoint);
358
- if (target.nodeId)
359
- peerIds.set(gatewayEndpointKey(target.endpoint), target.nodeId);
360
- }
361
- else if (option === '--allow-origin')
362
- allowOrigins.push(value);
363
- else
364
- usage();
365
- }
366
- // Naming a gateway replaces the default, so `--gateway` can point at another machine
367
- // without also picking up whatever happens to be running locally.
368
- if (sshSocket !== undefined && sshHost === undefined)
369
- usage();
370
- if (port < 0 || port > 65535)
371
- usage('--port must be an integer from 0 to 65535.');
372
- let tunnel;
373
- let hub;
374
- const abort = new AbortController();
375
- let stopping = false;
376
- let pending;
377
- let shutdown;
378
- const stop = (code = 0) => shutdown ??= (async () => {
379
- stopping = true;
380
- abort.abort();
381
- await pending?.catch(() => { });
382
- await hub?.close();
383
- await tunnel?.close();
384
- process.exit(code);
385
- })();
386
- // Install these before SSH can prompt, so cancellation also cleans partial startup.
387
- process.on('SIGINT', () => { void stop(130); });
388
- process.on('SIGTERM', () => { void stop(143); });
389
- try {
390
- if (sshHost !== undefined) {
391
- validateSshTarget(sshHost, sshSocket);
392
- tunnel = await (pending = startSshGateway({ host: sshHost, remoteSocket: sshSocket, signal: abort.signal }));
393
- peers.push({ kind: 'unix', socketPath: tunnel.socketPath });
394
- }
395
- const gateways = peers.length ? [createStaticLocator(peers, peerIds)] : undefined;
396
- hub = await (pending = startHub({ host, port, allowOrigins, gateways, discoverLan, autoGateway: autoGateway && !peers.length }));
397
- if (tunnel)
398
- void tunnel.exited.then((error) => {
399
- if (!stopping) {
400
- process.stderr.write(`[omochi-ui] ${error.message}\n`);
401
- void stop(1);
402
- }
403
- });
404
- }
405
- catch (error) {
406
- process.stderr.write(`[omochi-ui] ${error instanceof Error ? error.message : error}\n`);
407
- await stop(1);
408
- process.exit(1);
409
- }
410
- // Connections are still being established, so name the targets rather than count them.
411
- const named = peers.length
412
- ? peers.map((peer) => (peer.kind === 'unix' ? peer.socketPath : `${peer.host}:${peer.port}`)).join(', ')
413
- : 'the local gateway';
414
- const targets = discoverLan ? `${named} and any gateway on the local link` : named;
415
- process.stderr.write(`[omochi-ui] merging ${targets}\n[omochi-ui] ${hub.httpUrl}\nA2UI endpoint: ${hub.url}\n`);
416
- if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1') {
417
- process.stderr.write('[omochi-ui] warning: hub has no authentication or TLS\n');
418
- }
419
- }
420
- else {
421
- if (commandName !== 'dev')
422
- usage(`Unknown command: ${commandName}`);
423
- const separator = args.indexOf('--');
424
- if (separator < 0)
425
- usage('dev requires -- followed by the application command.');
426
- const optionArgs = args.slice(0, separator);
427
- const childCommand = args.slice(separator + 1);
428
- let host = '127.0.0.1';
429
- let port = 5555;
430
- for (let index = 0; index < optionArgs.length; index += 1) {
431
- const option = optionArgs[index];
432
- const value = optionArgs[index + 1];
433
- if (option === '--host' && value)
434
- host = value;
435
- else if (option === '--port' && value && Number.isInteger(Number(value)))
436
- port = Number(value);
437
- else
438
- usage();
439
- index += 1;
440
- }
441
- if (!childCommand.length)
442
- usage();
443
- if (port < 0 || port > 65535)
444
- usage('--port must be an integer from 0 to 65535.');
445
- const handle = await startStdioWebSocketBridge({ command: childCommand, host, port });
446
- process.stderr.write(`[omochi-ui] bridge: ${handle.url}\nStart the browser preview in another terminal: bun run --filter @omochi-ui/dev-server dev\nOpen: http://127.0.0.1:5173/?ws=${encodeURIComponent(handle.url)}\n`);
447
- if (host !== '127.0.0.1' && host !== 'localhost' && host !== '::1')
448
- process.stderr.write('[omochi-ui] warning: bridge has no authentication or TLS\n');
449
- const forward = (signal) => { if (handle.child.exitCode === null)
450
- handle.child.kill(signal); };
451
- process.on('SIGINT', forward);
452
- process.on('SIGTERM', forward);
453
- process.exitCode = await handle.exited;
454
- }
package/dist/help.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export declare function helpText(topic?: string): string | undefined;
2
- /** Only command words select a topic; options and child arguments never do. */
3
- export declare function helpTopic(args: string[]): string;
package/dist/help.js DELETED
@@ -1,187 +0,0 @@
1
- const overview = `omochi-ui — attach a declarative UI to your program
2
-
3
- Usage: omochi-ui <command> [options]
4
-
5
- Develop an app:
6
- dev -- <command> [args...] Run an app behind a stdio/WebSocket bridge
7
- generate catalog Generate typed bindings from a local catalog
8
-
9
- Compose dashboards:
10
- up [FILE] Start a composition and its dashboard preset
11
- hub Serve the dashboard UI; auto-start a local gateway
12
- gateway Publish running apps on this machine
13
-
14
- Connect machines:
15
- id Show or name this machine's identity
16
- pair HOST:PORT Pair with a gateway using its one-time code
17
- trust list List paired peers
18
- trust forget <nodeId> Remove a paired peer
19
-
20
- Start here:
21
- omochi-ui dev -- <command> Preview one app (requires a separate UI server)
22
- omochi-ui up --open Start ./omochi.toml and open its preset
23
- omochi-ui hub Open dashboards at http://127.0.0.1:5555/
24
-
25
- Use omochi-ui <command> --help or omochi-ui help <command> for options.
26
- `;
27
- const topics = {
28
- up: `Start services and a read-only dashboard preset from a TOML composition.
29
-
30
- Usage: omochi-ui up [options] [FILE]
31
-
32
- Options:
33
- --host HOST Hub bind address (default: 127.0.0.1)
34
- --port PORT Hub port (default: 5555; 0 selects a port)
35
- --allow-origin ORIGIN Allow an additional exact browser origin; repeatable
36
- --open Open the selected preset in the desktop browser
37
-
38
- FILE defaults to ./omochi.toml. Commands run without an implicit shell and
39
- receive OMOCHI_SERVICE. Utility entries invoke the matching omochi-utils mode.
40
- The preset is temporary; use Save as dashboard in the hub to keep and edit it.
41
- If a child service exits, its view becomes unavailable while the hub continues.
42
- `,
43
- dev: `Run an application and bridge its JSONL stdin/stdout to WebSocket.
44
-
45
- Usage: omochi-ui dev [options] -- <command> [args...]
46
-
47
- Options:
48
- --host HOST Bind address (default: 127.0.0.1)
49
- --port PORT WebSocket port (default: 5555; 0 selects an available port)
50
-
51
- This command starts the app and bridge. Start the browser preview separately:
52
- bun run --filter @omochi-ui/dev-server dev
53
- omochi-ui dev -- uv run --project examples/python examples/python/app.py
54
-
55
- Open http://127.0.0.1:5173/ with the default bridge port. For another port,
56
- use the ?ws= URL printed at startup. All arguments after -- go to the app.
57
- The bridge has no authentication or TLS; keep the loopback default.
58
- `,
59
- hub: `Serve dashboards combining surfaces from running applications.
60
-
61
- Usage: omochi-ui hub [options]
62
-
63
- Options:
64
- --host HOST HTTP/WebSocket bind address (default: 127.0.0.1)
65
- --port PORT HTTP/WebSocket port (default: 5555; 0 selects a port)
66
- --gateway TARGET Gateway socket path or <nodeId>@HOST:PORT; repeatable
67
- --ssh HOST Reach a remote gateway through SSH (one target)
68
- --ssh-socket PATH Override the remote socket; requires --ssh
69
- --no-auto-gateway Do not start a missing local gateway
70
- --discover-lan Also discover paired gateways through mDNS
71
- --allow-origin ORIGIN Allow an additional exact browser origin; repeatable
72
-
73
- With no target, the hub reuses or starts the local gateway. --gateway and
74
- --ssh replace that default; add --gateway with the local socket to include it.
75
- The hub serves its own UI at http://127.0.0.1:5555/; no Vite server is needed.
76
- Apps must already be running with UnixSocketServerTransport.
77
-
78
- Examples:
79
- omochi-ui hub
80
- omochi-ui hub --ssh user@remote
81
- omochi-ui hub --gateway <nodeId>@192.168.1.42:5556
82
-
83
- SSH requires a running remote gateway. TCP requires pairing first.
84
- The hub has no authentication or TLS; keep the loopback default.
85
- `,
86
- gateway: `Discover and publish the current user's running apps on one machine.
87
-
88
- Usage: omochi-ui gateway [options]
89
-
90
- Options:
91
- --root DIR App registry directory (default: $XDG_RUNTIME_DIR/omochi)
92
- --socket PATH Gateway socket (default: <root>/gateway.sock)
93
- --node-name NAME Set this machine's display name
94
- --listen HOST:PORT Enable the certificate-authenticated mTLS listener
95
- --advertise Advertise with mDNS; requires --listen or --lan
96
- --pair Open a one-time pairing window; requires --listen or --lan
97
- --lan Shorthand for --listen 0.0.0.0:5556 --advertise
98
-
99
- The gateway discovers apps; it does not launch them or serve a browser UI.
100
- Without network flags, it listens only on a Unix socket. If XDG_RUNTIME_DIR
101
- is unset, the registry is <temp>/omochi-<uid>/omochi.
102
-
103
- Examples:
104
- omochi-ui gateway
105
- omochi-ui gateway --lan --pair
106
- `,
107
- id: `Show this machine's persistent node ID and display name.
108
-
109
- Usage: omochi-ui id [--node-name NAME]
110
-
111
- Creates an identity on first use. --node-name sets its display name.
112
- Configuration: $XDG_CONFIG_HOME/omochi (default: ~/.config/omochi).
113
- `,
114
- pair: `Pair this machine with a gateway accepting a one-time code.
115
-
116
- Usage: omochi-ui pair HOST:PORT [--code CODE]
117
-
118
- Start omochi-ui gateway --lan --pair on the machine running the apps first.
119
- Without --code, read the code from stdin to keep it out of shell history.
120
- Use the printed random pairing port; it differs from the normal mTLS port.
121
- After pairing, use the printed hub --gateway command to connect.
122
-
123
- Example:
124
- omochi-ui pair 192.168.1.42:42317 # use the printed random pairing port
125
- `,
126
- trust: `Manage paired peers stored in $XDG_CONFIG_HOME/omochi/peers.json.
127
-
128
- Usage:
129
- omochi-ui trust list
130
- omochi-ui trust forget <nodeId>
131
-
132
- list shows peer IDs, roles, names, and pairing times.
133
- forget removes the local entry and waits for running processes to close revoked connections.
134
- The machines must pair again to reconnect.
135
- `,
136
- 'trust list': `List this machine's paired peers.
137
-
138
- Usage: omochi-ui trust list
139
-
140
- Prints peer IDs, roles, names, pairing times, and last-seen times when known.
141
- `,
142
- 'trust forget': `Remove a peer from this machine's trust store.
143
-
144
- Usage: omochi-ui trust forget <nodeId>
145
-
146
- Use omochi-ui trust list to find its ID. Pair again to reconnect.
147
- Returns exit code 1 if the peer was not found.
148
- `,
149
- generate: `Generate SDK bindings from local schema files.
150
-
151
- Usage: omochi-ui generate catalog [options]
152
-
153
- Use omochi-ui generate catalog --help for required inputs and output flags.
154
- `,
155
- 'generate catalog': `Generate typed SDK bindings from a local A2UI v0.9 catalog.
156
-
157
- Usage: omochi-ui generate catalog --catalog FILE [options]
158
-
159
- Options:
160
- --catalog FILE Required local catalog JSON file
161
- --name NAME Type name (default: catalog title or GeneratedCatalog)
162
- --typescript FILE Write TypeScript bindings
163
- --python FILE Write Python bindings
164
- --rust FILE Write Rust bindings
165
- --check Fail if output files are missing or stale; do not write
166
-
167
- Choose at least one output language. Output directories must already exist.
168
- Schema resolution is local-only; no network fetches are performed.
169
-
170
- Example:
171
- omochi-ui generate catalog --catalog ./catalog.json --typescript ./catalog.ts
172
- `,
173
- };
174
- export function helpText(topic = '') {
175
- if (!topic)
176
- return overview;
177
- const body = Object.hasOwn(topics, topic) ? topics[topic] : undefined;
178
- return body === undefined ? undefined : `${body}\n -h, --help Show this help\n`;
179
- }
180
- /** Only command words select a topic; options and child arguments never do. */
181
- export function helpTopic(args) {
182
- const command = args[0] ?? '';
183
- const subcommand = args[1];
184
- return (command === 'trust' || command === 'generate') && subcommand && !subcommand.startsWith('-')
185
- ? `${command} ${subcommand}`
186
- : command;
187
- }
@@ -1,17 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import { type Dashboard, type DashboardInput, type DashboardPreset } from '@omochi-ui/hub-ui/contracts';
3
- export declare function validateDashboard(value: unknown): DashboardInput;
4
- /** Re-read under the mutation queue and cross-process lock; failed writes never replace the last good file. */
5
- export declare class DashboardStore {
6
- private readonly root;
7
- private queue;
8
- private readonly file;
9
- constructor(root?: string);
10
- private read;
11
- list(): Promise<Dashboard[]>;
12
- mutate(id: string | null, input: unknown, remove?: boolean): Promise<Dashboard | null>;
13
- }
14
- export declare function dashboardEndpoints(store: DashboardStore, allowed: (origin: string | undefined) => boolean): (request: IncomingMessage, response: ServerResponse) => boolean;
15
- export declare function validatePresets(values: readonly DashboardPreset[]): DashboardPreset[];
16
- /** Read-only presets live only for the lifetime of this hub process. */
17
- export declare function presetEndpoints(presets: readonly DashboardPreset[], allowed: (origin: string | undefined) => boolean): (request: IncomingMessage, response: ServerResponse) => boolean;