@neta-art/cohub-cli 1.7.1 → 1.9.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 +8 -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 +101 -28
- 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 +4 -0
- package/dist/output.js +25 -4
- package/dist/space.d.ts +2 -0
- package/dist/space.js +14 -0
- package/package.json +3 -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> \
|
|
@@ -140,6 +142,12 @@ cohub generate "restyle this image" \
|
|
|
140
142
|
--image ./input.png \
|
|
141
143
|
--param size=1024x1024 \
|
|
142
144
|
--json
|
|
145
|
+
|
|
146
|
+
cohub generate "a calm lake" \
|
|
147
|
+
--model <model> \
|
|
148
|
+
--async
|
|
149
|
+
|
|
150
|
+
cohub tasks get <taskRunId> --json
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
Supported inputs:
|
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",
|
|
@@ -47,8 +48,8 @@ async function contentFromPathOrUrl(type, value) {
|
|
|
47
48
|
if (/^https?:\/\//.test(value))
|
|
48
49
|
return { type, source: { type: "url", url: value } };
|
|
49
50
|
const data = await readFile(value);
|
|
50
|
-
const
|
|
51
|
-
return { type, source: { type: "base64",
|
|
51
|
+
const mediaType = mimeByExt[extname(value).toLowerCase()] ?? "application/octet-stream";
|
|
52
|
+
return { type, source: { type: "base64", mediaType, data: data.toString("base64") } };
|
|
52
53
|
}
|
|
53
54
|
async function saveOutputs(output, outputPath) {
|
|
54
55
|
const outputs = output.filter((block) => block.type === "text" || block.type === "image" || block.type === "video" || block.type === "audio");
|
|
@@ -71,7 +72,7 @@ async function saveOutputs(output, outputPath) {
|
|
|
71
72
|
continue;
|
|
72
73
|
}
|
|
73
74
|
const source = block.source;
|
|
74
|
-
const target = isDir ? join(outputPath, outputName(block
|
|
75
|
+
const target = isDir ? join(outputPath, outputName(block, source.type === "url" ? source.url : undefined, i)) : outputPath;
|
|
75
76
|
if (source.type === "url") {
|
|
76
77
|
const response = await fetch(source.url);
|
|
77
78
|
if (!response.ok)
|
|
@@ -79,72 +80,144 @@ async function saveOutputs(output, outputPath) {
|
|
|
79
80
|
await writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
80
81
|
savedPaths.push(target);
|
|
81
82
|
}
|
|
82
|
-
else
|
|
83
|
+
else {
|
|
83
84
|
await writeFile(target, Buffer.from(source.data, "base64"));
|
|
84
85
|
savedPaths.push(target);
|
|
85
86
|
}
|
|
86
|
-
else {
|
|
87
|
-
throw new Error(`Cannot save space file output locally: ${source.space_id}:${source.path}`);
|
|
88
|
-
}
|
|
89
87
|
}
|
|
90
88
|
return savedPaths;
|
|
91
89
|
}
|
|
92
|
-
function outputName(
|
|
90
|
+
function outputName(block, url, index) {
|
|
93
91
|
const fromUrl = url ? basename(new URL(url).pathname) : "";
|
|
92
|
+
const label = slugOutputLabel(block);
|
|
94
93
|
if (fromUrl?.includes("."))
|
|
95
|
-
return `generation-${index + 1}-${fromUrl}`;
|
|
96
|
-
const ext = type === "video" ? "mp4" : type === "audio" ? "bin" : "png";
|
|
97
|
-
return `generation-${index + 1}.${ext}`;
|
|
94
|
+
return `generation-${index + 1}-${label}-${fromUrl}`;
|
|
95
|
+
const ext = block.type === "video" ? "mp4" : block.type === "audio" ? "bin" : block.type === "text" ? "txt" : "png";
|
|
96
|
+
return `generation-${index + 1}-${label}.${ext}`;
|
|
97
|
+
}
|
|
98
|
+
function metaRole(block) {
|
|
99
|
+
const role = block.meta?.role;
|
|
100
|
+
return typeof role === "string" && role.length > 0 ? role : undefined;
|
|
101
|
+
}
|
|
102
|
+
function humanizeRole(role) {
|
|
103
|
+
return role.replaceAll("_", " ").replaceAll("-", " ");
|
|
104
|
+
}
|
|
105
|
+
function slugify(value) {
|
|
106
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "") || "output";
|
|
107
|
+
}
|
|
108
|
+
function formatOutputLabel(block) {
|
|
109
|
+
const role = metaRole(block);
|
|
110
|
+
if (!role)
|
|
111
|
+
return block.type;
|
|
112
|
+
const label = humanizeRole(role);
|
|
113
|
+
return block.type === "image" && ["first_frame", "last_frame", "reference_image"].includes(role)
|
|
114
|
+
? label
|
|
115
|
+
: `${block.type} (role: ${role})`;
|
|
116
|
+
}
|
|
117
|
+
function slugOutputLabel(block) {
|
|
118
|
+
return slugify(metaRole(block) ?? block.type);
|
|
98
119
|
}
|
|
99
120
|
function printGeneration(output) {
|
|
100
121
|
for (const block of output) {
|
|
101
|
-
if (block.type === "text")
|
|
122
|
+
if (block.type === "text") {
|
|
102
123
|
console.log(block.text);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
else
|
|
108
|
-
console.log(`${block
|
|
124
|
+
}
|
|
125
|
+
else if (block.source.type === "url") {
|
|
126
|
+
console.log(`${formatOutputLabel(block)}: ${block.source.url}`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(`${formatOutputLabel(block)}: base64 ${block.source.mediaType} (${block.source.data.length} chars)`);
|
|
130
|
+
}
|
|
109
131
|
}
|
|
110
132
|
}
|
|
133
|
+
function resumeHint(taskRunId) {
|
|
134
|
+
return `Use \`cohub tasks get ${taskRunId} --json\` to inspect the task later.`;
|
|
135
|
+
}
|
|
136
|
+
function formatElapsed(ms) {
|
|
137
|
+
if (ms < 1000)
|
|
138
|
+
return `${ms}ms`;
|
|
139
|
+
const seconds = Math.floor(ms / 1000);
|
|
140
|
+
if (seconds < 60)
|
|
141
|
+
return `${seconds}s`;
|
|
142
|
+
const minutes = Math.floor(seconds / 60);
|
|
143
|
+
const restSeconds = seconds % 60;
|
|
144
|
+
return restSeconds > 0 ? `${minutes}m ${restSeconds}s` : `${minutes}m`;
|
|
145
|
+
}
|
|
146
|
+
function parseTimeoutMs(value) {
|
|
147
|
+
if (!value)
|
|
148
|
+
return undefined;
|
|
149
|
+
if (!/^\d+$/.test(value.trim()))
|
|
150
|
+
return error("Invalid timeout", "--timeout-ms must be a positive integer");
|
|
151
|
+
const timeoutMs = Number.parseInt(value, 10);
|
|
152
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0)
|
|
153
|
+
return error("Invalid timeout", "--timeout-ms must be a positive integer");
|
|
154
|
+
return timeoutMs;
|
|
155
|
+
}
|
|
111
156
|
export function registerGenerations(program) {
|
|
112
157
|
program
|
|
113
158
|
.command("generate")
|
|
114
159
|
.description("Generate multimodal outputs")
|
|
115
160
|
.argument("<prompt>", "Prompt text")
|
|
116
|
-
.requiredOption("--model <model>", "Multimodal model ID from `cohub models ls --model-type multimodal`")
|
|
161
|
+
.requiredOption("-m, --model <model>", "Multimodal model ID from `cohub models ls --model-type multimodal`")
|
|
117
162
|
.option("--image <path-or-url>", "Image input file path or URL; repeatable", collect, [])
|
|
118
163
|
.option("--video <path-or-url>", "Video input file path or URL; repeatable", collect, [])
|
|
119
164
|
.option("--audio <path-or-url>", "Audio input file path or URL; repeatable", collect, [])
|
|
120
165
|
.option("--param <key=value>", "Generation parameter; repeatable, values may be JSON/number/boolean", collect, [])
|
|
121
166
|
.option("--parameters <json>", "Generation parameters as a JSON object")
|
|
122
167
|
.option("--metadata <json>", "Metadata as a JSON object")
|
|
123
|
-
.option("--output <path>", "Save generated output to a file or directory")
|
|
168
|
+
.option("-o, --output <path>", "Save generated output to a file or directory")
|
|
169
|
+
.option("--async", "Queue the generation task and return immediately")
|
|
170
|
+
.option("--timeout-ms <ms>", "Maximum time to wait in synchronous mode")
|
|
124
171
|
.option("--json", "Output as JSON")
|
|
125
172
|
.addHelpText("after", `
|
|
126
173
|
|
|
127
174
|
Examples:
|
|
128
175
|
cohub models ls --model-type multimodal
|
|
129
|
-
cohub generate "A calm lake at sunrise"
|
|
130
|
-
cohub generate "Restyle this image"
|
|
176
|
+
cohub -s <space-id> generate "A calm lake at sunrise" -m <model> -o lake.png
|
|
177
|
+
COHUB_SPACE_ID=<space-id> cohub generate "Restyle this image" -m <model> --image input.png
|
|
178
|
+
cohub -s <space-id> generate "A calm lake" -m <model> --async
|
|
131
179
|
`)
|
|
132
180
|
.action(async (prompt, opts) => {
|
|
133
181
|
try {
|
|
182
|
+
const spaceId = resolveSpace(program);
|
|
134
183
|
const content = [{ type: "text", text: prompt }];
|
|
135
184
|
content.push(...await Promise.all(opts.image.map((value) => contentFromPathOrUrl("image", value))));
|
|
136
185
|
content.push(...await Promise.all(opts.video.map((value) => contentFromPathOrUrl("video", value))));
|
|
137
186
|
content.push(...await Promise.all(opts.audio.map((value) => contentFromPathOrUrl("audio", value))));
|
|
138
|
-
const
|
|
187
|
+
const client = createClient();
|
|
188
|
+
const created = await client.generations.create({
|
|
189
|
+
spaceId,
|
|
139
190
|
model: opts.model,
|
|
140
191
|
content,
|
|
141
192
|
parameters: parseParams(opts.param, opts.parameters),
|
|
142
193
|
metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
|
|
143
194
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
195
|
+
if (opts.async) {
|
|
196
|
+
if (jsonRequested(opts))
|
|
197
|
+
return outJson(created);
|
|
198
|
+
return ok(`Generation queued — task ID: ${created.taskRunId}\n ${resumeHint(created.taskRunId)}`);
|
|
199
|
+
}
|
|
200
|
+
const spin = spinner();
|
|
201
|
+
let pollCount = 0;
|
|
202
|
+
const waitStartedAt = Date.now();
|
|
203
|
+
if (!jsonRequested(opts)) {
|
|
204
|
+
process.stderr.write(` Generation queued — task ID: ${created.taskRunId}\n`);
|
|
205
|
+
process.stderr.write(` ${resumeHint(created.taskRunId)}\n`);
|
|
206
|
+
spin.start("Generating...");
|
|
207
|
+
}
|
|
208
|
+
const result = await client.generations.wait(created.taskRunId, {
|
|
209
|
+
timeoutMs: parseTimeoutMs(opts.timeoutMs),
|
|
210
|
+
onPoll: () => {
|
|
211
|
+
pollCount += 1;
|
|
212
|
+
spin.update(`Generating... ${formatElapsed(Date.now() - waitStartedAt)}, ${pollCount} polls`);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
if (!jsonRequested(opts))
|
|
216
|
+
spin.stop(`Generation completed — task ID: ${created.taskRunId}, ${formatElapsed(Date.now() - waitStartedAt)}, ${pollCount} polls`);
|
|
217
|
+
const savedPaths = opts.output ? await saveOutputs(result.output, opts.output) : [];
|
|
218
|
+
if (jsonRequested(opts))
|
|
219
|
+
return outJson(savedPaths.length > 0 ? { ...result, taskRunId: created.taskRunId, savedPaths } : { ...result, taskRunId: created.taskRunId });
|
|
220
|
+
printGeneration(result.output);
|
|
148
221
|
if (savedPaths.length > 0)
|
|
149
222
|
ok(`Saved to ${savedPaths.join(", ")}`);
|
|
150
223
|
}
|
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], [
|