@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 CHANGED
@@ -157,72 +157,44 @@ cohub -s <spaceId> boards capabilities <boardId>
157
157
  cohub -s <spaceId> boards watch <boardId> --json
158
158
  ```
159
159
 
160
- Pass nodes, effects, and sequences as JSON when creating a Board. The path and
161
- title stay explicit in the command. Inspect `boards capabilities --json` for the
162
- supported node types, enums, and coordinate spaces.
163
-
164
- ```json
165
- {
166
- "nodes": [
167
- {
168
- "nodeId": "goal",
169
- "type": "geo",
170
- "parentId": null,
171
- "orderKey": null,
172
- "x": 80,
173
- "y": 80,
174
- "width": 240,
175
- "height": 120,
176
- "rotation": 0,
177
- "refKind": null,
178
- "refPath": null,
179
- "refUrl": null,
180
- "view": {},
181
- "style": {},
182
- "data": {
183
- "geo": "rectangle",
184
- "text": "Ship",
185
- "color": "green",
186
- "fillOpacity": 0.12
187
- }
188
- }
189
- ]
190
- }
160
+ Pass semantic items, effects, and compositions as JSON when creating a Board. The path and title stay explicit in the command. Generate editable templates instead of guessing fields:
161
+
162
+ ```bash
163
+ cohub boards examples create > board-content.json
164
+ cohub boards examples item geo > item.json
165
+ cohub boards examples effect pulse > effect.json
166
+ cohub boards examples composition fade > intro.json
191
167
  ```
192
168
 
169
+ Inspect `boards capabilities --json` for supported Item types, animation channels, effect kinds, and coordinate spaces.
170
+
193
171
  ```bash
194
172
  cohub -s <spaceId> boards create boards/plan.board \
195
173
  --title "Plan" \
196
174
  --input board-content.json
197
175
  ```
198
176
 
199
- Transactions are JSON objects without `boardId`; the bound Board supplies it.
200
- `txId` is generated when omitted, while `baseVersion` must be provided in the
201
- input or with `--base-version`:
202
-
203
- ```json
204
- {
205
- "baseVersion": 3,
206
- "operations": [
207
- {
208
- "type": "board.patch",
209
- "payload": { "patch": { "title": "Updated plan" } }
210
- }
211
- ]
212
- }
177
+ Generate editable semantic JSON templates instead of authoring storage transactions:
178
+
179
+ ```bash
180
+ cohub boards examples item text > item.json
181
+ cohub boards items create <boardId> --input item.json
182
+
183
+ cohub boards examples composition fade > intro.json
184
+ cohub boards compositions apply <boardId> --input intro.json
185
+
186
+ cohub boards examples effect pulse > effect.json
187
+ cohub boards effects apply <boardId> --input effect.json
213
188
  ```
214
189
 
190
+ Use `--mutation-id` or `--command-id` when a script needs a stable idempotency key across retries.
191
+
215
192
  ```bash
216
- cohub -s <spaceId> boards validate <boardId> --input transaction.json
217
- cat transaction.json | cohub -s <spaceId> boards apply <boardId> --input - --json
218
- cohub -s <spaceId> boards play <boardId> <sequenceId>
193
+ cohub -s <spaceId> boards play <boardId> <compositionId>
219
194
  cohub -s <spaceId> boards seek <boardId> <playbackId> 400
220
195
  cohub -s <spaceId> boards stop <boardId> <playbackId>
221
196
  ```
222
197
 
223
- Pass `--tx-id` or `--command-id` when a script needs a stable idempotency key
224
- across retries.
225
-
226
198
  ## Search
227
199
 
228
200
  Search Spaces, Chats, and prior turns:
@@ -0,0 +1,11 @@
1
+ import type { AppGetResponse } from "@neta-art/cohub";
2
+ type DownloadResult = {
3
+ appId: string;
4
+ version: number;
5
+ kind: "file" | "directory";
6
+ output: string;
7
+ files: number;
8
+ bytes: number;
9
+ };
10
+ export declare function downloadApp(detail: AppGetResponse, outputOption?: string, fetcher?: typeof fetch): Promise<DownloadResult>;
11
+ export {};
@@ -9,11 +9,11 @@ const MAX_CONTENT_FILE_BYTES = 1024 * 1024 * 1024;
9
9
  const DOWNLOAD_CONCURRENCY = 4;
10
10
  function safeRelativePath(value, label) {
11
11
  if (!value || value.includes("\\") || value.includes("\0") || posix.isAbsolute(value)) {
12
- throw new Error(`Invalid ${label} in Work manifest`);
12
+ throw new Error(`Invalid ${label} in app manifest`);
13
13
  }
14
14
  const segments = value.split("/");
15
15
  if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
16
- throw new Error(`Invalid ${label} in Work manifest`);
16
+ throw new Error(`Invalid ${label} in app manifest`);
17
17
  }
18
18
  return posix.normalize(value);
19
19
  }
@@ -31,7 +31,7 @@ function isManifestFile(value) {
31
31
  }
32
32
  function parseManifest(value) {
33
33
  if (!value || typeof value !== "object")
34
- throw new Error("Work download manifest is invalid");
34
+ throw new Error("App download manifest is invalid");
35
35
  const manifest = value;
36
36
  if (manifest.kind !== "cohub.work.artifact-manifest"
37
37
  || manifest.version !== 1
@@ -45,7 +45,7 @@ function parseManifest(value) {
45
45
  || !Array.isArray(manifest.files)
46
46
  || !manifest.files.every(isManifestFile)
47
47
  || manifest.files.length !== manifest.fileCount) {
48
- throw new Error("Work download manifest is invalid");
48
+ throw new Error("App download manifest is invalid");
49
49
  }
50
50
  const seenArtifactPaths = new Set();
51
51
  const seenOutputPaths = new Set();
@@ -54,26 +54,26 @@ function parseManifest(value) {
54
54
  file.artifactPath = safeRelativePath(file.artifactPath, "artifact path");
55
55
  file.outputPath = safeRelativePath(file.outputPath, "output path");
56
56
  if (seenArtifactPaths.has(file.artifactPath) || seenOutputPaths.has(file.outputPath)) {
57
- throw new Error("Work download manifest contains duplicate paths");
57
+ throw new Error("App download manifest contains duplicate paths");
58
58
  }
59
59
  seenArtifactPaths.add(file.artifactPath);
60
60
  seenOutputPaths.add(file.outputPath);
61
61
  sizeBytes += file.sizeBytes;
62
62
  }
63
63
  if (sizeBytes !== manifest.sizeBytes)
64
- throw new Error("Work download manifest size is invalid");
64
+ throw new Error("App download manifest size is invalid");
65
65
  safeRelativePath(manifest.entrypoint, "entrypoint");
66
66
  return manifest;
67
67
  }
68
68
  async function readManifest(url, expectedSha256, fetcher) {
69
69
  const response = await fetcher(url);
70
70
  if (!response.ok)
71
- throw new Error(`Failed to download Work manifest (${response.status})`);
71
+ throw new Error(`Failed to download app manifest (${response.status})`);
72
72
  if (!response.body)
73
- throw new Error("Work download manifest is invalid");
73
+ throw new Error("App download manifest is invalid");
74
74
  const contentLength = Number(response.headers.get("content-length") ?? 0);
75
75
  if (Number.isFinite(contentLength) && contentLength > MANIFEST_MAX_BYTES) {
76
- throw new Error("Work download manifest is too large");
76
+ throw new Error("App download manifest is too large");
77
77
  }
78
78
  const chunks = [];
79
79
  let sizeBytes = 0;
@@ -81,19 +81,19 @@ async function readManifest(url, expectedSha256, fetcher) {
81
81
  const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
82
82
  sizeBytes += bytes.byteLength;
83
83
  if (sizeBytes > MANIFEST_MAX_BYTES)
84
- throw new Error("Work download manifest is too large");
84
+ throw new Error("App download manifest is too large");
85
85
  chunks.push(bytes);
86
86
  }
87
87
  const bytes = Buffer.concat(chunks, sizeBytes);
88
88
  const sha256 = createHash("sha256").update(bytes).digest("hex");
89
89
  if (sha256 !== expectedSha256)
90
- throw new Error("Work download manifest checksum mismatch");
90
+ throw new Error("App download manifest checksum mismatch");
91
91
  let parsed;
92
92
  try {
93
93
  parsed = JSON.parse(bytes.toString("utf8"));
94
94
  }
95
95
  catch {
96
- throw new Error("Work download manifest is invalid");
96
+ throw new Error("App download manifest is invalid");
97
97
  }
98
98
  return parseManifest(parsed);
99
99
  }
@@ -203,25 +203,25 @@ async function downloadFiles(input) {
203
203
  throw failure;
204
204
  return downloadedBytes.reduce((sum, size) => sum + size, 0);
205
205
  }
206
- export async function downloadWork(detail, outputOption, fetcher = fetch) {
207
- const { work, content } = detail;
206
+ export async function downloadApp(detail, outputOption, fetcher = fetch) {
207
+ const { app, content } = detail;
208
208
  if (!content)
209
- throw new Error("This Work has no published downloadable artifact");
209
+ throw new Error("This app has no published downloadable artifact");
210
210
  if (content.kind === "port")
211
- throw new Error("Port Works do not have a downloadable artifact");
211
+ throw new Error("Port apps do not have a downloadable artifact");
212
212
  if (content.kind === "board")
213
- throw new Error("Board Works do not have a restorable file or directory artifact");
213
+ throw new Error("Board apps do not have a restorable file or directory artifact");
214
214
  if (!content.download)
215
- throw new Error("This Work version does not support download");
215
+ throw new Error("This app version does not support download");
216
216
  const manifest = await readManifest(content.download.manifestUrl, content.download.manifestSha256, fetcher);
217
217
  if (manifest.targetType !== content.targetType || manifest.targetRef !== content.path) {
218
- throw new Error("Work download manifest does not match the published artifact");
218
+ throw new Error("App download manifest does not match the published artifact");
219
219
  }
220
220
  const entry = manifest.files.find((file) => file.artifactPath === manifest.entrypoint);
221
221
  if (!entry)
222
- throw new Error("Work download manifest entrypoint is missing");
222
+ throw new Error("App download manifest entrypoint is missing");
223
223
  const hasDirectoryOutput = manifest.targetType === "directory" || manifest.files.length > 1;
224
- const output = resolve(outputOption ?? (hasDirectoryOutput ? work.slug : basename(manifest.targetRef)));
224
+ const output = resolve(outputOption ?? (hasDirectoryOutput ? app.slug : basename(manifest.targetRef)));
225
225
  await assertOutputMissing(output);
226
226
  await mkdir(dirname(output), { recursive: true });
227
227
  const stage = await mkdtemp(join(dirname(output), `.${basename(output)}.cohub-download-`));
@@ -237,8 +237,8 @@ export async function downloadWork(detail, outputOption, fetcher = fetch) {
237
237
  await rm(stage, { recursive: true, force: true });
238
238
  }
239
239
  return {
240
- workId: work.id,
241
- version: work.latestVersion,
240
+ appId: app.id,
241
+ version: app.latestVersion,
242
242
  kind: hasDirectoryOutput ? "directory" : "file",
243
243
  output,
244
244
  files: files.length,
@@ -0,0 +1,5 @@
1
+ import type { CohubHttpClient, ParsedAppRef, AppGetResponse } from "@neta-art/cohub";
2
+ import { formatAppRef, parseAppRef } from "@neta-art/cohub";
3
+ export type { ParsedAppRef };
4
+ export { formatAppRef, parseAppRef };
5
+ export declare function getAppByRef(client: CohubHttpClient, input: string): Promise<AppGetResponse>;
@@ -0,0 +1,8 @@
1
+ import { formatAppRef, parseAppRef } from "@neta-art/cohub";
2
+ export { formatAppRef, parseAppRef };
3
+ export function getAppByRef(client, input) {
4
+ const ref = parseAppRef(input);
5
+ return "id" in ref
6
+ ? client.apps.get(ref.id)
7
+ : client.apps.getBySlug(ref.username, ref.spaceSlug, ref.appSlug);
8
+ }
@@ -9,7 +9,7 @@
9
9
  import { existsSync } from "node:fs";
10
10
  import { createRequire } from "node:module";
11
11
  import { dirname, join } from "node:path";
12
- import { boardBootstrapToDocument, boardImageKeySource, imageAssetKey, planBoardExport, selectBoardExportAssets, } from "@neta-art/cohub/board";
12
+ import { boardAuthoringSnapshotToDocument, boardImageKeySource, imageAssetKey, planBoardExport, selectBoardExportAssets, } from "@neta-art/cohub/board";
13
13
  import { createBoardHeadlessRenderer, exportBoardImageBytes, } from "@neta-art/cohub/board/headless";
14
14
  import { resolveBoardId } from "./board-command-support.js";
15
15
  import { createClient } from "./client.js";
@@ -64,11 +64,11 @@ export function resolveBundledFonts() {
64
64
  export async function loadBoardDocument(spaceId, target) {
65
65
  const client = createClient();
66
66
  const boardId = await resolveBoardId(spaceId, target);
67
- const bootstrap = await client.space(spaceId).board(boardId).inspect({ include: ["nodes"] });
67
+ const snapshot = await client.space(spaceId).board(boardId).authoring({ include: ["items", "connections"] });
68
68
  return {
69
- document: boardBootstrapToDocument(bootstrap),
70
- boardId: bootstrap.board.id,
71
- title: bootstrap.board.title ?? null,
69
+ document: boardAuthoringSnapshotToDocument(snapshot),
70
+ boardId: snapshot.board.id,
71
+ title: snapshot.board.title ?? null,
72
72
  };
73
73
  }
74
74
  /**
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerAppCommerce(appsCmd: Command): void;
@@ -69,31 +69,31 @@ function printOrder(order) {
69
69
  { key: "paidAt", label: "Paid At" },
70
70
  ]);
71
71
  }
72
- export function registerWorkCommerce(worksCmd) {
73
- const commerceCmd = worksCmd
72
+ export function registerAppCommerce(appsCmd) {
73
+ const commerceCmd = appsCmd
74
74
  .command("commerce")
75
- .description("Work commerce operations")
75
+ .description("App commerce operations")
76
76
  .addHelpText("after", `
77
77
  Examples:
78
- cohub works commerce products resolve --work-id <work-id> --product-key pro_pack
79
- cohub works commerce entitlements --work-id <work-id>
80
- cohub works commerce credits consume --work-id <work-id> --amount 100
78
+ cohub apps commerce products resolve --app-id <app-id> --product-key pro_pack
79
+ cohub apps commerce entitlements --app-id <app-id>
80
+ cohub apps commerce credits consume --app-id <app-id> --amount 100
81
81
  `);
82
82
  const productsCmd = commerceCmd
83
83
  .command("products")
84
84
  .description("Resolve commerce products");
85
85
  productsCmd
86
86
  .command("resolve")
87
- .description("Resolve public products for a work")
88
- .option("--work-id <id>", "Work ID")
87
+ .description("Resolve public products for an app")
88
+ .option("--app-id <id>", "App ID")
89
89
  .option("--product-key <key>", "Product key", collectOption)
90
90
  .option("--json", "Output as JSON")
91
91
  .action(async (opts) => {
92
- const workId = requireText(opts.workId, "work ID", "--work-id <id>");
92
+ const appId = requireText(opts.appId, "app ID", "--app-id <id>");
93
93
  const productKeys = requireList(opts.productKey, "product key", "--product-key <key>");
94
94
  const client = createClient();
95
95
  try {
96
- const result = await client.workCommerce.resolveProducts(workId, { productKeys });
96
+ const result = await client.appCommerce.resolveProducts(appId, { productKeys });
97
97
  if (jsonRequested(opts))
98
98
  return outJson(result);
99
99
  printProducts(result.products);
@@ -104,14 +104,14 @@ Examples:
104
104
  });
105
105
  commerceCmd
106
106
  .command("entitlements")
107
- .description("Show viewer entitlements and credit balance for a work")
108
- .option("--work-id <id>", "Work ID")
107
+ .description("Show viewer entitlements and credit balance for an app")
108
+ .option("--app-id <id>", "App ID")
109
109
  .option("--json", "Output as JSON")
110
110
  .action(async (opts) => {
111
- const workId = requireText(opts.workId, "work ID", "--work-id <id>");
111
+ const appId = requireText(opts.appId, "app ID", "--app-id <id>");
112
112
  const client = createClient();
113
113
  try {
114
- const result = await client.workCommerce.getEntitlements(workId);
114
+ const result = await client.appCommerce.getEntitlements(appId);
115
115
  if (jsonRequested(opts))
116
116
  return outJson(result);
117
117
  printEntitlements(result);
@@ -122,17 +122,17 @@ Examples:
122
122
  });
123
123
  const creditsCmd = commerceCmd
124
124
  .command("credits")
125
- .description("Consume credits for a work");
125
+ .description("Consume credits for an app");
126
126
  creditsCmd
127
127
  .command("consume")
128
- .description("Consume credits for a work (self by default)")
129
- .option("--work-id <id>", "Work ID")
128
+ .description("Consume credits for an app (self by default)")
129
+ .option("--app-id <id>", "App ID")
130
130
  .option("--amount <n>", "Positive integer credit amount")
131
131
  .option("--operation-id <id>", "Idempotency key (generated when omitted)")
132
132
  .option("--reason <text>", "Reason for the consumption")
133
133
  .option("--json", "Output as JSON")
134
134
  .action(async (opts) => {
135
- const workId = requireText(opts.workId, "work ID", "--work-id <id>");
135
+ const appId = requireText(opts.appId, "app ID", "--app-id <id>");
136
136
  const amountText = requireText(opts.amount, "amount", "--amount <n>");
137
137
  const amount = Number.parseInt(amountText, 10);
138
138
  if (!Number.isSafeInteger(amount) || amount <= 0) {
@@ -141,7 +141,7 @@ Examples:
141
141
  const operationId = opts.operationId?.trim() || crypto.randomUUID();
142
142
  const client = createClient();
143
143
  try {
144
- const result = await client.workCommerce.consumeCredits(workId, {
144
+ const result = await client.appCommerce.consumeCredits(appId, {
145
145
  amount,
146
146
  operationId,
147
147
  reason: opts.reason,
@@ -167,16 +167,16 @@ Examples:
167
167
  });
168
168
  commerceCmd
169
169
  .command("purchase")
170
- .description("Create a work purchase checkout")
171
- .option("--work-id <id>", "Work ID")
170
+ .description("Create an app purchase checkout")
171
+ .option("--app-id <id>", "App ID")
172
172
  .option("--product-key <key>", "Product key")
173
173
  .option("--json", "Output as JSON")
174
174
  .action(async (opts) => {
175
- const workId = requireText(opts.workId, "work ID", "--work-id <id>");
175
+ const appId = requireText(opts.appId, "app ID", "--app-id <id>");
176
176
  const productKey = requireText(opts.productKey, "product key", "--product-key <key>");
177
177
  const client = createClient();
178
178
  try {
179
- const result = await client.workCommerce.purchase(workId, { productKey });
179
+ const result = await client.appCommerce.purchase(appId, { productKey });
180
180
  if (jsonRequested(opts))
181
181
  return outJson(result);
182
182
  ok(`Checkout created: ${result.checkout.orderId}`);
@@ -205,16 +205,16 @@ Examples:
205
205
  .description("Look up commerce orders");
206
206
  ordersCmd
207
207
  .command("get")
208
- .description("Show a work commerce order")
209
- .option("--work-id <id>", "Work ID")
208
+ .description("Show an app commerce order")
209
+ .option("--app-id <id>", "App ID")
210
210
  .option("--order-id <id>", "Order ID")
211
211
  .option("--json", "Output as JSON")
212
212
  .action(async (opts) => {
213
- const workId = requireText(opts.workId, "work ID", "--work-id <id>");
213
+ const appId = requireText(opts.appId, "app ID", "--app-id <id>");
214
214
  const orderId = requireText(opts.orderId, "order ID", "--order-id <id>");
215
215
  const client = createClient();
216
216
  try {
217
- const result = await client.workCommerce.getOrder(workId, orderId);
217
+ const result = await client.appCommerce.getOrder(appId, orderId);
218
218
  if (jsonRequested(opts))
219
219
  return outJson(result);
220
220
  printOrder(result.order);
@@ -0,0 +1,4 @@
1
+ import { type CohubHttpClient, type AppViewStatsResponse } from "@neta-art/cohub";
2
+ import type { Command } from "commander";
3
+ export declare function getAppStatsByRef(client: CohubHttpClient, app: string): Promise<AppViewStatsResponse>;
4
+ export declare function registerApps(program: Command): void;