@neta-art/cohub-cli 1.1.4 → 1.3.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 +183 -13
- package/dist/auth.d.ts +41 -12
- package/dist/auth.js +249 -33
- package/dist/client.d.ts +1 -1
- package/dist/client.js +4 -2
- package/dist/commands/auth.js +93 -32
- package/dist/commands/channels.js +4 -14
- package/dist/commands/cron-jobs.js +6 -19
- package/dist/commands/generations.d.ts +2 -0
- package/dist/commands/generations.js +159 -0
- package/dist/commands/models.js +26 -6
- package/dist/commands/prompts.js +2 -6
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +71 -0
- package/dist/commands/session-access.js +4 -14
- package/dist/commands/spaces.d.ts +1 -0
- package/dist/commands/spaces.js +264 -150
- package/dist/commands/tasks.js +4 -11
- package/dist/index.js +42 -10
- package/dist/output.js +3 -0
- package/dist/self-update.d.ts +1 -0
- package/dist/self-update.js +136 -0
- package/package.json +2 -2
package/dist/commands/auth.js
CHANGED
|
@@ -1,56 +1,117 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authSource, loginWithDeviceFlow, readAuthSession, refreshAccessToken, requestDeviceCode, revokeAndClearAuthSession, verifyDeviceCode } from "../auth.js";
|
|
2
2
|
import { createClient } from "../client.js";
|
|
3
3
|
import { table, json as outJson, 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
|
|
7
|
-
.command("login
|
|
8
|
-
.description("
|
|
9
|
-
.
|
|
7
|
+
.command("login")
|
|
8
|
+
.description("Sign in with Logto device authorization")
|
|
9
|
+
.option("--request-code", "Request a device code without polling")
|
|
10
|
+
.option("--verify-code", "Exchange a previously requested device code")
|
|
11
|
+
.option("--json", "Output as JSON")
|
|
12
|
+
.action(async (opts, command) => {
|
|
13
|
+
const asJson = Boolean(opts.json || command.parent?.optsWithGlobals().json);
|
|
14
|
+
if (opts.requestCode && opts.verifyCode) {
|
|
15
|
+
return error("Conflicting options", "Use only one of --request-code or --verify-code");
|
|
16
|
+
}
|
|
10
17
|
try {
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
if (opts.requestCode) {
|
|
19
|
+
const code = await requestDeviceCode();
|
|
20
|
+
if (asJson)
|
|
21
|
+
return outJson(code);
|
|
22
|
+
printDeviceCode(code);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (opts.verifyCode) {
|
|
26
|
+
await verifyDeviceCode();
|
|
27
|
+
return showSignedIn(asJson);
|
|
28
|
+
}
|
|
29
|
+
const sp = spinner();
|
|
30
|
+
sp.start("Starting login");
|
|
31
|
+
await loginWithDeviceFlow((code) => {
|
|
32
|
+
sp.stop("Login started");
|
|
33
|
+
printDeviceCode(code);
|
|
34
|
+
process.stderr.write(" Waiting for authorization...\n");
|
|
35
|
+
});
|
|
36
|
+
return showSignedIn(asJson);
|
|
13
37
|
}
|
|
14
38
|
catch (e) {
|
|
15
|
-
|
|
39
|
+
handleHttp(e);
|
|
16
40
|
}
|
|
17
41
|
});
|
|
18
42
|
auth
|
|
19
43
|
.command("whoami")
|
|
20
44
|
.description("Show current user info")
|
|
21
45
|
.option("--json", "Output as JSON")
|
|
22
|
-
.action(async (opts) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
.action(async (opts, command) => {
|
|
47
|
+
const asJson = Boolean(opts.json || command.parent?.optsWithGlobals().json);
|
|
48
|
+
return showSignedIn(asJson);
|
|
49
|
+
});
|
|
50
|
+
auth
|
|
51
|
+
.command("refresh")
|
|
52
|
+
.description("Refresh the stored Logto access token")
|
|
53
|
+
.action(async () => {
|
|
29
54
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const u = user;
|
|
36
|
-
console.log(` Auth source: ${src}\n`);
|
|
37
|
-
table([u], [
|
|
38
|
-
{ key: "id", label: "ID" },
|
|
39
|
-
{ key: "name", label: "Name" },
|
|
40
|
-
{ key: "email", label: "Email" },
|
|
41
|
-
{ key: "created_at", label: "Created" },
|
|
42
|
-
]);
|
|
55
|
+
if (authSource() === "execution-token") {
|
|
56
|
+
return error("Cannot refresh COHUB_EXECUTION_TOKEN", "Run `cohub auth login` for long-lived Logto auth.");
|
|
57
|
+
}
|
|
58
|
+
await refreshAccessToken();
|
|
59
|
+
ok("Token refreshed");
|
|
43
60
|
}
|
|
44
61
|
catch (e) {
|
|
45
|
-
sp.stop("Failed");
|
|
46
62
|
handleHttp(e);
|
|
47
63
|
}
|
|
48
64
|
});
|
|
49
65
|
auth
|
|
50
66
|
.command("logout")
|
|
51
|
-
.description("Clear stored
|
|
52
|
-
.action(() => {
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
.description("Clear stored Logto session")
|
|
68
|
+
.action(async () => {
|
|
69
|
+
await revokeAndClearAuthSession();
|
|
70
|
+
if (process.env.COHUB_EXECUTION_TOKEN?.trim()) {
|
|
71
|
+
ok("Local session cleared. COHUB_EXECUTION_TOKEN is still set.");
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
ok("Signed out");
|
|
75
|
+
}
|
|
55
76
|
});
|
|
56
77
|
}
|
|
78
|
+
async function showSignedIn(asJson) {
|
|
79
|
+
const source = authSource();
|
|
80
|
+
if (!source)
|
|
81
|
+
return error("Not authenticated", "Run `cohub auth login`.");
|
|
82
|
+
const client = createClient();
|
|
83
|
+
const sp = spinner();
|
|
84
|
+
sp.start("Fetching user info");
|
|
85
|
+
try {
|
|
86
|
+
const user = await client.user.getMe();
|
|
87
|
+
sp.stop("Done");
|
|
88
|
+
const session = readAuthSession();
|
|
89
|
+
const payload = {
|
|
90
|
+
source,
|
|
91
|
+
refreshable: source === "logto" && Boolean(session?.refreshToken),
|
|
92
|
+
user,
|
|
93
|
+
};
|
|
94
|
+
if (asJson)
|
|
95
|
+
return outJson(payload);
|
|
96
|
+
const u = user;
|
|
97
|
+
console.log(` Auth source: ${source}`);
|
|
98
|
+
console.log(` Token: ${payload.refreshable ? "refreshable" : "ephemeral"}\n`);
|
|
99
|
+
table([u], [
|
|
100
|
+
{ key: "id", label: "ID" },
|
|
101
|
+
{ key: "name", label: "Name" },
|
|
102
|
+
{ key: "email", label: "Email" },
|
|
103
|
+
{ key: "created_at", label: "Created" },
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
sp.stop("Failed");
|
|
108
|
+
handleHttp(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function printDeviceCode(code) {
|
|
112
|
+
console.log("\nOpen this URL to sign in:\n");
|
|
113
|
+
console.log(` ${code.verificationUriComplete}\n`);
|
|
114
|
+
console.log("Or enter this code manually:\n");
|
|
115
|
+
console.log(` ${code.userCode}\n`);
|
|
116
|
+
console.log(`Code expires at ${new Date(code.expiresAt).toLocaleString()}.`);
|
|
117
|
+
}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { resolveToken } from "../auth.js";
|
|
2
1
|
import { createClient } from "../client.js";
|
|
3
2
|
import { table, json as outJson, ok, error, handleHttp } from "../output.js";
|
|
4
3
|
export function registerChannels(program) {
|
|
5
|
-
const cmd = program.command("channels").description("Channel
|
|
4
|
+
const cmd = program.command("channels", { hidden: true }).description("Channel integrations");
|
|
6
5
|
cmd
|
|
7
6
|
.command("ls")
|
|
8
7
|
.alias("list")
|
|
9
8
|
.description("List channels")
|
|
10
9
|
.option("--json", "Output as JSON")
|
|
11
10
|
.action(async (opts) => {
|
|
12
|
-
const
|
|
13
|
-
if (!token)
|
|
14
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
15
|
-
const client = createClient(token);
|
|
11
|
+
const client = createClient();
|
|
16
12
|
try {
|
|
17
13
|
const items = await client.channels.list();
|
|
18
14
|
if (opts.json)
|
|
@@ -38,9 +34,6 @@ export function registerChannels(program) {
|
|
|
38
34
|
.option("--credentials <json>", "Credentials as JSON string")
|
|
39
35
|
.option("--json", "Output as JSON")
|
|
40
36
|
.action(async (opts) => {
|
|
41
|
-
const token = resolveToken();
|
|
42
|
-
if (!token)
|
|
43
|
-
return error("Not authenticated");
|
|
44
37
|
let credentials = {};
|
|
45
38
|
if (opts.credentials) {
|
|
46
39
|
try {
|
|
@@ -50,7 +43,7 @@ export function registerChannels(program) {
|
|
|
50
43
|
return error("Invalid JSON", "--credentials must be valid JSON");
|
|
51
44
|
}
|
|
52
45
|
}
|
|
53
|
-
const client = createClient(
|
|
46
|
+
const client = createClient();
|
|
54
47
|
try {
|
|
55
48
|
const result = await client.channels.create({
|
|
56
49
|
provider: opts.provider,
|
|
@@ -69,10 +62,7 @@ export function registerChannels(program) {
|
|
|
69
62
|
.command("delete <id>")
|
|
70
63
|
.description("Delete a channel")
|
|
71
64
|
.action(async (id) => {
|
|
72
|
-
const
|
|
73
|
-
if (!token)
|
|
74
|
-
return error("Not authenticated");
|
|
75
|
-
const client = createClient(token);
|
|
65
|
+
const client = createClient();
|
|
76
66
|
try {
|
|
77
67
|
await client.channels.delete(id);
|
|
78
68
|
ok(`Channel deleted: ${id}`);
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { resolveToken } from "../auth.js";
|
|
2
1
|
import { createClient } from "../client.js";
|
|
3
|
-
import { table, json as outJson, ok,
|
|
2
|
+
import { table, json as outJson, ok, handleHttp } from "../output.js";
|
|
4
3
|
export function registerCronJobs(program) {
|
|
5
|
-
const cmd = program.command("cron-jobs").description("
|
|
4
|
+
const cmd = program.command("cron-jobs", { hidden: true }).description("Scheduled prompt jobs");
|
|
6
5
|
cmd
|
|
7
6
|
.command("ls [spaceId]")
|
|
8
7
|
.alias("list")
|
|
9
8
|
.description("List cron jobs")
|
|
10
9
|
.option("--json", "Output as JSON")
|
|
11
10
|
.action(async (spaceId, opts) => {
|
|
12
|
-
const
|
|
13
|
-
if (!token)
|
|
14
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
15
|
-
const client = createClient(token);
|
|
11
|
+
const client = createClient();
|
|
16
12
|
try {
|
|
17
13
|
const result = await client.cronJobs.list(spaceId);
|
|
18
14
|
if (opts.json)
|
|
@@ -35,10 +31,7 @@ export function registerCronJobs(program) {
|
|
|
35
31
|
.command("delete <id>")
|
|
36
32
|
.description("Delete a cron job")
|
|
37
33
|
.action(async (id) => {
|
|
38
|
-
const
|
|
39
|
-
if (!token)
|
|
40
|
-
return error("Not authenticated");
|
|
41
|
-
const client = createClient(token);
|
|
34
|
+
const client = createClient();
|
|
42
35
|
try {
|
|
43
36
|
await client.cronJobs.delete(id);
|
|
44
37
|
ok(`Cron job deleted: ${id}`);
|
|
@@ -51,11 +44,8 @@ export function registerCronJobs(program) {
|
|
|
51
44
|
.command("toggle <id> <on|off>")
|
|
52
45
|
.description("Enable or disable a cron job")
|
|
53
46
|
.action(async (id, state) => {
|
|
54
|
-
const token = resolveToken();
|
|
55
|
-
if (!token)
|
|
56
|
-
return error("Not authenticated");
|
|
57
47
|
const enabled = state === "on";
|
|
58
|
-
const client = createClient(
|
|
48
|
+
const client = createClient();
|
|
59
49
|
try {
|
|
60
50
|
await client.cronJobs.toggle(id, enabled);
|
|
61
51
|
ok(`Cron job ${enabled ? "enabled" : "disabled"}: ${id}`);
|
|
@@ -69,10 +59,7 @@ export function registerCronJobs(program) {
|
|
|
69
59
|
.description("List cron job runs")
|
|
70
60
|
.option("--json", "Output as JSON")
|
|
71
61
|
.action(async (id, opts) => {
|
|
72
|
-
const
|
|
73
|
-
if (!token)
|
|
74
|
-
return error("Not authenticated");
|
|
75
|
-
const client = createClient(token);
|
|
62
|
+
const client = createClient();
|
|
76
63
|
try {
|
|
77
64
|
const result = await client.cronJobs.runs(id);
|
|
78
65
|
if (opts.json)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
3
|
+
import { createClient } from "../client.js";
|
|
4
|
+
import { json as outJson, ok, handleHttp } from "../output.js";
|
|
5
|
+
const mimeByExt = {
|
|
6
|
+
".png": "image/png",
|
|
7
|
+
".jpg": "image/jpeg",
|
|
8
|
+
".jpeg": "image/jpeg",
|
|
9
|
+
".webp": "image/webp",
|
|
10
|
+
".gif": "image/gif",
|
|
11
|
+
".mp4": "video/mp4",
|
|
12
|
+
".webm": "video/webm",
|
|
13
|
+
".mov": "video/quicktime",
|
|
14
|
+
".mp3": "audio/mpeg",
|
|
15
|
+
".wav": "audio/wav",
|
|
16
|
+
".ogg": "audio/ogg",
|
|
17
|
+
};
|
|
18
|
+
function parseValue(value) {
|
|
19
|
+
const trimmed = value.trim();
|
|
20
|
+
if (trimmed === "true")
|
|
21
|
+
return true;
|
|
22
|
+
if (trimmed === "false")
|
|
23
|
+
return false;
|
|
24
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed))
|
|
25
|
+
return Number(trimmed);
|
|
26
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(trimmed);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function parseParams(param, parameters) {
|
|
37
|
+
const result = parameters ? JSON.parse(parameters) : {};
|
|
38
|
+
for (const item of param ?? []) {
|
|
39
|
+
const index = item.indexOf("=");
|
|
40
|
+
if (index <= 0)
|
|
41
|
+
throw new Error(`Invalid --param value: ${item}`);
|
|
42
|
+
result[item.slice(0, index)] = parseValue(item.slice(index + 1));
|
|
43
|
+
}
|
|
44
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
45
|
+
}
|
|
46
|
+
async function contentFromPathOrUrl(type, value) {
|
|
47
|
+
if (/^https?:\/\//.test(value))
|
|
48
|
+
return { type, source: { type: "url", url: value } };
|
|
49
|
+
const data = await readFile(value);
|
|
50
|
+
const media_type = mimeByExt[extname(value).toLowerCase()] ?? "application/octet-stream";
|
|
51
|
+
return { type, source: { type: "base64", media_type, data: data.toString("base64") } };
|
|
52
|
+
}
|
|
53
|
+
async function saveOutputs(output, outputPath) {
|
|
54
|
+
const outputs = output.filter((block) => block.type === "text" || block.type === "image" || block.type === "video" || block.type === "audio");
|
|
55
|
+
if (outputs.length === 0)
|
|
56
|
+
return [];
|
|
57
|
+
const info = await stat(outputPath).catch(() => null);
|
|
58
|
+
const isDir = info?.isDirectory() ?? (!extname(outputPath) && outputs.length > 1);
|
|
59
|
+
if (outputs.length > 1 && !isDir)
|
|
60
|
+
throw new Error("--output must be a directory when generation returns multiple outputs");
|
|
61
|
+
if (isDir)
|
|
62
|
+
await mkdir(outputPath, { recursive: true });
|
|
63
|
+
else
|
|
64
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
65
|
+
const savedPaths = [];
|
|
66
|
+
for (const [i, block] of outputs.entries()) {
|
|
67
|
+
if (block.type === "text") {
|
|
68
|
+
const target = isDir ? join(outputPath, `generation-${i + 1}.txt`) : outputPath;
|
|
69
|
+
await writeFile(target, block.text, "utf-8");
|
|
70
|
+
savedPaths.push(target);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const source = block.source;
|
|
74
|
+
const target = isDir ? join(outputPath, outputName(block.type, source.type === "url" ? source.url : undefined, i)) : outputPath;
|
|
75
|
+
if (source.type === "url") {
|
|
76
|
+
const response = await fetch(source.url);
|
|
77
|
+
if (!response.ok)
|
|
78
|
+
throw new Error(`Failed to download ${source.url}: HTTP ${response.status}`);
|
|
79
|
+
await writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
80
|
+
savedPaths.push(target);
|
|
81
|
+
}
|
|
82
|
+
else if (source.type === "base64") {
|
|
83
|
+
await writeFile(target, Buffer.from(source.data, "base64"));
|
|
84
|
+
savedPaths.push(target);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
throw new Error(`Cannot save space file output locally: ${source.space_id}:${source.path}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return savedPaths;
|
|
91
|
+
}
|
|
92
|
+
function outputName(type, url, index) {
|
|
93
|
+
const fromUrl = url ? basename(new URL(url).pathname) : "";
|
|
94
|
+
if (fromUrl && fromUrl.includes("."))
|
|
95
|
+
return `generation-${index + 1}-${fromUrl}`;
|
|
96
|
+
const ext = type === "video" ? "mp4" : type === "audio" ? "bin" : "png";
|
|
97
|
+
return `generation-${index + 1}.${ext}`;
|
|
98
|
+
}
|
|
99
|
+
function printGeneration(output) {
|
|
100
|
+
for (const block of output) {
|
|
101
|
+
if (block.type === "text")
|
|
102
|
+
console.log(block.text);
|
|
103
|
+
else if (block.source.type === "url")
|
|
104
|
+
console.log(`${block.type}: ${block.source.url}`);
|
|
105
|
+
else if (block.source.type === "base64")
|
|
106
|
+
console.log(`${block.type}: base64 ${block.source.media_type} (${block.source.data.length} chars)`);
|
|
107
|
+
else
|
|
108
|
+
console.log(`${block.type}: ${block.source.space_id}:${block.source.path}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function registerGenerations(program) {
|
|
112
|
+
program
|
|
113
|
+
.command("generate")
|
|
114
|
+
.description("Generate multimodal outputs")
|
|
115
|
+
.argument("<prompt>", "Prompt text")
|
|
116
|
+
.requiredOption("--model <model>", "Multimodal model ID from `cohub models ls --model-type multimodal`")
|
|
117
|
+
.option("--image <path-or-url>", "Image input file path or URL; repeatable", collect, [])
|
|
118
|
+
.option("--video <path-or-url>", "Video input file path or URL; repeatable", collect, [])
|
|
119
|
+
.option("--audio <path-or-url>", "Audio input file path or URL; repeatable", collect, [])
|
|
120
|
+
.option("--param <key=value>", "Generation parameter; repeatable, values may be JSON/number/boolean", collect, [])
|
|
121
|
+
.option("--parameters <json>", "Generation parameters as a JSON object")
|
|
122
|
+
.option("--metadata <json>", "Metadata as a JSON object")
|
|
123
|
+
.option("--output <path>", "Save generated output to a file or directory")
|
|
124
|
+
.option("--json", "Output as JSON")
|
|
125
|
+
.addHelpText("after", `
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
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
|
|
131
|
+
`)
|
|
132
|
+
.action(async (prompt, opts) => {
|
|
133
|
+
try {
|
|
134
|
+
const content = [{ type: "text", text: prompt }];
|
|
135
|
+
content.push(...await Promise.all(opts.image.map((value) => contentFromPathOrUrl("image", value))));
|
|
136
|
+
content.push(...await Promise.all(opts.video.map((value) => contentFromPathOrUrl("video", value))));
|
|
137
|
+
content.push(...await Promise.all(opts.audio.map((value) => contentFromPathOrUrl("audio", value))));
|
|
138
|
+
const generation = await createClient().generations.create({
|
|
139
|
+
model: opts.model,
|
|
140
|
+
content,
|
|
141
|
+
parameters: parseParams(opts.param, opts.parameters),
|
|
142
|
+
metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
|
|
143
|
+
});
|
|
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 ?? []);
|
|
148
|
+
if (savedPaths.length > 0)
|
|
149
|
+
ok(`Saved to ${savedPaths.join(", ")}`);
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
handleHttp(e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function collect(value, previous) {
|
|
157
|
+
previous.push(value);
|
|
158
|
+
return previous;
|
|
159
|
+
}
|
package/dist/commands/models.js
CHANGED
|
@@ -1,19 +1,39 @@
|
|
|
1
|
-
import { resolveToken } from "../auth.js";
|
|
2
1
|
import { createClient } from "../client.js";
|
|
3
2
|
import { table, json as outJson, error, handleHttp } from "../output.js";
|
|
4
3
|
export function registerModels(program) {
|
|
5
|
-
const cmd = program
|
|
4
|
+
const cmd = program
|
|
5
|
+
.command("models")
|
|
6
|
+
.description("List available LLM and multimodal models")
|
|
7
|
+
.addHelpText("after", `
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
cohub models ls
|
|
11
|
+
cohub models ls --model-type multimodal
|
|
12
|
+
cohub models ls --model-type multimodal --json
|
|
13
|
+
`);
|
|
6
14
|
cmd
|
|
7
15
|
.command("ls")
|
|
8
16
|
.alias("list")
|
|
9
17
|
.description("List available models")
|
|
18
|
+
.option("--model-type <type>", "Model type: llm | multimodal", "llm")
|
|
10
19
|
.option("--json", "Output as JSON")
|
|
11
20
|
.action(async (opts) => {
|
|
12
|
-
const
|
|
13
|
-
if (!token)
|
|
14
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
15
|
-
const client = createClient(token);
|
|
21
|
+
const client = createClient();
|
|
16
22
|
try {
|
|
23
|
+
if (opts.modelType === "multimodal") {
|
|
24
|
+
const response = await client.models.listMultimodal();
|
|
25
|
+
if (opts.json)
|
|
26
|
+
return outJson(response);
|
|
27
|
+
table(response.models, [
|
|
28
|
+
{ key: "model", label: "Model" },
|
|
29
|
+
{ key: "title", label: "Title" },
|
|
30
|
+
{ key: "description", label: "Description" },
|
|
31
|
+
]);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (opts.modelType && opts.modelType !== "llm") {
|
|
35
|
+
return error("Invalid model type", "Use --model-type llm or --model-type multimodal");
|
|
36
|
+
}
|
|
17
37
|
const catalog = await client.models.list();
|
|
18
38
|
if (opts.json)
|
|
19
39
|
return outJson(catalog);
|
package/dist/commands/prompts.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { resolveToken } from "../auth.js";
|
|
2
1
|
import { createClient } from "../client.js";
|
|
3
|
-
import { table, json as outJson,
|
|
2
|
+
import { table, json as outJson, handleHttp } from "../output.js";
|
|
4
3
|
export function registerPrompts(program) {
|
|
5
4
|
const cmd = program.command("prompts").description("Prompt template management");
|
|
6
5
|
cmd
|
|
@@ -10,10 +9,7 @@ export function registerPrompts(program) {
|
|
|
10
9
|
.option("--space <id>", "Filter by space")
|
|
11
10
|
.option("--json", "Output as JSON")
|
|
12
11
|
.action(async (opts) => {
|
|
13
|
-
const
|
|
14
|
-
if (!token)
|
|
15
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
16
|
-
const client = createClient(token);
|
|
12
|
+
const client = createClient();
|
|
17
13
|
try {
|
|
18
14
|
const result = await client.prompts.list({ spaceId: opts.space });
|
|
19
15
|
if (opts.json)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createClient } from "../client.js";
|
|
2
|
+
import { table, json as outJson, handleHttp } from "../output.js";
|
|
3
|
+
const DEFAULT_LIMIT = 20;
|
|
4
|
+
const MAX_TITLE_LENGTH = 72;
|
|
5
|
+
const MAX_CONTEXT_LENGTH = 42;
|
|
6
|
+
function clampLimit(value) {
|
|
7
|
+
const parsed = Number(value ?? DEFAULT_LIMIT);
|
|
8
|
+
if (!Number.isFinite(parsed))
|
|
9
|
+
return DEFAULT_LIMIT;
|
|
10
|
+
return Math.min(Math.max(Math.floor(parsed), 1), 50);
|
|
11
|
+
}
|
|
12
|
+
function truncate(value, maxLength) {
|
|
13
|
+
const text = (value ?? "").replace(/\s+/g, " ").trim();
|
|
14
|
+
if (text.length <= maxLength)
|
|
15
|
+
return text;
|
|
16
|
+
return `${text.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
17
|
+
}
|
|
18
|
+
function contextFor(item) {
|
|
19
|
+
if (item.type === "space")
|
|
20
|
+
return item.spaceName ?? "";
|
|
21
|
+
if (item.type === "session")
|
|
22
|
+
return item.spaceName ?? "";
|
|
23
|
+
return item.sessionTitle || item.spaceName || "";
|
|
24
|
+
}
|
|
25
|
+
function rowsFor(items) {
|
|
26
|
+
return items.map((item) => ({
|
|
27
|
+
type: item.type,
|
|
28
|
+
title: truncate(item.title || item.excerpt, MAX_TITLE_LENGTH),
|
|
29
|
+
context: truncate(contextFor(item), MAX_CONTEXT_LENGTH),
|
|
30
|
+
match: item.matchedField,
|
|
31
|
+
updated: item.updatedAt ? item.updatedAt.slice(0, 10) : "",
|
|
32
|
+
href: item.href,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
export function registerSearch(program) {
|
|
36
|
+
program
|
|
37
|
+
.command("search")
|
|
38
|
+
.description("Search spaces, chats, and turns")
|
|
39
|
+
.argument("<query>", "Search query")
|
|
40
|
+
.option("--limit <n>", "Maximum results, 1-50", String(DEFAULT_LIMIT))
|
|
41
|
+
.option("--json", "Output as JSON")
|
|
42
|
+
.addHelpText("after", `
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
cohub search "release notes"
|
|
46
|
+
cohub search "failing tests" --limit 10
|
|
47
|
+
cohub search "design review" --json
|
|
48
|
+
`)
|
|
49
|
+
.action(async (query, opts) => {
|
|
50
|
+
const client = createClient();
|
|
51
|
+
try {
|
|
52
|
+
const result = await client.search.query({ q: query, limit: clampLimit(opts.limit) });
|
|
53
|
+
if (opts.json)
|
|
54
|
+
return outJson(result);
|
|
55
|
+
if (result.degraded) {
|
|
56
|
+
process.stderr.write(" Search is temporarily degraded; results may be incomplete.\n");
|
|
57
|
+
}
|
|
58
|
+
table(rowsFor(result.items), [
|
|
59
|
+
{ key: "type", label: "Type" },
|
|
60
|
+
{ key: "title", label: "Title" },
|
|
61
|
+
{ key: "context", label: "Context" },
|
|
62
|
+
{ key: "match", label: "Match" },
|
|
63
|
+
{ key: "updated", label: "Updated" },
|
|
64
|
+
{ key: "href", label: "Href" },
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
handleHttp(e);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { resolveToken } from "../auth.js";
|
|
2
1
|
import { createClient } from "../client.js";
|
|
3
|
-
import { table, json as outJson, ok,
|
|
2
|
+
import { table, json as outJson, ok, handleHttp } from "../output.js";
|
|
4
3
|
export function registerSessionAccess(program) {
|
|
5
4
|
const cmd = program
|
|
6
5
|
.command("session-access")
|
|
@@ -10,10 +9,7 @@ export function registerSessionAccess(program) {
|
|
|
10
9
|
.description("Get session access policy")
|
|
11
10
|
.option("--json", "Output as JSON")
|
|
12
11
|
.action(async (id, opts) => {
|
|
13
|
-
const
|
|
14
|
-
if (!token)
|
|
15
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
16
|
-
const client = createClient(token);
|
|
12
|
+
const client = createClient();
|
|
17
13
|
try {
|
|
18
14
|
const policy = await client.sessionAccess.get(id);
|
|
19
15
|
if (opts.json)
|
|
@@ -33,10 +29,7 @@ export function registerSessionAccess(program) {
|
|
|
33
29
|
.option("--anonymous <role>", "Anonymous role (host|builder|guest|null)")
|
|
34
30
|
.option("--json", "Output as JSON")
|
|
35
31
|
.action(async (id, opts) => {
|
|
36
|
-
const
|
|
37
|
-
if (!token)
|
|
38
|
-
return error("Not authenticated");
|
|
39
|
-
const client = createClient(token);
|
|
32
|
+
const client = createClient();
|
|
40
33
|
try {
|
|
41
34
|
const policy = await client.sessionAccess.set(id, {
|
|
42
35
|
anonymous_user: (opts.anonymous ?? null),
|
|
@@ -57,10 +50,7 @@ export function registerSessionAccess(program) {
|
|
|
57
50
|
.command("remove <id>")
|
|
58
51
|
.description("Remove session access override")
|
|
59
52
|
.action(async (id) => {
|
|
60
|
-
const
|
|
61
|
-
if (!token)
|
|
62
|
-
return error("Not authenticated");
|
|
63
|
-
const client = createClient(token);
|
|
53
|
+
const client = createClient();
|
|
64
54
|
try {
|
|
65
55
|
await client.sessionAccess.remove(id);
|
|
66
56
|
ok(`Session access override removed: ${id}`);
|