@neta-art/cohub-cli 6.7.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,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { createReadStream } from "node:fs";
3
3
  import { basename } from "node:path";
4
- import { HttpError } from "@neta-art/cohub";
4
+ import { getCohubContext, HttpError } from "@neta-art/cohub";
5
5
  import { createClient, createClientWithAccessToken } from "../client.js";
6
6
  import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
7
7
  import { resolveSpace } from "../space.js";
@@ -124,6 +124,9 @@ async function uploadLocalAppSource(client, spaceId, source) {
124
124
  const manifestAsset = manifestPlan.asset;
125
125
  return { sourceRef: manifestAsset.objectKey, targetRef: source.targetType === "file" ? files[0]?.path ?? "" : "." };
126
126
  }
127
+ function resolveDefaultAppSource() {
128
+ return getCohubContext().runtime.kind === "sandbox" ? "workspace" : "local";
129
+ }
127
130
  function resolveTarget(opts) {
128
131
  const targets = [
129
132
  opts.file ? { targetType: "file", targetRef: opts.file } : null,
@@ -264,7 +267,7 @@ export function registerApps(program) {
264
267
  .description("List apps in the target space")
265
268
  .option("--json", "Output as JSON")
266
269
  .action(async (opts) => {
267
- const spaceId = resolveSpace(appsCmd);
270
+ const spaceId = await resolveSpace(appsCmd);
268
271
  const client = createClient();
269
272
  try {
270
273
  const result = await client.apps.listBySpace(spaceId);
@@ -388,7 +391,7 @@ export function registerApps(program) {
388
391
  appsCmd
389
392
  .command("publish <slug>")
390
393
  .description("Create or publish an app in the target space")
391
- .option("--source <source>", "Source: workspace (default) or local")
394
+ .option("--source <source>", "Source: workspace or local (auto-detected from runtime by default)")
392
395
  .option("--file <path>", "Publish a file from the selected source")
393
396
  .option("--dir <path>", "Publish a directory site from the selected source")
394
397
  .option("--port <port>", "Publish a public sandbox port")
@@ -407,10 +410,10 @@ export function registerApps(program) {
407
410
  const target = resolveTarget(opts);
408
411
  if (!target)
409
412
  return error("Missing target", "Use one of --file, --dir, or --port.");
410
- const source = opts.source ? parseChoice(opts.source, "source", ["workspace", "local"]) : "workspace";
413
+ const source = opts.source ? parseChoice(opts.source, "source", ["workspace", "local"]) : resolveDefaultAppSource();
411
414
  if (target.targetType === "port" && opts.source)
412
415
  return error("Invalid source", "--source applies only to --file and --dir.");
413
- const spaceId = resolveSpace(appsCmd);
416
+ const spaceId = await resolveSpace(appsCmd);
414
417
  const client = createClient();
415
418
  let { targetType, targetRef } = target;
416
419
  let sourceRef = null;
@@ -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);
@@ -168,7 +168,7 @@ async function putUploadEntry(entry, uploadUrl, headers) {
168
168
  }
169
169
  }
170
170
  async function uploadFiles(command, paths, opts) {
171
- const spaceId = resolveSpace(command);
171
+ const spaceId = await resolveSpace(command);
172
172
  const client = createClient();
173
173
  try {
174
174
  const files = await collectUploadFiles(paths);
@@ -242,7 +242,7 @@ async function sendPrompt(command, words, opts) {
242
242
  && !new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]).has(thinkingLevel)) {
243
243
  return error("Invalid thinking level", "Use off|minimal|low|medium|high|xhigh|max");
244
244
  }
245
- const spaceId = resolveSpace(command);
245
+ const spaceId = await resolveSpace(command);
246
246
  const client = createClient();
247
247
  try {
248
248
  const schedule = opts.delayMs
@@ -304,7 +304,7 @@ async function sendPrompt(command, words, opts) {
304
304
  }
305
305
  }
306
306
  async function runCompletionCommand(command, words, opts) {
307
- const spaceId = resolveSpace(command);
307
+ const spaceId = await resolveSpace(command);
308
308
  const content = words.join(" ").trim();
309
309
  if (!content && process.stdin.isTTY) {
310
310
  return error("Message required", "Pass content args or pipe via stdin");
@@ -490,7 +490,7 @@ export function registerSpaces(program) {
490
490
  .description("Show space details")
491
491
  .option("--json", "Output as JSON")
492
492
  .action(async (id, opts) => {
493
- const spaceId = id?.trim() || resolveSpace(spacesCmd);
493
+ const spaceId = id?.trim() || await resolveSpace(spacesCmd);
494
494
  const client = createClient();
495
495
  try {
496
496
  const space = await client.spaces.get(spaceId);
@@ -562,7 +562,7 @@ export function registerSpaces(program) {
562
562
  .description("Upload the space avatar")
563
563
  .option("--json", "Output as JSON")
564
564
  .action(async (path, opts) => {
565
- const spaceId = resolveSpace(spacesCmd);
565
+ const spaceId = await resolveSpace(spacesCmd);
566
566
  const client = createClient();
567
567
  try {
568
568
  const asset = await uploadAvatarAsset({ client, purpose: "space_avatar", spaceId, path });
@@ -699,7 +699,7 @@ export function registerSpaces(program) {
699
699
  .description("Space usage statistics (default: 30 days)")
700
700
  .option("--json", "Output as JSON")
701
701
  .action(async (days, opts) => {
702
- const spaceId = resolveSpace(spacesCmd);
702
+ const spaceId = await resolveSpace(spacesCmd);
703
703
  const client = createClient();
704
704
  try {
705
705
  const usage = await client.space(spaceId).usage.get(parseInteger(days ?? "30", "days", { min: 1 }));
@@ -737,14 +737,14 @@ function registerLabels(spacesCmd) {
737
737
  const labelsCmd = spacesCmd
738
738
  .command("labels")
739
739
  .description("Manage labels")
740
- .hook("preAction", () => { resolveSpace(spacesCmd); });
740
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
741
741
  labelsCmd
742
742
  .command("ls")
743
743
  .alias("list")
744
744
  .description("List labels")
745
745
  .option("--json", "Output as JSON")
746
746
  .action(async (opts) => {
747
- const spaceId = resolveSpace(spacesCmd);
747
+ const spaceId = await resolveSpace(spacesCmd);
748
748
  const client = createClient();
749
749
  try {
750
750
  const result = await client.space(spaceId).labels.list();
@@ -764,7 +764,7 @@ function registerLabels(spacesCmd) {
764
764
  .description("Create a label")
765
765
  .option("--json", "Output as JSON")
766
766
  .action(async (labelRef, opts) => {
767
- const spaceId = resolveSpace(spacesCmd);
767
+ const spaceId = await resolveSpace(spacesCmd);
768
768
  const client = createClient();
769
769
  try {
770
770
  const result = await client.space(spaceId).labels.create(labelRef);
@@ -784,7 +784,7 @@ function registerLabels(spacesCmd) {
784
784
  .option("--rank <n>", "Sort rank")
785
785
  .option("--json", "Output as JSON")
786
786
  .action(async (labelRef, opts) => {
787
- const spaceId = resolveSpace(spacesCmd);
787
+ const spaceId = await resolveSpace(spacesCmd);
788
788
  const client = createClient();
789
789
  try {
790
790
  const result = await client.space(spaceId).labels.update(labelRef, {
@@ -805,7 +805,7 @@ function registerLabels(spacesCmd) {
805
805
  .alias("delete")
806
806
  .description("Delete a label")
807
807
  .action(async (labelRef) => {
808
- const spaceId = resolveSpace(spacesCmd);
808
+ const spaceId = await resolveSpace(spacesCmd);
809
809
  const client = createClient();
810
810
  try {
811
811
  await client.space(spaceId).labels.delete(labelRef);
@@ -820,7 +820,7 @@ function registerLabels(spacesCmd) {
820
820
  .description("Reorder labels")
821
821
  .option("--json", "Output as JSON")
822
822
  .action(async (labelRefs, opts) => {
823
- const spaceId = resolveSpace(spacesCmd);
823
+ const spaceId = await resolveSpace(spacesCmd);
824
824
  const client = createClient();
825
825
  try {
826
826
  const result = await client.space(spaceId).labels.reorder(labelRefs);
@@ -839,7 +839,7 @@ function registerLabels(spacesCmd) {
839
839
  .option("--cursor <cursor>", "Page cursor")
840
840
  .option("--json", "Output as JSON")
841
841
  .action(async (labelRef, opts) => {
842
- const spaceId = resolveSpace(spacesCmd);
842
+ const spaceId = await resolveSpace(spacesCmd);
843
843
  const client = createClient();
844
844
  try {
845
845
  const result = await client.space(spaceId).labels.listItems(labelRef, {
@@ -864,7 +864,7 @@ function registerLabels(spacesCmd) {
864
864
  .description("Attach a label")
865
865
  .option("--json", "Output as JSON")
866
866
  .action(async (labelRef, resourceType, resourceRef, opts) => {
867
- const spaceId = resolveSpace(spacesCmd);
867
+ const spaceId = await resolveSpace(spacesCmd);
868
868
  const client = createClient();
869
869
  try {
870
870
  const result = await client.space(spaceId).labels.attach(labelRef, { resourceType: parseLabelResourceType(resourceType), resourceRef });
@@ -880,7 +880,7 @@ function registerLabels(spacesCmd) {
880
880
  .command("detach <labelRef> <resourceType> <resourceRef>")
881
881
  .description("Detach a label")
882
882
  .action(async (labelRef, resourceType, resourceRef) => {
883
- const spaceId = resolveSpace(spacesCmd);
883
+ const spaceId = await resolveSpace(spacesCmd);
884
884
  const client = createClient();
885
885
  try {
886
886
  await client.space(spaceId).labels.detach(labelRef, { resourceType: parseLabelResourceType(resourceType), resourceRef });
@@ -897,7 +897,7 @@ function registerLabels(spacesCmd) {
897
897
  .option("--remove <refs>", "Comma-separated label refs to remove")
898
898
  .option("--json", "Output as JSON")
899
899
  .action(async (resourceType, resourceRef, opts) => {
900
- const spaceId = resolveSpace(spacesCmd);
900
+ const spaceId = await resolveSpace(spacesCmd);
901
901
  const client = createClient();
902
902
  try {
903
903
  const result = await client.space(spaceId).labels.patchResourceLabels(parseLabelResourceType(resourceType), resourceRef, {
@@ -918,7 +918,7 @@ function registerLabels(spacesCmd) {
918
918
  .option("--labels <refs>", "Comma-separated label refs")
919
919
  .option("--json", "Output as JSON")
920
920
  .action(async (resourceType, resourceRef, labelRefs, opts) => {
921
- const spaceId = resolveSpace(spacesCmd);
921
+ const spaceId = await resolveSpace(spacesCmd);
922
922
  const client = createClient();
923
923
  try {
924
924
  const refs = [...parseLabelRefs(opts.labels), ...labelRefs];
@@ -936,14 +936,14 @@ function registerMods(spacesCmd) {
936
936
  const modsCmd = spacesCmd
937
937
  .command("mods")
938
938
  .description("Manage space mods")
939
- .hook("preAction", () => { resolveSpace(spacesCmd); });
939
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
940
940
  modsCmd
941
941
  .command("ls")
942
942
  .alias("list")
943
943
  .description("List mods")
944
944
  .option("--json", "Output as JSON")
945
945
  .action(async (opts) => {
946
- const spaceId = resolveSpace(spacesCmd);
946
+ const spaceId = await resolveSpace(spacesCmd);
947
947
  const client = createClient();
948
948
  try {
949
949
  const result = await client.space(spaceId).mods.list();
@@ -969,7 +969,7 @@ function registerMods(spacesCmd) {
969
969
  .option("--json", "Output as JSON")
970
970
  .action(async (modSpaceId, opts) => {
971
971
  await confirmRestart(opts);
972
- const spaceId = resolveSpace(spacesCmd);
972
+ const spaceId = await resolveSpace(spacesCmd);
973
973
  const client = createClient();
974
974
  try {
975
975
  const result = await client.space(spaceId).mods.create({ modSpaceId, name: opts.name, mountSlug: opts.slug });
@@ -988,7 +988,7 @@ function registerMods(spacesCmd) {
988
988
  .option("--json", "Output as JSON")
989
989
  .action(async (modId, opts) => {
990
990
  await confirmRestart(opts);
991
- const spaceId = resolveSpace(spacesCmd);
991
+ const spaceId = await resolveSpace(spacesCmd);
992
992
  const client = createClient();
993
993
  try {
994
994
  const result = await client.space(spaceId).mods.update(modId, { enabled: true });
@@ -1007,7 +1007,7 @@ function registerMods(spacesCmd) {
1007
1007
  .option("--json", "Output as JSON")
1008
1008
  .action(async (modId, opts) => {
1009
1009
  await confirmRestart(opts);
1010
- const spaceId = resolveSpace(spacesCmd);
1010
+ const spaceId = await resolveSpace(spacesCmd);
1011
1011
  const client = createClient();
1012
1012
  try {
1013
1013
  const result = await client.space(spaceId).mods.update(modId, { enabled: false });
@@ -1027,7 +1027,7 @@ function registerMods(spacesCmd) {
1027
1027
  .option("--json", "Output as JSON")
1028
1028
  .action(async (modId, opts) => {
1029
1029
  await confirmRestart(opts);
1030
- const spaceId = resolveSpace(spacesCmd);
1030
+ const spaceId = await resolveSpace(spacesCmd);
1031
1031
  const client = createClient();
1032
1032
  try {
1033
1033
  const result = await client.space(spaceId).mods.remove(modId);
@@ -1111,14 +1111,14 @@ function registerFiles(spacesCmd) {
1111
1111
  const filesCmd = spacesCmd
1112
1112
  .command("files")
1113
1113
  .description("File operations")
1114
- .hook("preAction", () => { resolveSpace(spacesCmd); });
1114
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
1115
1115
  filesCmd
1116
1116
  .command("ls [path]")
1117
1117
  .alias("list")
1118
1118
  .description("List directory tree")
1119
1119
  .option("--json", "Output as JSON")
1120
1120
  .action(async (path, opts) => {
1121
- const spaceId = resolveSpace(spacesCmd);
1121
+ const spaceId = await resolveSpace(spacesCmd);
1122
1122
  const client = createClient();
1123
1123
  try {
1124
1124
  const tree = await client.space(spaceId).files.list(path ?? "");
@@ -1143,7 +1143,7 @@ function registerFiles(spacesCmd) {
1143
1143
  .command("cat <path>")
1144
1144
  .description("Read file content")
1145
1145
  .action(async (path) => {
1146
- const spaceId = resolveSpace(spacesCmd);
1146
+ const spaceId = await resolveSpace(spacesCmd);
1147
1147
  const client = createClient();
1148
1148
  try {
1149
1149
  const file = await client.space(spaceId).files.read(path);
@@ -1173,7 +1173,7 @@ function registerFiles(spacesCmd) {
1173
1173
  }
1174
1174
  if (!content)
1175
1175
  return error("No content provided", "Use -c or pipe via stdin");
1176
- const spaceId = resolveSpace(spacesCmd);
1176
+ const spaceId = await resolveSpace(spacesCmd);
1177
1177
  const client = createClient();
1178
1178
  try {
1179
1179
  const result = await client.space(spaceId).files.write({
@@ -1197,7 +1197,7 @@ function registerFiles(spacesCmd) {
1197
1197
  .command("mkdir <path>")
1198
1198
  .description("Create a directory")
1199
1199
  .action(async (path) => {
1200
- const spaceId = resolveSpace(spacesCmd);
1200
+ const spaceId = await resolveSpace(spacesCmd);
1201
1201
  const client = createClient();
1202
1202
  try {
1203
1203
  await client.space(spaceId).files.createDir(path);
@@ -1212,7 +1212,7 @@ function registerFiles(spacesCmd) {
1212
1212
  .description("Delete a file or directory")
1213
1213
  .option("-r, --recursive", "Delete recursively")
1214
1214
  .action(async (path, opts) => {
1215
- const spaceId = resolveSpace(spacesCmd);
1215
+ const spaceId = await resolveSpace(spacesCmd);
1216
1216
  const client = createClient();
1217
1217
  try {
1218
1218
  await client.space(spaceId).files.delete(path, opts.recursive ?? false);
@@ -1226,7 +1226,7 @@ function registerFiles(spacesCmd) {
1226
1226
  .command("mv <from> <to>")
1227
1227
  .description("Move or rename")
1228
1228
  .action(async (from, to) => {
1229
- const spaceId = resolveSpace(spacesCmd);
1229
+ const spaceId = await resolveSpace(spacesCmd);
1230
1230
  const client = createClient();
1231
1231
  try {
1232
1232
  await client.space(spaceId).files.move({ fromPath: from, toPath: to });
@@ -1241,7 +1241,7 @@ function registerFiles(spacesCmd) {
1241
1241
  .description("Show pending workspace changes vs last checkpoint")
1242
1242
  .option("--json", "Output as JSON")
1243
1243
  .action(async (path, opts) => {
1244
- const spaceId = resolveSpace(spacesCmd);
1244
+ const spaceId = await resolveSpace(spacesCmd);
1245
1245
  const client = createClient();
1246
1246
  try {
1247
1247
  if (path) {
@@ -1272,14 +1272,14 @@ function registerSessions(spacesCmd) {
1272
1272
  const sessionsCmd = spacesCmd
1273
1273
  .command("sessions")
1274
1274
  .description("Browse sessions and turns")
1275
- .hook("preAction", () => { resolveSpace(spacesCmd); });
1275
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
1276
1276
  sessionsCmd
1277
1277
  .command("ls")
1278
1278
  .alias("list")
1279
1279
  .description("List sessions")
1280
1280
  .option("--json", "Output as JSON")
1281
1281
  .action(async (opts) => {
1282
- const spaceId = resolveSpace(spacesCmd);
1282
+ const spaceId = await resolveSpace(spacesCmd);
1283
1283
  const client = createClient();
1284
1284
  try {
1285
1285
  const result = await client.space(spaceId).sessions.list();
@@ -1306,7 +1306,7 @@ function registerSessions(spacesCmd) {
1306
1306
  .option("--label <ref>", "Attach a label, e.g. Bug or Area/Frontend", collectOption, [])
1307
1307
  .option("--json", "Output as JSON")
1308
1308
  .action(async (title, opts) => {
1309
- const spaceId = resolveSpace(spacesCmd);
1309
+ const spaceId = await resolveSpace(spacesCmd);
1310
1310
  const client = createClient();
1311
1311
  try {
1312
1312
  const result = await client.space(spaceId).sessions.create({
@@ -1330,7 +1330,7 @@ function registerSessions(spacesCmd) {
1330
1330
  .description("Session details")
1331
1331
  .option("--json", "Output as JSON")
1332
1332
  .action(async (id, opts) => {
1333
- const spaceId = resolveSpace(spacesCmd);
1333
+ const spaceId = await resolveSpace(spacesCmd);
1334
1334
  const client = createClient();
1335
1335
  try {
1336
1336
  const result = await client.space(spaceId).session(id).get();
@@ -1352,7 +1352,7 @@ function registerSessions(spacesCmd) {
1352
1352
  .command("rename <id> <name>")
1353
1353
  .description("Rename a session")
1354
1354
  .action(async (id, name) => {
1355
- const spaceId = resolveSpace(spacesCmd);
1355
+ const spaceId = await resolveSpace(spacesCmd);
1356
1356
  const client = createClient();
1357
1357
  try {
1358
1358
  await client.space(spaceId).session(id).rename(name);
@@ -1368,7 +1368,7 @@ function registerSessions(spacesCmd) {
1368
1368
  .description("Stream realtime session events")
1369
1369
  .option("--json", "Output as JSON")
1370
1370
  .action(async (id, opts) => {
1371
- const spaceId = resolveSpace(spacesCmd);
1371
+ const spaceId = await resolveSpace(spacesCmd);
1372
1372
  const client = createClient();
1373
1373
  const session = client.space(spaceId).session(id);
1374
1374
  process.stdout.write(" Listening for events...\n\n");
@@ -1423,7 +1423,7 @@ function registerTurns(sessionsCmd) {
1423
1423
  .option("--limit <n>", "Page size", "30")
1424
1424
  .option("--json", "Output as JSON")
1425
1425
  .action(async (sessionId, opts) => {
1426
- const spaceId = resolveSpace(sessionsCmd);
1426
+ const spaceId = await resolveSpace(sessionsCmd);
1427
1427
  const client = createClient();
1428
1428
  try {
1429
1429
  const result = await client.space(spaceId).session(sessionId).turns.listPaginated({
@@ -1455,7 +1455,7 @@ function registerTurns(sessionsCmd) {
1455
1455
  .description("Show turn details")
1456
1456
  .option("--json", "Output as JSON")
1457
1457
  .action(async (sessionId, turnId, opts) => {
1458
- const spaceId = resolveSpace(sessionsCmd);
1458
+ const spaceId = await resolveSpace(sessionsCmd);
1459
1459
  const client = createClient();
1460
1460
  try {
1461
1461
  const result = await client.space(spaceId).session(sessionId).turns.get(turnId);
@@ -1484,7 +1484,7 @@ function registerTurns(sessionsCmd) {
1484
1484
  .description("Run a queued follow-up now")
1485
1485
  .option("--json", "Output as JSON")
1486
1486
  .action(async (sessionId, turnId, opts) => {
1487
- const spaceId = resolveSpace(sessionsCmd);
1487
+ const spaceId = await resolveSpace(sessionsCmd);
1488
1488
  const client = createClient();
1489
1489
  try {
1490
1490
  const result = await client.space(spaceId).session(sessionId).steerTurn(turnId);
@@ -1501,7 +1501,7 @@ function registerTurns(sessionsCmd) {
1501
1501
  .description("Cancel a queued follow-up")
1502
1502
  .option("--json", "Output as JSON")
1503
1503
  .action(async (sessionId, turnId, opts) => {
1504
- const spaceId = resolveSpace(sessionsCmd);
1504
+ const spaceId = await resolveSpace(sessionsCmd);
1505
1505
  const client = createClient();
1506
1506
  try {
1507
1507
  const result = await client.space(spaceId).session(sessionId).cancelTurn(turnId);
@@ -1520,7 +1520,7 @@ function registerTurns(sessionsCmd) {
1520
1520
  .option("--limit <n>", "Page size", "100")
1521
1521
  .option("--json", "Output as JSON")
1522
1522
  .action(async (sessionId, opts) => {
1523
- const spaceId = resolveSpace(sessionsCmd);
1523
+ const spaceId = await resolveSpace(sessionsCmd);
1524
1524
  const client = createClient();
1525
1525
  try {
1526
1526
  const result = await client.space(spaceId).session(sessionId).turns.index({
@@ -1554,7 +1554,7 @@ function registerTurns(sessionsCmd) {
1554
1554
  .option("--after <n>", "Turns after anchor", "20")
1555
1555
  .option("--json", "Output as JSON")
1556
1556
  .action(async (sessionId, opts) => {
1557
- const spaceId = resolveSpace(sessionsCmd);
1557
+ const spaceId = await resolveSpace(sessionsCmd);
1558
1558
  if (!opts.sequence && !opts.turn)
1559
1559
  return error("Missing anchor", "Use --sequence <n> or --turn <id>");
1560
1560
  const client = createClient();
@@ -1647,14 +1647,14 @@ function registerMembers(spacesCmd) {
1647
1647
  const memCmd = spacesCmd
1648
1648
  .command("members")
1649
1649
  .description("Member management")
1650
- .hook("preAction", () => { resolveSpace(spacesCmd); });
1650
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
1651
1651
  memCmd
1652
1652
  .command("ls")
1653
1653
  .alias("list")
1654
1654
  .description("List space members")
1655
1655
  .option("--json", "Output as JSON")
1656
1656
  .action(async (opts) => {
1657
- const spaceId = resolveSpace(spacesCmd);
1657
+ const spaceId = await resolveSpace(spacesCmd);
1658
1658
  const client = createClient();
1659
1659
  try {
1660
1660
  const result = await client.space(spaceId).members.list();
@@ -1678,7 +1678,7 @@ function registerMembers(spacesCmd) {
1678
1678
  .command("update <userId> <role>")
1679
1679
  .description("Change member role (host | builder | guest)")
1680
1680
  .action(async (userId, role) => {
1681
- const spaceId = resolveSpace(spacesCmd);
1681
+ const spaceId = await resolveSpace(spacesCmd);
1682
1682
  const client = createClient();
1683
1683
  try {
1684
1684
  await client.space(spaceId).members.update(userId, parseChoice(role, "role", SPACE_ROLES));
@@ -1692,7 +1692,7 @@ function registerMembers(spacesCmd) {
1692
1692
  .command("remove <userId>")
1693
1693
  .description("Remove a member")
1694
1694
  .action(async (userId) => {
1695
- const spaceId = resolveSpace(spacesCmd);
1695
+ const spaceId = await resolveSpace(spacesCmd);
1696
1696
  const client = createClient();
1697
1697
  try {
1698
1698
  await client.space(spaceId).members.remove(userId);
@@ -1708,13 +1708,13 @@ function registerAccess(spacesCmd) {
1708
1708
  const accCmd = spacesCmd
1709
1709
  .command("access")
1710
1710
  .description("Access control")
1711
- .hook("preAction", () => { resolveSpace(spacesCmd); });
1711
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
1712
1712
  accCmd
1713
1713
  .command("get")
1714
1714
  .description("Get access policy")
1715
1715
  .option("--json", "Output as JSON")
1716
1716
  .action(async (opts) => {
1717
- const spaceId = resolveSpace(spacesCmd);
1717
+ const spaceId = await resolveSpace(spacesCmd);
1718
1718
  const client = createClient();
1719
1719
  try {
1720
1720
  const policy = await client.space(spaceId).access.get();
@@ -1736,7 +1736,7 @@ function registerAccess(spacesCmd) {
1736
1736
  .option("--anonymous <role>", "Role for anonymous users (host|builder|guest|null)")
1737
1737
  .option("--json", "Output as JSON")
1738
1738
  .action(async (opts) => {
1739
- const spaceId = resolveSpace(spacesCmd);
1739
+ const spaceId = await resolveSpace(spacesCmd);
1740
1740
  const client = createClient();
1741
1741
  try {
1742
1742
  const policy = await client.space(spaceId).access.set({
@@ -1761,7 +1761,7 @@ function registerCheckpoints(spacesCmd) {
1761
1761
  const cpCmd = spacesCmd
1762
1762
  .command("checkpoints")
1763
1763
  .description("Checkpoint management")
1764
- .hook("preAction", () => { resolveSpace(spacesCmd); });
1764
+ .hook("preAction", async () => { await resolveSpace(spacesCmd); });
1765
1765
  cpCmd
1766
1766
  .command("ls")
1767
1767
  .alias("list")
@@ -1770,7 +1770,7 @@ function registerCheckpoints(spacesCmd) {
1770
1770
  .option("--cursor <cursor>", "Pagination cursor")
1771
1771
  .option("--json", "Output as JSON")
1772
1772
  .action(async (opts) => {
1773
- const spaceId = resolveSpace(spacesCmd);
1773
+ const spaceId = await resolveSpace(spacesCmd);
1774
1774
  const client = createClient();
1775
1775
  try {
1776
1776
  const result = await client.space(spaceId).checkpoints.list({
@@ -1799,7 +1799,7 @@ function registerCheckpoints(spacesCmd) {
1799
1799
  .description("Checkpoint details")
1800
1800
  .option("--json", "Output as JSON")
1801
1801
  .action(async (id, opts) => {
1802
- const spaceId = resolveSpace(spacesCmd);
1802
+ const spaceId = await resolveSpace(spacesCmd);
1803
1803
  const client = createClient();
1804
1804
  try {
1805
1805
  const result = await client.space(spaceId).checkpoints.get(id);
@@ -1822,7 +1822,7 @@ function registerCheckpoints(spacesCmd) {
1822
1822
  .description("Create a checkpoint")
1823
1823
  .option("--json", "Output as JSON")
1824
1824
  .action(async (description, opts) => {
1825
- const spaceId = resolveSpace(spacesCmd);
1825
+ const spaceId = await resolveSpace(spacesCmd);
1826
1826
  const client = createClient();
1827
1827
  try {
1828
1828
  const result = await client.space(spaceId).checkpoints.create(description ?? null);
@@ -1839,7 +1839,7 @@ function registerCheckpoints(spacesCmd) {
1839
1839
  .description("List checkpoint tree")
1840
1840
  .option("--json", "Output as JSON")
1841
1841
  .action(async (checkpointId, path, opts) => {
1842
- const spaceId = resolveSpace(spacesCmd);
1842
+ const spaceId = await resolveSpace(spacesCmd);
1843
1843
  const client = createClient();
1844
1844
  try {
1845
1845
  const tree = await client.space(spaceId).checkpoints(checkpointId).files.list(path ?? "");
@@ -1866,7 +1866,7 @@ function registerCheckpoints(spacesCmd) {
1866
1866
  .description("Show checkpoint file content")
1867
1867
  .option("--json", "Output as JSON")
1868
1868
  .action(async (checkpointId, path, opts) => {
1869
- const spaceId = resolveSpace(spacesCmd);
1869
+ const spaceId = await resolveSpace(spacesCmd);
1870
1870
  const client = createClient();
1871
1871
  try {
1872
1872
  const file = await client.space(spaceId).checkpoints(checkpointId).files.read(path);
@@ -1888,7 +1888,7 @@ function registerCheckpoints(spacesCmd) {
1888
1888
  .option("--base <checkpointId>", "Compare against another checkpoint")
1889
1889
  .option("--json", "Output as JSON")
1890
1890
  .action(async (checkpointId, path, opts) => {
1891
- const spaceId = resolveSpace(spacesCmd);
1891
+ const spaceId = await resolveSpace(spacesCmd);
1892
1892
  const client = createClient();
1893
1893
  try {
1894
1894
  if (path) {
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ program
36
36
  .summary("Work with Cohub from your terminal")
37
37
  .description("Send prompts, manage Space files, and publish public output.")
38
38
  .version(VERSION, "-v, --version", "Show version")
39
- .option("-s, --space <id>", "Target Space ID")
39
+ .option("-s, --space <id>", "Target Space ID (defaults to your Home space)")
40
40
  .option("--json", "Print machine-readable JSON when supported")
41
41
  .helpOption("-h, --help", "Show help")
42
42
  .addHelpText("after", `
@@ -45,9 +45,9 @@ Common commands:
45
45
  cohub auth login
46
46
  cohub profile avatar ./avatar.png
47
47
  cohub spaces ls
48
- cohub -s <space-id> prompt "Fix the failing tests"
49
- cohub -s <space-id> completion "Summarize AGENTS.md" --system-prompt AGENTS.md --stream
50
- cohub -s <space-id> run -- git status
48
+ cohub prompt "Fix the failing tests"
49
+ cohub completion "Summarize AGENTS.md" --system-prompt AGENTS.md --stream
50
+ cohub run -- git status
51
51
  cohub sandbox up ./my-project
52
52
  cohub search "release notes"
53
53
  cohub -s <space-id> boards inspect <board-id>
@@ -62,7 +62,11 @@ Common commands:
62
62
  cohub models ls --model-type multimodal
63
63
  cohub generate "A calm lake at sunrise" --model <model> --output lake.png
64
64
 
65
+ Target space:
66
+ -s <space-id>, then COHUB_SPACE_ID, then your Home space
67
+
65
68
  Environment:
69
+ COHUB_SPACE_ID Target Space ID when -s is omitted
66
70
  COHUB_EXECUTION_TOKEN Use this token instead of the stored Logto session
67
71
  ENV=dev Use the development Cohub environment
68
72
  `);
package/dist/space.d.ts CHANGED
@@ -1,2 +1,32 @@
1
1
  import type { Command } from "commander";
2
- export declare function resolveSpace(program: Command): string;
2
+ /**
3
+ * Cache key aligned with auth: execution token is exclusive (same as
4
+ * `resolveAccessToken`) and never falls back to a local Logto session.
5
+ * Execution grants identify the actor as `actorUserId`, not `sub`.
6
+ */
7
+ export declare function identityKeyFrom(input: {
8
+ env: string;
9
+ executionToken?: string | null;
10
+ idToken?: string | null;
11
+ accessToken?: string | null;
12
+ }): string | null;
13
+ /** Exported for tests; production always uses `CACHE_PATH`. */
14
+ export declare function readDefaultSpaceCache(path: string, key: string, now?: number): string | null;
15
+ export declare function clearDefaultSpaceCache(): void;
16
+ /** Explicit target from `-s/--space` (any ancestor) or `COHUB_SPACE_ID`, else null. */
17
+ export declare function explicitSpace(program: Command): string | null;
18
+ /**
19
+ * Resolve the user's home space when no target is given. Cached locally per
20
+ * identity so repeated invocations skip the network entirely, and memoized
21
+ * in-process so preAction hooks and actions share a single lookup.
22
+ * Network and auth failures propagate so callers can report them faithfully.
23
+ */
24
+ export declare function resolveDefaultSpace(): Promise<string | null>;
25
+ /** Shared exit for commands that need a space but resolved none. */
26
+ export declare function missingSpaceError(): never;
27
+ /**
28
+ * Target space for a command: explicit `-s`/`COHUB_SPACE_ID` first, then the
29
+ * user's home space. Exits with guidance when neither is available; request
30
+ * failures go through the shared HTTP error handler.
31
+ */
32
+ export declare function resolveSpace(program: Command): Promise<string>;
package/dist/space.js CHANGED
@@ -1,5 +1,85 @@
1
- import { error } from "./output.js";
2
- export function resolveSpace(program) {
1
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { resolveCohubEnvironment } from "@neta-art/cohub";
5
+ import { readAuthSession } from "./auth.js";
6
+ import { createClient } from "./client.js";
7
+ import { error, handleHttp } from "./output.js";
8
+ const CONFIG_DIR = join(homedir(), ".config", "cohub");
9
+ const CACHE_PATH = join(CONFIG_DIR, "default-space.json");
10
+ /** Home space is stable; a one-day TTL bounds how long a stale hit survives. */
11
+ const CACHE_TTL_MS = 86_400_000;
12
+ function jwtClaim(token, key) {
13
+ const payload = token?.split(".")[1];
14
+ if (!payload)
15
+ return null;
16
+ try {
17
+ const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
18
+ const value = parsed[key];
19
+ return typeof value === "string" && value ? value : null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /**
26
+ * Cache key aligned with auth: execution token is exclusive (same as
27
+ * `resolveAccessToken`) and never falls back to a local Logto session.
28
+ * Execution grants identify the actor as `actorUserId`, not `sub`.
29
+ */
30
+ export function identityKeyFrom(input) {
31
+ if (input.executionToken) {
32
+ const actor = jwtClaim(input.executionToken, "actorUserId") ?? jwtClaim(input.executionToken, "sub");
33
+ return actor ? `${input.env}:${actor}` : null;
34
+ }
35
+ const sub = jwtClaim(input.idToken, "sub") ?? jwtClaim(input.accessToken, "sub");
36
+ return sub ? `${input.env}:${sub}` : null;
37
+ }
38
+ function identityKey() {
39
+ const session = readAuthSession();
40
+ return identityKeyFrom({
41
+ env: resolveCohubEnvironment(),
42
+ executionToken: process.env.COHUB_EXECUTION_TOKEN?.trim(),
43
+ idToken: session?.idToken,
44
+ accessToken: session?.accessToken,
45
+ });
46
+ }
47
+ /** Exported for tests; production always uses `CACHE_PATH`. */
48
+ export function readDefaultSpaceCache(path, key, now = Date.now()) {
49
+ try {
50
+ const cache = JSON.parse(readFileSync(path, "utf-8"));
51
+ if (cache.key !== key || typeof cache.spaceId !== "string" || typeof cache.cachedAt !== "number")
52
+ return null;
53
+ if (now - cache.cachedAt > CACHE_TTL_MS)
54
+ return null;
55
+ return cache.spaceId;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function writeCachedDefaultSpace(key, spaceId) {
62
+ try {
63
+ mkdirSync(CONFIG_DIR, { recursive: true });
64
+ const cache = { key, spaceId, cachedAt: Date.now() };
65
+ writeFileSync(CACHE_PATH, `${JSON.stringify(cache, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
66
+ }
67
+ catch {
68
+ // Cache is best-effort; never fail the command over it.
69
+ }
70
+ }
71
+ let defaultSpacePromise = null;
72
+ export function clearDefaultSpaceCache() {
73
+ defaultSpacePromise = null;
74
+ try {
75
+ rmSync(CACHE_PATH, { force: true });
76
+ }
77
+ catch {
78
+ // Best-effort, same as writes.
79
+ }
80
+ }
81
+ /** Explicit target from `-s/--space` (any ancestor) or `COHUB_SPACE_ID`, else null. */
82
+ export function explicitSpace(program) {
3
83
  let current = program;
4
84
  while (current) {
5
85
  const opts = current.opts();
@@ -7,8 +87,39 @@ export function resolveSpace(program) {
7
87
  return opts.space.trim();
8
88
  current = current.parent ?? null;
9
89
  }
10
- const envSpace = process.env.COHUB_SPACE_ID?.trim();
11
- if (envSpace)
12
- return envSpace;
13
- return error("Missing required space", "Add -s, --space <id> or set COHUB_SPACE_ID.");
90
+ return process.env.COHUB_SPACE_ID?.trim() || null;
91
+ }
92
+ /**
93
+ * Resolve the user's home space when no target is given. Cached locally per
94
+ * identity so repeated invocations skip the network entirely, and memoized
95
+ * in-process so preAction hooks and actions share a single lookup.
96
+ * Network and auth failures propagate so callers can report them faithfully.
97
+ */
98
+ export function resolveDefaultSpace() {
99
+ defaultSpacePromise ??= (async () => {
100
+ const key = identityKey();
101
+ if (key) {
102
+ const cached = readDefaultSpaceCache(CACHE_PATH, key);
103
+ if (cached)
104
+ return cached;
105
+ }
106
+ const space = (await createClient().spaces.getDefault()).space ?? null;
107
+ // Recent-space fallback from getDefault() is not stable enough to cache.
108
+ if (space?.id && space.slug === "home" && key)
109
+ writeCachedDefaultSpace(key, space.id);
110
+ return space?.id ?? null;
111
+ })();
112
+ return defaultSpacePromise;
113
+ }
114
+ /** Shared exit for commands that need a space but resolved none. */
115
+ export function missingSpaceError() {
116
+ return error("No target space", "Add -s, --space <id> or set COHUB_SPACE_ID. Run `cohub auth login` to use your home space.");
117
+ }
118
+ /**
119
+ * Target space for a command: explicit `-s`/`COHUB_SPACE_ID` first, then the
120
+ * user's home space. Exits with guidance when neither is available; request
121
+ * failures go through the shared HTTP error handler.
122
+ */
123
+ export async function resolveSpace(program) {
124
+ return explicitSpace(program) ?? (await resolveDefaultSpace().catch(handleHttp)) ?? missingSpaceError();
14
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "6.7.0",
3
+ "version": "6.8.0",
4
4
  "description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -19,7 +19,7 @@
19
19
  "commander": "^15.0.0",
20
20
  "pixi.js": "^8.20.1",
21
21
  "sharp": "^0.35.4",
22
- "@neta-art/cohub": "8.10.0"
22
+ "@neta-art/cohub": "8.10.1"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"