@neta-art/cohub-cli 6.6.0 → 6.8.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 +6 -6
- package/dist/commands/apps.js +97 -11
- package/dist/commands/auth.js +2 -0
- package/dist/commands/boards/context.js +1 -1
- package/dist/commands/boards.js +9 -9
- package/dist/commands/desktop.js +1 -0
- package/dist/commands/generations.js +1 -1
- package/dist/commands/public.js +3 -3
- package/dist/commands/run.js +6 -6
- package/dist/commands/sandbox.js +16 -12
- package/dist/commands/sandboxd-binary.js +1 -1
- package/dist/commands/space-activity.js +1 -1
- package/dist/commands/space-commerce.js +15 -15
- package/dist/commands/space-invitations.js +3 -3
- package/dist/commands/space-turns.js +4 -4
- package/dist/commands/spaces.js +57 -57
- package/dist/index.js +8 -4
- package/dist/space.d.ts +31 -1
- package/dist/space.js +117 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -299,10 +299,9 @@ Confirm before deleting files or directories.
|
|
|
299
299
|
|
|
300
300
|
Publish and manage Apps from a Space workspace. Public App URLs require a username and a Space slug.
|
|
301
301
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
Space-side path.
|
|
302
|
+
`apps publish` detects the source from the runtime by default: local CLI runs use local
|
|
303
|
+
filesystem paths, while Cohub Sandbox runs use Space workspace paths. Pass
|
|
304
|
+
`--source workspace` or `--source local` to override the default explicitly.
|
|
306
305
|
|
|
307
306
|
```bash
|
|
308
307
|
cohub profile update --username <username>
|
|
@@ -311,8 +310,9 @@ cohub -s <spaceId> apps ls --json
|
|
|
311
310
|
cohub apps get <appId|url|username/space/app> --json
|
|
312
311
|
cohub apps stats <appId|url|username/space/app>
|
|
313
312
|
cohub apps download <appId|url|username/space/app> --output <path>
|
|
314
|
-
cohub -s <spaceId> apps publish demo --file dist/index.html
|
|
315
|
-
cohub -s <spaceId> apps publish site --dir dist
|
|
313
|
+
cohub -s <spaceId> apps publish demo --file ./dist/index.html
|
|
314
|
+
cohub -s <spaceId> apps publish site --dir ./dist
|
|
315
|
+
cohub -s <spaceId> apps publish site --source workspace --dir dist
|
|
316
316
|
cohub -s <spaceId> apps publish app --port 3000
|
|
317
317
|
cohub apps publish-version <appId>
|
|
318
318
|
cohub apps versions <appId> --json
|
package/dist/commands/apps.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { getCohubContext, HttpError } from "@neta-art/cohub";
|
|
2
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";
|
|
@@ -6,6 +9,7 @@ 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];
|
|
@@ -57,6 +61,72 @@ function withCohubBarMeta(input) {
|
|
|
57
61
|
delete meta.presentation;
|
|
58
62
|
return Object.keys(meta).length > 0 ? meta : null;
|
|
59
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
|
+
}
|
|
127
|
+
function resolveDefaultAppSource() {
|
|
128
|
+
return getCohubContext().runtime.kind === "sandbox" ? "workspace" : "local";
|
|
129
|
+
}
|
|
60
130
|
function resolveTarget(opts) {
|
|
61
131
|
const targets = [
|
|
62
132
|
opts.file ? { targetType: "file", targetRef: opts.file } : null,
|
|
@@ -197,7 +267,7 @@ export function registerApps(program) {
|
|
|
197
267
|
.description("List apps in the target space")
|
|
198
268
|
.option("--json", "Output as JSON")
|
|
199
269
|
.action(async (opts) => {
|
|
200
|
-
const spaceId = resolveSpace(appsCmd);
|
|
270
|
+
const spaceId = await resolveSpace(appsCmd);
|
|
201
271
|
const client = createClient();
|
|
202
272
|
try {
|
|
203
273
|
const result = await client.apps.listBySpace(spaceId);
|
|
@@ -321,8 +391,9 @@ export function registerApps(program) {
|
|
|
321
391
|
appsCmd
|
|
322
392
|
.command("publish <slug>")
|
|
323
393
|
.description("Create or publish an app in the target space")
|
|
324
|
-
.option("--
|
|
325
|
-
.option("--
|
|
394
|
+
.option("--source <source>", "Source: workspace or local (auto-detected from runtime by default)")
|
|
395
|
+
.option("--file <path>", "Publish a file from the selected source")
|
|
396
|
+
.option("--dir <path>", "Publish a directory site from the selected source")
|
|
326
397
|
.option("--port <port>", "Publish a public sandbox port")
|
|
327
398
|
.option("--disabled", "Create as disabled")
|
|
328
399
|
.option("--status <status>", "App status: published, disabled")
|
|
@@ -339,27 +410,42 @@ export function registerApps(program) {
|
|
|
339
410
|
const target = resolveTarget(opts);
|
|
340
411
|
if (!target)
|
|
341
412
|
return error("Missing target", "Use one of --file, --dir, or --port.");
|
|
342
|
-
const
|
|
413
|
+
const source = opts.source ? parseChoice(opts.source, "source", ["workspace", "local"]) : resolveDefaultAppSource();
|
|
414
|
+
if (target.targetType === "port" && opts.source)
|
|
415
|
+
return error("Invalid source", "--source applies only to --file and --dir.");
|
|
416
|
+
const spaceId = await resolveSpace(appsCmd);
|
|
343
417
|
const client = createClient();
|
|
344
|
-
|
|
345
|
-
|
|
418
|
+
let { targetType, targetRef } = target;
|
|
419
|
+
let sourceRef = null;
|
|
420
|
+
if (source === "local") {
|
|
421
|
+
const uploaded = await uploadLocalAppSource(client, spaceId, target);
|
|
422
|
+
if (!uploaded)
|
|
423
|
+
return error("Invalid source", "--source local applies only to --file and --dir.");
|
|
424
|
+
targetRef = uploaded.targetRef;
|
|
425
|
+
sourceRef = uploaded.sourceRef;
|
|
426
|
+
}
|
|
427
|
+
else if (targetType !== "port") {
|
|
346
428
|
await guardAppTarget(client, spaceId, { targetType, targetRef });
|
|
429
|
+
}
|
|
347
430
|
const status = resolveStatus(opts);
|
|
348
431
|
const meta = withCohubBarMeta({
|
|
349
432
|
meta: parseJsonObject(opts.meta, "meta"),
|
|
350
433
|
hideCohubBar: opts.hideCohubBar,
|
|
351
434
|
showCohubBar: opts.showCohubBar,
|
|
352
435
|
});
|
|
436
|
+
const publishMeta = source === "local"
|
|
437
|
+
? { ...(meta ?? {}), runtime: { source: { type: "upload", ref: sourceRef } } }
|
|
438
|
+
: meta;
|
|
353
439
|
const input = {
|
|
354
440
|
spaceId,
|
|
355
441
|
slug,
|
|
356
442
|
status,
|
|
357
443
|
visibility: resolveVisibility(opts.visibility),
|
|
358
444
|
targetType: target.targetType,
|
|
359
|
-
targetRef
|
|
445
|
+
targetRef,
|
|
360
446
|
appScopes: opts.appScope,
|
|
361
447
|
allowedViewerScopes: opts.viewerScope,
|
|
362
|
-
meta,
|
|
448
|
+
meta: publishMeta,
|
|
363
449
|
};
|
|
364
450
|
try {
|
|
365
451
|
const result = await client.apps.create(input);
|
|
@@ -381,10 +467,10 @@ export function registerApps(program) {
|
|
|
381
467
|
status: status === "published" && existingApp.status !== "published" ? existingApp.status : status,
|
|
382
468
|
visibility: resolveVisibility(opts.visibility),
|
|
383
469
|
targetType: target.targetType,
|
|
384
|
-
targetRef
|
|
470
|
+
targetRef,
|
|
385
471
|
appScopes: opts.appScope,
|
|
386
472
|
allowedViewerScopes: opts.viewerScope,
|
|
387
|
-
meta,
|
|
473
|
+
meta: publishMeta,
|
|
388
474
|
});
|
|
389
475
|
const publishedVersion = status === "published"
|
|
390
476
|
? await client.apps.publishVersion(app.id)
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { authSource, loginWithDeviceFlow, readAuthSession, refreshAccessToken, requestDeviceCode, revokeAndClearAuthSession, verifyDeviceCode } from "../auth.js";
|
|
2
|
+
import { clearDefaultSpaceCache } from "../space.js";
|
|
2
3
|
import { createClient } from "../client.js";
|
|
3
4
|
import { table, json as outJson, jsonRequested, ok, error, spinner, handleHttp } from "../output.js";
|
|
4
5
|
export function registerAuth(program) {
|
|
@@ -67,6 +68,7 @@ export function registerAuth(program) {
|
|
|
67
68
|
.description("Clear stored Logto session")
|
|
68
69
|
.action(async () => {
|
|
69
70
|
await revokeAndClearAuthSession();
|
|
71
|
+
clearDefaultSpaceCache();
|
|
70
72
|
if (process.env.COHUB_EXECUTION_TOKEN?.trim()) {
|
|
71
73
|
ok("Local session cleared. COHUB_EXECUTION_TOKEN is still set.");
|
|
72
74
|
}
|
|
@@ -24,7 +24,7 @@ export function readOptions(command, label = "Comma-separated resource IDs") {
|
|
|
24
24
|
return command.option("--ids <ids>", label);
|
|
25
25
|
}
|
|
26
26
|
export async function resolvedBoard(boards, target) {
|
|
27
|
-
const spaceId = resolveSpace(boards);
|
|
27
|
+
const spaceId = await resolveSpace(boards);
|
|
28
28
|
const boardId = await resolveBoardId(spaceId, target);
|
|
29
29
|
return createClient().space(spaceId).board(boardId);
|
|
30
30
|
}
|
package/dist/commands/boards.js
CHANGED
|
@@ -171,7 +171,7 @@ function registerExportCommand(boards) {
|
|
|
171
171
|
if (!out)
|
|
172
172
|
throw new Error("--out is required");
|
|
173
173
|
const result = await runBoardExport({
|
|
174
|
-
spaceId: resolveSpace(boards),
|
|
174
|
+
spaceId: await resolveSpace(boards),
|
|
175
175
|
target: board,
|
|
176
176
|
region: parseExportRegion(options),
|
|
177
177
|
scale: parseNumber(options.scale ?? "2", "scale", { min: 0.01, max: 16 }),
|
|
@@ -213,7 +213,7 @@ export function registerBoards(program) {
|
|
|
213
213
|
const boards = program
|
|
214
214
|
.command("boards")
|
|
215
215
|
.description("Inspect and update Boards by ID or .board path")
|
|
216
|
-
.hook("preAction", () => { resolveSpace(boards); });
|
|
216
|
+
.hook("preAction", async () => { await resolveSpace(boards); });
|
|
217
217
|
withJson(boards.command("create <path>")
|
|
218
218
|
.description("Create a Board")
|
|
219
219
|
.option("--title <title>", "Board title")
|
|
@@ -239,7 +239,7 @@ Generate an editable seed:
|
|
|
239
239
|
(typeof content.mutationId === "string" ? content.mutationId : randomUUID()),
|
|
240
240
|
...(options.title ? { title: options.title } : {}),
|
|
241
241
|
};
|
|
242
|
-
const result = await createClient().space(resolveSpace(boards)).boards.create(input);
|
|
242
|
+
const result = await createClient().space(await resolveSpace(boards)).boards.create(input);
|
|
243
243
|
if (jsonRequested(options))
|
|
244
244
|
return outJson({ ...result, path });
|
|
245
245
|
ok(`Board created: ${path}`);
|
|
@@ -262,7 +262,7 @@ Apply multiple changes atomically:
|
|
|
262
262
|
cohub boards batch <board> --input changes.json --dry-run`))
|
|
263
263
|
.action(async (target, options) => {
|
|
264
264
|
try {
|
|
265
|
-
const spaceId = resolveSpace(boards);
|
|
265
|
+
const spaceId = await resolveSpace(boards);
|
|
266
266
|
const boardId = await resolveBoardId(spaceId, target);
|
|
267
267
|
const result = await createClient().space(spaceId).board(boardId).summary();
|
|
268
268
|
if (jsonRequested(options))
|
|
@@ -283,7 +283,7 @@ Coordinates:
|
|
|
283
283
|
Use boards examples for editable starter JSON.`))
|
|
284
284
|
.action(async (target, options) => {
|
|
285
285
|
try {
|
|
286
|
-
const spaceId = resolveSpace(boards);
|
|
286
|
+
const spaceId = await resolveSpace(boards);
|
|
287
287
|
const boardId = await resolveBoardId(spaceId, target);
|
|
288
288
|
const result = await createClient().space(spaceId).board(boardId).capabilities();
|
|
289
289
|
if (jsonRequested(options))
|
|
@@ -330,7 +330,7 @@ Use boards examples for editable starter JSON.`))
|
|
|
330
330
|
.option("--command-id <id>", "Idempotency command ID"))
|
|
331
331
|
.action(async (target, compositionId, options) => {
|
|
332
332
|
try {
|
|
333
|
-
const spaceId = resolveSpace(boards);
|
|
333
|
+
const spaceId = await resolveSpace(boards);
|
|
334
334
|
const boardId = await resolveBoardId(spaceId, target);
|
|
335
335
|
const result = await createClient().space(spaceId).board(boardId).play({
|
|
336
336
|
commandId: commandId(options),
|
|
@@ -351,7 +351,7 @@ Use boards examples for editable starter JSON.`))
|
|
|
351
351
|
});
|
|
352
352
|
const playbackAction = (type) => async (target, playbackId, options) => {
|
|
353
353
|
try {
|
|
354
|
-
const spaceId = resolveSpace(boards);
|
|
354
|
+
const spaceId = await resolveSpace(boards);
|
|
355
355
|
const boardId = await resolveBoardId(spaceId, target);
|
|
356
356
|
const board = createClient().space(spaceId).board(boardId);
|
|
357
357
|
const id = commandId(options);
|
|
@@ -375,7 +375,7 @@ Use boards examples for editable starter JSON.`))
|
|
|
375
375
|
.option("--command-id <id>", "Idempotency command ID"))
|
|
376
376
|
.action(async (target, playbackId, position, options) => {
|
|
377
377
|
try {
|
|
378
|
-
const spaceId = resolveSpace(boards);
|
|
378
|
+
const spaceId = await resolveSpace(boards);
|
|
379
379
|
const boardId = await resolveBoardId(spaceId, target);
|
|
380
380
|
const result = await createClient().space(spaceId).board(boardId).seek({
|
|
381
381
|
commandId: commandId(options),
|
|
@@ -399,7 +399,7 @@ Use boards examples for editable starter JSON.`))
|
|
|
399
399
|
.description("Stream Board events"))
|
|
400
400
|
.action(async (target, options) => {
|
|
401
401
|
try {
|
|
402
|
-
const spaceId = resolveSpace(boards);
|
|
402
|
+
const spaceId = await resolveSpace(boards);
|
|
403
403
|
const boardId = await resolveBoardId(spaceId, target);
|
|
404
404
|
const client = createRealtimeClient();
|
|
405
405
|
const board = client.space(spaceId).board(boardId);
|
package/dist/commands/desktop.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getAppByRef } from "../app-ref.js";
|
|
|
7
7
|
const FILE_SCHEME = "file://";
|
|
8
8
|
const APP_SCHEME = "app://";
|
|
9
9
|
const LEGACY_WORK_SCHEME = "work://";
|
|
10
|
+
/** Optional disambiguation for file:// vs app:// — do not fall back to Home. */
|
|
10
11
|
function optionalSpaceId(command) {
|
|
11
12
|
let current = command;
|
|
12
13
|
while (current) {
|
|
@@ -257,7 +257,7 @@ Examples:
|
|
|
257
257
|
`)
|
|
258
258
|
.action(async (prompt, opts) => {
|
|
259
259
|
try {
|
|
260
|
-
const spaceId = resolveSpace(program);
|
|
260
|
+
const spaceId = await resolveSpace(program);
|
|
261
261
|
const content = [{ type: "text", text: prompt }];
|
|
262
262
|
content.push(...await Promise.all(opts.image.map((value) => contentFromPathOrUrl("image", value))));
|
|
263
263
|
content.push(...await Promise.all(opts.video.map((value) => contentFromPathOrUrl("video", value))));
|
package/dist/commands/public.js
CHANGED
|
@@ -179,7 +179,7 @@ function publicUrlPrefix(destination, entry) {
|
|
|
179
179
|
}
|
|
180
180
|
async function uploadPublic(command, source, destination, opts, deps) {
|
|
181
181
|
const client = deps.createClient?.() ?? createClient();
|
|
182
|
-
const spaceId = resolveSpace(command);
|
|
182
|
+
const spaceId = await resolveSpace(command);
|
|
183
183
|
try {
|
|
184
184
|
const upload = await collectPublicUpload(source, destination);
|
|
185
185
|
const plan = await client.space(spaceId).publicFiles.createUpload({
|
|
@@ -244,7 +244,7 @@ async function uploadPublic(command, source, destination, opts, deps) {
|
|
|
244
244
|
async function listPublic(command, path, opts, deps) {
|
|
245
245
|
const client = deps.createClient?.() ?? createClient();
|
|
246
246
|
try {
|
|
247
|
-
const publicFiles = client.space(resolveSpace(command)).publicFiles;
|
|
247
|
+
const publicFiles = client.space(await resolveSpace(command)).publicFiles;
|
|
248
248
|
const entries = [];
|
|
249
249
|
let cursor;
|
|
250
250
|
do {
|
|
@@ -271,7 +271,7 @@ async function listPublic(command, path, opts, deps) {
|
|
|
271
271
|
async function printPublicUrl(command, path, deps) {
|
|
272
272
|
const client = deps.createClient?.() ?? createClient();
|
|
273
273
|
try {
|
|
274
|
-
const result = await client.space(resolveSpace(command)).publicFiles.url(path);
|
|
274
|
+
const result = await client.space(await resolveSpace(command)).publicFiles.url(path);
|
|
275
275
|
console.log(result.url);
|
|
276
276
|
}
|
|
277
277
|
catch (exception) {
|
package/dist/commands/run.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createClient } from "../client.js";
|
|
2
2
|
import { error, handleHttp, json as outJson, spinner } from "../output.js";
|
|
3
|
+
import { missingSpaceError, resolveDefaultSpace } from "../space.js";
|
|
3
4
|
const DEFAULT_WAIT_TIMEOUT_MS = (6 * 60 * 60 + 60) * 1000;
|
|
4
5
|
const DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
5
6
|
function shellQuote(value) {
|
|
@@ -43,13 +44,13 @@ function parseSpaceId(tokens) {
|
|
|
43
44
|
}
|
|
44
45
|
return undefined;
|
|
45
46
|
}
|
|
46
|
-
function parseRunCliOptions(argv) {
|
|
47
|
+
async function parseRunCliOptions(argv) {
|
|
47
48
|
const runIndex = topLevelRunIndex(argv);
|
|
48
49
|
if (runIndex < 0)
|
|
49
50
|
return error("Invalid invocation", "Use `cohub run [options] <command>`");
|
|
50
51
|
const beforeRun = argv.slice(0, runIndex);
|
|
51
52
|
const afterRun = argv.slice(runIndex + 1);
|
|
52
|
-
|
|
53
|
+
const explicitSpaceId = parseSpaceId(beforeRun) ?? process.env.COHUB_SPACE_ID?.trim() ?? "";
|
|
53
54
|
let json = beforeRun.includes("--json");
|
|
54
55
|
let async = false;
|
|
55
56
|
let commandOption = null;
|
|
@@ -103,9 +104,7 @@ function parseRunCliOptions(argv) {
|
|
|
103
104
|
if (!command) {
|
|
104
105
|
return error("No command", "Pass --command <shell command>, or use `--` followed by the command.");
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
-
return error("Missing required space", "Add -s, --space <id> before `run` or set COHUB_SPACE_ID.");
|
|
108
|
-
}
|
|
107
|
+
const spaceId = explicitSpaceId || (await resolveDefaultSpace().catch(handleHttp)) || missingSpaceError();
|
|
109
108
|
return { spaceId, json, async, command };
|
|
110
109
|
}
|
|
111
110
|
function printRunHelp() {
|
|
@@ -126,6 +125,7 @@ Examples:
|
|
|
126
125
|
cohub -s <spaceId> run -- git status -sb
|
|
127
126
|
|
|
128
127
|
Notes:
|
|
128
|
+
- Without -s or COHUB_SPACE_ID, the command targets your Home space.
|
|
129
129
|
- Use --command for commands that contain leading flags, or use -- before the shell command.
|
|
130
130
|
- The command runs in /workspace.
|
|
131
131
|
`);
|
|
@@ -199,7 +199,7 @@ async function waitForRunCompletion(taskRunId, showSpinner) {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
async function handleRunCli(argv) {
|
|
202
|
-
const opts = parseRunCliOptions(argv);
|
|
202
|
+
const opts = await parseRunCliOptions(argv);
|
|
203
203
|
const client = createClient();
|
|
204
204
|
try {
|
|
205
205
|
const { taskRunId } = await client.space(opts.spaceId).runCommand({ command: opts.command });
|
package/dist/commands/sandbox.js
CHANGED
|
@@ -5,7 +5,7 @@ import { basename, resolve } from "node:path";
|
|
|
5
5
|
import { resolveCohubEnvironment, resolveWebsocketUrl } from "@neta-art/cohub";
|
|
6
6
|
import { requireAccessToken } from "../auth.js";
|
|
7
7
|
import { createClient } from "../client.js";
|
|
8
|
-
import { error, json as outJson, jsonRequested, ok, spinner } from "../output.js";
|
|
8
|
+
import { error, handleHttp, json as outJson, jsonRequested, ok, spinner } from "../output.js";
|
|
9
9
|
import { resolveSpace } from "../space.js";
|
|
10
10
|
import { ensureSandboxdBinary, SandboxdDownloadError } from "./sandboxd-binary.js";
|
|
11
11
|
// Derive the gateway relay control endpoint from the realtime websocket URL,
|
|
@@ -154,18 +154,22 @@ export function registerSandbox(program) {
|
|
|
154
154
|
.option("-s, --space <id>", "Target space ID")
|
|
155
155
|
.option("--json", "Output as JSON")
|
|
156
156
|
.action(async (opts) => {
|
|
157
|
-
const spaceId = opts.space?.trim() || resolveSpace(program);
|
|
157
|
+
const spaceId = opts.space?.trim() || await resolveSpace(program);
|
|
158
158
|
const client = createClient();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
try {
|
|
160
|
+
const sandbox = (await client.space(spaceId).sandbox.get()).sandbox ?? null;
|
|
161
|
+
if (jsonRequested(opts))
|
|
162
|
+
return outJson({ spaceId, sandbox });
|
|
163
|
+
if (!sandbox) {
|
|
164
|
+
console.log(" (no sandbox)");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
console.log(` space: ${spaceId}`);
|
|
168
|
+
console.log(` provider: ${sandbox.provider ?? "cloud"}`);
|
|
169
|
+
console.log(` status: ${sandbox.status ?? "unknown"}`);
|
|
170
|
+
}
|
|
171
|
+
catch (cause) {
|
|
172
|
+
handleHttp(cause);
|
|
166
173
|
}
|
|
167
|
-
console.log(` space: ${spaceId}`);
|
|
168
|
-
console.log(` provider: ${sandbox.provider ?? "cloud"}`);
|
|
169
|
-
console.log(` status: ${sandbox.status ?? "unknown"}`);
|
|
170
174
|
});
|
|
171
175
|
}
|
|
@@ -19,7 +19,7 @@ const BINARY_NAME = "cohub-sandboxd";
|
|
|
19
19
|
// Public CDN prefix hosting the release archives (the repo is private, so the
|
|
20
20
|
// GitHub Release assets are not publicly downloadable). Overridable for staging
|
|
21
21
|
// or self-hosting.
|
|
22
|
-
const cdnBaseUrl = () => (process.env.COHUB_SANDBOXD_CDN_BASE_URL?.trim() || "https://public.cohub.
|
|
22
|
+
const cdnBaseUrl = () => (process.env.COHUB_SANDBOXD_CDN_BASE_URL?.trim() || "https://public.cohub.live/sandboxd").replace(/\/+$/, "");
|
|
23
23
|
const DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
24
24
|
const LOCK_STALE_MS = 5 * 60 * 1000;
|
|
25
25
|
// Map Node's platform/arch to the Go GOOS/GOARCH used in release asset names.
|
|
@@ -124,7 +124,7 @@ export function registerSpaceActivity(spacesCmd, dependencies = {}) {
|
|
|
124
124
|
.description("Space activity overview: usage, contributors, rankings")
|
|
125
125
|
.option("--json", "Output as JSON")
|
|
126
126
|
.action(async (days, opts) => {
|
|
127
|
-
const spaceId = resolveSpace(spacesCmd);
|
|
127
|
+
const spaceId = await resolveSpace(spacesCmd);
|
|
128
128
|
let parsedDays;
|
|
129
129
|
try {
|
|
130
130
|
parsedDays = parseActivityDays(days);
|
|
@@ -153,8 +153,8 @@ async function confirmDanger(opts, detail) {
|
|
|
153
153
|
if (answer !== "y" && answer !== "yes")
|
|
154
154
|
return error("Cancelled");
|
|
155
155
|
}
|
|
156
|
-
function commerceClient(spacesCmd) {
|
|
157
|
-
const spaceId = resolveSpace(spacesCmd);
|
|
156
|
+
async function commerceClient(spacesCmd) {
|
|
157
|
+
const spaceId = await resolveSpace(spacesCmd);
|
|
158
158
|
return { spaceId, commerce: createClient().space(spaceId).commerce };
|
|
159
159
|
}
|
|
160
160
|
export function registerSpaceCommerce(spacesCmd) {
|
|
@@ -172,7 +172,7 @@ Examples:
|
|
|
172
172
|
.description("Initialize commerce for the target space")
|
|
173
173
|
.option("--json", "Output as JSON")
|
|
174
174
|
.action(async (opts) => {
|
|
175
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
175
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
176
176
|
try {
|
|
177
177
|
const result = await commerce.setup();
|
|
178
178
|
if (jsonRequested(opts))
|
|
@@ -196,7 +196,7 @@ Examples:
|
|
|
196
196
|
.description("List products")
|
|
197
197
|
.option("--json", "Output as JSON")
|
|
198
198
|
.action(async (opts) => {
|
|
199
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
199
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
200
200
|
try {
|
|
201
201
|
const result = await commerce.listProducts();
|
|
202
202
|
if (jsonRequested(opts))
|
|
@@ -235,7 +235,7 @@ Examples:
|
|
|
235
235
|
status: parseChoice(opts.status, "status", PRODUCT_STATUSES),
|
|
236
236
|
visibility: parseChoice(opts.visibility, "visibility", PRODUCT_VISIBILITIES),
|
|
237
237
|
};
|
|
238
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
238
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
239
239
|
try {
|
|
240
240
|
const result = await commerce.createProduct(input);
|
|
241
241
|
if (jsonRequested(opts))
|
|
@@ -269,7 +269,7 @@ Examples:
|
|
|
269
269
|
});
|
|
270
270
|
if (Object.keys(input).length === 0)
|
|
271
271
|
return error("Nothing to update", "Pass --name, --description, --clear-description, --status, or --visibility.");
|
|
272
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
272
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
273
273
|
try {
|
|
274
274
|
const result = await commerce.updateProduct(productKey, input);
|
|
275
275
|
if (jsonRequested(opts))
|
|
@@ -290,7 +290,7 @@ Examples:
|
|
|
290
290
|
.action(async (opts) => {
|
|
291
291
|
const productKey = requireText(opts.productKey, "product key", "--product-key <key>");
|
|
292
292
|
await confirmDanger(opts, `archive product "${productKey}"`);
|
|
293
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
293
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
294
294
|
try {
|
|
295
295
|
const result = await commerce.updateProduct(productKey, { status: "archived" });
|
|
296
296
|
if (jsonRequested(opts))
|
|
@@ -314,7 +314,7 @@ Examples:
|
|
|
314
314
|
.description("List benefits")
|
|
315
315
|
.option("--json", "Output as JSON")
|
|
316
316
|
.action(async (opts) => {
|
|
317
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
317
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
318
318
|
try {
|
|
319
319
|
const result = await commerce.listBenefits();
|
|
320
320
|
if (jsonRequested(opts))
|
|
@@ -353,7 +353,7 @@ Examples:
|
|
|
353
353
|
? parseInteger(opts.expiresInDays, "expires-in-days", { fallback: 0, min: 1 })
|
|
354
354
|
: undefined,
|
|
355
355
|
};
|
|
356
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
356
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
357
357
|
try {
|
|
358
358
|
const result = await commerce.createBenefit(input);
|
|
359
359
|
if (jsonRequested(opts))
|
|
@@ -373,7 +373,7 @@ Examples:
|
|
|
373
373
|
type: "feature",
|
|
374
374
|
metadata: parseMetadataJson(opts.metadataJson),
|
|
375
375
|
};
|
|
376
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
376
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
377
377
|
try {
|
|
378
378
|
const result = await commerce.createBenefit(input);
|
|
379
379
|
if (jsonRequested(opts))
|
|
@@ -407,7 +407,7 @@ Examples:
|
|
|
407
407
|
});
|
|
408
408
|
if (Object.keys(input).length === 0)
|
|
409
409
|
return error("Nothing to update", "Pass --name, --description, --clear-description, --status, or --metadata-json.");
|
|
410
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
410
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
411
411
|
try {
|
|
412
412
|
const result = await commerce.updateBenefit(benefitKey, input);
|
|
413
413
|
if (jsonRequested(opts))
|
|
@@ -428,7 +428,7 @@ Examples:
|
|
|
428
428
|
.action(async (opts) => {
|
|
429
429
|
const benefitKey = requireText(opts.benefitKey, "benefit key", "--benefit-key <key>");
|
|
430
430
|
await confirmDanger(opts, `archive benefit "${benefitKey}"`);
|
|
431
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
431
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
432
432
|
try {
|
|
433
433
|
const result = await commerce.updateBenefit(benefitKey, { status: "archived" });
|
|
434
434
|
if (jsonRequested(opts))
|
|
@@ -450,7 +450,7 @@ Examples:
|
|
|
450
450
|
productKey: requireText(opts.productKey, "product key", "--product-key <key>"),
|
|
451
451
|
benefitKey: requireText(opts.benefitKey, "benefit key", "--benefit-key <key>"),
|
|
452
452
|
};
|
|
453
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
453
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
454
454
|
try {
|
|
455
455
|
const result = await commerce.bindProductBenefit(input);
|
|
456
456
|
if (jsonRequested(opts))
|
|
@@ -474,7 +474,7 @@ Examples:
|
|
|
474
474
|
benefitKey: requireText(opts.benefitKey, "benefit key", "--benefit-key <key>"),
|
|
475
475
|
};
|
|
476
476
|
await confirmDanger(opts, `unbind benefit "${input.benefitKey}" from product "${input.productKey}"`);
|
|
477
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
477
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
478
478
|
try {
|
|
479
479
|
const result = await commerce.unbindProductBenefit(input);
|
|
480
480
|
if (jsonRequested(opts))
|
|
@@ -495,7 +495,7 @@ Examples:
|
|
|
495
495
|
.option("--limit <limit>", "Page size, max 50")
|
|
496
496
|
.option("--json", "Output as JSON")
|
|
497
497
|
.action(async (opts) => {
|
|
498
|
-
const { commerce } = commerceClient(spacesCmd);
|
|
498
|
+
const { commerce } = await commerceClient(spacesCmd);
|
|
499
499
|
try {
|
|
500
500
|
const result = await commerce.listOrders({
|
|
501
501
|
page: parseInteger(opts.page, "page", { fallback: 1, min: 1 }),
|
|
@@ -72,7 +72,7 @@ export function registerSpaceInvitations(spacesCommand, dependencies = {
|
|
|
72
72
|
.option("--max-uses <count>", "Usage limit, or 0 for unlimited", "0")
|
|
73
73
|
.option("--json", "Output as JSON")
|
|
74
74
|
.action(async (options) => {
|
|
75
|
-
const spaceId = resolveSpace(spacesCommand);
|
|
75
|
+
const spaceId = await resolveSpace(spacesCommand);
|
|
76
76
|
let input;
|
|
77
77
|
try {
|
|
78
78
|
input = parseSpaceInvitationCreateOptions(options);
|
|
@@ -111,7 +111,7 @@ export function registerSpaceInvitations(spacesCommand, dependencies = {
|
|
|
111
111
|
.description("List invite links")
|
|
112
112
|
.option("--json", "Output as JSON")
|
|
113
113
|
.action(async (options) => {
|
|
114
|
-
const spaceId = resolveSpace(spacesCommand);
|
|
114
|
+
const spaceId = await resolveSpace(spacesCommand);
|
|
115
115
|
try {
|
|
116
116
|
const result = await dependencies
|
|
117
117
|
.createClient()
|
|
@@ -150,7 +150,7 @@ export function registerSpaceInvitations(spacesCommand, dependencies = {
|
|
|
150
150
|
.option("--json", "Output as JSON")
|
|
151
151
|
.action(async (code, options) => {
|
|
152
152
|
await confirmRevoke(options);
|
|
153
|
-
const spaceId = resolveSpace(spacesCommand);
|
|
153
|
+
const spaceId = await resolveSpace(spacesCommand);
|
|
154
154
|
try {
|
|
155
155
|
const result = await dependencies
|
|
156
156
|
.createClient()
|
|
@@ -82,8 +82,8 @@ export function registerSpaceTurns(spacesCmd, dependencies = {}) {
|
|
|
82
82
|
const turnsCmd = spacesCmd
|
|
83
83
|
.command("turns")
|
|
84
84
|
.description("Browse turns across the space")
|
|
85
|
-
.hook("preAction", () => {
|
|
86
|
-
resolveSpace(spacesCmd);
|
|
85
|
+
.hook("preAction", async () => {
|
|
86
|
+
await resolveSpace(spacesCmd);
|
|
87
87
|
});
|
|
88
88
|
turnsCmd
|
|
89
89
|
.command("ls")
|
|
@@ -108,7 +108,7 @@ export function registerSpaceTurns(spacesCmd, dependencies = {}) {
|
|
|
108
108
|
}
|
|
109
109
|
throw cause;
|
|
110
110
|
}
|
|
111
|
-
const spaceId = resolveSpace(spacesCmd);
|
|
111
|
+
const spaceId = await resolveSpace(spacesCmd);
|
|
112
112
|
const client = dependencies.createClient?.() ?? createClient();
|
|
113
113
|
try {
|
|
114
114
|
try {
|
|
@@ -179,7 +179,7 @@ export function registerSpaceTurns(spacesCmd, dependencies = {}) {
|
|
|
179
179
|
.description("Read persisted intermediate messages from the CDN archive")
|
|
180
180
|
.option("--json", "Output as JSON")
|
|
181
181
|
.action(async (sessionId, turnId, options) => {
|
|
182
|
-
const spaceId = resolveSpace(spacesCmd);
|
|
182
|
+
const spaceId = await resolveSpace(spacesCmd);
|
|
183
183
|
const client = dependencies.createClient?.() ?? createClient();
|
|
184
184
|
try {
|
|
185
185
|
const archive = await client.space(spaceId).session(sessionId).turns.intermediate.get(turnId);
|