@neta-art/cohub-cli 1.7.0 → 1.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
@@ -129,6 +129,8 @@ cohub search "query" --limit 20 --json
129
129
  ```bash
130
130
  cohub models ls --json
131
131
  cohub models ls --model-type multimodal --json
132
+ cohub models show <model>
133
+ cohub models show <model> --json
132
134
 
133
135
  cohub generate "a calm lake at sunrise" \
134
136
  --model <model> \
package/dist/auth.d.ts CHANGED
@@ -30,7 +30,9 @@ export declare class AuthRequiredError extends Error {
30
30
  export declare const readAuthSession: () => AuthSession | null;
31
31
  export declare const clearAuthSession: () => void;
32
32
  export declare const authSource: () => AuthSource;
33
- export declare function resolveAccessToken(): Promise<string | null>;
33
+ export declare function resolveAccessToken(options?: {
34
+ forceRefresh?: boolean;
35
+ }): Promise<string | null>;
34
36
  export declare function requireAccessToken(): Promise<string>;
35
37
  export declare function refreshAccessToken(session?: AuthSession | null): Promise<string | null>;
36
38
  export declare function requestDeviceCode(): Promise<DeviceCode>;
package/dist/auth.js CHANGED
@@ -125,14 +125,14 @@ export const authSource = () => {
125
125
  return "logto";
126
126
  return null;
127
127
  };
128
- export async function resolveAccessToken() {
128
+ export async function resolveAccessToken(options) {
129
129
  const executionToken = process.env.COHUB_EXECUTION_TOKEN?.trim();
130
130
  if (executionToken)
131
131
  return executionToken;
132
132
  const session = readAuthSession();
133
133
  if (!session)
134
134
  throw new AuthRequiredError();
135
- if (session.accessTokenExpiresAt - Date.now() > EXPIRY_SKEW_MS)
135
+ if (!options?.forceRefresh && session.accessTokenExpiresAt - Date.now() > EXPIRY_SKEW_MS)
136
136
  return session.accessToken;
137
137
  return refreshAccessToken(session);
138
138
  }
@@ -1,6 +1,6 @@
1
1
  import { authSource, loginWithDeviceFlow, readAuthSession, refreshAccessToken, requestDeviceCode, revokeAndClearAuthSession, verifyDeviceCode } from "../auth.js";
2
2
  import { createClient } from "../client.js";
3
- import { table, json as outJson, ok, error, spinner, handleHttp } from "../output.js";
3
+ import { table, json as outJson, jsonRequested, ok, error, spinner, handleHttp } from "../output.js";
4
4
  export function registerAuth(program) {
5
5
  const auth = program.command("auth").description("Authentication management");
6
6
  auth
@@ -9,8 +9,8 @@ export function registerAuth(program) {
9
9
  .option("--request-code", "Request a device code without polling")
10
10
  .option("--verify-code", "Exchange a previously requested device code")
11
11
  .option("--json", "Output as JSON")
12
- .action(async (opts, command) => {
13
- const asJson = Boolean(opts.json || command.parent?.optsWithGlobals().json);
12
+ .action(async (opts) => {
13
+ const asJson = jsonRequested(opts);
14
14
  if (opts.requestCode && opts.verifyCode) {
15
15
  return error("Conflicting options", "Use only one of --request-code or --verify-code");
16
16
  }
@@ -43,8 +43,8 @@ export function registerAuth(program) {
43
43
  .command("whoami")
44
44
  .description("Show current user info")
45
45
  .option("--json", "Output as JSON")
46
- .action(async (opts, command) => {
47
- const asJson = Boolean(opts.json || command.parent?.optsWithGlobals().json);
46
+ .action(async (opts) => {
47
+ const asJson = jsonRequested(opts);
48
48
  return showSignedIn(asJson);
49
49
  });
50
50
  auth
@@ -80,11 +80,11 @@ async function showSignedIn(asJson) {
80
80
  if (!source)
81
81
  return error("Not authenticated", "Run `cohub auth login`.");
82
82
  const client = createClient();
83
- const sp = spinner();
84
- sp.start("Fetching user info");
83
+ const sp = asJson ? null : spinner();
84
+ sp?.start("Fetching user info");
85
85
  try {
86
86
  const user = await client.user.getMe();
87
- sp.stop("Done");
87
+ sp?.stop("Done");
88
88
  const session = readAuthSession();
89
89
  const payload = {
90
90
  source,
@@ -93,22 +93,77 @@ async function showSignedIn(asJson) {
93
93
  };
94
94
  if (asJson)
95
95
  return outJson(payload);
96
- const u = user;
96
+ const u = flattenMeForTable(user);
97
97
  console.log(` Auth source: ${source}`);
98
98
  console.log(` Token: ${payload.refreshable ? "refreshable" : "ephemeral"}\n`);
99
99
  table([u], [
100
- { key: "id", label: "ID" },
100
+ { key: "uuid", label: "UUID" },
101
101
  { key: "username", label: "Username" },
102
- { key: "name", label: "Name" },
102
+ { key: "displayName", label: "Name" },
103
103
  { key: "email", label: "Email" },
104
- { key: "created_at", label: "Created" },
105
104
  ]);
106
105
  }
107
106
  catch (e) {
108
- sp.stop("Failed");
107
+ if (source === "execution-token") {
108
+ sp?.stop("Using local execution token");
109
+ return showExecutionTokenFallback(asJson);
110
+ }
111
+ sp?.stop("Failed");
109
112
  handleHttp(e);
110
113
  }
111
114
  }
115
+ function flattenMeForTable(user) {
116
+ const profile = user.profile && typeof user.profile === "object" ? user.profile : {};
117
+ return {
118
+ uuid: user.uuid,
119
+ username: profile.username,
120
+ displayName: profile.displayName,
121
+ email: user.email,
122
+ };
123
+ }
124
+ function decodeExecutionTokenPayload() {
125
+ const token = process.env.COHUB_EXECUTION_TOKEN?.trim();
126
+ const payload = token?.split(".")[1];
127
+ if (!payload)
128
+ return null;
129
+ try {
130
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
131
+ const parsed = JSON.parse(decoded);
132
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ function showExecutionTokenFallback(asJson) {
139
+ const payload = decodeExecutionTokenPayload();
140
+ const execution = {
141
+ actorUserId: typeof payload?.actorUserId === "string" ? payload.actorUserId : null,
142
+ spaceId: typeof payload?.spaceId === "string" ? payload.spaceId : null,
143
+ sessionId: typeof payload?.sessionId === "string" ? payload.sessionId : null,
144
+ source: typeof payload?.source === "string" ? payload.source : null,
145
+ expiresAt: typeof payload?.exp === "number" ? new Date(payload.exp * 1000).toISOString() : null,
146
+ };
147
+ const result = {
148
+ source: "execution-token",
149
+ refreshable: false,
150
+ user: execution.actorUserId ? { uuid: execution.actorUserId } : null,
151
+ execution,
152
+ };
153
+ if (asJson) {
154
+ outJson(result);
155
+ return;
156
+ }
157
+ console.log(" Auth source: execution-token");
158
+ console.log(" Token: ephemeral\n");
159
+ table([execution], [
160
+ { key: "actorUserId", label: "Actor" },
161
+ { key: "spaceId", label: "Space" },
162
+ { key: "sessionId", label: "Session" },
163
+ { key: "source", label: "Source" },
164
+ { key: "expiresAt", label: "Expires" },
165
+ ]);
166
+ }
112
167
  function printDeviceCode(code) {
113
168
  console.log("\nOpen this URL to sign in:\n");
114
169
  console.log(` ${code.verificationUriComplete}\n`);
@@ -1,5 +1,5 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, ok, error, handleHttp } from "../output.js";
2
+ import { table, json as outJson, jsonRequested, ok, error, handleHttp } from "../output.js";
3
3
  export function registerChannels(program) {
4
4
  const cmd = program.command("channels", { hidden: true }).description("Channel integrations");
5
5
  cmd
@@ -11,7 +11,7 @@ export function registerChannels(program) {
11
11
  const client = createClient();
12
12
  try {
13
13
  const items = await client.channels.list();
14
- if (opts.json)
14
+ if (jsonRequested(opts))
15
15
  return outJson(items);
16
16
  if (items.length === 0)
17
17
  return console.log(" (empty)");
@@ -50,7 +50,7 @@ export function registerChannels(program) {
50
50
  name: opts.name,
51
51
  credentials,
52
52
  });
53
- if (opts.json)
53
+ if (jsonRequested(opts))
54
54
  return outJson(result);
55
55
  ok("Channel created");
56
56
  }
@@ -1,5 +1,5 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, ok, handleHttp } from "../output.js";
2
+ import { table, json as outJson, jsonRequested, ok, error, handleHttp } from "../output.js";
3
3
  export function registerCronJobs(program) {
4
4
  const cmd = program.command("cron-jobs", { hidden: true }).description("Scheduled prompt jobs");
5
5
  cmd
@@ -11,7 +11,7 @@ export function registerCronJobs(program) {
11
11
  const client = createClient();
12
12
  try {
13
13
  const result = await client.cronJobs.list(spaceId);
14
- if (opts.json)
14
+ if (jsonRequested(opts))
15
15
  return outJson(result);
16
16
  if (result.jobs.length === 0)
17
17
  return console.log(" (empty)");
@@ -44,6 +44,8 @@ export function registerCronJobs(program) {
44
44
  .command("toggle <id> <on|off>")
45
45
  .description("Enable or disable a cron job")
46
46
  .action(async (id, state) => {
47
+ if (state !== "on" && state !== "off")
48
+ return error("Invalid state", "Use on or off");
47
49
  const enabled = state === "on";
48
50
  const client = createClient();
49
51
  try {
@@ -62,7 +64,7 @@ export function registerCronJobs(program) {
62
64
  const client = createClient();
63
65
  try {
64
66
  const result = await client.cronJobs.runs(id);
65
- if (opts.json)
67
+ if (jsonRequested(opts))
66
68
  return outJson(result);
67
69
  if (result.runs.length === 0)
68
70
  return console.log(" (empty)");
@@ -1,7 +1,8 @@
1
1
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import { basename, dirname, extname, join } from "node:path";
3
3
  import { createClient } from "../client.js";
4
- import { json as outJson, ok, handleHttp } from "../output.js";
4
+ import { resolveSpace } from "../space.js";
5
+ import { json as outJson, jsonRequested, ok, error, handleHttp, spinner } from "../output.js";
5
6
  const mimeByExt = {
6
7
  ".png": "image/png",
7
8
  ".jpg": "image/jpeg",
@@ -108,43 +109,72 @@ function printGeneration(output) {
108
109
  console.log(`${block.type}: ${block.source.space_id}:${block.source.path}`);
109
110
  }
110
111
  }
112
+ function parseTimeoutMs(value) {
113
+ if (!value)
114
+ return undefined;
115
+ if (!/^\d+$/.test(value.trim()))
116
+ return error("Invalid timeout", "--timeout-ms must be a positive integer");
117
+ const timeoutMs = Number.parseInt(value, 10);
118
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0)
119
+ return error("Invalid timeout", "--timeout-ms must be a positive integer");
120
+ return timeoutMs;
121
+ }
111
122
  export function registerGenerations(program) {
112
123
  program
113
124
  .command("generate")
114
125
  .description("Generate multimodal outputs")
115
126
  .argument("<prompt>", "Prompt text")
116
- .requiredOption("--model <model>", "Multimodal model ID from `cohub models ls --model-type multimodal`")
127
+ .requiredOption("-m, --model <model>", "Multimodal model ID from `cohub models ls --model-type multimodal`")
117
128
  .option("--image <path-or-url>", "Image input file path or URL; repeatable", collect, [])
118
129
  .option("--video <path-or-url>", "Video input file path or URL; repeatable", collect, [])
119
130
  .option("--audio <path-or-url>", "Audio input file path or URL; repeatable", collect, [])
120
131
  .option("--param <key=value>", "Generation parameter; repeatable, values may be JSON/number/boolean", collect, [])
121
132
  .option("--parameters <json>", "Generation parameters as a JSON object")
122
133
  .option("--metadata <json>", "Metadata as a JSON object")
123
- .option("--output <path>", "Save generated output to a file or directory")
134
+ .option("-o, --output <path>", "Save generated output to a file or directory")
135
+ .option("--async", "Queue the generation task and return immediately")
136
+ .option("--timeout-ms <ms>", "Maximum time to wait in synchronous mode")
124
137
  .option("--json", "Output as JSON")
125
138
  .addHelpText("after", `
126
139
 
127
140
  Examples:
128
141
  cohub models ls --model-type multimodal
129
- cohub generate "A calm lake at sunrise" --model <model> --output lake.png
130
- cohub generate "Restyle this image" --model <model> --image input.png --param size=1024x1024
142
+ cohub -s <space-id> generate "A calm lake at sunrise" -m <model> -o lake.png
143
+ COHUB_SPACE_ID=<space-id> cohub generate "Restyle this image" -m <model> --image input.png
144
+ cohub -s <space-id> generate "A calm lake" -m <model> --async
131
145
  `)
132
146
  .action(async (prompt, opts) => {
133
147
  try {
148
+ const spaceId = resolveSpace(program);
134
149
  const content = [{ type: "text", text: prompt }];
135
150
  content.push(...await Promise.all(opts.image.map((value) => contentFromPathOrUrl("image", value))));
136
151
  content.push(...await Promise.all(opts.video.map((value) => contentFromPathOrUrl("video", value))));
137
152
  content.push(...await Promise.all(opts.audio.map((value) => contentFromPathOrUrl("audio", value))));
138
- const generation = await createClient().generations.create({
153
+ const client = createClient();
154
+ const created = await client.generations.create({
155
+ spaceId,
139
156
  model: opts.model,
140
157
  content,
141
158
  parameters: parseParams(opts.param, opts.parameters),
142
159
  metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
143
160
  });
144
- const savedPaths = opts.output && generation.output ? await saveOutputs(generation.output, opts.output) : [];
145
- if (opts.json)
146
- return outJson(savedPaths.length > 0 ? { ...generation, savedPaths } : generation);
147
- printGeneration(generation.output ?? []);
161
+ if (opts.async) {
162
+ if (jsonRequested(opts))
163
+ return outJson(created);
164
+ return ok(`Generation queued — taskRunId: ${created.taskRunId}`);
165
+ }
166
+ const spin = spinner();
167
+ if (!jsonRequested(opts))
168
+ spin.start("Generating");
169
+ const result = await client.generations.wait(created.taskRunId, {
170
+ timeoutMs: parseTimeoutMs(opts.timeoutMs),
171
+ });
172
+ if (!jsonRequested(opts))
173
+ spin.stop("Generation completed");
174
+ const savedPaths = opts.output ? await saveOutputs(result.output, opts.output) : [];
175
+ if (jsonRequested(opts))
176
+ return outJson(savedPaths.length > 0 ? { ...result, taskRunId: created.taskRunId, savedPaths } : { ...result, taskRunId: created.taskRunId });
177
+ printGeneration(result.output);
148
178
  if (savedPaths.length > 0)
149
179
  ok(`Saved to ${savedPaths.join(", ")}`);
150
180
  }
@@ -1,5 +1,69 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, error, handleHttp } from "../output.js";
2
+ import { table, json as outJson, jsonRequested, error, handleHttp } from "../output.js";
3
+ function toMultimodalModelSummary(model) {
4
+ return {
5
+ model: model.model,
6
+ ...(model.title ? { title: model.title } : {}),
7
+ ...(model.description ? { description: model.description } : {}),
8
+ };
9
+ }
10
+ function printSection(title, lines) {
11
+ if (lines.length === 0)
12
+ return;
13
+ console.log(`\n${title}`);
14
+ for (const line of lines)
15
+ console.log(` ${line}`);
16
+ }
17
+ function formatContentSpec(spec) {
18
+ const details = [];
19
+ details.push(spec.required === false ? "optional" : "required");
20
+ if (typeof spec.min === "number")
21
+ details.push(`min ${spec.min}`);
22
+ if (typeof spec.max === "number")
23
+ details.push(`max ${spec.max}`);
24
+ if (spec.sources?.length)
25
+ details.push(`sources: ${spec.sources.join(", ")}`);
26
+ if (spec.merge)
27
+ details.push(`merge: ${spec.merge}`);
28
+ if (spec.description)
29
+ details.push(spec.description);
30
+ return `${spec.type}${details.length > 0 ? ` — ${details.join("; ")}` : ""}`;
31
+ }
32
+ function formatParameter(name, spec) {
33
+ const lines = [`${name}`];
34
+ const details = [`type: ${spec.type}`];
35
+ if (spec.optional)
36
+ details.push("optional");
37
+ if ("default" in spec && spec.default !== undefined)
38
+ details.push(`default: ${String(spec.default)}`);
39
+ if ("min" in spec && typeof spec.min === "number")
40
+ details.push(`min: ${spec.min}`);
41
+ if ("max" in spec && typeof spec.max === "number")
42
+ details.push(`max: ${spec.max}`);
43
+ if ("enum" in spec && spec.enum?.length)
44
+ details.push(`values: ${spec.enum.join(", ")}`);
45
+ lines.push(` ${details.join("; ")}`);
46
+ if (spec.description)
47
+ lines.push(` ${spec.description}`);
48
+ if ("examples" in spec && spec.examples?.length)
49
+ lines.push(` examples: ${spec.examples.map(String).join(", ")}`);
50
+ return lines;
51
+ }
52
+ function printMultimodalModel(model) {
53
+ console.log(model.title ?? model.model);
54
+ printSection("Model", [model.model]);
55
+ if (model.description)
56
+ printSection("Description", [model.description]);
57
+ printSection("Input", model.content.input.map(formatContentSpec));
58
+ const parameterLines = Object.entries(model.parameters ?? {}).flatMap(([name, spec]) => formatParameter(name, spec));
59
+ printSection("Parameters", parameterLines);
60
+ const examples = model.examples ?? [];
61
+ printSection("Examples", examples.map((example, index) => {
62
+ const title = example.title ? `${example.title}: ` : "";
63
+ const prompt = example.request.content.find((block) => block.type === "text")?.text;
64
+ return `${index + 1}. ${title}${prompt ? `"${prompt}"` : example.request.model}`;
65
+ }));
66
+ }
3
67
  export function registerModels(program) {
4
68
  const cmd = program
5
69
  .command("models")
@@ -10,6 +74,8 @@ Examples:
10
74
  cohub models ls
11
75
  cohub models ls --model-type multimodal
12
76
  cohub models ls --model-type multimodal --json
77
+ cohub models show <model>
78
+ cohub models show <model> --json
13
79
  `);
14
80
  cmd
15
81
  .command("ls")
@@ -22,9 +88,10 @@ Examples:
22
88
  try {
23
89
  if (opts.modelType === "multimodal") {
24
90
  const response = await client.models.listMultimodal();
25
- if (opts.json)
26
- return outJson(response);
27
- table(response.models, [
91
+ const models = response.models.map(toMultimodalModelSummary);
92
+ if (jsonRequested(opts))
93
+ return outJson({ models });
94
+ table(models, [
28
95
  { key: "model", label: "Model" },
29
96
  { key: "title", label: "Title" },
30
97
  { key: "description", label: "Description" },
@@ -35,7 +102,7 @@ Examples:
35
102
  return error("Invalid model type", "Use --model-type llm or --model-type multimodal");
36
103
  }
37
104
  const catalog = await client.models.list();
38
- if (opts.json)
105
+ if (jsonRequested(opts))
39
106
  return outJson(catalog);
40
107
  // catalog is Record<provider, ModelCatalogEntry[]>
41
108
  for (const [provider, entries] of Object.entries(catalog)) {
@@ -51,4 +118,25 @@ Examples:
51
118
  handleHttp(e);
52
119
  }
53
120
  });
121
+ cmd
122
+ .command("show")
123
+ .description("Show full multimodal model details")
124
+ .argument("<model>", "Multimodal model ID")
125
+ .option("--json", "Output as JSON")
126
+ .action(async (modelId, opts) => {
127
+ const client = createClient();
128
+ try {
129
+ const response = await client.models.listMultimodal();
130
+ const model = response.models.find((item) => item.model === modelId);
131
+ if (!model) {
132
+ return error("Model not found", `No multimodal model named ${modelId}`);
133
+ }
134
+ if (jsonRequested(opts))
135
+ return outJson(model);
136
+ printMultimodalModel(model);
137
+ }
138
+ catch (e) {
139
+ handleHttp(e);
140
+ }
141
+ });
54
142
  }
@@ -1,6 +1,6 @@
1
1
  import { uploadAvatarAsset } from "../avatar.js";
2
2
  import { createClient } from "../client.js";
3
- import { json as outJson, ok, handleHttp } from "../output.js";
3
+ import { json as outJson, jsonRequested, ok, handleHttp } from "../output.js";
4
4
  export function registerProfile(program) {
5
5
  const profileCmd = program.command("profile").description("Manage your profile");
6
6
  profileCmd
@@ -12,7 +12,7 @@ export function registerProfile(program) {
12
12
  try {
13
13
  const asset = await uploadAvatarAsset({ client, purpose: "user_avatar", path });
14
14
  const result = await client.user.updateProfile({ avatarUrl: asset.publicUrl });
15
- if (opts.json)
15
+ if (jsonRequested(opts))
16
16
  return outJson({ ...result, asset });
17
17
  ok("Avatar updated");
18
18
  }
@@ -1,5 +1,5 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, handleHttp } from "../output.js";
2
+ import { table, json as outJson, jsonRequested, handleHttp } from "../output.js";
3
3
  export function registerPrompts(program) {
4
4
  const cmd = program.command("prompts").description("Prompt template management");
5
5
  cmd
@@ -12,7 +12,7 @@ export function registerPrompts(program) {
12
12
  const client = createClient();
13
13
  try {
14
14
  const result = await client.prompts.list({ spaceId: opts.space });
15
- if (opts.json)
15
+ if (jsonRequested(opts))
16
16
  return outJson(result);
17
17
  if (result.prompts.length === 0)
18
18
  return console.log(" (empty)");
@@ -1,5 +1,5 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, error, handleHttp } from "../output.js";
2
+ import { table, json as outJson, jsonRequested, error, handleHttp } from "../output.js";
3
3
  const DEFAULT_LIMIT = 20;
4
4
  const MAX_TITLE_LENGTH = 72;
5
5
  const MAX_CONTEXT_LENGTH = 42;
@@ -81,7 +81,7 @@ Examples:
81
81
  types: input.types,
82
82
  spaceId: input.spaceId,
83
83
  });
84
- if (opts.json)
84
+ if (jsonRequested(opts))
85
85
  return outJson(result);
86
86
  if (result.degraded) {
87
87
  process.stderr.write(" Search is temporarily degraded; results may be incomplete.\n");
@@ -1,5 +1,13 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, handleHttp } from "../output.js";
2
+ import { table, json as outJson, jsonRequested, error, handleHttp } from "../output.js";
3
+ const SPACE_ROLES = ["host", "builder", "guest"];
4
+ function parseAnonymousRole(value) {
5
+ if (value === undefined || value === "null")
6
+ return null;
7
+ if (SPACE_ROLES.includes(value))
8
+ return value;
9
+ return error("Invalid anonymous role", "Use one of: host, builder, guest, null");
10
+ }
3
11
  export function registerSessionAccess(program) {
4
12
  const cmd = program
5
13
  .command("session-access")
@@ -12,7 +20,7 @@ export function registerSessionAccess(program) {
12
20
  const client = createClient();
13
21
  try {
14
22
  const policy = await client.sessionAccess.get(id);
15
- if (opts.json)
23
+ if (jsonRequested(opts))
16
24
  return outJson(policy);
17
25
  table([policy], [
18
26
  { key: "signed_in_user", label: "Signed-in" },
@@ -32,9 +40,9 @@ export function registerSessionAccess(program) {
32
40
  const client = createClient();
33
41
  try {
34
42
  const policy = await client.sessionAccess.set(id, {
35
- anonymous_user: (opts.anonymous ?? null),
43
+ anonymous_user: parseAnonymousRole(opts.anonymous),
36
44
  });
37
- if (opts.json)
45
+ if (jsonRequested(opts))
38
46
  return outJson(policy);
39
47
  console.log("Session access updated");
40
48
  table([policy], [