@neta-art/cohub-cli 1.17.4 → 1.17.5
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 +24 -1
- package/dist/avatar.d.ts +15 -0
- package/dist/avatar.js +23 -18
- package/dist/commands/spaces.js +33 -7
- package/dist/commands/works.d.ts +2 -0
- package/dist/commands/works.js +266 -0
- package/dist/index.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -173,6 +173,29 @@ cohub -s <spaceId> spaces files rm <path>
|
|
|
173
173
|
|
|
174
174
|
Confirm before deleting files or directories.
|
|
175
175
|
|
|
176
|
+
## Works
|
|
177
|
+
|
|
178
|
+
Publish and manage Work entries from a Space workspace.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
cohub -s <spaceId> works ls --json
|
|
182
|
+
cohub works get <workId> --json
|
|
183
|
+
cohub -s <spaceId> works publish demo --file dist/index.html
|
|
184
|
+
cohub -s <spaceId> works publish site --dir dist
|
|
185
|
+
cohub -s <spaceId> works publish app --port 3000
|
|
186
|
+
cohub works update <workId> --publish-version
|
|
187
|
+
cohub works versions <workId> --json
|
|
188
|
+
cohub works rm <workId> --yes
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Resolve a published Work by public identity:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
cohub works resolve <workSlug> --owner <username> --space-slug <spaceSlug>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Use `--json` for machine-readable output. The resolve command requires both `--owner` and `--space-slug` so missing public profile data fails with a clear message.
|
|
198
|
+
|
|
176
199
|
## Saves
|
|
177
200
|
|
|
178
201
|
```bash
|
|
@@ -210,7 +233,7 @@ Confirm before enabling, disabling, or deleting recurring scheduled prompts.
|
|
|
210
233
|
|
|
211
234
|
Confirm before:
|
|
212
235
|
|
|
213
|
-
- deleting files or
|
|
236
|
+
- deleting files, directories, or Works
|
|
214
237
|
- creating scheduled or recurring prompts with side effects
|
|
215
238
|
- enabling, disabling, or deleting recurring scheduled prompts
|
|
216
239
|
- changing access policies, member roles, or membership
|
package/dist/avatar.d.ts
CHANGED
|
@@ -13,3 +13,18 @@ export declare function uploadAvatarAsset(input: {
|
|
|
13
13
|
uploadUrl: string;
|
|
14
14
|
uploadFields: Record<string, string>;
|
|
15
15
|
}>;
|
|
16
|
+
export declare function normalizeChatImageFile(path: string): Promise<Buffer>;
|
|
17
|
+
export declare function uploadChatImageAsset(input: {
|
|
18
|
+
client: CohubHttpClient;
|
|
19
|
+
spaceId: string;
|
|
20
|
+
sessionId: string;
|
|
21
|
+
path: string;
|
|
22
|
+
}): Promise<{
|
|
23
|
+
purpose: PublicAssetPurpose;
|
|
24
|
+
objectKey: string;
|
|
25
|
+
publicUrl: string;
|
|
26
|
+
uploadMethod: "POST";
|
|
27
|
+
uploadUrl: string;
|
|
28
|
+
uploadFields: Record<string, string>;
|
|
29
|
+
size: number;
|
|
30
|
+
}>;
|
package/dist/avatar.js
CHANGED
|
@@ -10,26 +10,31 @@ export async function normalizeAvatarFile(path) {
|
|
|
10
10
|
}
|
|
11
11
|
export async function uploadAvatarAsset(input) {
|
|
12
12
|
const body = await normalizeAvatarFile(input.path);
|
|
13
|
-
|
|
13
|
+
return input.client.publicAssets.upload({
|
|
14
14
|
purpose: input.purpose,
|
|
15
15
|
spaceId: input.spaceId,
|
|
16
|
-
file: {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
16
|
+
file: new Blob([new Uint8Array(body)], { type: "image/webp" }),
|
|
17
|
+
mimeType: "image/webp",
|
|
18
|
+
filename: "avatar.webp",
|
|
20
19
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
}
|
|
21
|
+
const CHAT_IMAGE_MAX_EDGE = 2160;
|
|
22
|
+
const CHAT_IMAGE_QUALITY = 86;
|
|
23
|
+
export async function normalizeChatImageFile(path) {
|
|
24
|
+
return sharp(path)
|
|
25
|
+
.rotate()
|
|
26
|
+
.resize(CHAT_IMAGE_MAX_EDGE, CHAT_IMAGE_MAX_EDGE, { fit: "inside", withoutEnlargement: true })
|
|
27
|
+
.webp({ quality: CHAT_IMAGE_QUALITY })
|
|
28
|
+
.toBuffer();
|
|
29
|
+
}
|
|
30
|
+
export async function uploadChatImageAsset(input) {
|
|
31
|
+
const body = await normalizeChatImageFile(input.path);
|
|
32
|
+
const asset = await input.client.publicAssets.uploadChatImageAttachment({
|
|
33
|
+
spaceId: input.spaceId,
|
|
34
|
+
sessionId: input.sessionId,
|
|
35
|
+
file: new Blob([new Uint8Array(body)], { type: "image/webp" }),
|
|
36
|
+
mimeType: "image/webp",
|
|
37
|
+
filename: "image.webp",
|
|
29
38
|
});
|
|
30
|
-
|
|
31
|
-
const detail = await response.text().catch(() => "");
|
|
32
|
-
throw new Error(`Avatar upload failed: HTTP ${response.status}${detail ? ` — ${detail}` : ""}`);
|
|
33
|
-
}
|
|
34
|
-
return plan.asset;
|
|
39
|
+
return { ...asset, size: body.byteLength };
|
|
35
40
|
}
|
package/dist/commands/spaces.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createReadStream } from "node:fs";
|
|
|
3
3
|
import { readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { resolveCohubEnvironment } from "@neta-art/cohub";
|
|
6
|
-
import { uploadAvatarAsset } from "../avatar.js";
|
|
6
|
+
import { uploadAvatarAsset, uploadChatImageAsset } from "../avatar.js";
|
|
7
7
|
import { createClient } from "../client.js";
|
|
8
8
|
import { table, json as outJson, jsonRequested, ok, error, handleHttp } from "../output.js";
|
|
9
9
|
import { resolveSpace } from "../space.js";
|
|
@@ -156,7 +156,7 @@ async function confirmRestart(opts) {
|
|
|
156
156
|
if (answer !== "y" && answer !== "yes")
|
|
157
157
|
return error("Cancelled");
|
|
158
158
|
}
|
|
159
|
-
async function readPromptContent(words) {
|
|
159
|
+
async function readPromptContent(words, options = {}) {
|
|
160
160
|
let content = words.join(" ");
|
|
161
161
|
if (!content && !process.stdin.isTTY) {
|
|
162
162
|
const chunks = [];
|
|
@@ -164,12 +164,12 @@ async function readPromptContent(words) {
|
|
|
164
164
|
chunks.push(chunk);
|
|
165
165
|
content = Buffer.concat(chunks).toString().trim();
|
|
166
166
|
}
|
|
167
|
-
if (!content)
|
|
167
|
+
if (!content && !options.allowEmpty)
|
|
168
168
|
return error("No content", "Pass as argument or pipe via stdin");
|
|
169
169
|
return content;
|
|
170
170
|
}
|
|
171
171
|
async function sendPrompt(command, words, opts) {
|
|
172
|
-
const content = await readPromptContent(words);
|
|
172
|
+
const content = await readPromptContent(words, { allowEmpty: Boolean(opts.image?.length) });
|
|
173
173
|
const scheduleFlags = [opts.delayMs, opts.at, opts.cron].filter((value) => value !== undefined);
|
|
174
174
|
if (scheduleFlags.length > 1)
|
|
175
175
|
return error("Conflicting schedule", "Use only one of --delay-ms, --at, or --cron");
|
|
@@ -185,11 +185,35 @@ async function sendPrompt(command, words, opts) {
|
|
|
185
185
|
: opts.cron
|
|
186
186
|
? { mode: "repeat", cronExpression: opts.cron, timezone: opts.timezone }
|
|
187
187
|
: undefined;
|
|
188
|
+
const sessionId = opts.session;
|
|
189
|
+
const imagePaths = opts.image ?? [];
|
|
190
|
+
const imageSessionId = imagePaths.length
|
|
191
|
+
? sessionId ?? error("Missing session", "Pass --session when attaching images.")
|
|
192
|
+
: "";
|
|
193
|
+
const imageBlocks = imagePaths.length
|
|
194
|
+
? await Promise.all(imagePaths.map(async (path) => {
|
|
195
|
+
const asset = await uploadChatImageAsset({ client, spaceId, sessionId: imageSessionId, path });
|
|
196
|
+
return {
|
|
197
|
+
type: "image",
|
|
198
|
+
source: { type: "url", url: asset.publicUrl },
|
|
199
|
+
_meta: {
|
|
200
|
+
filename: basename(path),
|
|
201
|
+
mediaType: "image/webp",
|
|
202
|
+
size: asset.size,
|
|
203
|
+
objectKey: asset.objectKey,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}))
|
|
207
|
+
: [];
|
|
208
|
+
const promptContent = [
|
|
209
|
+
...(content ? [{ type: "text", text: content }] : []),
|
|
210
|
+
...imageBlocks,
|
|
211
|
+
];
|
|
188
212
|
const result = await client.space(spaceId).prompt({
|
|
189
|
-
sessionId
|
|
190
|
-
title: opts.title,
|
|
213
|
+
sessionId,
|
|
214
|
+
title: sessionId === opts.session ? opts.title : undefined,
|
|
191
215
|
source: opts.source?.trim() || "cli",
|
|
192
|
-
content:
|
|
216
|
+
content: promptContent,
|
|
193
217
|
model: opts.model,
|
|
194
218
|
provider: opts.provider,
|
|
195
219
|
accessMode: opts.readOnly ? "read_only" : "full_access",
|
|
@@ -225,6 +249,7 @@ export function registerPrompt(program) {
|
|
|
225
249
|
.option("--cron <expression>", "Repeat using a 5-field cron expression")
|
|
226
250
|
.option("--timezone <tz>", "IANA timezone for --cron, e.g. Asia/Shanghai")
|
|
227
251
|
.option("--label <ref>", "Attach a label, e.g. Bug or Area/Frontend", collectOption, [])
|
|
252
|
+
.option("--image <path>", "Attach an image", collectOption, [])
|
|
228
253
|
.option("--json", "Output as JSON")
|
|
229
254
|
.action((words, opts) => sendPrompt(program, words, opts));
|
|
230
255
|
}
|
|
@@ -385,6 +410,7 @@ export function registerSpaces(program) {
|
|
|
385
410
|
.option("--cron <expression>", "Repeat using a 5-field cron expression")
|
|
386
411
|
.option("--timezone <tz>", "IANA timezone for --cron, e.g. Asia/Shanghai")
|
|
387
412
|
.option("--label <ref>", "Attach a label, e.g. Bug or Area/Frontend", collectOption, [])
|
|
413
|
+
.option("--image <path>", "Attach an image", collectOption, [])
|
|
388
414
|
.option("--json", "Output as JSON")
|
|
389
415
|
.action((words, opts) => sendPrompt(spacesCmd, words, opts));
|
|
390
416
|
// ── spaces files ──
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { createClient } from "../client.js";
|
|
2
|
+
import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
|
|
3
|
+
import { resolveSpace } from "../space.js";
|
|
4
|
+
const WORK_STATUSES = ["draft", "published", "disabled"];
|
|
5
|
+
const collectOption = (value, previous = []) => [...previous, value];
|
|
6
|
+
function parseChoice(value, name, choices) {
|
|
7
|
+
if (choices.includes(value))
|
|
8
|
+
return value;
|
|
9
|
+
return error(`Invalid ${name}`, `Use one of: ${choices.join(", ")}`);
|
|
10
|
+
}
|
|
11
|
+
function parseJsonObject(value, name) {
|
|
12
|
+
if (!value)
|
|
13
|
+
return undefined;
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(value);
|
|
16
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// handled below
|
|
21
|
+
}
|
|
22
|
+
return error(`Invalid ${name}`, `${name} must be a JSON object`);
|
|
23
|
+
}
|
|
24
|
+
function compactObject(input) {
|
|
25
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
26
|
+
}
|
|
27
|
+
function resolveTarget(opts) {
|
|
28
|
+
const targets = [
|
|
29
|
+
opts.file ? { targetType: "file", targetRef: opts.file } : null,
|
|
30
|
+
opts.dir ? { targetType: "directory", targetRef: opts.dir } : null,
|
|
31
|
+
opts.port ? { targetType: "port", targetRef: opts.port } : null,
|
|
32
|
+
].filter((target) => Boolean(target));
|
|
33
|
+
if (targets.length === 0)
|
|
34
|
+
return null;
|
|
35
|
+
if (targets.length > 1)
|
|
36
|
+
return error("Conflicting target", "Use only one of --file, --dir, or --port");
|
|
37
|
+
return targets[0] ?? null;
|
|
38
|
+
}
|
|
39
|
+
function resolveStatus(opts) {
|
|
40
|
+
const values = [opts.status, opts.draft ? "draft" : undefined, opts.disabled ? "disabled" : undefined].filter(Boolean);
|
|
41
|
+
if (values.length > 1)
|
|
42
|
+
return error("Conflicting status", "Use only one of --status, --draft, or --disabled");
|
|
43
|
+
return values[0] ? parseChoice(values[0], "status", WORK_STATUSES) : "published";
|
|
44
|
+
}
|
|
45
|
+
function printWork(work) {
|
|
46
|
+
table([work], [
|
|
47
|
+
{ key: "id", label: "ID" },
|
|
48
|
+
{ key: "slug", label: "Slug" },
|
|
49
|
+
{ key: "status", label: "Status" },
|
|
50
|
+
{ key: "targetType", label: "Target" },
|
|
51
|
+
{ key: "targetRef", label: "Ref" },
|
|
52
|
+
{ key: "latestVersion", label: "Version" },
|
|
53
|
+
{ key: "publishedAt", label: "Published" },
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
async function confirmDelete(opts) {
|
|
57
|
+
if (opts.yes)
|
|
58
|
+
return;
|
|
59
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
60
|
+
return error("Confirmation required", "Pass --yes to delete the work.");
|
|
61
|
+
process.stdout.write("Deleting a work also removes its versions and viewer grants. Continue? [y/N] ");
|
|
62
|
+
const chunks = [];
|
|
63
|
+
for await (const chunk of process.stdin) {
|
|
64
|
+
chunks.push(chunk);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const answer = Buffer.concat(chunks).toString().trim().toLowerCase();
|
|
68
|
+
if (answer !== "y" && answer !== "yes")
|
|
69
|
+
return error("Cancelled");
|
|
70
|
+
}
|
|
71
|
+
export function registerWorks(program) {
|
|
72
|
+
const worksCmd = program.command("works").description("Work management");
|
|
73
|
+
worksCmd
|
|
74
|
+
.command("ls")
|
|
75
|
+
.alias("list")
|
|
76
|
+
.description("List works in the target space")
|
|
77
|
+
.option("--json", "Output as JSON")
|
|
78
|
+
.action(async (opts) => {
|
|
79
|
+
const spaceId = resolveSpace(worksCmd);
|
|
80
|
+
const client = createClient();
|
|
81
|
+
try {
|
|
82
|
+
const result = await client.works.listBySpace(spaceId);
|
|
83
|
+
if (jsonRequested(opts))
|
|
84
|
+
return outJson(result);
|
|
85
|
+
table(result.works, [
|
|
86
|
+
{ key: "id", label: "ID" },
|
|
87
|
+
{ key: "slug", label: "Slug" },
|
|
88
|
+
{ key: "status", label: "Status" },
|
|
89
|
+
{ key: "targetType", label: "Target" },
|
|
90
|
+
{ key: "targetRef", label: "Ref" },
|
|
91
|
+
{ key: "latestVersion", label: "Version" },
|
|
92
|
+
{ key: "publishedAt", label: "Published" },
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
handleHttp(e);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
worksCmd
|
|
100
|
+
.command("get <id>")
|
|
101
|
+
.description("Show work details")
|
|
102
|
+
.option("--json", "Output as JSON")
|
|
103
|
+
.action(async (id, opts) => {
|
|
104
|
+
const client = createClient();
|
|
105
|
+
try {
|
|
106
|
+
const result = await client.works.get(id);
|
|
107
|
+
if (jsonRequested(opts))
|
|
108
|
+
return outJson(result);
|
|
109
|
+
printWork(result.work);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
handleHttp(e);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
worksCmd
|
|
116
|
+
.command("resolve <workSlug>")
|
|
117
|
+
.description("Resolve a published work by owner and space slug")
|
|
118
|
+
.option("--owner <username>", "Owner username")
|
|
119
|
+
.option("--space-slug <slug>", "Space slug")
|
|
120
|
+
.option("--json", "Output as JSON")
|
|
121
|
+
.action(async (workSlug, opts) => {
|
|
122
|
+
if (!opts.owner?.trim())
|
|
123
|
+
return error("Missing owner username", "Pass --owner <username>.");
|
|
124
|
+
if (!opts.spaceSlug?.trim())
|
|
125
|
+
return error("Missing space slug", "Pass --space-slug <slug>.");
|
|
126
|
+
const client = createClient();
|
|
127
|
+
try {
|
|
128
|
+
const result = await client.works.getBySlug(opts.owner.trim(), opts.spaceSlug.trim(), workSlug);
|
|
129
|
+
if (jsonRequested(opts))
|
|
130
|
+
return outJson(result);
|
|
131
|
+
printWork(result.work);
|
|
132
|
+
if (result.content?.url)
|
|
133
|
+
console.log(`\nURL: ${result.content.url}`);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
handleHttp(e);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
worksCmd
|
|
140
|
+
.command("publish <slug>")
|
|
141
|
+
.description("Create or publish a work in the target space")
|
|
142
|
+
.option("--file <path>", "Publish a HTML file")
|
|
143
|
+
.option("--dir <path>", "Publish a directory site")
|
|
144
|
+
.option("--port <port>", "Publish a public sandbox port")
|
|
145
|
+
.option("--draft", "Create as draft")
|
|
146
|
+
.option("--disabled", "Create as disabled")
|
|
147
|
+
.option("--status <status>", "Work status: draft, published, disabled")
|
|
148
|
+
.option("--work-scope <scope>", "Scope granted to the work runtime", collectOption, [])
|
|
149
|
+
.option("--viewer-scope <scope>", "Scope viewers may request", collectOption, [])
|
|
150
|
+
.option("--meta <json>", "Work metadata as a JSON object")
|
|
151
|
+
.option("--json", "Output as JSON")
|
|
152
|
+
.action(async (slug, opts) => {
|
|
153
|
+
const target = resolveTarget(opts);
|
|
154
|
+
if (!target)
|
|
155
|
+
return error("Missing target", "Use one of --file, --dir, or --port.");
|
|
156
|
+
const spaceId = resolveSpace(worksCmd);
|
|
157
|
+
const client = createClient();
|
|
158
|
+
const status = resolveStatus(opts);
|
|
159
|
+
const input = {
|
|
160
|
+
spaceId,
|
|
161
|
+
slug,
|
|
162
|
+
status,
|
|
163
|
+
targetType: target.targetType,
|
|
164
|
+
targetRef: target.targetRef,
|
|
165
|
+
workScopes: opts.workScope,
|
|
166
|
+
allowedViewerScopes: opts.viewerScope,
|
|
167
|
+
meta: parseJsonObject(opts.meta, "meta"),
|
|
168
|
+
};
|
|
169
|
+
try {
|
|
170
|
+
const result = await client.works.create(input);
|
|
171
|
+
if (jsonRequested(opts))
|
|
172
|
+
return outJson(result);
|
|
173
|
+
ok(`Work published: ${result.work.id}`);
|
|
174
|
+
printWork(result.work);
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
handleHttp(e);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
worksCmd
|
|
181
|
+
.command("update <id>")
|
|
182
|
+
.description("Update work settings or publish a new version")
|
|
183
|
+
.option("--slug <slug>", "New work slug")
|
|
184
|
+
.option("--file <path>", "Use a HTML file target")
|
|
185
|
+
.option("--dir <path>", "Use a directory site target")
|
|
186
|
+
.option("--port <port>", "Use a public sandbox port target")
|
|
187
|
+
.option("--draft", "Set status to draft")
|
|
188
|
+
.option("--disabled", "Set status to disabled")
|
|
189
|
+
.option("--status <status>", "Work status: draft, published, disabled")
|
|
190
|
+
.option("--publish-version", "Force publishing a new version")
|
|
191
|
+
.option("--work-scope <scope>", "Scope granted to the work runtime", collectOption, [])
|
|
192
|
+
.option("--viewer-scope <scope>", "Scope viewers may request", collectOption, [])
|
|
193
|
+
.option("--clear-work-scopes", "Clear work runtime scopes")
|
|
194
|
+
.option("--clear-viewer-scopes", "Clear viewer-requestable scopes")
|
|
195
|
+
.option("--meta <json>", "Work metadata as a JSON object")
|
|
196
|
+
.option("--json", "Output as JSON")
|
|
197
|
+
.action(async (id, opts) => {
|
|
198
|
+
const target = resolveTarget(opts);
|
|
199
|
+
if (opts.clearWorkScopes && opts.workScope?.length)
|
|
200
|
+
return error("Conflicting work scopes", "Use either --work-scope or --clear-work-scopes.");
|
|
201
|
+
if (opts.clearViewerScopes && opts.viewerScope?.length)
|
|
202
|
+
return error("Conflicting viewer scopes", "Use either --viewer-scope or --clear-viewer-scopes.");
|
|
203
|
+
const input = compactObject({
|
|
204
|
+
slug: opts.slug,
|
|
205
|
+
status: opts.status || opts.draft || opts.disabled ? resolveStatus(opts) : undefined,
|
|
206
|
+
targetType: target?.targetType,
|
|
207
|
+
targetRef: target?.targetRef,
|
|
208
|
+
publishVersion: opts.publishVersion || undefined,
|
|
209
|
+
workScopes: opts.clearWorkScopes ? [] : opts.workScope?.length ? opts.workScope : undefined,
|
|
210
|
+
allowedViewerScopes: opts.clearViewerScopes ? [] : opts.viewerScope?.length ? opts.viewerScope : undefined,
|
|
211
|
+
meta: opts.meta !== undefined ? parseJsonObject(opts.meta, "meta") ?? null : undefined,
|
|
212
|
+
});
|
|
213
|
+
if (Object.keys(input).length === 0)
|
|
214
|
+
return error("Nothing to update", "Pass --slug, --file, --dir, --port, --status, --publish-version, --work-scope, --viewer-scope, --clear-work-scopes, --clear-viewer-scopes, or --meta.");
|
|
215
|
+
const client = createClient();
|
|
216
|
+
try {
|
|
217
|
+
const result = await client.works.update(id, input);
|
|
218
|
+
if (jsonRequested(opts))
|
|
219
|
+
return outJson(result);
|
|
220
|
+
ok("Work updated");
|
|
221
|
+
printWork(result.work);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
handleHttp(e);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
worksCmd
|
|
228
|
+
.command("versions <id>")
|
|
229
|
+
.description("List work versions")
|
|
230
|
+
.option("--json", "Output as JSON")
|
|
231
|
+
.action(async (id, opts) => {
|
|
232
|
+
const client = createClient();
|
|
233
|
+
try {
|
|
234
|
+
const result = await client.works.listVersions(id);
|
|
235
|
+
if (jsonRequested(opts))
|
|
236
|
+
return outJson(result);
|
|
237
|
+
table(result.versions, [
|
|
238
|
+
{ key: "version", label: "Version" },
|
|
239
|
+
{ key: "id", label: "ID" },
|
|
240
|
+
{ key: "status", label: "Status" },
|
|
241
|
+
{ key: "targetType", label: "Target" },
|
|
242
|
+
{ key: "targetRef", label: "Ref" },
|
|
243
|
+
{ key: "publishedAt", label: "Published" },
|
|
244
|
+
]);
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
handleHttp(e);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
worksCmd
|
|
251
|
+
.command("rm <id>")
|
|
252
|
+
.alias("delete")
|
|
253
|
+
.description("Delete a work")
|
|
254
|
+
.option("-y, --yes", "Confirm deletion")
|
|
255
|
+
.action(async (id, opts) => {
|
|
256
|
+
await confirmDelete(opts);
|
|
257
|
+
const client = createClient();
|
|
258
|
+
try {
|
|
259
|
+
await client.works.delete(id);
|
|
260
|
+
ok("Work deleted");
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
handleHttp(e);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerProfile } from "./commands/profile.js";
|
|
|
10
10
|
import { registerSearch } from "./commands/search.js";
|
|
11
11
|
import { registerPrompt, registerSpaces } from "./commands/spaces.js";
|
|
12
12
|
import { registerTasks } from "./commands/tasks.js";
|
|
13
|
+
import { registerWorks } from "./commands/works.js";
|
|
13
14
|
import { ensureCliSelfUpdated } from "./self-update.js";
|
|
14
15
|
const VERSION = (() => {
|
|
15
16
|
try {
|
|
@@ -39,6 +40,7 @@ Common commands:
|
|
|
39
40
|
cohub search "release notes"
|
|
40
41
|
cohub -s <space-id> spaces sessions turns ls <session-id>
|
|
41
42
|
cohub -s <space-id> spaces files ls
|
|
43
|
+
cohub -s <space-id> works publish demo --file dist/index.html
|
|
42
44
|
cohub models ls
|
|
43
45
|
cohub models ls --model-type multimodal
|
|
44
46
|
cohub generate "A calm lake at sunrise" --model <model> --output lake.png
|
|
@@ -57,6 +59,7 @@ registerModels(program);
|
|
|
57
59
|
registerSearch(program);
|
|
58
60
|
registerTasks(program);
|
|
59
61
|
registerCronJobs(program);
|
|
62
|
+
registerWorks(program);
|
|
60
63
|
const isVersionRequest = (argv) => argv.some((arg) => arg === "-v" || arg === "--version");
|
|
61
64
|
try {
|
|
62
65
|
if (!isVersionRequest(process.argv.slice(2))) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.5",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@neta-art/generation": "^0.1.5",
|
|
17
17
|
"commander": "^14.0.3",
|
|
18
18
|
"sharp": "^0.34.5",
|
|
19
|
-
"@neta-art/cohub": "1.28.
|
|
19
|
+
"@neta-art/cohub": "1.28.2"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|