@neta-art/cohub-cli 1.7.1 → 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 +2 -0
- package/dist/auth.d.ts +3 -1
- package/dist/auth.js +2 -2
- package/dist/commands/auth.js +68 -13
- package/dist/commands/channels.js +3 -3
- package/dist/commands/cron-jobs.js +5 -3
- package/dist/commands/generations.js +40 -10
- package/dist/commands/models.js +93 -5
- package/dist/commands/profile.js +2 -2
- package/dist/commands/prompts.js +2 -2
- package/dist/commands/search.js +2 -2
- package/dist/commands/session-access.js +12 -4
- package/dist/commands/spaces.js +113 -101
- package/dist/commands/tasks.js +3 -3
- package/dist/output.d.ts +3 -0
- package/dist/output.js +3 -0
- package/dist/space.d.ts +2 -0
- package/dist/space.js +14 -0
- package/package.json +2 -2
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(
|
|
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
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -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
|
|
13
|
-
const asJson =
|
|
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
|
|
47
|
-
const asJson =
|
|
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
|
|
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
|
|
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: "
|
|
100
|
+
{ key: "uuid", label: "UUID" },
|
|
101
101
|
{ key: "username", label: "Username" },
|
|
102
|
-
{ key: "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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"
|
|
130
|
-
cohub generate "Restyle this image"
|
|
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
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
}
|
package/dist/commands/models.js
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
}
|
package/dist/commands/profile.js
CHANGED
|
@@ -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
|
|
15
|
+
if (jsonRequested(opts))
|
|
16
16
|
return outJson({ ...result, asset });
|
|
17
17
|
ok("Avatar updated");
|
|
18
18
|
}
|
package/dist/commands/prompts.js
CHANGED
|
@@ -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
|
|
15
|
+
if (jsonRequested(opts))
|
|
16
16
|
return outJson(result);
|
|
17
17
|
if (result.prompts.length === 0)
|
|
18
18
|
return console.log(" (empty)");
|
package/dist/commands/search.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
43
|
+
anonymous_user: parseAnonymousRole(opts.anonymous),
|
|
36
44
|
});
|
|
37
|
-
if (opts
|
|
45
|
+
if (jsonRequested(opts))
|
|
38
46
|
return outJson(policy);
|
|
39
47
|
console.log("Session access updated");
|
|
40
48
|
table([policy], [
|
package/dist/commands/spaces.js
CHANGED
|
@@ -2,21 +2,36 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveCohubEnvironment } from "@neta-art/cohub";
|
|
6
6
|
import { uploadAvatarAsset } from "../avatar.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const opts = current.opts();
|
|
12
|
-
if (opts.space)
|
|
13
|
-
return String(opts.space);
|
|
14
|
-
current = current.parent ?? null;
|
|
15
|
-
}
|
|
16
|
-
return error("Missing required option", "Add -s, --space <id> to target a space");
|
|
17
|
-
}
|
|
18
|
-
const cliEnv = (process.env.ENV === "prod" ? "prod" : "dev");
|
|
7
|
+
import { createClient } from "../client.js";
|
|
8
|
+
import { table, json as outJson, jsonRequested, ok, error, handleHttp } from "../output.js";
|
|
9
|
+
import { resolveSpace } from "../space.js";
|
|
10
|
+
const cliEnv = resolveCohubEnvironment();
|
|
19
11
|
const defaultIdleTtlSeconds = cliEnv === "prod" ? 12 * 60 * 60 : 10 * 60;
|
|
12
|
+
const SPACE_ROLES = ["host", "builder", "guest"];
|
|
13
|
+
function parseInteger(value, name, options = {}) {
|
|
14
|
+
if (!/^-?\d+$/.test(value.trim()))
|
|
15
|
+
return error(`Invalid ${name}`, `${name} must be an integer`);
|
|
16
|
+
const parsed = Number.parseInt(value, 10);
|
|
17
|
+
if (!Number.isSafeInteger(parsed))
|
|
18
|
+
return error(`Invalid ${name}`, `${name} must be a safe integer`);
|
|
19
|
+
if (options.min !== undefined && parsed < options.min)
|
|
20
|
+
return error(`Invalid ${name}`, `${name} must be at least ${options.min}`);
|
|
21
|
+
if (options.max !== undefined && parsed > options.max)
|
|
22
|
+
return error(`Invalid ${name}`, `${name} must be at most ${options.max}`);
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
function parseChoice(value, name, choices) {
|
|
26
|
+
if (choices.includes(value))
|
|
27
|
+
return value;
|
|
28
|
+
return error(`Invalid ${name}`, `Use one of: ${choices.join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
function parseNullableRole(value, name) {
|
|
31
|
+
if (value === undefined || value === "null")
|
|
32
|
+
return null;
|
|
33
|
+
return parseChoice(value, name, SPACE_ROLES);
|
|
34
|
+
}
|
|
20
35
|
const parseAutoDestroy = (opts) => {
|
|
21
36
|
const mode = opts.autoDestroy ?? (opts.idleTtl ? "idle" : undefined);
|
|
22
37
|
if (!mode)
|
|
@@ -25,10 +40,7 @@ const parseAutoDestroy = (opts) => {
|
|
|
25
40
|
return { mode: "never" };
|
|
26
41
|
if (mode !== "idle")
|
|
27
42
|
return error("Invalid auto destroy mode", "Use --auto-destroy idle or --auto-destroy never");
|
|
28
|
-
const ttlSeconds =
|
|
29
|
-
if (!Number.isSafeInteger(ttlSeconds) || ttlSeconds < 60 || ttlSeconds > 30 * 24 * 60 * 60) {
|
|
30
|
-
return error("Invalid idle TTL", "--idle-ttl must be an integer between 60 and 2592000 seconds");
|
|
31
|
-
}
|
|
43
|
+
const ttlSeconds = parseInteger(opts.idleTtl ?? String(defaultIdleTtlSeconds), "idle TTL", { min: 60, max: 30 * 24 * 60 * 60 });
|
|
32
44
|
return { mode: "idle", ttlSeconds };
|
|
33
45
|
};
|
|
34
46
|
const formatAutoDestroy = (policy) => {
|
|
@@ -93,7 +105,7 @@ async function putUploadEntry(entry, uploadUrl, headers) {
|
|
|
93
105
|
}
|
|
94
106
|
}
|
|
95
107
|
async function uploadFiles(command, paths, opts) {
|
|
96
|
-
const spaceId =
|
|
108
|
+
const spaceId = resolveSpace(command);
|
|
97
109
|
const client = createClient();
|
|
98
110
|
try {
|
|
99
111
|
const files = await collectUploadFiles(paths);
|
|
@@ -117,7 +129,7 @@ async function uploadFiles(command, paths, opts) {
|
|
|
117
129
|
const result = await client.space(spaceId).files.completeUpload(plan.uploadId, {
|
|
118
130
|
entries: plan.entries.map((entry) => ({ id: entry.id })),
|
|
119
131
|
});
|
|
120
|
-
if (opts
|
|
132
|
+
if (jsonRequested(opts))
|
|
121
133
|
return outJson({ ...result, uploadId: plan.uploadId, files: files.length });
|
|
122
134
|
ok(`Uploaded ${files.length} file${files.length === 1 ? "" : "s"}`);
|
|
123
135
|
}
|
|
@@ -159,11 +171,11 @@ async function sendPrompt(command, words, opts) {
|
|
|
159
171
|
return error("Conflicting schedule", "Use only one of --delay-ms, --at, or --cron");
|
|
160
172
|
if (opts.cron && !opts.timezone)
|
|
161
173
|
return error("Missing timezone", "--timezone is required with --cron");
|
|
162
|
-
const spaceId =
|
|
174
|
+
const spaceId = resolveSpace(command);
|
|
163
175
|
const client = createClient();
|
|
164
176
|
try {
|
|
165
177
|
const schedule = opts.delayMs
|
|
166
|
-
? { mode: "delay", delayMs:
|
|
178
|
+
? { mode: "delay", delayMs: parseInteger(opts.delayMs, "delay", { min: 1 }) }
|
|
167
179
|
: opts.at
|
|
168
180
|
? { mode: "at", sendAt: opts.at }
|
|
169
181
|
: opts.cron
|
|
@@ -177,7 +189,7 @@ async function sendPrompt(command, words, opts) {
|
|
|
177
189
|
provider: opts.provider,
|
|
178
190
|
schedule,
|
|
179
191
|
});
|
|
180
|
-
if (opts
|
|
192
|
+
if (jsonRequested(opts))
|
|
181
193
|
return outJson(result);
|
|
182
194
|
if (result.mode === "immediate")
|
|
183
195
|
return ok(`Prompt sent — sessionId: ${result.sessionId}, turnId: ${result.turnId}`);
|
|
@@ -216,7 +228,7 @@ export function registerSpaces(program) {
|
|
|
216
228
|
const client = createClient();
|
|
217
229
|
try {
|
|
218
230
|
const items = await client.spaces.list();
|
|
219
|
-
if (opts
|
|
231
|
+
if (jsonRequested(opts))
|
|
220
232
|
return outJson(items);
|
|
221
233
|
table(items, [
|
|
222
234
|
{ key: "id", label: "ID" },
|
|
@@ -237,7 +249,7 @@ export function registerSpaces(program) {
|
|
|
237
249
|
const client = createClient();
|
|
238
250
|
try {
|
|
239
251
|
const space = await client.spaces.get(id);
|
|
240
|
-
if (opts
|
|
252
|
+
if (jsonRequested(opts))
|
|
241
253
|
return outJson(space);
|
|
242
254
|
table([space], [
|
|
243
255
|
{ key: "id", label: "ID" },
|
|
@@ -269,7 +281,7 @@ export function registerSpaces(program) {
|
|
|
269
281
|
description: opts.description,
|
|
270
282
|
...(autoDestroy ? { config: { sandbox: { autoDestroy } } } : {}),
|
|
271
283
|
});
|
|
272
|
-
if (opts
|
|
284
|
+
if (jsonRequested(opts))
|
|
273
285
|
return outJson(result);
|
|
274
286
|
ok(`Space created: ${result.space.id}`);
|
|
275
287
|
table([result.space], [
|
|
@@ -302,12 +314,12 @@ export function registerSpaces(program) {
|
|
|
302
314
|
.description("Upload the space avatar")
|
|
303
315
|
.option("--json", "Output as JSON")
|
|
304
316
|
.action(async (path, opts) => {
|
|
305
|
-
const spaceId =
|
|
317
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
306
318
|
const client = createClient();
|
|
307
319
|
try {
|
|
308
320
|
const asset = await uploadAvatarAsset({ client, purpose: "space_avatar", spaceId, path });
|
|
309
321
|
const result = await client.space(spaceId).profile({ avatarUrl: asset.publicUrl });
|
|
310
|
-
if (opts
|
|
322
|
+
if (jsonRequested(opts))
|
|
311
323
|
return outJson({ ...result, asset });
|
|
312
324
|
ok("Space avatar updated");
|
|
313
325
|
}
|
|
@@ -328,13 +340,13 @@ export function registerSpaces(program) {
|
|
|
328
340
|
const autoDestroy = parseAutoDestroy(opts);
|
|
329
341
|
if (autoDestroy) {
|
|
330
342
|
const result = await client.space(id).updateConfig({ sandbox: { autoDestroy } });
|
|
331
|
-
if (opts
|
|
343
|
+
if (jsonRequested(opts))
|
|
332
344
|
return outJson(result);
|
|
333
345
|
ok(`Space config updated — sandbox auto destroy: ${formatAutoDestroy(autoDestroy)}`);
|
|
334
346
|
return;
|
|
335
347
|
}
|
|
336
348
|
const result = await client.space(id).getConfig();
|
|
337
|
-
if (opts
|
|
349
|
+
if (jsonRequested(opts))
|
|
338
350
|
return outJson(result);
|
|
339
351
|
table([{ key: "sandbox.autoDestroy", value: formatAutoDestroy(result.config.sandbox.autoDestroy) }], [
|
|
340
352
|
{ key: "key", label: "Key" },
|
|
@@ -378,11 +390,11 @@ export function registerSpaces(program) {
|
|
|
378
390
|
.description("Space usage statistics (default: 30 days)")
|
|
379
391
|
.option("--json", "Output as JSON")
|
|
380
392
|
.action(async (days, opts) => {
|
|
381
|
-
const spaceId =
|
|
393
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
382
394
|
const client = createClient();
|
|
383
395
|
try {
|
|
384
|
-
const usage = await client.space(spaceId).usage.get(
|
|
385
|
-
if (opts
|
|
396
|
+
const usage = await client.space(spaceId).usage.get(parseInteger(days ?? "30", "days", { min: 1 }));
|
|
397
|
+
if (jsonRequested(opts))
|
|
386
398
|
return outJson(usage);
|
|
387
399
|
console.log("\n Summary:");
|
|
388
400
|
table([usage.summary], [
|
|
@@ -402,18 +414,18 @@ function registerMods(spacesCmd) {
|
|
|
402
414
|
const modsCmd = spacesCmd
|
|
403
415
|
.command("mods")
|
|
404
416
|
.description("Manage space mods")
|
|
405
|
-
.hook("preAction", () => {
|
|
417
|
+
.hook("preAction", () => { resolveSpace(spacesCmd); });
|
|
406
418
|
modsCmd
|
|
407
419
|
.command("ls")
|
|
408
420
|
.alias("list")
|
|
409
421
|
.description("List mods")
|
|
410
422
|
.option("--json", "Output as JSON")
|
|
411
423
|
.action(async (opts) => {
|
|
412
|
-
const spaceId =
|
|
424
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
413
425
|
const client = createClient();
|
|
414
426
|
try {
|
|
415
427
|
const result = await client.space(spaceId).mods.list();
|
|
416
|
-
if (opts
|
|
428
|
+
if (jsonRequested(opts))
|
|
417
429
|
return outJson(result.items);
|
|
418
430
|
table(result.items, [
|
|
419
431
|
{ key: "id", label: "ID" },
|
|
@@ -435,11 +447,11 @@ function registerMods(spacesCmd) {
|
|
|
435
447
|
.option("--json", "Output as JSON")
|
|
436
448
|
.action(async (modSpaceId, opts) => {
|
|
437
449
|
await confirmRestart(opts);
|
|
438
|
-
const spaceId =
|
|
450
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
439
451
|
const client = createClient();
|
|
440
452
|
try {
|
|
441
453
|
const result = await client.space(spaceId).mods.create({ modSpaceId, name: opts.name, mountSlug: opts.slug });
|
|
442
|
-
if (opts
|
|
454
|
+
if (jsonRequested(opts))
|
|
443
455
|
return outJson(result);
|
|
444
456
|
ok(`Mod added — ${result.item.mountPath}; sandbox restarting`);
|
|
445
457
|
}
|
|
@@ -454,11 +466,11 @@ function registerMods(spacesCmd) {
|
|
|
454
466
|
.option("--json", "Output as JSON")
|
|
455
467
|
.action(async (modId, opts) => {
|
|
456
468
|
await confirmRestart(opts);
|
|
457
|
-
const spaceId =
|
|
469
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
458
470
|
const client = createClient();
|
|
459
471
|
try {
|
|
460
472
|
const result = await client.space(spaceId).mods.update(modId, { enabled: true });
|
|
461
|
-
if (opts
|
|
473
|
+
if (jsonRequested(opts))
|
|
462
474
|
return outJson(result);
|
|
463
475
|
ok("Mod enabled; sandbox restarting");
|
|
464
476
|
}
|
|
@@ -473,11 +485,11 @@ function registerMods(spacesCmd) {
|
|
|
473
485
|
.option("--json", "Output as JSON")
|
|
474
486
|
.action(async (modId, opts) => {
|
|
475
487
|
await confirmRestart(opts);
|
|
476
|
-
const spaceId =
|
|
488
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
477
489
|
const client = createClient();
|
|
478
490
|
try {
|
|
479
491
|
const result = await client.space(spaceId).mods.update(modId, { enabled: false });
|
|
480
|
-
if (opts
|
|
492
|
+
if (jsonRequested(opts))
|
|
481
493
|
return outJson(result);
|
|
482
494
|
ok("Mod disabled; sandbox restarting");
|
|
483
495
|
}
|
|
@@ -493,11 +505,11 @@ function registerMods(spacesCmd) {
|
|
|
493
505
|
.option("--json", "Output as JSON")
|
|
494
506
|
.action(async (modId, opts) => {
|
|
495
507
|
await confirmRestart(opts);
|
|
496
|
-
const spaceId =
|
|
508
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
497
509
|
const client = createClient();
|
|
498
510
|
try {
|
|
499
511
|
const result = await client.space(spaceId).mods.remove(modId);
|
|
500
|
-
if (opts
|
|
512
|
+
if (jsonRequested(opts))
|
|
501
513
|
return outJson(result);
|
|
502
514
|
ok("Mod removed; sandbox restarting");
|
|
503
515
|
}
|
|
@@ -511,18 +523,18 @@ function registerFiles(spacesCmd) {
|
|
|
511
523
|
const filesCmd = spacesCmd
|
|
512
524
|
.command("files")
|
|
513
525
|
.description("File operations")
|
|
514
|
-
.hook("preAction", () => {
|
|
526
|
+
.hook("preAction", () => { resolveSpace(spacesCmd); });
|
|
515
527
|
filesCmd
|
|
516
528
|
.command("ls [path]")
|
|
517
529
|
.alias("list")
|
|
518
530
|
.description("List directory tree")
|
|
519
531
|
.option("--json", "Output as JSON")
|
|
520
532
|
.action(async (path, opts) => {
|
|
521
|
-
const spaceId =
|
|
533
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
522
534
|
const client = createClient();
|
|
523
535
|
try {
|
|
524
536
|
const tree = await client.space(spaceId).files.list(path ?? "");
|
|
525
|
-
if (opts
|
|
537
|
+
if (jsonRequested(opts))
|
|
526
538
|
return outJson(tree);
|
|
527
539
|
if (tree.entries.length === 0) {
|
|
528
540
|
console.log(" (empty)");
|
|
@@ -543,7 +555,7 @@ function registerFiles(spacesCmd) {
|
|
|
543
555
|
.command("cat <path>")
|
|
544
556
|
.description("Read file content")
|
|
545
557
|
.action(async (path) => {
|
|
546
|
-
const spaceId =
|
|
558
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
547
559
|
const client = createClient();
|
|
548
560
|
try {
|
|
549
561
|
const file = await client.space(spaceId).files.read(path);
|
|
@@ -573,7 +585,7 @@ function registerFiles(spacesCmd) {
|
|
|
573
585
|
}
|
|
574
586
|
if (!content)
|
|
575
587
|
return error("No content provided", "Use -c or pipe via stdin");
|
|
576
|
-
const spaceId =
|
|
588
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
577
589
|
const client = createClient();
|
|
578
590
|
try {
|
|
579
591
|
const result = await client.space(spaceId).files.write({
|
|
@@ -597,7 +609,7 @@ function registerFiles(spacesCmd) {
|
|
|
597
609
|
.command("mkdir <path>")
|
|
598
610
|
.description("Create a directory")
|
|
599
611
|
.action(async (path) => {
|
|
600
|
-
const spaceId =
|
|
612
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
601
613
|
const client = createClient();
|
|
602
614
|
try {
|
|
603
615
|
await client.space(spaceId).files.createDir(path);
|
|
@@ -612,7 +624,7 @@ function registerFiles(spacesCmd) {
|
|
|
612
624
|
.description("Delete a file or directory")
|
|
613
625
|
.option("-r, --recursive", "Delete recursively")
|
|
614
626
|
.action(async (path, opts) => {
|
|
615
|
-
const spaceId =
|
|
627
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
616
628
|
const client = createClient();
|
|
617
629
|
try {
|
|
618
630
|
await client.space(spaceId).files.delete(path, opts.recursive ?? false);
|
|
@@ -626,7 +638,7 @@ function registerFiles(spacesCmd) {
|
|
|
626
638
|
.command("mv <from> <to>")
|
|
627
639
|
.description("Move or rename")
|
|
628
640
|
.action(async (from, to) => {
|
|
629
|
-
const spaceId =
|
|
641
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
630
642
|
const client = createClient();
|
|
631
643
|
try {
|
|
632
644
|
await client.space(spaceId).files.move({ fromPath: from, toPath: to });
|
|
@@ -642,18 +654,18 @@ function registerSessions(spacesCmd) {
|
|
|
642
654
|
const sessionsCmd = spacesCmd
|
|
643
655
|
.command("sessions")
|
|
644
656
|
.description("Browse sessions and turns")
|
|
645
|
-
.hook("preAction", () => {
|
|
657
|
+
.hook("preAction", () => { resolveSpace(spacesCmd); });
|
|
646
658
|
sessionsCmd
|
|
647
659
|
.command("ls")
|
|
648
660
|
.alias("list")
|
|
649
661
|
.description("List sessions")
|
|
650
662
|
.option("--json", "Output as JSON")
|
|
651
663
|
.action(async (opts) => {
|
|
652
|
-
const spaceId =
|
|
664
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
653
665
|
const client = createClient();
|
|
654
666
|
try {
|
|
655
667
|
const result = await client.space(spaceId).sessions.list();
|
|
656
|
-
if (opts
|
|
668
|
+
if (jsonRequested(opts))
|
|
657
669
|
return outJson(result);
|
|
658
670
|
if (result.sessions.length === 0) {
|
|
659
671
|
console.log(" (empty)");
|
|
@@ -675,11 +687,11 @@ function registerSessions(spacesCmd) {
|
|
|
675
687
|
.description("Create a session")
|
|
676
688
|
.option("--json", "Output as JSON")
|
|
677
689
|
.action(async (title, opts) => {
|
|
678
|
-
const spaceId =
|
|
690
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
679
691
|
const client = createClient();
|
|
680
692
|
try {
|
|
681
693
|
const result = await client.space(spaceId).sessions.create({ title });
|
|
682
|
-
if (opts
|
|
694
|
+
if (jsonRequested(opts))
|
|
683
695
|
return outJson(result);
|
|
684
696
|
ok(`Session created: ${result.session.id}`);
|
|
685
697
|
table([result.session], [
|
|
@@ -696,11 +708,11 @@ function registerSessions(spacesCmd) {
|
|
|
696
708
|
.description("Session details")
|
|
697
709
|
.option("--json", "Output as JSON")
|
|
698
710
|
.action(async (id, opts) => {
|
|
699
|
-
const spaceId =
|
|
711
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
700
712
|
const client = createClient();
|
|
701
713
|
try {
|
|
702
714
|
const result = await client.space(spaceId).session(id).get();
|
|
703
|
-
if (opts
|
|
715
|
+
if (jsonRequested(opts))
|
|
704
716
|
return outJson(result);
|
|
705
717
|
table([result.session], [
|
|
706
718
|
{ key: "id", label: "ID" },
|
|
@@ -718,7 +730,7 @@ function registerSessions(spacesCmd) {
|
|
|
718
730
|
.command("rename <id> <name>")
|
|
719
731
|
.description("Rename a session")
|
|
720
732
|
.action(async (id, name) => {
|
|
721
|
-
const spaceId =
|
|
733
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
722
734
|
const client = createClient();
|
|
723
735
|
try {
|
|
724
736
|
await client.space(spaceId).session(id).rename(name);
|
|
@@ -734,13 +746,13 @@ function registerSessions(spacesCmd) {
|
|
|
734
746
|
.description("Stream realtime session events")
|
|
735
747
|
.option("--json", "Output as JSON")
|
|
736
748
|
.action(async (id, opts) => {
|
|
737
|
-
const spaceId =
|
|
749
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
738
750
|
const client = createClient();
|
|
739
751
|
const session = client.space(spaceId).session(id);
|
|
740
752
|
process.stdout.write(" Listening for events...\n\n");
|
|
741
753
|
let lastAppendPath = null;
|
|
742
754
|
session.on("turn.patch", (e) => {
|
|
743
|
-
if (opts
|
|
755
|
+
if (jsonRequested(opts)) {
|
|
744
756
|
console.log(JSON.stringify(e));
|
|
745
757
|
}
|
|
746
758
|
else {
|
|
@@ -767,7 +779,7 @@ function registerSessions(spacesCmd) {
|
|
|
767
779
|
});
|
|
768
780
|
session.on("turn.error", (e) => {
|
|
769
781
|
process.stderr.write(`\n ✗ Error\n`);
|
|
770
|
-
if (opts
|
|
782
|
+
if (jsonRequested(opts))
|
|
771
783
|
process.stderr.write(`${JSON.stringify(e)}\n`);
|
|
772
784
|
process.exit(1);
|
|
773
785
|
});
|
|
@@ -789,15 +801,15 @@ function registerTurns(sessionsCmd) {
|
|
|
789
801
|
.option("--limit <n>", "Page size", "30")
|
|
790
802
|
.option("--json", "Output as JSON")
|
|
791
803
|
.action(async (sessionId, opts) => {
|
|
792
|
-
const spaceId =
|
|
804
|
+
const spaceId = resolveSpace(sessionsCmd);
|
|
793
805
|
const client = createClient();
|
|
794
806
|
try {
|
|
795
807
|
const result = await client.space(spaceId).session(sessionId).turns.listPaginated({
|
|
796
|
-
cursor: opts.cursor === undefined ? undefined :
|
|
797
|
-
direction: opts.direction,
|
|
798
|
-
limit:
|
|
808
|
+
cursor: opts.cursor === undefined ? undefined : parseInteger(opts.cursor, "cursor", { min: 0 }),
|
|
809
|
+
direction: parseChoice(opts.direction ?? "older", "direction", ["older", "newer"]),
|
|
810
|
+
limit: parseInteger(opts.limit ?? "30", "limit", { min: 1, max: 100 }),
|
|
799
811
|
});
|
|
800
|
-
if (opts
|
|
812
|
+
if (jsonRequested(opts))
|
|
801
813
|
return outJson(result);
|
|
802
814
|
if (result.turns.length === 0)
|
|
803
815
|
return console.log(" No turns found");
|
|
@@ -821,11 +833,11 @@ function registerTurns(sessionsCmd) {
|
|
|
821
833
|
.description("Show turn details")
|
|
822
834
|
.option("--json", "Output as JSON")
|
|
823
835
|
.action(async (sessionId, turnId, opts) => {
|
|
824
|
-
const spaceId =
|
|
836
|
+
const spaceId = resolveSpace(sessionsCmd);
|
|
825
837
|
const client = createClient();
|
|
826
838
|
try {
|
|
827
839
|
const result = await client.space(spaceId).session(sessionId).turns.get(turnId);
|
|
828
|
-
if (opts
|
|
840
|
+
if (jsonRequested(opts))
|
|
829
841
|
return outJson(result);
|
|
830
842
|
table([result.turn], [
|
|
831
843
|
{ key: "sequence", label: "Seq" },
|
|
@@ -852,14 +864,14 @@ function registerTurns(sessionsCmd) {
|
|
|
852
864
|
.option("--limit <n>", "Page size", "100")
|
|
853
865
|
.option("--json", "Output as JSON")
|
|
854
866
|
.action(async (sessionId, opts) => {
|
|
855
|
-
const spaceId =
|
|
867
|
+
const spaceId = resolveSpace(sessionsCmd);
|
|
856
868
|
const client = createClient();
|
|
857
869
|
try {
|
|
858
870
|
const result = await client.space(spaceId).session(sessionId).turns.index({
|
|
859
|
-
cursor: opts.cursor === undefined ? undefined :
|
|
860
|
-
limit:
|
|
871
|
+
cursor: opts.cursor === undefined ? undefined : parseInteger(opts.cursor, "cursor", { min: 0 }),
|
|
872
|
+
limit: parseInteger(opts.limit ?? "100", "limit", { min: 1, max: 500 }),
|
|
861
873
|
});
|
|
862
|
-
if (opts
|
|
874
|
+
if (jsonRequested(opts))
|
|
863
875
|
return outJson(result);
|
|
864
876
|
if (result.turns.length === 0)
|
|
865
877
|
return console.log(" No turns found");
|
|
@@ -886,18 +898,18 @@ function registerTurns(sessionsCmd) {
|
|
|
886
898
|
.option("--after <n>", "Turns after anchor", "20")
|
|
887
899
|
.option("--json", "Output as JSON")
|
|
888
900
|
.action(async (sessionId, opts) => {
|
|
889
|
-
const spaceId =
|
|
901
|
+
const spaceId = resolveSpace(sessionsCmd);
|
|
890
902
|
if (!opts.sequence && !opts.turn)
|
|
891
903
|
return error("Missing anchor", "Use --sequence <n> or --turn <id>");
|
|
892
904
|
const client = createClient();
|
|
893
905
|
try {
|
|
894
906
|
const result = await client.space(spaceId).session(sessionId).turns.window({
|
|
895
|
-
sequence: opts.sequence === undefined ? undefined :
|
|
907
|
+
sequence: opts.sequence === undefined ? undefined : parseInteger(opts.sequence, "sequence", { min: 0 }),
|
|
896
908
|
turnId: opts.turn,
|
|
897
|
-
before:
|
|
898
|
-
after:
|
|
909
|
+
before: parseInteger(opts.before ?? "10", "before", { min: 0, max: 200 }),
|
|
910
|
+
after: parseInteger(opts.after ?? "20", "after", { min: 0, max: 200 }),
|
|
899
911
|
});
|
|
900
|
-
if (opts
|
|
912
|
+
if (jsonRequested(opts))
|
|
901
913
|
return outJson(result);
|
|
902
914
|
if (result.turns.length === 0)
|
|
903
915
|
return console.log(" No turns found");
|
|
@@ -926,7 +938,7 @@ function registerSessionAccess(sessionsCmd) {
|
|
|
926
938
|
const client = createClient();
|
|
927
939
|
try {
|
|
928
940
|
const policy = await client.sessionAccess.get(id);
|
|
929
|
-
if (opts
|
|
941
|
+
if (jsonRequested(opts))
|
|
930
942
|
return outJson(policy);
|
|
931
943
|
table([policy], [
|
|
932
944
|
{ key: "signed_in_user", label: "Signed-in" },
|
|
@@ -946,9 +958,9 @@ function registerSessionAccess(sessionsCmd) {
|
|
|
946
958
|
const client = createClient();
|
|
947
959
|
try {
|
|
948
960
|
const policy = await client.sessionAccess.set(id, {
|
|
949
|
-
anonymous_user: (opts.anonymous
|
|
961
|
+
anonymous_user: parseNullableRole(opts.anonymous, "anonymous role"),
|
|
950
962
|
});
|
|
951
|
-
if (opts
|
|
963
|
+
if (jsonRequested(opts))
|
|
952
964
|
return outJson(policy);
|
|
953
965
|
ok("Session access updated");
|
|
954
966
|
table([policy], [
|
|
@@ -979,18 +991,18 @@ function registerMembers(spacesCmd) {
|
|
|
979
991
|
const memCmd = spacesCmd
|
|
980
992
|
.command("members")
|
|
981
993
|
.description("Member management")
|
|
982
|
-
.hook("preAction", () => {
|
|
994
|
+
.hook("preAction", () => { resolveSpace(spacesCmd); });
|
|
983
995
|
memCmd
|
|
984
996
|
.command("ls")
|
|
985
997
|
.alias("list")
|
|
986
998
|
.description("List space members")
|
|
987
999
|
.option("--json", "Output as JSON")
|
|
988
1000
|
.action(async (opts) => {
|
|
989
|
-
const spaceId =
|
|
1001
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
990
1002
|
const client = createClient();
|
|
991
1003
|
try {
|
|
992
1004
|
const result = await client.space(spaceId).members.list();
|
|
993
|
-
if (opts
|
|
1005
|
+
if (jsonRequested(opts))
|
|
994
1006
|
return outJson(result);
|
|
995
1007
|
if (result.items.length === 0) {
|
|
996
1008
|
console.log(" (empty)");
|
|
@@ -1010,10 +1022,10 @@ function registerMembers(spacesCmd) {
|
|
|
1010
1022
|
.command("update <userId> <role>")
|
|
1011
1023
|
.description("Change member role (host | builder | guest)")
|
|
1012
1024
|
.action(async (userId, role) => {
|
|
1013
|
-
const spaceId =
|
|
1025
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1014
1026
|
const client = createClient();
|
|
1015
1027
|
try {
|
|
1016
|
-
await client.space(spaceId).members.update(userId, role);
|
|
1028
|
+
await client.space(spaceId).members.update(userId, parseChoice(role, "role", SPACE_ROLES));
|
|
1017
1029
|
ok(`${userId} → ${role}`);
|
|
1018
1030
|
}
|
|
1019
1031
|
catch (e) {
|
|
@@ -1024,7 +1036,7 @@ function registerMembers(spacesCmd) {
|
|
|
1024
1036
|
.command("remove <userId>")
|
|
1025
1037
|
.description("Remove a member")
|
|
1026
1038
|
.action(async (userId) => {
|
|
1027
|
-
const spaceId =
|
|
1039
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1028
1040
|
const client = createClient();
|
|
1029
1041
|
try {
|
|
1030
1042
|
await client.space(spaceId).members.remove(userId);
|
|
@@ -1040,17 +1052,17 @@ function registerAccess(spacesCmd) {
|
|
|
1040
1052
|
const accCmd = spacesCmd
|
|
1041
1053
|
.command("access")
|
|
1042
1054
|
.description("Access control")
|
|
1043
|
-
.hook("preAction", () => {
|
|
1055
|
+
.hook("preAction", () => { resolveSpace(spacesCmd); });
|
|
1044
1056
|
accCmd
|
|
1045
1057
|
.command("get")
|
|
1046
1058
|
.description("Get access policy")
|
|
1047
1059
|
.option("--json", "Output as JSON")
|
|
1048
1060
|
.action(async (opts) => {
|
|
1049
|
-
const spaceId =
|
|
1061
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1050
1062
|
const client = createClient();
|
|
1051
1063
|
try {
|
|
1052
1064
|
const policy = await client.space(spaceId).access.get();
|
|
1053
|
-
if (opts
|
|
1065
|
+
if (jsonRequested(opts))
|
|
1054
1066
|
return outJson(policy);
|
|
1055
1067
|
table([policy], [
|
|
1056
1068
|
{ key: "signed_in_user", label: "Signed-in" },
|
|
@@ -1068,14 +1080,14 @@ function registerAccess(spacesCmd) {
|
|
|
1068
1080
|
.option("--anonymous <role>", "Role for anonymous users (host|builder|guest|null)")
|
|
1069
1081
|
.option("--json", "Output as JSON")
|
|
1070
1082
|
.action(async (opts) => {
|
|
1071
|
-
const spaceId =
|
|
1083
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1072
1084
|
const client = createClient();
|
|
1073
1085
|
try {
|
|
1074
1086
|
const policy = await client.space(spaceId).access.set({
|
|
1075
|
-
signed_in_user: (opts.signedIn
|
|
1076
|
-
anonymous_user: (opts.anonymous
|
|
1087
|
+
signed_in_user: parseNullableRole(opts.signedIn, "signed-in role"),
|
|
1088
|
+
anonymous_user: parseNullableRole(opts.anonymous, "anonymous role"),
|
|
1077
1089
|
});
|
|
1078
|
-
if (opts
|
|
1090
|
+
if (jsonRequested(opts))
|
|
1079
1091
|
return outJson(policy);
|
|
1080
1092
|
ok("Access policy updated");
|
|
1081
1093
|
table([policy], [
|
|
@@ -1093,18 +1105,18 @@ function registerCheckpoints(spacesCmd) {
|
|
|
1093
1105
|
const cpCmd = spacesCmd
|
|
1094
1106
|
.command("checkpoints")
|
|
1095
1107
|
.description("Checkpoint management")
|
|
1096
|
-
.hook("preAction", () => {
|
|
1108
|
+
.hook("preAction", () => { resolveSpace(spacesCmd); });
|
|
1097
1109
|
cpCmd
|
|
1098
1110
|
.command("ls")
|
|
1099
1111
|
.alias("list")
|
|
1100
1112
|
.description("List checkpoints")
|
|
1101
1113
|
.option("--json", "Output as JSON")
|
|
1102
1114
|
.action(async (opts) => {
|
|
1103
|
-
const spaceId =
|
|
1115
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1104
1116
|
const client = createClient();
|
|
1105
1117
|
try {
|
|
1106
1118
|
const result = await client.space(spaceId).checkpoints.list();
|
|
1107
|
-
if (opts
|
|
1119
|
+
if (jsonRequested(opts))
|
|
1108
1120
|
return outJson(result);
|
|
1109
1121
|
if (result.checkpoints.length === 0) {
|
|
1110
1122
|
console.log(" (empty)");
|
|
@@ -1126,11 +1138,11 @@ function registerCheckpoints(spacesCmd) {
|
|
|
1126
1138
|
.description("Checkpoint details")
|
|
1127
1139
|
.option("--json", "Output as JSON")
|
|
1128
1140
|
.action(async (id, opts) => {
|
|
1129
|
-
const spaceId =
|
|
1141
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1130
1142
|
const client = createClient();
|
|
1131
1143
|
try {
|
|
1132
1144
|
const result = await client.space(spaceId).checkpoints.get(id);
|
|
1133
|
-
if (opts
|
|
1145
|
+
if (jsonRequested(opts))
|
|
1134
1146
|
return outJson(result);
|
|
1135
1147
|
table([result.checkpoint], [
|
|
1136
1148
|
{ key: "id", label: "ID" },
|
|
@@ -1149,11 +1161,11 @@ function registerCheckpoints(spacesCmd) {
|
|
|
1149
1161
|
.description("Create a checkpoint")
|
|
1150
1162
|
.option("--json", "Output as JSON")
|
|
1151
1163
|
.action(async (description, opts) => {
|
|
1152
|
-
const spaceId =
|
|
1164
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1153
1165
|
const client = createClient();
|
|
1154
1166
|
try {
|
|
1155
1167
|
const result = await client.space(spaceId).checkpoints.create(description ?? null);
|
|
1156
|
-
if (opts
|
|
1168
|
+
if (jsonRequested(opts))
|
|
1157
1169
|
return outJson(result);
|
|
1158
1170
|
ok(`Checkpoint created — taskRunId: ${result.taskRunId}`);
|
|
1159
1171
|
}
|
package/dist/commands/tasks.js
CHANGED
|
@@ -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 registerTasks(program) {
|
|
4
4
|
const cmd = program.command("tasks", { hidden: true }).description("Task runs");
|
|
5
5
|
cmd
|
|
@@ -18,7 +18,7 @@ export function registerTasks(program) {
|
|
|
18
18
|
if (opts.space)
|
|
19
19
|
filters.spaceId = opts.space;
|
|
20
20
|
const result = await client.tasks.list(filters);
|
|
21
|
-
if (opts
|
|
21
|
+
if (jsonRequested(opts))
|
|
22
22
|
return outJson(result);
|
|
23
23
|
if (result.runs.length === 0)
|
|
24
24
|
return console.log(" (empty)");
|
|
@@ -41,7 +41,7 @@ export function registerTasks(program) {
|
|
|
41
41
|
const client = createClient();
|
|
42
42
|
try {
|
|
43
43
|
const result = await client.tasks.get(id);
|
|
44
|
-
if (opts
|
|
44
|
+
if (jsonRequested(opts))
|
|
45
45
|
return outJson(result);
|
|
46
46
|
table([result.run], [
|
|
47
47
|
{ key: "id", label: "ID" },
|
package/dist/output.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export declare function table(rows: Row[], columns: {
|
|
|
4
4
|
label: string;
|
|
5
5
|
}[]): void;
|
|
6
6
|
export declare function json(data: unknown): void;
|
|
7
|
+
export declare function jsonRequested(opts?: {
|
|
8
|
+
json?: boolean;
|
|
9
|
+
}): boolean;
|
|
7
10
|
export declare function ok(msg: string): void;
|
|
8
11
|
export declare function error(msg: string, detail?: string): never;
|
|
9
12
|
export declare function handleHttp(e: unknown): never;
|
package/dist/output.js
CHANGED
|
@@ -35,6 +35,9 @@ export function table(rows, columns) {
|
|
|
35
35
|
export function json(data) {
|
|
36
36
|
console.log(JSON.stringify(data, null, 2));
|
|
37
37
|
}
|
|
38
|
+
export function jsonRequested(opts) {
|
|
39
|
+
return Boolean(opts?.json || process.argv.includes("--json"));
|
|
40
|
+
}
|
|
38
41
|
export function ok(msg) {
|
|
39
42
|
console.log(`\n ✓ ${msg}\n`);
|
|
40
43
|
}
|
package/dist/space.d.ts
ADDED
package/dist/space.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { error } from "./output.js";
|
|
2
|
+
export function resolveSpace(program) {
|
|
3
|
+
let current = program;
|
|
4
|
+
while (current) {
|
|
5
|
+
const opts = current.opts();
|
|
6
|
+
if (typeof opts.space === "string" && opts.space.trim())
|
|
7
|
+
return opts.space.trim();
|
|
8
|
+
current = current.parent ?? null;
|
|
9
|
+
}
|
|
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.");
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"commander": "^13.1.0",
|
|
17
17
|
"sharp": "^0.34.5",
|
|
18
|
-
"@neta-art/cohub": "1.
|
|
18
|
+
"@neta-art/cohub": "1.16.0"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|