@neta-art/cohub-cli 6.5.0 → 6.7.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/dist/client.d.ts +1 -0
- package/dist/client.js +3 -0
- package/dist/commands/app-commerce.js +9 -5
- package/dist/commands/apps.js +128 -9
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -10,6 +10,9 @@ const clientOptions = () => ({
|
|
|
10
10
|
export function createClient() {
|
|
11
11
|
return new CohubHttpClient(clientOptions());
|
|
12
12
|
}
|
|
13
|
+
export function createClientWithAccessToken(token) {
|
|
14
|
+
return new CohubHttpClient({ ...clientOptions(), getAccessToken: () => token });
|
|
15
|
+
}
|
|
13
16
|
export function createRealtimeClient() {
|
|
14
17
|
return new CohubClient(clientOptions());
|
|
15
18
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveExecutionAppId, } from "@neta-art/cohub";
|
|
1
2
|
import { createClient } from "../client.js";
|
|
2
3
|
import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
|
|
3
4
|
function collectOption(value, previous = []) {
|
|
@@ -9,6 +10,9 @@ function requireText(value, label, flag) {
|
|
|
9
10
|
return text;
|
|
10
11
|
return error(`Missing ${label}`, `Pass ${flag}.`);
|
|
11
12
|
}
|
|
13
|
+
function requireAppId(value) {
|
|
14
|
+
return requireText(value ?? resolveExecutionAppId() ?? undefined, "app ID", "--app-id <id>");
|
|
15
|
+
}
|
|
12
16
|
function requireList(values, label, flag) {
|
|
13
17
|
const items = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))];
|
|
14
18
|
if (items.length > 0)
|
|
@@ -89,7 +93,7 @@ Examples:
|
|
|
89
93
|
.option("--product-key <key>", "Product key", collectOption)
|
|
90
94
|
.option("--json", "Output as JSON")
|
|
91
95
|
.action(async (opts) => {
|
|
92
|
-
const appId =
|
|
96
|
+
const appId = requireAppId(opts.appId);
|
|
93
97
|
const productKeys = requireList(opts.productKey, "product key", "--product-key <key>");
|
|
94
98
|
const client = createClient();
|
|
95
99
|
try {
|
|
@@ -108,7 +112,7 @@ Examples:
|
|
|
108
112
|
.option("--app-id <id>", "App ID")
|
|
109
113
|
.option("--json", "Output as JSON")
|
|
110
114
|
.action(async (opts) => {
|
|
111
|
-
const appId =
|
|
115
|
+
const appId = requireAppId(opts.appId);
|
|
112
116
|
const client = createClient();
|
|
113
117
|
try {
|
|
114
118
|
const result = await client.appCommerce.getEntitlements(appId);
|
|
@@ -132,7 +136,7 @@ Examples:
|
|
|
132
136
|
.option("--reason <text>", "Reason for the consumption")
|
|
133
137
|
.option("--json", "Output as JSON")
|
|
134
138
|
.action(async (opts) => {
|
|
135
|
-
const appId =
|
|
139
|
+
const appId = requireAppId(opts.appId);
|
|
136
140
|
const amountText = requireText(opts.amount, "amount", "--amount <n>");
|
|
137
141
|
const amount = Number.parseInt(amountText, 10);
|
|
138
142
|
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
@@ -172,7 +176,7 @@ Examples:
|
|
|
172
176
|
.option("--product-key <key>", "Product key")
|
|
173
177
|
.option("--json", "Output as JSON")
|
|
174
178
|
.action(async (opts) => {
|
|
175
|
-
const appId =
|
|
179
|
+
const appId = requireAppId(opts.appId);
|
|
176
180
|
const productKey = requireText(opts.productKey, "product key", "--product-key <key>");
|
|
177
181
|
const client = createClient();
|
|
178
182
|
try {
|
|
@@ -210,7 +214,7 @@ Examples:
|
|
|
210
214
|
.option("--order-id <id>", "Order ID")
|
|
211
215
|
.option("--json", "Output as JSON")
|
|
212
216
|
.action(async (opts) => {
|
|
213
|
-
const appId =
|
|
217
|
+
const appId = requireAppId(opts.appId);
|
|
214
218
|
const orderId = requireText(opts.orderId, "order ID", "--order-id <id>");
|
|
215
219
|
const client = createClient();
|
|
216
220
|
try {
|
package/dist/commands/apps.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { basename } from "node:path";
|
|
1
4
|
import { HttpError } from "@neta-art/cohub";
|
|
2
|
-
import { createClient } from "../client.js";
|
|
5
|
+
import { createClient, createClientWithAccessToken } from "../client.js";
|
|
3
6
|
import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
|
|
4
7
|
import { resolveSpace } from "../space.js";
|
|
5
8
|
import { downloadApp } from "../app-download.js";
|
|
6
9
|
import { getAppByRef, parseAppRef } from "../app-ref.js";
|
|
7
10
|
import { checkAppTarget } from "../app-target.js";
|
|
8
11
|
import { registerAppCommerce } from "./app-commerce.js";
|
|
12
|
+
import { collectPublicUpload } from "./public.js";
|
|
9
13
|
const APP_STATUSES = ["published", "disabled"];
|
|
10
14
|
const APP_VISIBILITIES = ["public", "space"];
|
|
11
15
|
const collectOption = (value, previous = []) => [...previous, value];
|
|
@@ -27,6 +31,16 @@ function parseJsonObject(value, name) {
|
|
|
27
31
|
}
|
|
28
32
|
return error(`Invalid ${name}`, `${name} must be a JSON object`);
|
|
29
33
|
}
|
|
34
|
+
function parseJsonValue(value, name) {
|
|
35
|
+
if (!value)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(value);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return error(`Invalid ${name}`, `${name} must be valid JSON`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
30
44
|
function compactObject(input) {
|
|
31
45
|
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
32
46
|
}
|
|
@@ -47,6 +61,69 @@ function withCohubBarMeta(input) {
|
|
|
47
61
|
delete meta.presentation;
|
|
48
62
|
return Object.keys(meta).length > 0 ? meta : null;
|
|
49
63
|
}
|
|
64
|
+
const MAX_APP_SOURCE_FILES = 1000;
|
|
65
|
+
const MAX_APP_SOURCE_BYTES = 1024 * 1024 * 1024;
|
|
66
|
+
const APP_SOURCE_UPLOAD_CONCURRENCY = 4;
|
|
67
|
+
async function uploadLocalAppSource(client, spaceId, source) {
|
|
68
|
+
if (source.targetType === "port")
|
|
69
|
+
return null;
|
|
70
|
+
const upload = await collectPublicUpload(source.targetRef);
|
|
71
|
+
const totalBytes = upload.files.reduce((sum, file) => sum + file.size, 0);
|
|
72
|
+
if (upload.files.length > MAX_APP_SOURCE_FILES)
|
|
73
|
+
return error("App source is too large", `Use no more than ${MAX_APP_SOURCE_FILES} files.`);
|
|
74
|
+
if (totalBytes > MAX_APP_SOURCE_BYTES)
|
|
75
|
+
return error("App source is too large", "The total source size must not exceed 1 GiB.");
|
|
76
|
+
const uploadId = randomUUID();
|
|
77
|
+
const directoryPrefix = source.targetType === "directory" ? upload.destination.replace(/\/$/, "") : "";
|
|
78
|
+
const files = new Array(upload.files.length);
|
|
79
|
+
let nextIndex = 0;
|
|
80
|
+
const workers = Array.from({ length: Math.min(APP_SOURCE_UPLOAD_CONCURRENCY, upload.files.length) }, async () => {
|
|
81
|
+
while (nextIndex < upload.files.length) {
|
|
82
|
+
const index = nextIndex++;
|
|
83
|
+
const file = upload.files[index];
|
|
84
|
+
if (!file)
|
|
85
|
+
return;
|
|
86
|
+
const plan = await client.publicAssets.createUpload({
|
|
87
|
+
purpose: "app_source",
|
|
88
|
+
uploadProtocol: "presigned_put_v1",
|
|
89
|
+
spaceId,
|
|
90
|
+
sessionId: uploadId,
|
|
91
|
+
file: { size: file.size, mimeType: file.mimeType, filename: basename(file.publicPath) },
|
|
92
|
+
});
|
|
93
|
+
const response = await fetch(plan.asset.uploadUrl, {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
headers: plan.asset.uploadHeaders,
|
|
96
|
+
body: createReadStream(file.localPath),
|
|
97
|
+
duplex: "half",
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok)
|
|
100
|
+
throw new Error(`Failed to upload ${file.publicPath}: HTTP ${response.status}`);
|
|
101
|
+
const path = directoryPrefix && file.publicPath.startsWith(`${directoryPrefix}/`)
|
|
102
|
+
? file.publicPath.slice(directoryPrefix.length + 1)
|
|
103
|
+
: basename(file.publicPath);
|
|
104
|
+
files[index] = { path, objectKey: plan.asset.objectKey, size: file.size, mimeType: file.mimeType };
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
await Promise.all(workers);
|
|
108
|
+
const manifest = JSON.stringify({ kind: "cohub.app-source", version: 1, targetType: source.targetType, files });
|
|
109
|
+
const manifestBlob = new Blob([manifest], { type: "application/json" });
|
|
110
|
+
const manifestPlan = await client.publicAssets.createUpload({
|
|
111
|
+
purpose: "app_source",
|
|
112
|
+
uploadProtocol: "presigned_put_v1",
|
|
113
|
+
spaceId,
|
|
114
|
+
sessionId: uploadId,
|
|
115
|
+
file: { size: manifestBlob.size, mimeType: "application/json", filename: "manifest.json" },
|
|
116
|
+
});
|
|
117
|
+
const manifestResponse = await fetch(manifestPlan.asset.uploadUrl, {
|
|
118
|
+
method: "PUT",
|
|
119
|
+
headers: manifestPlan.asset.uploadHeaders,
|
|
120
|
+
body: manifestBlob,
|
|
121
|
+
});
|
|
122
|
+
if (!manifestResponse.ok)
|
|
123
|
+
throw new Error(`Failed to upload app source manifest: HTTP ${manifestResponse.status}`);
|
|
124
|
+
const manifestAsset = manifestPlan.asset;
|
|
125
|
+
return { sourceRef: manifestAsset.objectKey, targetRef: source.targetType === "file" ? files[0]?.path ?? "" : "." };
|
|
126
|
+
}
|
|
50
127
|
function resolveTarget(opts) {
|
|
51
128
|
const targets = [
|
|
52
129
|
opts.file ? { targetType: "file", targetRef: opts.file } : null,
|
|
@@ -259,6 +336,32 @@ export function registerApps(program) {
|
|
|
259
336
|
handleHttp(e);
|
|
260
337
|
}
|
|
261
338
|
});
|
|
339
|
+
const actionsCmd = appsCmd
|
|
340
|
+
.command("actions")
|
|
341
|
+
.description("Run published App Actions");
|
|
342
|
+
actionsCmd
|
|
343
|
+
.command("run <app> <action>")
|
|
344
|
+
.description("Run an App Action")
|
|
345
|
+
.option("--input <json>", "JSON input", "null")
|
|
346
|
+
.option("--json", "Output as JSON")
|
|
347
|
+
.action(async (appRef, action, opts) => {
|
|
348
|
+
const client = createClient();
|
|
349
|
+
try {
|
|
350
|
+
const ref = parseAppRef(appRef);
|
|
351
|
+
const detail = "id" in ref
|
|
352
|
+
? await client.apps.getPublicById(ref.id)
|
|
353
|
+
: await client.apps.getBySlug(ref.username, ref.spaceSlug, ref.appSlug);
|
|
354
|
+
const session = await client.apps.createSession(detail.app.id);
|
|
355
|
+
const appClient = createClientWithAccessToken(session.token);
|
|
356
|
+
const result = await appClient.apps.runAction(detail.app.id, action, parseJsonValue(opts.input, "input"));
|
|
357
|
+
if (jsonRequested(opts))
|
|
358
|
+
return outJson(result);
|
|
359
|
+
ok(`App Action queued: ${result.taskRunId}`);
|
|
360
|
+
}
|
|
361
|
+
catch (e) {
|
|
362
|
+
handleHttp(e);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
262
365
|
appsCmd
|
|
263
366
|
.command("resolve <appSlug>")
|
|
264
367
|
.description("Resolve a published app by owner and space slug")
|
|
@@ -285,8 +388,9 @@ export function registerApps(program) {
|
|
|
285
388
|
appsCmd
|
|
286
389
|
.command("publish <slug>")
|
|
287
390
|
.description("Create or publish an app in the target space")
|
|
288
|
-
.option("--
|
|
289
|
-
.option("--
|
|
391
|
+
.option("--source <source>", "Source: workspace (default) or local")
|
|
392
|
+
.option("--file <path>", "Publish a file from the selected source")
|
|
393
|
+
.option("--dir <path>", "Publish a directory site from the selected source")
|
|
290
394
|
.option("--port <port>", "Publish a public sandbox port")
|
|
291
395
|
.option("--disabled", "Create as disabled")
|
|
292
396
|
.option("--status <status>", "App status: published, disabled")
|
|
@@ -303,27 +407,42 @@ export function registerApps(program) {
|
|
|
303
407
|
const target = resolveTarget(opts);
|
|
304
408
|
if (!target)
|
|
305
409
|
return error("Missing target", "Use one of --file, --dir, or --port.");
|
|
410
|
+
const source = opts.source ? parseChoice(opts.source, "source", ["workspace", "local"]) : "workspace";
|
|
411
|
+
if (target.targetType === "port" && opts.source)
|
|
412
|
+
return error("Invalid source", "--source applies only to --file and --dir.");
|
|
306
413
|
const spaceId = resolveSpace(appsCmd);
|
|
307
414
|
const client = createClient();
|
|
308
|
-
|
|
309
|
-
|
|
415
|
+
let { targetType, targetRef } = target;
|
|
416
|
+
let sourceRef = null;
|
|
417
|
+
if (source === "local") {
|
|
418
|
+
const uploaded = await uploadLocalAppSource(client, spaceId, target);
|
|
419
|
+
if (!uploaded)
|
|
420
|
+
return error("Invalid source", "--source local applies only to --file and --dir.");
|
|
421
|
+
targetRef = uploaded.targetRef;
|
|
422
|
+
sourceRef = uploaded.sourceRef;
|
|
423
|
+
}
|
|
424
|
+
else if (targetType !== "port") {
|
|
310
425
|
await guardAppTarget(client, spaceId, { targetType, targetRef });
|
|
426
|
+
}
|
|
311
427
|
const status = resolveStatus(opts);
|
|
312
428
|
const meta = withCohubBarMeta({
|
|
313
429
|
meta: parseJsonObject(opts.meta, "meta"),
|
|
314
430
|
hideCohubBar: opts.hideCohubBar,
|
|
315
431
|
showCohubBar: opts.showCohubBar,
|
|
316
432
|
});
|
|
433
|
+
const publishMeta = source === "local"
|
|
434
|
+
? { ...(meta ?? {}), runtime: { source: { type: "upload", ref: sourceRef } } }
|
|
435
|
+
: meta;
|
|
317
436
|
const input = {
|
|
318
437
|
spaceId,
|
|
319
438
|
slug,
|
|
320
439
|
status,
|
|
321
440
|
visibility: resolveVisibility(opts.visibility),
|
|
322
441
|
targetType: target.targetType,
|
|
323
|
-
targetRef
|
|
442
|
+
targetRef,
|
|
324
443
|
appScopes: opts.appScope,
|
|
325
444
|
allowedViewerScopes: opts.viewerScope,
|
|
326
|
-
meta,
|
|
445
|
+
meta: publishMeta,
|
|
327
446
|
};
|
|
328
447
|
try {
|
|
329
448
|
const result = await client.apps.create(input);
|
|
@@ -345,10 +464,10 @@ export function registerApps(program) {
|
|
|
345
464
|
status: status === "published" && existingApp.status !== "published" ? existingApp.status : status,
|
|
346
465
|
visibility: resolveVisibility(opts.visibility),
|
|
347
466
|
targetType: target.targetType,
|
|
348
|
-
targetRef
|
|
467
|
+
targetRef,
|
|
349
468
|
appScopes: opts.appScope,
|
|
350
469
|
allowedViewerScopes: opts.viewerScope,
|
|
351
|
-
meta,
|
|
470
|
+
meta: publishMeta,
|
|
352
471
|
});
|
|
353
472
|
const publishedVersion = status === "published"
|
|
354
473
|
? await client.apps.publishVersion(app.id)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
"NOTICE"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@neta-art/generation": "^0.1.
|
|
18
|
+
"@neta-art/generation": "^0.1.25",
|
|
19
19
|
"commander": "^15.0.0",
|
|
20
20
|
"pixi.js": "^8.20.1",
|
|
21
21
|
"sharp": "^0.35.4",
|
|
22
|
-
"@neta-art/cohub": "8.
|
|
22
|
+
"@neta-art/cohub": "8.10.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|