@neta-art/cohub-cli 8.4.0 → 9.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 +25 -2
- package/dist/avatar.js +3 -1
- package/dist/board-export.d.ts +22 -41
- package/dist/board-export.js +178 -115
- package/dist/board-fonts.d.ts +7 -0
- package/dist/board-fonts.js +10 -0
- package/dist/board-kernel/browserAll.js +2760 -0
- package/dist/board-kernel/getPo2TextureFromSource.js +13138 -0
- package/dist/board-kernel/init.js +867 -0
- package/dist/board-kernel/webworkerAll.js +3 -0
- package/dist/board-kernel.d.ts +2 -0
- package/dist/board-kernel.js +28812 -0
- package/dist/board-time.d.ts +3 -0
- package/dist/board-time.js +28 -0
- package/dist/commands/apps.js +2 -2
- package/dist/commands/auth.js +2 -2
- package/dist/commands/boards/examples.d.ts +8 -2
- package/dist/commands/boards/examples.js +79 -411
- package/dist/commands/boards.d.ts +4 -9
- package/dist/commands/boards.js +356 -286
- package/dist/commands/desktop.d.ts +1 -5
- package/dist/commands/desktop.js +10 -19
- package/dist/commands/generations.js +1 -1
- package/dist/commands/me.js +1 -1
- package/dist/commands/public.js +3 -3
- package/dist/commands/references.js +3 -0
- package/dist/commands/referrals.js +2 -2
- package/dist/commands/run.js +3 -3
- package/dist/commands/runtime.d.ts +1 -1
- package/dist/commands/runtime.js +12 -8
- package/dist/commands/search.js +15 -7
- package/dist/commands/space-activity.js +3 -5
- package/dist/commands/space-displays.d.ts +2 -0
- package/dist/commands/space-displays.js +347 -0
- package/dist/commands/space-files.d.ts +5 -0
- package/dist/commands/space-files.js +215 -0
- package/dist/commands/space-invitations.js +3 -3
- package/dist/commands/spaces.d.ts +3 -11
- package/dist/commands/spaces.js +146 -75
- package/dist/fonts/geist-latin-400-normal.woff2 +0 -0
- package/dist/fonts/geist-latin-500-normal.woff2 +0 -0
- package/dist/fonts/geist-latin-600-normal.woff2 +0 -0
- package/dist/fonts/geist-latin-700-normal.woff2 +0 -0
- package/dist/fonts/geist-mono-latin-500-normal.woff2 +0 -0
- package/dist/fonts/geist-mono-latin-600-normal.woff2 +0 -0
- package/dist/fonts/geist-mono-latin-700-normal.woff2 +0 -0
- package/dist/index.js +7 -4
- package/dist/runtime/launch.d.ts +2 -0
- package/dist/runtime/launch.js +35 -8
- package/dist/runtime/presentation.d.ts +1 -0
- package/dist/runtime/presentation.js +3 -2
- package/dist/runtime/supervisor.d.ts +1 -0
- package/dist/runtime/supervisor.js +2 -1
- package/dist/self-update.d.ts +1 -0
- package/dist/self-update.js +52 -16
- package/dist/space-files/limit.d.ts +2 -0
- package/dist/space-files/limit.js +16 -0
- package/dist/space-files/operands.d.ts +16 -0
- package/dist/space-files/operands.js +33 -0
- package/dist/space-files/transfer.d.ts +53 -0
- package/dist/space-files/transfer.js +296 -0
- package/dist/space-files/upload.d.ts +14 -0
- package/dist/space-files/upload.js +53 -0
- package/dist/space.d.ts +18 -16
- package/dist/space.js +86 -39
- package/dist/web.d.ts +1 -0
- package/dist/web.js +6 -0
- package/package.json +8 -4
- package/dist/commands/board-domain.d.ts +0 -2
- package/dist/commands/board-domain.js +0 -16
- package/dist/commands/boards/animation.d.ts +0 -2
- package/dist/commands/boards/animation.js +0 -171
- package/dist/commands/boards/appearance.d.ts +0 -2
- package/dist/commands/boards/appearance.js +0 -127
- package/dist/commands/boards/batch.d.ts +0 -4
- package/dist/commands/boards/batch.js +0 -62
- package/dist/commands/boards/connections.d.ts +0 -2
- package/dist/commands/boards/connections.js +0 -95
- package/dist/commands/boards/context.d.ts +0 -31
- package/dist/commands/boards/context.js +0 -71
- package/dist/commands/boards/items.d.ts +0 -2
- package/dist/commands/boards/items.js +0 -184
- package/dist/commands/boards/transactions.d.ts +0 -12
- package/dist/commands/boards/transactions.js +0 -69
package/dist/space.js
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { resolveCohubEnvironment } from "@neta-art/cohub";
|
|
4
|
+
import { HOME_SPACE_SLUG, parseSpaceRef, resolveCohubEnvironment } from "@neta-art/cohub";
|
|
5
5
|
import { readAuthSession } from "./auth.js";
|
|
6
6
|
import { createClient } from "./client.js";
|
|
7
7
|
import { getRuntimeSpaceBinding } from "./runtime/space-binding.js";
|
|
8
8
|
import { error, handleHttp } from "./output.js";
|
|
9
9
|
const CONFIG_DIR = join(homedir(), ".config", "cohub");
|
|
10
|
-
const CACHE_PATH = join(CONFIG_DIR, "
|
|
10
|
+
const CACHE_PATH = join(CONFIG_DIR, "home-space.json");
|
|
11
|
+
/** Legacy cache that may name another user's Home; never read. */
|
|
12
|
+
const LEGACY_CACHE_PATH = join(CONFIG_DIR, "default-space.json");
|
|
11
13
|
/** Home space is stable; a one-day TTL bounds how long a stale hit survives. */
|
|
12
14
|
const CACHE_TTL_MS = 86_400_000;
|
|
15
|
+
const SPACE_REF_HINT = "Use a Space ID, a slug you own such as home, or username/slug.";
|
|
16
|
+
export class SpaceTargetError extends Error {
|
|
17
|
+
detail;
|
|
18
|
+
name = "SpaceTargetError";
|
|
19
|
+
constructor(message, detail) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.detail = detail;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
13
24
|
function jwtClaim(token, key) {
|
|
14
25
|
const payload = token?.split(".")[1];
|
|
15
26
|
if (!payload)
|
|
@@ -46,7 +57,7 @@ export function currentIdentityKey() {
|
|
|
46
57
|
});
|
|
47
58
|
}
|
|
48
59
|
/** Exported for tests; production always uses `CACHE_PATH`. */
|
|
49
|
-
export function
|
|
60
|
+
export function readHomeSpaceCache(path, key, now = Date.now()) {
|
|
50
61
|
try {
|
|
51
62
|
const cache = JSON.parse(readFileSync(path, "utf-8"));
|
|
52
63
|
if (cache.key !== key || typeof cache.spaceId !== "string" || typeof cache.cachedAt !== "number")
|
|
@@ -59,21 +70,25 @@ export function readDefaultSpaceCache(path, key, now = Date.now()) {
|
|
|
59
70
|
return null;
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
|
-
function
|
|
73
|
+
function writeHomeSpaceCache(key, spaceId) {
|
|
63
74
|
try {
|
|
64
75
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
65
76
|
const cache = { key, spaceId, cachedAt: Date.now() };
|
|
66
77
|
writeFileSync(CACHE_PATH, `${JSON.stringify(cache, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
78
|
+
rmSync(LEGACY_CACHE_PATH, { force: true });
|
|
67
79
|
}
|
|
68
80
|
catch {
|
|
69
81
|
// Cache is best-effort; never fail the command over it.
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
let homeSpacePromise = null;
|
|
85
|
+
const spaceRefs = new Map();
|
|
86
|
+
export function clearHomeSpaceCache() {
|
|
87
|
+
homeSpacePromise = null;
|
|
88
|
+
spaceRefs.clear();
|
|
75
89
|
try {
|
|
76
90
|
rmSync(CACHE_PATH, { force: true });
|
|
91
|
+
rmSync(LEGACY_CACHE_PATH, { force: true });
|
|
77
92
|
}
|
|
78
93
|
catch {
|
|
79
94
|
// Best-effort, same as writes.
|
|
@@ -90,48 +105,80 @@ export function explicitSpace(program) {
|
|
|
90
105
|
}
|
|
91
106
|
return process.env.COHUB_SPACE_ID?.trim() || null;
|
|
92
107
|
}
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* in-process so preAction hooks and actions share a single lookup.
|
|
97
|
-
* Network and auth failures propagate so callers can report them faithfully.
|
|
98
|
-
*/
|
|
99
|
-
export function resolveDefaultSpace() {
|
|
100
|
-
defaultSpacePromise ??= (async () => {
|
|
108
|
+
/** Cached per identity and memoized per process. */
|
|
109
|
+
export function resolveHomeSpace() {
|
|
110
|
+
homeSpacePromise ??= (async () => {
|
|
101
111
|
const key = currentIdentityKey();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (space?.id && space.slug === "home" && key)
|
|
110
|
-
writeCachedDefaultSpace(key, space.id);
|
|
111
|
-
return space?.id ?? null;
|
|
112
|
+
const cached = key ? readHomeSpaceCache(CACHE_PATH, key) : null;
|
|
113
|
+
if (cached)
|
|
114
|
+
return cached;
|
|
115
|
+
const space = await createClient().spaces.ensureHome();
|
|
116
|
+
if (key)
|
|
117
|
+
writeHomeSpaceCache(key, space.id);
|
|
118
|
+
return space.id;
|
|
112
119
|
})();
|
|
113
|
-
return
|
|
120
|
+
return homeSpacePromise;
|
|
121
|
+
}
|
|
122
|
+
async function lookupSpaceRef(value) {
|
|
123
|
+
const ref = parseSpaceRef(value);
|
|
124
|
+
if (!ref)
|
|
125
|
+
throw new SpaceTargetError(`Invalid space '${value}'`, SPACE_REF_HINT);
|
|
126
|
+
if (ref.kind === "id")
|
|
127
|
+
return ref.id;
|
|
128
|
+
if (ref.kind === "owned" && ref.slug === HOME_SPACE_SLUG)
|
|
129
|
+
return resolveHomeSpace();
|
|
130
|
+
const spaces = createClient().spaces;
|
|
131
|
+
try {
|
|
132
|
+
const space = ref.kind === "owned"
|
|
133
|
+
? await spaces.getOwnedBySlug(ref.slug)
|
|
134
|
+
: await spaces.getBySlug(ref.username, ref.slug);
|
|
135
|
+
return space.id;
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
if (e.status === 404)
|
|
139
|
+
throw new SpaceTargetError(`Space '${value}' not found`, SPACE_REF_HINT);
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
export function resolveSpaceRef(value) {
|
|
144
|
+
const key = value.trim();
|
|
145
|
+
let resolved = spaceRefs.get(key);
|
|
146
|
+
if (!resolved) {
|
|
147
|
+
resolved = lookupSpaceRef(key);
|
|
148
|
+
spaceRefs.set(key, resolved);
|
|
149
|
+
}
|
|
150
|
+
return resolved;
|
|
151
|
+
}
|
|
152
|
+
export function failSpaceTarget(e) {
|
|
153
|
+
if (e instanceof SpaceTargetError)
|
|
154
|
+
return error(e.message, e.detail);
|
|
155
|
+
return handleHttp(e);
|
|
114
156
|
}
|
|
115
157
|
/** Shared exit for commands that need a space but resolved none. */
|
|
116
158
|
export function missingSpaceError() {
|
|
117
|
-
return error("No target space", "
|
|
159
|
+
return error("No target space", "Pass -s <space> (an ID, home, or username/slug), set COHUB_SPACE_ID, or run in a directory bound with `cohub runtime up`.");
|
|
118
160
|
}
|
|
119
|
-
/**
|
|
120
|
-
* Resolve a Space from an optional explicit target, the current directory
|
|
121
|
-
* binding, and finally the user's Home Space.
|
|
122
|
-
*/
|
|
123
161
|
export async function resolveBoundSpace(options = {}) {
|
|
124
162
|
const bound = await getRuntimeSpaceBinding(options.cwd ?? process.cwd(), currentIdentityKey(), options.bindingsPath).catch(handleHttp);
|
|
125
163
|
return bound?.spaceId ?? null;
|
|
126
164
|
}
|
|
165
|
+
/** Explicit target or `COHUB_SPACE_ID`, then the directory binding, then Home when allowed. */
|
|
127
166
|
export async function resolveSpaceTarget(target, options = {}) {
|
|
128
167
|
const explicit = target?.trim() || process.env.COHUB_SPACE_ID?.trim() || null;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
168
|
+
try {
|
|
169
|
+
if (explicit)
|
|
170
|
+
return await resolveSpaceRef(explicit);
|
|
171
|
+
const bound = await resolveBoundSpace(options);
|
|
172
|
+
if (bound)
|
|
173
|
+
return bound;
|
|
174
|
+
if (options.home)
|
|
175
|
+
return await resolveHomeSpace();
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
return failSpaceTarget(e);
|
|
179
|
+
}
|
|
180
|
+
return missingSpaceError();
|
|
181
|
+
}
|
|
182
|
+
export function resolveSpace(command, options = {}) {
|
|
183
|
+
return resolveSpaceTarget(explicitSpace(command), options);
|
|
137
184
|
}
|
package/dist/web.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function webUrl(path: string): string;
|
package/dist/web.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -20,25 +20,29 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@neta-art/generation": "^0.1.30",
|
|
22
22
|
"commander": "^15.0.0",
|
|
23
|
-
"pixi.js": "^8.20.1",
|
|
24
23
|
"sharp": "^0.35.4",
|
|
25
24
|
"ws": "^8.21.3",
|
|
26
|
-
"@neta-art/cohub": "
|
|
25
|
+
"@neta-art/cohub": "9.0.0"
|
|
27
26
|
},
|
|
28
27
|
"publishConfig": {
|
|
29
28
|
"access": "public"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
31
|
+
"@fontsource/geist": "^5.3.0",
|
|
32
|
+
"@fontsource/geist-mono": "^5.3.0",
|
|
32
33
|
"@types/node": "^26.4.0",
|
|
33
34
|
"@types/ws": "^8.18.1",
|
|
35
|
+
"pixi.js": "^8.20.1",
|
|
36
|
+
"tsdown": "^0.22.14",
|
|
34
37
|
"typescript": "^7.0.2"
|
|
35
38
|
},
|
|
36
39
|
"optionalDependencies": {
|
|
37
40
|
"@napi-rs/canvas": "^1.0.8"
|
|
38
41
|
},
|
|
39
42
|
"scripts": {
|
|
40
|
-
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"build": "tsc -p tsconfig.build.json && tsdown",
|
|
41
44
|
"verify:sandboxd": "node --import tsx scripts/verify-sandboxd-release.ts",
|
|
45
|
+
"verify:board-kernel": "node --import tsx scripts/verify-board-kernel.ts",
|
|
42
46
|
"test": "node ../../scripts/test/run.mjs 'tests/**/*.test.ts'",
|
|
43
47
|
"test:runtime": "node --import tsx --test tests/runtime-*.integration.ts",
|
|
44
48
|
"test:runtime:native": "node --import tsx tests/runtime-native.smoke.ts",
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { registerBoardAnimationCommands } from "./boards/animation.js";
|
|
2
|
-
import { registerBoardAppearanceCommands } from "./boards/appearance.js";
|
|
3
|
-
import { registerBoardExampleCommands } from "./boards/examples.js";
|
|
4
|
-
import { registerBoardBatchCommand } from "./boards/batch.js";
|
|
5
|
-
import { registerBoardConnectionCommands } from "./boards/connections.js";
|
|
6
|
-
import { registerBoardItemCommands } from "./boards/items.js";
|
|
7
|
-
import { registerBoardTransactionCommands } from "./boards/transactions.js";
|
|
8
|
-
export function registerBoardDomainCommands(boards) {
|
|
9
|
-
registerBoardExampleCommands(boards);
|
|
10
|
-
registerBoardAppearanceCommands(boards);
|
|
11
|
-
registerBoardBatchCommand(boards);
|
|
12
|
-
registerBoardConnectionCommands(boards);
|
|
13
|
-
registerBoardItemCommands(boards);
|
|
14
|
-
registerBoardAnimationCommands(boards);
|
|
15
|
-
registerBoardTransactionCommands(boards);
|
|
16
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { parseBoardEffectInput, parseBoardCompositionInput, } from "@neta-art/cohub";
|
|
2
|
-
import { BOARD_DOMAIN_INPUT_MAX_BYTES, readBoardJsonObject, } from "../../board-command-support.js";
|
|
3
|
-
import { handleHttp, json, jsonRequested, table } from "../../output.js";
|
|
4
|
-
import { mutateSemantic, readInput, readOptions, resolvedBoard, showUpdated, withJson, } from "./context.js";
|
|
5
|
-
export function registerBoardAnimationCommands(boards) {
|
|
6
|
-
const effects = boards.command("effects").description("Manage Board effects");
|
|
7
|
-
readOptions(withJson(effects.command("list <board>").alias("ls").description("List effects")))
|
|
8
|
-
.action(async (target, options) => {
|
|
9
|
-
try {
|
|
10
|
-
const board = await resolvedBoard(boards, target);
|
|
11
|
-
const result = await board.authoring(readInput(options, "effects"));
|
|
12
|
-
const effects = result.effects ?? [];
|
|
13
|
-
if (jsonRequested(options))
|
|
14
|
-
return json(effects);
|
|
15
|
-
table(effects, [
|
|
16
|
-
{ key: "id", label: "ID" },
|
|
17
|
-
{ key: "kind", label: "KIND" },
|
|
18
|
-
{ key: "enabled", label: "ENABLED" },
|
|
19
|
-
{ key: "layer", label: "LAYER" },
|
|
20
|
-
]);
|
|
21
|
-
}
|
|
22
|
-
catch (cause) {
|
|
23
|
-
handleHttp(cause);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
withJson(effects.command("get <board> <effect-id>").description("Get one complete Board effect"))
|
|
27
|
-
.action(async (target, effectId, options) => {
|
|
28
|
-
try {
|
|
29
|
-
const board = await resolvedBoard(boards, target);
|
|
30
|
-
const result = await board.authoring({ include: ["effects"], effectIds: [effectId] });
|
|
31
|
-
const effect = result.effects?.[0];
|
|
32
|
-
if (!effect)
|
|
33
|
-
throw new Error(`Effect not found: ${effectId}`);
|
|
34
|
-
if (jsonRequested(options))
|
|
35
|
-
return json(effect);
|
|
36
|
-
table([effect], [
|
|
37
|
-
{ key: "id", label: "ID" },
|
|
38
|
-
{ key: "kind", label: "KIND" },
|
|
39
|
-
{ key: "enabled", label: "ENABLED" },
|
|
40
|
-
{ key: "layer", label: "LAYER" },
|
|
41
|
-
]);
|
|
42
|
-
}
|
|
43
|
-
catch (cause) {
|
|
44
|
-
handleHttp(cause);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
withJson(effects.command("apply <board>")
|
|
48
|
-
.description("Atomically create or replace an effect")
|
|
49
|
-
.requiredOption("-i, --input <file>", "Board effect JSON; use - for stdin")
|
|
50
|
-
.addHelpText("after", `
|
|
51
|
-
Create a template:
|
|
52
|
-
cohub boards examples effect pulse > effect.json
|
|
53
|
-
|
|
54
|
-
Discover supported effect kinds:
|
|
55
|
-
cohub boards capabilities <board> --json`))
|
|
56
|
-
.action(async (target, options) => {
|
|
57
|
-
try {
|
|
58
|
-
const input = await readBoardJsonObject(options.input, BOARD_DOMAIN_INPUT_MAX_BYTES);
|
|
59
|
-
const effect = parseBoardEffectInput(input);
|
|
60
|
-
const board = await resolvedBoard(boards, target);
|
|
61
|
-
showUpdated(await mutateSemantic(board, [{ type: "effect.apply", effect }]), options);
|
|
62
|
-
}
|
|
63
|
-
catch (cause) {
|
|
64
|
-
handleHttp(cause);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
withJson(effects.command("delete <board> <effect-id>").alias("rm").description("Delete an effect"))
|
|
68
|
-
.action(async (target, effectId, options) => {
|
|
69
|
-
try {
|
|
70
|
-
const board = await resolvedBoard(boards, target);
|
|
71
|
-
showUpdated(await mutateSemantic(board, [{ type: "effect.delete", effectId }]), options);
|
|
72
|
-
}
|
|
73
|
-
catch (cause) {
|
|
74
|
-
handleHttp(cause);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
const compositions = boards
|
|
78
|
-
.command("compositions")
|
|
79
|
-
.description("Manage atomic Board animation compositions");
|
|
80
|
-
readOptions(withJson(compositions.command("list <board>").alias("ls").description("List compositions")))
|
|
81
|
-
.action(async (target, options) => {
|
|
82
|
-
try {
|
|
83
|
-
const board = await resolvedBoard(boards, target);
|
|
84
|
-
const result = await board.authoring(readInput(options, "compositions"));
|
|
85
|
-
const compositions = result.compositions ?? [];
|
|
86
|
-
if (jsonRequested(options))
|
|
87
|
-
return json(compositions);
|
|
88
|
-
table(compositions.map((composition) => ({
|
|
89
|
-
id: composition.id,
|
|
90
|
-
name: composition.name,
|
|
91
|
-
duration: composition.timeline.duration,
|
|
92
|
-
tracks: composition.timeline.tracks.length,
|
|
93
|
-
clips: composition.timeline.clips.length,
|
|
94
|
-
revision: composition.revision,
|
|
95
|
-
})), [
|
|
96
|
-
{ key: "id", label: "ID" },
|
|
97
|
-
{ key: "name", label: "NAME" },
|
|
98
|
-
{ key: "duration", label: "DURATION" },
|
|
99
|
-
{ key: "tracks", label: "TRACKS" },
|
|
100
|
-
{ key: "clips", label: "CLIPS" },
|
|
101
|
-
{ key: "revision", label: "REVISION" },
|
|
102
|
-
]);
|
|
103
|
-
}
|
|
104
|
-
catch (cause) {
|
|
105
|
-
handleHttp(cause);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
withJson(compositions.command("get <board> <composition-id>").description("Get one complete composition"))
|
|
109
|
-
.action(async (target, compositionId, options) => {
|
|
110
|
-
try {
|
|
111
|
-
const board = await resolvedBoard(boards, target);
|
|
112
|
-
const result = await board.authoring({ include: ["compositions"], compositionIds: [compositionId] });
|
|
113
|
-
const composition = result.compositions?.[0];
|
|
114
|
-
if (!composition)
|
|
115
|
-
throw new Error(`Composition not found: ${compositionId}`);
|
|
116
|
-
if (jsonRequested(options))
|
|
117
|
-
return json(composition);
|
|
118
|
-
table([{
|
|
119
|
-
id: composition.id,
|
|
120
|
-
name: composition.name,
|
|
121
|
-
duration: composition.timeline.duration,
|
|
122
|
-
tracks: composition.timeline.tracks.length,
|
|
123
|
-
clips: composition.timeline.clips.length,
|
|
124
|
-
revision: composition.revision,
|
|
125
|
-
}], [
|
|
126
|
-
{ key: "id", label: "ID" },
|
|
127
|
-
{ key: "name", label: "NAME" },
|
|
128
|
-
{ key: "duration", label: "DURATION" },
|
|
129
|
-
{ key: "tracks", label: "TRACKS" },
|
|
130
|
-
{ key: "clips", label: "CLIPS" },
|
|
131
|
-
{ key: "revision", label: "REVISION" },
|
|
132
|
-
]);
|
|
133
|
-
}
|
|
134
|
-
catch (cause) {
|
|
135
|
-
handleHttp(cause);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
withJson(compositions.command("apply <board>")
|
|
139
|
-
.description("Atomically create or replace a composition")
|
|
140
|
-
.requiredOption("-i, --input <file>", "BoardComposition JSON; use - for stdin")
|
|
141
|
-
.addHelpText("after", `
|
|
142
|
-
Property changes use timeline.tracks with registered channels and keyframes.
|
|
143
|
-
Procedural behavior such as text reveal, particles, and camera focus uses timeline.clips.
|
|
144
|
-
Run boards capabilities to discover channels and clip schemas.
|
|
145
|
-
Create an editable template with:
|
|
146
|
-
cohub boards examples composition fade > intro.json
|
|
147
|
-
|
|
148
|
-
Minimal fade composition:
|
|
149
|
-
{"id":"intro","name":"Intro","timeline":{"duration":800,"tracks":[{"id":"title-opacity","target":{"type":"item","itemId":"title"},"channel":"style.opacity","fill":"both","keyframes":[{"time":0,"value":0},{"time":800,"value":1,"easing":"ease-out-cubic"}]}],"clips":[],"markers":[]},"playback":{"loop":false,"endBehavior":"hold","reducedMotion":{"mode":"base"}}}`))
|
|
150
|
-
.action(async (target, options) => {
|
|
151
|
-
try {
|
|
152
|
-
const input = await readBoardJsonObject(options.input, BOARD_DOMAIN_INPUT_MAX_BYTES);
|
|
153
|
-
const composition = parseBoardCompositionInput(input);
|
|
154
|
-
const board = await resolvedBoard(boards, target);
|
|
155
|
-
showUpdated(await mutateSemantic(board, [{ type: "composition.apply", composition }]), options);
|
|
156
|
-
}
|
|
157
|
-
catch (cause) {
|
|
158
|
-
handleHttp(cause);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
withJson(compositions.command("delete <board> <composition-id>").alias("rm").description("Delete a composition"))
|
|
162
|
-
.action(async (target, compositionId, options) => {
|
|
163
|
-
try {
|
|
164
|
-
const board = await resolvedBoard(boards, target);
|
|
165
|
-
showUpdated(await mutateSemantic(board, [{ type: "composition.delete", compositionId }]), options);
|
|
166
|
-
}
|
|
167
|
-
catch (cause) {
|
|
168
|
-
handleHttp(cause);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { BoardAppearanceSchema, DEFAULT_BOARD_APPEARANCE, patchBoardAppearance, } from "@neta-art/cohub/board";
|
|
2
|
-
import { handleHttp } from "../../output.js";
|
|
3
|
-
import { finite, mutateSemantic, resolvedBoard, showUpdated, withJson, } from "./context.js";
|
|
4
|
-
function appearanceFrom(metadata) {
|
|
5
|
-
const parsed = BoardAppearanceSchema.safeParse(metadata.appearance);
|
|
6
|
-
return parsed.success ? parsed.data : DEFAULT_BOARD_APPEARANCE;
|
|
7
|
-
}
|
|
8
|
-
export function registerBoardAppearanceCommands(boards) {
|
|
9
|
-
withJson(boards.command("rename <board> <title>").description("Rename a Board"))
|
|
10
|
-
.action(async (target, title, options) => {
|
|
11
|
-
try {
|
|
12
|
-
const board = await resolvedBoard(boards, target);
|
|
13
|
-
showUpdated(await mutateSemantic(board, [{ type: "board.patch", patch: { title } }]), options);
|
|
14
|
-
}
|
|
15
|
-
catch (cause) {
|
|
16
|
-
handleHttp(cause);
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
withJson(boards.command("background <board>")
|
|
20
|
-
.description("Configure the Board background")
|
|
21
|
-
.option("--color <color>", "Solid CSS color")
|
|
22
|
-
.option("--image <url>", "Public image URL")
|
|
23
|
-
.option("--fit <mode>", "cover, contain, or repeat", "cover")
|
|
24
|
-
.option("--position <position>", "center, top, bottom, left, or right", "center")
|
|
25
|
-
.option("--opacity <value>", "Image opacity from 0 to 1", "1")
|
|
26
|
-
.option("--reset", "Restore the default background")
|
|
27
|
-
.addHelpText("after", `
|
|
28
|
-
Examples:
|
|
29
|
-
cohub boards background plan.board --color "#123456"
|
|
30
|
-
cohub boards background plan.board --image https://example.com/bg.webp --fit cover --opacity 0.8`))
|
|
31
|
-
.action(async (target, options) => {
|
|
32
|
-
try {
|
|
33
|
-
const selected = [options.color, options.image, options.reset ? "reset" : undefined].filter(Boolean);
|
|
34
|
-
if (selected.length !== 1)
|
|
35
|
-
throw new Error("Choose one of --color, --image, or --reset");
|
|
36
|
-
if (!["cover", "contain", "repeat"].includes(options.fit))
|
|
37
|
-
throw new Error("--fit must be cover, contain, or repeat");
|
|
38
|
-
if (!["center", "top", "bottom", "left", "right"].includes(options.position))
|
|
39
|
-
throw new Error("--position must be center, top, bottom, left, or right");
|
|
40
|
-
const opacity = finite(options.opacity, "opacity");
|
|
41
|
-
if (opacity < 0 || opacity > 1)
|
|
42
|
-
throw new Error("opacity must be between 0 and 1");
|
|
43
|
-
const board = await resolvedBoard(boards, target);
|
|
44
|
-
const current = await board.summary();
|
|
45
|
-
const appearance = appearanceFrom(current.board.metadata);
|
|
46
|
-
const background = options.reset
|
|
47
|
-
? { kind: "solid" }
|
|
48
|
-
: options.color
|
|
49
|
-
? { kind: "solid", color: options.color }
|
|
50
|
-
: {
|
|
51
|
-
kind: "image",
|
|
52
|
-
imageUrl: options.image,
|
|
53
|
-
fit: options.fit,
|
|
54
|
-
position: options.position,
|
|
55
|
-
opacity,
|
|
56
|
-
color: appearance.background.color,
|
|
57
|
-
};
|
|
58
|
-
const result = await mutateSemantic(board, [{
|
|
59
|
-
type: "board.patch",
|
|
60
|
-
patch: { metadataPatch: { appearance: patchBoardAppearance(appearance, { background }) } },
|
|
61
|
-
}], { baseVersion: current.board.version });
|
|
62
|
-
showUpdated(result, options);
|
|
63
|
-
}
|
|
64
|
-
catch (cause) {
|
|
65
|
-
handleHttp(cause);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
withJson(boards.command("motion <board>")
|
|
69
|
-
.description("Configure the default Board motion for newly added nodes")
|
|
70
|
-
.option("--preset <preset>", "Enter motion preset (deal)")
|
|
71
|
-
.option("--clear", "Disable the default enter motion")
|
|
72
|
-
.addHelpText("after", `
|
|
73
|
-
Examples:
|
|
74
|
-
cohub boards motion plan.board --preset deal
|
|
75
|
-
cohub boards motion plan.board --clear`))
|
|
76
|
-
.action(async (target, options) => {
|
|
77
|
-
try {
|
|
78
|
-
if (Boolean(options.clear) === Boolean(options.preset))
|
|
79
|
-
throw new Error("Use --preset or --clear");
|
|
80
|
-
if (options.preset && options.preset !== "deal")
|
|
81
|
-
throw new Error("--preset must be deal");
|
|
82
|
-
const board = await resolvedBoard(boards, target);
|
|
83
|
-
const current = await board.summary();
|
|
84
|
-
const appearance = appearanceFrom(current.board.metadata);
|
|
85
|
-
const next = patchBoardAppearance(appearance, {
|
|
86
|
-
motion: options.clear
|
|
87
|
-
? undefined
|
|
88
|
-
: { enter: { kind: "effects.deal", kindVersion: 1, params: {} } },
|
|
89
|
-
});
|
|
90
|
-
showUpdated(await mutateSemantic(board, [{
|
|
91
|
-
type: "board.patch",
|
|
92
|
-
patch: { metadataPatch: { appearance: next } },
|
|
93
|
-
}], { baseVersion: current.board.version }), options);
|
|
94
|
-
}
|
|
95
|
-
catch (cause) {
|
|
96
|
-
handleHttp(cause);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
withJson(boards.command("playback-policy <board>")
|
|
100
|
-
.description("Configure automatic Board playback")
|
|
101
|
-
.option("--composition <id>", "Composition to play")
|
|
102
|
-
.option("--delay <ms>", "Delay before playback", "0")
|
|
103
|
-
.option("--clear", "Remove the playback policy"))
|
|
104
|
-
.action(async (target, options) => {
|
|
105
|
-
try {
|
|
106
|
-
if (options.clear === Boolean(options.composition))
|
|
107
|
-
throw new Error("Use --composition or --clear");
|
|
108
|
-
const delayMs = finite(options.delay, "delay");
|
|
109
|
-
if (delayMs < 0)
|
|
110
|
-
throw new Error("delay must be non-negative");
|
|
111
|
-
const policy = options.clear
|
|
112
|
-
? null
|
|
113
|
-
: { compositionId: options.composition, delayMs };
|
|
114
|
-
const board = await resolvedBoard(boards, target);
|
|
115
|
-
const current = await board.summary();
|
|
116
|
-
const patch = policy
|
|
117
|
-
? { metadataPatch: { playback: policy } }
|
|
118
|
-
: { metadata: { ...current.board.metadata, playback: undefined } };
|
|
119
|
-
if (!policy)
|
|
120
|
-
delete patch.metadata.playback;
|
|
121
|
-
showUpdated(await mutateSemantic(board, [{ type: "board.patch", patch }], { baseVersion: current.board.version }), options);
|
|
122
|
-
}
|
|
123
|
-
catch (cause) {
|
|
124
|
-
handleHttp(cause);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { type BoardSemanticCommand } from "@neta-art/cohub";
|
|
2
|
-
import type { Command } from "commander";
|
|
3
|
-
export declare function parseBatchCommands(input: Record<string, unknown>): BoardSemanticCommand[];
|
|
4
|
-
export declare function registerBoardBatchCommand(boards: Command): void;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { BoardSemanticCommandSchema } from "@neta-art/cohub";
|
|
3
|
-
import { BOARD_TRANSACTION_INPUT_MAX_BYTES, readBoardJsonObject, } from "../../board-command-support.js";
|
|
4
|
-
import { handleHttp, json, jsonRequested, ok } from "../../output.js";
|
|
5
|
-
import { mutateSemantic, resolvedBoard, withJson, } from "./context.js";
|
|
6
|
-
export function parseBatchCommands(input) {
|
|
7
|
-
if (!Array.isArray(input.commands) || input.commands.length === 0) {
|
|
8
|
-
throw new Error("batch input must contain a non-empty commands array");
|
|
9
|
-
}
|
|
10
|
-
return input.commands.map((command, index) => {
|
|
11
|
-
const parsed = BoardSemanticCommandSchema.safeParse(command);
|
|
12
|
-
if (!parsed.success) {
|
|
13
|
-
const issue = parsed.error.issues[0];
|
|
14
|
-
const path = issue?.path.length ? `.${issue.path.join(".")}` : "";
|
|
15
|
-
throw new Error(`commands[${index}]${path}: ${issue?.message ?? "invalid command"}`);
|
|
16
|
-
}
|
|
17
|
-
return parsed.data;
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
export function registerBoardBatchCommand(boards) {
|
|
21
|
-
withJson(boards.command("batch <board>")
|
|
22
|
-
.description("Apply an atomic batch of semantic Board changes")
|
|
23
|
-
.requiredOption("-i, --input <file>", "Batch JSON with a commands array; use - for stdin")
|
|
24
|
-
.option("--base-version <version>", "Expected Board version; defaults to latest")
|
|
25
|
-
.option("--mutation-id <id>", "Stable id for safe retries")
|
|
26
|
-
.option("--dry-run", "Validate the whole batch without writing")
|
|
27
|
-
.addHelpText("after", `
|
|
28
|
-
Input format:
|
|
29
|
-
{"commands":[{"type":"item.patch","itemId":"title","patch":{"props":{"text":"Updated"}}}]}
|
|
30
|
-
|
|
31
|
-
Commands are applied atomically. See boards capabilities for supported fields.`))
|
|
32
|
-
.action(async (target, options) => {
|
|
33
|
-
try {
|
|
34
|
-
const input = await readBoardJsonObject(options.input, BOARD_TRANSACTION_INPUT_MAX_BYTES);
|
|
35
|
-
const commands = parseBatchCommands(input);
|
|
36
|
-
const parsedVersion = options.baseVersion === undefined ? undefined : Number(options.baseVersion);
|
|
37
|
-
if (parsedVersion !== undefined && (!Number.isSafeInteger(parsedVersion) || parsedVersion < 0)) {
|
|
38
|
-
throw new Error("base version must be a non-negative integer");
|
|
39
|
-
}
|
|
40
|
-
const board = await resolvedBoard(boards, target);
|
|
41
|
-
const result = await mutateSemantic(board, commands, {
|
|
42
|
-
...(parsedVersion === undefined ? {} : { baseVersion: parsedVersion }),
|
|
43
|
-
mutationId: options.mutationId?.trim() || randomUUID(),
|
|
44
|
-
dryRun: Boolean(options.dryRun),
|
|
45
|
-
});
|
|
46
|
-
if (jsonRequested(options))
|
|
47
|
-
return json(result);
|
|
48
|
-
if (result.outcome === "dry-run") {
|
|
49
|
-
ok(`Validated ${commands.length} changes; no changes written`);
|
|
50
|
-
}
|
|
51
|
-
else if (result.outcome === "noop" || result.status === "validated") {
|
|
52
|
-
ok(`Board already matches the requested ${commands.length} changes`);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
ok(`${result.replayed ? "Replayed" : "Applied"} ${commands.length} changes at Board version ${result.board.version}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (cause) {
|
|
59
|
-
handleHttp(cause);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|