@neta-art/cohub-cli 3.12.0 → 5.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.
@@ -1,15 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { BOARD_CREATE_INPUT_MAX_BYTES, BOARD_TRANSACTION_INPUT_MAX_BYTES, parseBoardJsonObject, readBoardJsonObject, resolveBoardId, writeBoardOutput, } from "../board-command-support.js";
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", "sequences", "clips", "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,47 +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
- export function createTransactionInput(input, options) {
55
- if ("boardId" in input)
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
- sequences: result.sequences.length,
84
- clips: result.clips.length,
85
- },
86
- ], [
41
+ function showCreated(result) {
42
+ table([result.board], [
87
43
  { key: "id", label: "ID" },
88
44
  { key: "title", label: "Title" },
89
45
  { key: "version", label: "Version" },
90
- { key: "nodes", label: "Nodes" },
91
- { key: "connections", label: "Connections" },
92
- { key: "effects", label: "Effects" },
93
- { key: "sequences", label: "Sequences" },
94
- { key: "clips", label: "Clips" },
95
46
  ]);
96
47
  }
97
48
  function showSummary(result) {
@@ -107,35 +58,18 @@ function showSummary(result) {
107
58
  { key: "id", label: "ID" },
108
59
  { key: "title", label: "TITLE" },
109
60
  { key: "version", label: "VERSION" },
110
- { key: "nodes", label: "NODES" },
61
+ { key: "items", label: "ITEMS" },
111
62
  { key: "connections", label: "CONNECTIONS" },
112
63
  { key: "effects", label: "EFFECTS" },
113
- { key: "sequences", label: "SEQUENCES" },
64
+ { key: "compositions", label: "COMPOSITIONS" },
114
65
  { key: "background", label: "BACKGROUND" },
115
66
  { key: "updatedAt", label: "UPDATED" },
116
67
  ]);
117
68
  }
118
- function showValidation(result) {
119
- table([{ valid: result.valid, diagnostics: result.diagnostics.length }], [
120
- { key: "valid", label: "Valid" },
121
- { key: "diagnostics", label: "Diagnostics" },
122
- ]);
123
- if (result.diagnostics.length > 0) {
124
- console.log();
125
- table(result.diagnostics, [
126
- { key: "severity", label: "Severity" },
127
- { key: "code", label: "Code" },
128
- { key: "path", label: "Path" },
129
- { key: "message", label: "Message" },
130
- ]);
131
- }
132
- console.log();
133
- table([result.peakCost], Object.keys(result.peakCost).map((key) => ({ key, label: key })));
134
- }
135
69
  function showPlayback(result) {
136
70
  table([result], [
137
71
  { key: "playbackId", label: "Playback ID" },
138
- { key: "sequenceId", label: "Sequence" },
72
+ { key: "compositionId", label: "Composition" },
139
73
  { key: "status", label: "Status" },
140
74
  { key: "position", label: "Position" },
141
75
  { key: "timeScale", label: "Time Scale" },
@@ -158,38 +92,6 @@ function capabilityUnits(schema) {
158
92
  return detail ? [`${field}:${detail}`] : [];
159
93
  }).join(", ");
160
94
  }
161
- function registerTransactionCommand(boards, name) {
162
- withJson(boards.command(`${name} <board>`)
163
- .description(name === "validate" ? "Validate a transaction" : "Apply a transaction")
164
- .requiredOption("-i, --input <file>", "Transaction JSON file; use - for stdin")
165
- .option("--tx-id <id>", "Override txId; generated when omitted")
166
- .option("--base-version <version>", "Override baseVersion")
167
- .addHelpText("after", `
168
- Input example:
169
- {"baseVersion":12,"operations":[{"type":"board.patch","payload":{"patch":{"title":"Launch plan"}}}]}
170
-
171
- Prefer semantic commands such as boards background, nodes, effects, or sequences for common edits.`))
172
- .action(async (target, options) => {
173
- try {
174
- const transaction = createTransactionInput(await readJsonObject(options.input, BOARD_TRANSACTION_INPUT_MAX_BYTES), options);
175
- const spaceId = resolveSpace(boards);
176
- const boardId = await resolveBoardId(spaceId, target);
177
- const board = createClient().space(spaceId).board(boardId);
178
- const result = await board[name](transaction);
179
- if (jsonRequested(options))
180
- return outJson(result);
181
- if (name === "validate")
182
- showValidation(result);
183
- else {
184
- ok(`Board updated to version ${result.board.version}`);
185
- showBoard(result);
186
- }
187
- }
188
- catch (cause) {
189
- handleHttp(cause);
190
- }
191
- });
192
- }
193
95
  function commandId(options) {
194
96
  return options.commandId?.trim() || randomUUID();
195
97
  }
@@ -315,14 +217,16 @@ export function registerBoards(program) {
315
217
  .description("Create a Board")
316
218
  .option("--title <title>", "Board title")
317
219
  .option("--mutation-id <id>", "Stable id for safe retries")
318
- .option("-i, --input <file>", "BoardCreateInput fields; use - for stdin")
220
+ .option("-i, --input <file>", "Semantic Board seed JSON; use - for stdin")
319
221
  .addHelpText("after", `
320
- For normal use, create an empty Board and add content with boards nodes, effects, and sequences.
321
- --input is intended for bulk creation and accepts BoardCreateInput fields except path and title.`))
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`))
322
226
  .action(async (path, options) => {
323
227
  try {
324
228
  const content = options.input
325
- ? await readJsonObject(options.input, BOARD_CREATE_INPUT_MAX_BYTES)
229
+ ? await readBoardJsonObject(options.input, BOARD_CREATE_INPUT_MAX_BYTES)
326
230
  : {};
327
231
  if ("path" in content || "title" in content) {
328
232
  throw new Error("create input must not contain path or title; use the command argument and --title");
@@ -338,7 +242,7 @@ For normal use, create an empty Board and add content with boards nodes, effects
338
242
  if (jsonRequested(options))
339
243
  return outJson(result);
340
244
  ok(`Board created: ${result.board.id}`);
341
- showBoard(result);
245
+ showCreated(result);
342
246
  }
343
247
  catch (cause) {
344
248
  handleHttp(cause);
@@ -346,31 +250,30 @@ For normal use, create an empty Board and add content with boards nodes, effects
346
250
  });
347
251
  withJson(boards.command("inspect <board>")
348
252
  .alias("get")
349
- .description("Inspect a Board")
350
- .option("--include <sections>", "Comma-separated nodes,connections,effects,sequences,clips,playback")
351
- .option("--viewport <rect>", "Viewport as x,y,width,height"))
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`))
352
259
  .action(async (target, options) => {
353
260
  try {
354
261
  const spaceId = resolveSpace(boards);
355
262
  const boardId = await resolveBoardId(spaceId, target);
356
- const board = createClient().space(spaceId).board(boardId);
357
- if (!jsonRequested(options) && !options.include && !options.viewport) {
358
- return showSummary(await board.summary());
359
- }
360
- const result = await board.inspect({
361
- include: parseInspectSections(options.include),
362
- viewport: parseViewport(options.viewport),
363
- });
263
+ const result = await createClient().space(spaceId).board(boardId).summary();
364
264
  if (jsonRequested(options))
365
265
  return outJson(result);
366
- showBoard(result);
266
+ showSummary(result);
367
267
  }
368
268
  catch (cause) {
369
269
  handleHttp(cause);
370
270
  }
371
271
  });
372
272
  withJson(boards.command("capabilities <board>")
373
- .description("Show supported capabilities"))
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.`))
374
277
  .action(async (target, options) => {
375
278
  try {
376
279
  const spaceId = resolveSpace(boards);
@@ -390,46 +293,41 @@ For normal use, create an empty Board and add content with boards nodes, effects
390
293
  { key: "coordinates", label: "Coordinates / units" },
391
294
  { key: "digest", label: "Digest" },
392
295
  ]);
393
- const nodes = result.nodes;
394
- if (nodes) {
395
- console.log();
396
- table([{
397
- types: nodes.types.join(", "),
398
- colors: nodes.colors.join(", "),
399
- geos: nodes.geos.join(", "),
400
- drawPoints: nodes.coordinates.drawPoints,
401
- arrowEndpoints: nodes.coordinates.arrowEndpoints,
402
- }], [
403
- { key: "types", label: "Node types" },
404
- { key: "colors", label: "Colors" },
405
- { key: "geos", label: "Geo kinds" },
406
- { key: "drawPoints", label: "Draw points" },
407
- { key: "arrowEndpoints", label: "Arrow endpoints" },
408
- ]);
409
- }
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
+ ]);
410
310
  }
411
311
  catch (cause) {
412
312
  handleHttp(cause);
413
313
  }
414
314
  });
415
- registerTransactionCommand(boards, "validate");
416
- registerTransactionCommand(boards, "apply");
417
315
  registerBoardDomainCommands(boards);
418
316
  registerExportCommand(boards);
419
- withJson(boards.command("play <board> <sequence-id>")
317
+ withJson(boards.command("play <board> <composition-id>")
420
318
  .description("Start shared playback")
421
319
  .option("--position <time>", "Initial position in milliseconds")
422
320
  .option("--time-scale <scale>", "Playback speed from 0 to 4")
423
321
  .option("--seed <seed>", "Deterministic playback seed")
424
322
  .option("--command-id <id>", "Idempotency command ID"))
425
- .action(async (target, sequenceId, options) => {
323
+ .action(async (target, compositionId, options) => {
426
324
  try {
427
325
  const spaceId = resolveSpace(boards);
428
326
  const boardId = await resolveBoardId(spaceId, target);
429
327
  const result = await createClient().space(spaceId).board(boardId).play({
430
328
  commandId: commandId(options),
431
329
  type: "play",
432
- sequenceId,
330
+ compositionId,
433
331
  shared: true,
434
332
  ...(options.position === undefined ? {} : { position: parseNumber(options.position, "position", { min: 0 }) }),
435
333
  ...(options.timeScale === undefined ? {} : { timeScale: parseNumber(options.timeScale, "timeScale", { min: Number.EPSILON, max: 4 }) }),
@@ -516,13 +414,13 @@ For normal use, create an empty Board and add content with boards nodes, effects
516
414
  process.stdout.write(`${JSON.stringify(event)}\n`);
517
415
  return;
518
416
  }
519
- if (event.type === "board.transaction.applied") {
520
- process.stdout.write(`version ${event.payload.version} transaction ${event.payload.txId} operations ${event.payload.operations.length}\n`);
417
+ if (event.type === "board.changed") {
418
+ process.stdout.write(`version ${event.payload.version} mutation ${event.payload.mutationId}\n`);
521
419
  }
522
420
  else if (event.type === "board.playback.changed") {
523
- process.stdout.write(`${event.payload.status} sequence ${event.payload.sequenceId} position ${event.payload.position}\n`);
421
+ process.stdout.write(`${event.payload.status} composition ${event.payload.compositionId} position ${event.payload.position}\n`);
524
422
  }
525
- else {
423
+ else if (event.type === "board.awareness.updated") {
526
424
  process.stdout.write(`awareness ${event.payload.actorName} ${event.payload.update.type}\n`);
527
425
  }
528
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", { hidden: true }).description("Scheduled jobs");
12
+ const cmd = program.command("cron-jobs").description("Scheduled jobs");
13
13
  cmd
14
14
  .command("ls [spaceId]")
15
15
  .alias("list")
@@ -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", { hidden: true }).description("Task runs");
5
+ const cmd = program.command("tasks").description("Task runs");
6
6
  cmd
7
7
  .command("ls")
8
8
  .alias("list")
@@ -259,7 +259,7 @@ export function registerWorks(program) {
259
259
  .option("--status <status>", "Work status: published, disabled")
260
260
  .option("--visibility <visibility>", "Work visibility: public, space")
261
261
  .option("--work-scope <scope>", "Scope granted to the work runtime (space.view, session.view, file.view, taskrun.view)", collectOption, [])
262
- .option("--viewer-scope <scope>", "Scope viewers may request (session.prompt.readonly, session.prompt.fullaccess, generation.create, user.space.list, user.session.list, user.usage.read)", collectOption, [])
262
+ .option("--viewer-scope <scope>", "Scope viewers may request (taskrun.view, session.prompt.readonly, session.prompt.fullaccess, generation.create, user.space.list, user.session.list, user.usage.read)", collectOption, [])
263
263
  .option("--meta <json>", "Work metadata as a JSON object")
264
264
  .option("--hide-cohub-bar", "Hide the Cohub footer bar on the public work page")
265
265
  .option("--show-cohub-bar", "Show the Cohub footer bar on the public work page")
@@ -338,7 +338,7 @@ export function registerWorks(program) {
338
338
  .option("--status <status>", "Work status: published, disabled")
339
339
  .option("--visibility <visibility>", "Work visibility: public, space")
340
340
  .option("--work-scope <scope>", "Scope granted to the work runtime (space.view, session.view, file.view, taskrun.view)", collectOption, [])
341
- .option("--viewer-scope <scope>", "Scope viewers may request (session.prompt.readonly, session.prompt.fullaccess, generation.create, user.space.list, user.session.list, user.usage.read)", collectOption, [])
341
+ .option("--viewer-scope <scope>", "Scope viewers may request (taskrun.view, session.prompt.readonly, session.prompt.fullaccess, generation.create, user.space.list, user.session.list, user.usage.read)", collectOption, [])
342
342
  .option("--clear-work-scopes", "Clear work runtime scopes")
343
343
  .option("--clear-viewer-scopes", "Clear viewer-requestable scopes")
344
344
  .option("--meta <json>", "Work metadata as a JSON object")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "3.12.0",
3
+ "version": "5.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": "5.10.0"
22
+ "@neta-art/cohub": "7.0.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"