@neta-art/cohub-cli 4.0.0 → 6.0.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 +23 -51
- package/dist/app-download.d.ts +11 -0
- package/dist/{work-download.js → app-download.js} +23 -23
- package/dist/app-ref.d.ts +5 -0
- package/dist/app-ref.js +8 -0
- package/dist/board-export.js +5 -5
- package/dist/commands/app-commerce.d.ts +2 -0
- package/dist/commands/{work-commerce.js → app-commerce.js} +27 -27
- package/dist/commands/apps.d.ts +4 -0
- package/dist/commands/{works.js → apps.js} +136 -127
- package/dist/commands/board-domain.js +2 -0
- package/dist/commands/boards/animation.js +26 -21
- package/dist/commands/boards/appearance.js +28 -24
- package/dist/commands/boards/context.d.ts +17 -1
- package/dist/commands/boards/context.js +38 -1
- package/dist/commands/boards/examples.d.ts +4 -0
- package/dist/commands/boards/examples.js +224 -0
- package/dist/commands/boards/items.js +12 -16
- package/dist/commands/boards/nodes.js +5 -8
- package/dist/commands/boards.d.ts +6 -11
- package/dist/commands/boards.js +40 -140
- package/dist/commands/cron-jobs.js +1 -1
- package/dist/commands/desktop.d.ts +3 -0
- package/dist/commands/desktop.js +219 -0
- package/dist/commands/profile.js +5 -5
- package/dist/commands/tasks.js +1 -1
- package/dist/index.js +7 -6
- package/package.json +2 -2
- package/dist/commands/ui.d.ts +0 -2
- package/dist/commands/ui.js +0 -199
- package/dist/commands/work-commerce.d.ts +0 -2
- package/dist/commands/works.d.ts +0 -4
- package/dist/work-download.d.ts +0 -11
- package/dist/work-ref.d.ts +0 -5
- package/dist/work-ref.js +0 -8
package/dist/commands/boards.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { BOARD_CREATE_INPUT_MAX_BYTES,
|
|
2
|
+
import { BOARD_CREATE_INPUT_MAX_BYTES, parseBoardJsonObject, readBoardJsonObject, resolveBoardId, writeBoardOutput, } from "../board-command-support.js";
|
|
3
3
|
import { BOARD_EXPORT_FORMATS, formatFromPath, runBoardExport } from "../board-export.js";
|
|
4
4
|
import { registerBoardDomainCommands } from "./board-domain.js";
|
|
5
5
|
import { createClient, createRealtimeClient } from "../client.js";
|
|
6
6
|
import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
|
|
7
7
|
import { resolveSpace } from "../space.js";
|
|
8
|
-
const INSPECT_SECTIONS = ["nodes", "connections", "effects", "compositions", "playback"];
|
|
9
8
|
export const parseJsonObject = parseBoardJsonObject;
|
|
10
|
-
export async function readJsonObject(source, maxBytes = BOARD_TRANSACTION_INPUT_MAX_BYTES) {
|
|
11
|
-
return readBoardJsonObject(source, maxBytes);
|
|
12
|
-
}
|
|
13
9
|
function parseNumber(value, name, options = {}) {
|
|
14
10
|
if (!value.trim())
|
|
15
11
|
throw new Error(`${name} must be a finite number`);
|
|
@@ -24,15 +20,6 @@ function parseNumber(value, name, options = {}) {
|
|
|
24
20
|
throw new Error(`${name} must be at most ${options.max}`);
|
|
25
21
|
return parsed;
|
|
26
22
|
}
|
|
27
|
-
export function parseInspectSections(value) {
|
|
28
|
-
if (!value)
|
|
29
|
-
return undefined;
|
|
30
|
-
const sections = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
31
|
-
const unknown = sections.filter((section) => !INSPECT_SECTIONS.includes(section));
|
|
32
|
-
if (unknown.length > 0)
|
|
33
|
-
throw new Error(`Unknown Board section: ${unknown.join(", ")}`);
|
|
34
|
-
return [...new Set(sections)];
|
|
35
|
-
}
|
|
36
23
|
export function parseViewport(value) {
|
|
37
24
|
if (!value)
|
|
38
25
|
return undefined;
|
|
@@ -51,45 +38,11 @@ export function parseViewport(value) {
|
|
|
51
38
|
throw new Error("viewport width and height must be greater than zero");
|
|
52
39
|
return { x, y, width, height };
|
|
53
40
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw new Error("transaction input must not contain boardId");
|
|
57
|
-
if (!Array.isArray(input.operations))
|
|
58
|
-
throw new Error("transaction input must contain an operations array");
|
|
59
|
-
const rawBaseVersion = options.baseVersion ?? input.baseVersion;
|
|
60
|
-
if (rawBaseVersion === undefined)
|
|
61
|
-
throw new Error("baseVersion is required in input or --base-version");
|
|
62
|
-
const baseVersion = parseNumber(String(rawBaseVersion), "baseVersion", { min: 0, integer: true });
|
|
63
|
-
const rawTxId = options.txId ?? input.txId;
|
|
64
|
-
if (rawTxId !== undefined && (typeof rawTxId !== "string" || !rawTxId.trim())) {
|
|
65
|
-
throw new Error("txId must be a non-empty string");
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
...input,
|
|
69
|
-
txId: typeof rawTxId === "string" ? rawTxId : randomUUID(),
|
|
70
|
-
baseVersion,
|
|
71
|
-
operations: input.operations,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function showBoard(result) {
|
|
75
|
-
table([
|
|
76
|
-
{
|
|
77
|
-
id: result.board.id,
|
|
78
|
-
title: result.board.title,
|
|
79
|
-
version: result.board.version,
|
|
80
|
-
nodes: result.nodes.length,
|
|
81
|
-
connections: result.connections.length,
|
|
82
|
-
effects: result.effects.length,
|
|
83
|
-
compositions: result.compositions.length,
|
|
84
|
-
},
|
|
85
|
-
], [
|
|
41
|
+
function showCreated(result) {
|
|
42
|
+
table([result.board], [
|
|
86
43
|
{ key: "id", label: "ID" },
|
|
87
44
|
{ key: "title", label: "Title" },
|
|
88
45
|
{ key: "version", label: "Version" },
|
|
89
|
-
{ key: "nodes", label: "Nodes" },
|
|
90
|
-
{ key: "connections", label: "Connections" },
|
|
91
|
-
{ key: "effects", label: "Effects" },
|
|
92
|
-
{ key: "compositions", label: "Compositions" },
|
|
93
46
|
]);
|
|
94
47
|
}
|
|
95
48
|
function showSummary(result) {
|
|
@@ -105,7 +58,7 @@ function showSummary(result) {
|
|
|
105
58
|
{ key: "id", label: "ID" },
|
|
106
59
|
{ key: "title", label: "TITLE" },
|
|
107
60
|
{ key: "version", label: "VERSION" },
|
|
108
|
-
{ key: "
|
|
61
|
+
{ key: "items", label: "ITEMS" },
|
|
109
62
|
{ key: "connections", label: "CONNECTIONS" },
|
|
110
63
|
{ key: "effects", label: "EFFECTS" },
|
|
111
64
|
{ key: "compositions", label: "COMPOSITIONS" },
|
|
@@ -113,23 +66,6 @@ function showSummary(result) {
|
|
|
113
66
|
{ key: "updatedAt", label: "UPDATED" },
|
|
114
67
|
]);
|
|
115
68
|
}
|
|
116
|
-
function showValidation(result) {
|
|
117
|
-
table([{ valid: result.valid, diagnostics: result.diagnostics.length }], [
|
|
118
|
-
{ key: "valid", label: "Valid" },
|
|
119
|
-
{ key: "diagnostics", label: "Diagnostics" },
|
|
120
|
-
]);
|
|
121
|
-
if (result.diagnostics.length > 0) {
|
|
122
|
-
console.log();
|
|
123
|
-
table(result.diagnostics, [
|
|
124
|
-
{ key: "severity", label: "Severity" },
|
|
125
|
-
{ key: "code", label: "Code" },
|
|
126
|
-
{ key: "path", label: "Path" },
|
|
127
|
-
{ key: "message", label: "Message" },
|
|
128
|
-
]);
|
|
129
|
-
}
|
|
130
|
-
console.log();
|
|
131
|
-
table([result.peakCost], Object.keys(result.peakCost).map((key) => ({ key, label: key })));
|
|
132
|
-
}
|
|
133
69
|
function showPlayback(result) {
|
|
134
70
|
table([result], [
|
|
135
71
|
{ key: "playbackId", label: "Playback ID" },
|
|
@@ -156,38 +92,6 @@ function capabilityUnits(schema) {
|
|
|
156
92
|
return detail ? [`${field}:${detail}`] : [];
|
|
157
93
|
}).join(", ");
|
|
158
94
|
}
|
|
159
|
-
function registerTransactionCommand(boards, name) {
|
|
160
|
-
withJson(boards.command(`${name} <board>`)
|
|
161
|
-
.description(name === "validate" ? "Validate a transaction" : "Apply a transaction")
|
|
162
|
-
.requiredOption("-i, --input <file>", "Transaction JSON file; use - for stdin")
|
|
163
|
-
.option("--tx-id <id>", "Override txId; generated when omitted")
|
|
164
|
-
.option("--base-version <version>", "Override baseVersion")
|
|
165
|
-
.addHelpText("after", `
|
|
166
|
-
Input example:
|
|
167
|
-
{"baseVersion":12,"operations":[{"type":"board.patch","payload":{"patch":{"title":"Launch plan"}}}]}
|
|
168
|
-
|
|
169
|
-
Prefer semantic commands such as boards background, nodes, effects, or compositions for common edits.`))
|
|
170
|
-
.action(async (target, options) => {
|
|
171
|
-
try {
|
|
172
|
-
const transaction = createTransactionInput(await readJsonObject(options.input, BOARD_TRANSACTION_INPUT_MAX_BYTES), options);
|
|
173
|
-
const spaceId = resolveSpace(boards);
|
|
174
|
-
const boardId = await resolveBoardId(spaceId, target);
|
|
175
|
-
const board = createClient().space(spaceId).board(boardId);
|
|
176
|
-
const result = await board[name](transaction);
|
|
177
|
-
if (jsonRequested(options))
|
|
178
|
-
return outJson(result);
|
|
179
|
-
if (name === "validate")
|
|
180
|
-
showValidation(result);
|
|
181
|
-
else {
|
|
182
|
-
const receipt = result;
|
|
183
|
-
ok(`Board updated to version ${receipt.board.version}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (cause) {
|
|
187
|
-
handleHttp(cause);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
95
|
function commandId(options) {
|
|
192
96
|
return options.commandId?.trim() || randomUUID();
|
|
193
97
|
}
|
|
@@ -313,14 +217,16 @@ export function registerBoards(program) {
|
|
|
313
217
|
.description("Create a Board")
|
|
314
218
|
.option("--title <title>", "Board title")
|
|
315
219
|
.option("--mutation-id <id>", "Stable id for safe retries")
|
|
316
|
-
.option("-i, --input <file>", "
|
|
220
|
+
.option("-i, --input <file>", "Semantic Board seed JSON; use - for stdin")
|
|
317
221
|
.addHelpText("after", `
|
|
318
|
-
|
|
319
|
-
|
|
222
|
+
Create an empty Board, or provide items, connections, effects, compositions, and metadata in --input.
|
|
223
|
+
Generate an editable seed:
|
|
224
|
+
cohub boards examples create > board.json
|
|
225
|
+
cohub boards create plan.board -i board.json`))
|
|
320
226
|
.action(async (path, options) => {
|
|
321
227
|
try {
|
|
322
228
|
const content = options.input
|
|
323
|
-
? await
|
|
229
|
+
? await readBoardJsonObject(options.input, BOARD_CREATE_INPUT_MAX_BYTES)
|
|
324
230
|
: {};
|
|
325
231
|
if ("path" in content || "title" in content) {
|
|
326
232
|
throw new Error("create input must not contain path or title; use the command argument and --title");
|
|
@@ -336,7 +242,7 @@ For normal use, create an empty Board and add content with boards nodes, effects
|
|
|
336
242
|
if (jsonRequested(options))
|
|
337
243
|
return outJson(result);
|
|
338
244
|
ok(`Board created: ${result.board.id}`);
|
|
339
|
-
|
|
245
|
+
showCreated(result);
|
|
340
246
|
}
|
|
341
247
|
catch (cause) {
|
|
342
248
|
handleHttp(cause);
|
|
@@ -344,31 +250,30 @@ For normal use, create an empty Board and add content with boards nodes, effects
|
|
|
344
250
|
});
|
|
345
251
|
withJson(boards.command("inspect <board>")
|
|
346
252
|
.alias("get")
|
|
347
|
-
.description("
|
|
348
|
-
.
|
|
349
|
-
|
|
253
|
+
.description("Show Board metadata and semantic resource counts")
|
|
254
|
+
.addHelpText("after", `
|
|
255
|
+
Inspect content with:
|
|
256
|
+
cohub boards items list <board> --json
|
|
257
|
+
cohub boards effects list <board> --json
|
|
258
|
+
cohub boards compositions list <board> --json`))
|
|
350
259
|
.action(async (target, options) => {
|
|
351
260
|
try {
|
|
352
261
|
const spaceId = resolveSpace(boards);
|
|
353
262
|
const boardId = await resolveBoardId(spaceId, target);
|
|
354
|
-
const
|
|
355
|
-
if (!jsonRequested(options) && !options.include && !options.viewport) {
|
|
356
|
-
return showSummary(await board.summary());
|
|
357
|
-
}
|
|
358
|
-
const result = await board.inspect({
|
|
359
|
-
include: parseInspectSections(options.include),
|
|
360
|
-
viewport: parseViewport(options.viewport),
|
|
361
|
-
});
|
|
263
|
+
const result = await createClient().space(spaceId).board(boardId).summary();
|
|
362
264
|
if (jsonRequested(options))
|
|
363
265
|
return outJson(result);
|
|
364
|
-
|
|
266
|
+
showSummary(result);
|
|
365
267
|
}
|
|
366
268
|
catch (cause) {
|
|
367
269
|
handleHttp(cause);
|
|
368
270
|
}
|
|
369
271
|
});
|
|
370
272
|
withJson(boards.command("capabilities <board>")
|
|
371
|
-
.description("Show
|
|
273
|
+
.description("Show authoring schemas and runtime capabilities")
|
|
274
|
+
.addHelpText("after", `
|
|
275
|
+
Use --json for complete Item, patch, mutation, effect, Composition, and create JSON Schemas.
|
|
276
|
+
Use boards examples for editable starter JSON.`))
|
|
372
277
|
.action(async (target, options) => {
|
|
373
278
|
try {
|
|
374
279
|
const spaceId = resolveSpace(boards);
|
|
@@ -388,30 +293,25 @@ For normal use, create an empty Board and add content with boards nodes, effects
|
|
|
388
293
|
{ key: "coordinates", label: "Coordinates / units" },
|
|
389
294
|
{ key: "digest", label: "Digest" },
|
|
390
295
|
]);
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
{ key: "arrowEndpoints", label: "Arrow endpoints" },
|
|
406
|
-
]);
|
|
407
|
-
}
|
|
296
|
+
console.log();
|
|
297
|
+
table([{
|
|
298
|
+
types: result.items.types.join(", "),
|
|
299
|
+
colors: result.items.colors.join(", "),
|
|
300
|
+
shapes: result.items.shapes.join(", "),
|
|
301
|
+
drawPoints: result.items.coordinates.drawPoints,
|
|
302
|
+
arrowEndpoints: result.items.coordinates.arrowEndpoints,
|
|
303
|
+
}], [
|
|
304
|
+
{ key: "types", label: "Item types" },
|
|
305
|
+
{ key: "colors", label: "Colors" },
|
|
306
|
+
{ key: "shapes", label: "Shapes" },
|
|
307
|
+
{ key: "drawPoints", label: "Draw points" },
|
|
308
|
+
{ key: "arrowEndpoints", label: "Arrow endpoints" },
|
|
309
|
+
]);
|
|
408
310
|
}
|
|
409
311
|
catch (cause) {
|
|
410
312
|
handleHttp(cause);
|
|
411
313
|
}
|
|
412
314
|
});
|
|
413
|
-
registerTransactionCommand(boards, "validate");
|
|
414
|
-
registerTransactionCommand(boards, "apply");
|
|
415
315
|
registerBoardDomainCommands(boards);
|
|
416
316
|
registerExportCommand(boards);
|
|
417
317
|
withJson(boards.command("play <board> <composition-id>")
|
|
@@ -514,13 +414,13 @@ For normal use, create an empty Board and add content with boards nodes, effects
|
|
|
514
414
|
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
515
415
|
return;
|
|
516
416
|
}
|
|
517
|
-
if (event.type === "board.
|
|
518
|
-
process.stdout.write(`version ${event.payload.version}
|
|
417
|
+
if (event.type === "board.changed") {
|
|
418
|
+
process.stdout.write(`version ${event.payload.version} mutation ${event.payload.mutationId}\n`);
|
|
519
419
|
}
|
|
520
420
|
else if (event.type === "board.playback.changed") {
|
|
521
421
|
process.stdout.write(`${event.payload.status} composition ${event.payload.compositionId} position ${event.payload.position}\n`);
|
|
522
422
|
}
|
|
523
|
-
else {
|
|
423
|
+
else if (event.type === "board.awareness.updated") {
|
|
524
424
|
process.stdout.write(`awareness ${event.payload.actorName} ${event.payload.update.type}\n`);
|
|
525
425
|
}
|
|
526
426
|
},
|
|
@@ -9,7 +9,7 @@ function readJsonObject(pathOrJson) {
|
|
|
9
9
|
return parsed;
|
|
10
10
|
}
|
|
11
11
|
export function registerCronJobs(program) {
|
|
12
|
-
const cmd = program.command("cron-jobs"
|
|
12
|
+
const cmd = program.command("cron-jobs").description("Scheduled jobs");
|
|
13
13
|
cmd
|
|
14
14
|
.command("ls [spaceId]")
|
|
15
15
|
.alias("list")
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { HttpError, } from "@neta-art/cohub";
|
|
3
|
+
import { parseAppRef, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, } from "@neta-art/cohub";
|
|
4
|
+
import { createClient } from "../client.js";
|
|
5
|
+
import { error, handleHttp, json as outJson, jsonRequested, ok } from "../output.js";
|
|
6
|
+
import { getAppByRef } from "../app-ref.js";
|
|
7
|
+
const FILE_SCHEME = "file://";
|
|
8
|
+
const APP_SCHEME = "app://";
|
|
9
|
+
const LEGACY_WORK_SCHEME = "work://";
|
|
10
|
+
function optionalSpaceId(command) {
|
|
11
|
+
let current = command;
|
|
12
|
+
while (current) {
|
|
13
|
+
const opts = current.opts();
|
|
14
|
+
if (typeof opts.space === "string" && opts.space.trim())
|
|
15
|
+
return opts.space.trim();
|
|
16
|
+
current = current.parent ?? null;
|
|
17
|
+
}
|
|
18
|
+
return process.env.COHUB_SPACE_ID?.trim() || undefined;
|
|
19
|
+
}
|
|
20
|
+
function parseFilePath(value) {
|
|
21
|
+
const path = value.slice(FILE_SCHEME.length).trim();
|
|
22
|
+
if (!path ||
|
|
23
|
+
path.startsWith("/") ||
|
|
24
|
+
path.includes("\0") ||
|
|
25
|
+
path.split("/").some((segment) => segment === "..") ||
|
|
26
|
+
path.includes("\\")) {
|
|
27
|
+
return error("Invalid file path", "Use a relative Space path after file://.");
|
|
28
|
+
}
|
|
29
|
+
return path;
|
|
30
|
+
}
|
|
31
|
+
function hasFileScheme(value) {
|
|
32
|
+
return value.toLowerCase().startsWith(FILE_SCHEME);
|
|
33
|
+
}
|
|
34
|
+
function hasAppScheme(value) {
|
|
35
|
+
const lowered = value.toLowerCase();
|
|
36
|
+
return lowered.startsWith(APP_SCHEME) || lowered.startsWith(LEGACY_WORK_SCHEME);
|
|
37
|
+
}
|
|
38
|
+
function readCallInput(opts) {
|
|
39
|
+
if (opts.data !== undefined && opts.input !== undefined) {
|
|
40
|
+
return error("Conflicting input", "Use either --data or --input, not both.");
|
|
41
|
+
}
|
|
42
|
+
const raw = opts.data !== undefined
|
|
43
|
+
? opts.data
|
|
44
|
+
: opts.input !== undefined
|
|
45
|
+
? opts.input === "-"
|
|
46
|
+
? readFileSync(0, "utf-8")
|
|
47
|
+
: readFileSync(opts.input, "utf-8")
|
|
48
|
+
: undefined;
|
|
49
|
+
if (raw === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return error("Invalid input", "Call input must be valid JSON.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function parseTimeout(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS;
|
|
61
|
+
const parsed = Math.floor(Number(value));
|
|
62
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > DESKTOP_COMMAND_MAX_TIMEOUT_MS) {
|
|
63
|
+
return error("Invalid timeout", `--timeout-ms must be between 1 and ${DESKTOP_COMMAND_MAX_TIMEOUT_MS} milliseconds.`);
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
async function resolveAppTarget(client, ref) {
|
|
68
|
+
const normalized = hasAppScheme(ref) ? ref.replace(/^[a-zA-Z]+:\/\//, "") : ref;
|
|
69
|
+
const parsed = parseAppRef(normalized);
|
|
70
|
+
const detail = await getAppByRef(client, normalized);
|
|
71
|
+
return {
|
|
72
|
+
kind: "app",
|
|
73
|
+
appId: detail.app.id,
|
|
74
|
+
label: detail.app.slug,
|
|
75
|
+
launch: {
|
|
76
|
+
...(parsed.search ? { search: parsed.search } : {}),
|
|
77
|
+
...(parsed.hash ? { hash: parsed.hash } : {}),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function resolveOpenTarget(client, command, value) {
|
|
82
|
+
if (hasFileScheme(value))
|
|
83
|
+
return { kind: "file", path: parseFilePath(value) };
|
|
84
|
+
if (hasAppScheme(value))
|
|
85
|
+
return resolveAppTarget(client, value);
|
|
86
|
+
const spaceId = optionalSpaceId(command);
|
|
87
|
+
if (spaceId) {
|
|
88
|
+
try {
|
|
89
|
+
await client.space(spaceId).files.read(value);
|
|
90
|
+
return { kind: "file", path: value };
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
if (!(cause instanceof HttpError) || cause.status !== 404)
|
|
94
|
+
throw cause;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return resolveAppTarget(client, value);
|
|
98
|
+
}
|
|
99
|
+
function reportDispatch(record) {
|
|
100
|
+
if (record.status !== "pending") {
|
|
101
|
+
reportOutcome(record, Boolean(record.command.call));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
ok(`Desktop command dispatched (${record.commandId})`);
|
|
105
|
+
}
|
|
106
|
+
function reportOutcome(record, called) {
|
|
107
|
+
if (record.status === "applied") {
|
|
108
|
+
ok(called ? "App window shown and method called" : "Window shown");
|
|
109
|
+
if (record.result !== undefined) {
|
|
110
|
+
console.log(typeof record.result === "string" ? record.result : JSON.stringify(record.result, null, 2));
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
error(`Desktop command ${record.status}`, record.error?.message ?? "The Cohub desktop did not apply this command.");
|
|
115
|
+
}
|
|
116
|
+
async function openWindow(target, opts, command) {
|
|
117
|
+
const callInput = opts.call ? readCallInput(opts) : undefined;
|
|
118
|
+
if (!opts.call && (opts.data !== undefined || opts.input !== undefined)) {
|
|
119
|
+
return error("Missing --call", "--data and --input only apply together with --call.");
|
|
120
|
+
}
|
|
121
|
+
if (opts.noWait && opts.timeoutMs !== undefined) {
|
|
122
|
+
return error("Conflicting wait options", "Use either --no-wait or --timeout-ms, not both.");
|
|
123
|
+
}
|
|
124
|
+
const timeoutMs = parseTimeout(opts.timeoutMs);
|
|
125
|
+
const client = createClient();
|
|
126
|
+
try {
|
|
127
|
+
const resolved = await resolveOpenTarget(client, command, target);
|
|
128
|
+
if (resolved.kind === "file" && opts.call) {
|
|
129
|
+
return error("Unsupported option", "--call only applies to app targets.");
|
|
130
|
+
}
|
|
131
|
+
const call = opts.call
|
|
132
|
+
? { method: opts.call, ...(callInput === undefined ? {} : { input: callInput }) }
|
|
133
|
+
: undefined;
|
|
134
|
+
const input = {
|
|
135
|
+
command: {
|
|
136
|
+
type: "desktop.open",
|
|
137
|
+
target: resolved.kind === "file"
|
|
138
|
+
? resolved
|
|
139
|
+
: {
|
|
140
|
+
kind: "app",
|
|
141
|
+
appId: resolved.appId,
|
|
142
|
+
label: resolved.label,
|
|
143
|
+
...(resolved.launch.search || resolved.launch.hash ? { launch: resolved.launch } : {}),
|
|
144
|
+
},
|
|
145
|
+
...(call ? { call } : {}),
|
|
146
|
+
},
|
|
147
|
+
...(opts.client ? { targetClientId: opts.client } : {}),
|
|
148
|
+
...(opts.commandId ? { commandId: opts.commandId } : {}),
|
|
149
|
+
};
|
|
150
|
+
const record = opts.noWait
|
|
151
|
+
? (await client.desktop.create(input)).command
|
|
152
|
+
: await client.desktop.run(input, { timeoutMs });
|
|
153
|
+
if (jsonRequested(opts))
|
|
154
|
+
return outJson(record);
|
|
155
|
+
if (opts.noWait)
|
|
156
|
+
return reportDispatch(record);
|
|
157
|
+
reportOutcome(record, Boolean(call));
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
if (e instanceof Error && e.name === "AppRefParseError")
|
|
161
|
+
return error(e.message);
|
|
162
|
+
handleHttp(e);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const OPEN_HELP = `
|
|
166
|
+
Commands reach only the desktop that originated the current chat, resolved
|
|
167
|
+
from request provenance. Nothing else can be targeted.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
cohub desktop open <app-or-file>
|
|
171
|
+
cohub desktop open file://src/main.ts
|
|
172
|
+
cohub desktop open app://alice/studio/launch
|
|
173
|
+
cohub desktop open alice/studio/launch
|
|
174
|
+
cohub desktop open https://cohub.live/alice/studio/w/launch?view=timeline
|
|
175
|
+
cohub desktop open <app-id> --call selection.get
|
|
176
|
+
cohub desktop open <app-id> --call board.focus --data '{"nodeId":"n1"}'
|
|
177
|
+
`;
|
|
178
|
+
const OPEN_NOTES = `
|
|
179
|
+
Notes:
|
|
180
|
+
- Use file:// and app:// to make the target explicit; the legacy work://
|
|
181
|
+
scheme is still accepted.
|
|
182
|
+
- A plain target checks the current Space for a file before resolving an app.
|
|
183
|
+
- Opening a window is idempotent; repeating it re-activates the same tab.
|
|
184
|
+
- --call waits for the app to announce readiness, then invokes the method.
|
|
185
|
+
- Which methods exist is up to the app author.
|
|
186
|
+
`;
|
|
187
|
+
function registerOpen(parent, deprecated) {
|
|
188
|
+
const open = parent
|
|
189
|
+
.command(deprecated ? "preview <app-or-file>" : "open <app-or-file>")
|
|
190
|
+
.description(deprecated ? "Deprecated: use `cohub desktop open`" : "Open a file or app window on the Cohub desktop, optionally calling an app method")
|
|
191
|
+
.option("--call <method>", "Method the app registered via client.app.surface.handle()")
|
|
192
|
+
.option("--data <json>", "Inline JSON input for --call")
|
|
193
|
+
.option("-i, --input <file>", "JSON input file for --call; use - for stdin")
|
|
194
|
+
.option("--client <clientId>", "Target a specific desktop instance of your account")
|
|
195
|
+
.option("--command-id <id>", "Stable id so retries never dispatch twice")
|
|
196
|
+
.option("--no-wait", "Dispatch the command and exit without waiting for a result")
|
|
197
|
+
.option("--timeout-ms <ms>", `How long to wait for the desktop (default: ${DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS}; max: ${DESKTOP_COMMAND_MAX_TIMEOUT_MS})`)
|
|
198
|
+
.option("--json", "Output as JSON")
|
|
199
|
+
.action(async (target, opts, thisCommand) => {
|
|
200
|
+
if (deprecated) {
|
|
201
|
+
ok("Deprecated: `cohub ui preview` is now `cohub desktop open`.");
|
|
202
|
+
}
|
|
203
|
+
await openWindow(target, opts, thisCommand);
|
|
204
|
+
});
|
|
205
|
+
open.addHelpText("after", deprecated ? OPEN_NOTES : OPEN_HELP + OPEN_NOTES);
|
|
206
|
+
}
|
|
207
|
+
export function registerDesktop(program) {
|
|
208
|
+
const desktop = program
|
|
209
|
+
.command("desktop")
|
|
210
|
+
.description("Drive the Cohub desktop that started this chat");
|
|
211
|
+
registerOpen(desktop, false);
|
|
212
|
+
}
|
|
213
|
+
export function registerLegacyUi(program) {
|
|
214
|
+
const ui = program
|
|
215
|
+
.command("ui", { hidden: true })
|
|
216
|
+
.description("Deprecated: use `cohub desktop open`")
|
|
217
|
+
.addHelpText("after", OPEN_HELP);
|
|
218
|
+
registerOpen(ui, true);
|
|
219
|
+
}
|
package/dist/commands/profile.js
CHANGED
|
@@ -16,7 +16,7 @@ export function registerProfile(program) {
|
|
|
16
16
|
const handle = result.profile.username ? `@${result.profile.username}` : result.profile.userUuid;
|
|
17
17
|
console.log(`\n ${result.profile.displayName} (${handle})`);
|
|
18
18
|
console.log(` Spaces: ${result.spaces.length}`);
|
|
19
|
-
console.log(`
|
|
19
|
+
console.log(` Apps: ${result.apps.length}\n`);
|
|
20
20
|
if (result.spaces.length > 0) {
|
|
21
21
|
console.log(" Spaces:");
|
|
22
22
|
for (const space of result.spaces) {
|
|
@@ -24,10 +24,10 @@ export function registerProfile(program) {
|
|
|
24
24
|
}
|
|
25
25
|
console.log("");
|
|
26
26
|
}
|
|
27
|
-
if (result.
|
|
28
|
-
console.log("
|
|
29
|
-
for (const
|
|
30
|
-
console.log(` - ${
|
|
27
|
+
if (result.apps.length > 0) {
|
|
28
|
+
console.log(" Apps:");
|
|
29
|
+
for (const app of result.apps) {
|
|
30
|
+
console.log(` - ${app.title} ${app.publicUrl}`);
|
|
31
31
|
}
|
|
32
32
|
console.log("");
|
|
33
33
|
}
|
package/dist/commands/tasks.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createClient } from "../client.js";
|
|
|
2
2
|
import { table, json as outJson, jsonRequested, handleHttp } from "../output.js";
|
|
3
3
|
export function registerTasks(program, dependencies = {}) {
|
|
4
4
|
const getClient = dependencies.createClient ?? createClient;
|
|
5
|
-
const cmd = program.command("tasks"
|
|
5
|
+
const cmd = program.command("tasks").description("Task runs");
|
|
6
6
|
cmd
|
|
7
7
|
.command("ls")
|
|
8
8
|
.alias("list")
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,8 @@ import { registerPrompt, registerSpaces } from "./commands/spaces.js";
|
|
|
19
19
|
import { maybeHandleRunCommand } from "./commands/run.js";
|
|
20
20
|
import { registerSandbox } from "./commands/sandbox.js";
|
|
21
21
|
import { registerTasks } from "./commands/tasks.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { registerDesktop, registerLegacyUi } from "./commands/desktop.js";
|
|
23
|
+
import { registerApps } from "./commands/apps.js";
|
|
24
24
|
const VERSION = (() => {
|
|
25
25
|
try {
|
|
26
26
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
@@ -55,8 +55,8 @@ Common commands:
|
|
|
55
55
|
cohub -s <space-id> spaces sessions turns ls <session-id>
|
|
56
56
|
cohub -s <space-id> spaces files ls
|
|
57
57
|
cohub -s <space-id> public upload ./dist demo
|
|
58
|
-
cohub -s <space-id>
|
|
59
|
-
cohub
|
|
58
|
+
cohub -s <space-id> apps publish demo --file dist/index.html
|
|
59
|
+
cohub desktop open <app-id> --call selection.get
|
|
60
60
|
cohub -s <space-id> spaces commerce products list
|
|
61
61
|
cohub models ls
|
|
62
62
|
cohub models ls --model-type multimodal
|
|
@@ -84,8 +84,9 @@ registerReferences(program);
|
|
|
84
84
|
registerReferrals(program);
|
|
85
85
|
registerTasks(program);
|
|
86
86
|
registerCronJobs(program);
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
registerApps(program);
|
|
88
|
+
registerDesktop(program);
|
|
89
|
+
registerLegacyUi(program);
|
|
89
90
|
const argv = process.argv.slice(2);
|
|
90
91
|
if (await maybeHandleRunCommand(argv)) {
|
|
91
92
|
process.exit();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"commander": "^15.0.0",
|
|
20
20
|
"pixi.js": "^8.19.0",
|
|
21
21
|
"sharp": "^0.35.3",
|
|
22
|
-
"@neta-art/cohub": "
|
|
22
|
+
"@neta-art/cohub": "8.0.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
package/dist/commands/ui.d.ts
DELETED