@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 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
- `--file` and `--dir` take paths relative to the Space workspace the same paths
303
- `spaces files ls` shows, not your local filesystem. To publish local build
304
- output, upload it first (`spaces files upload <dir>`), then publish the
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
@@ -1,4 +1,7 @@
1
- import { HttpError } from "@neta-art/cohub";
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("--file <path>", "Publish a file (HTML page, board, or any other file) from the Space workspace")
325
- .option("--dir <path>", "Publish a directory site from the Space workspace")
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 spaceId = resolveSpace(appsCmd);
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
- const { targetType, targetRef } = target;
345
- if (targetType !== "port")
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: target.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: target.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)
@@ -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
  }
@@ -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);
@@ -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))));
@@ -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) {
@@ -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
- let spaceId = parseSpaceId(beforeRun) ?? process.env.COHUB_SPACE_ID?.trim() ?? "";
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
- if (!spaceId) {
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 });
@@ -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
- const result = await client.space(spaceId).sandbox.get().catch(() => null);
160
- const sandbox = result?.sandbox ?? null;
161
- if (jsonRequested(opts))
162
- return outJson({ spaceId, sandbox });
163
- if (!sandbox) {
164
- console.log(" (no sandbox)");
165
- return;
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.run/sandboxd").replace(/\/+$/, "");
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);