@neta-art/cohub-cli 1.2.0 → 1.4.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.js +45 -50
- 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 +104 -0
- package/dist/commands/session-access.js +4 -14
- package/dist/commands/spaces.d.ts +1 -0
- package/dist/commands/spaces.js +360 -150
- package/dist/commands/tasks.js +4 -11
- package/dist/index.js +40 -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 +3 -3
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)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { basename, extname, join } from "node:path";
|
|
3
|
-
import { resolveToken } from "../auth.js";
|
|
2
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
4
3
|
import { createClient } from "../client.js";
|
|
5
|
-
import {
|
|
4
|
+
import { json as outJson, ok, handleHttp } from "../output.js";
|
|
6
5
|
const mimeByExt = {
|
|
7
6
|
".png": "image/png",
|
|
8
7
|
".jpg": "image/jpeg",
|
|
@@ -52,16 +51,25 @@ async function contentFromPathOrUrl(type, value) {
|
|
|
52
51
|
return { type, source: { type: "base64", media_type, data: data.toString("base64") } };
|
|
53
52
|
}
|
|
54
53
|
async function saveOutputs(output, outputPath) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
return;
|
|
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 [];
|
|
58
57
|
const info = await stat(outputPath).catch(() => null);
|
|
59
|
-
const isDir = info?.isDirectory() ?? (!extname(outputPath) &&
|
|
60
|
-
if (
|
|
61
|
-
throw new Error("--output must be a directory when generation returns multiple
|
|
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");
|
|
62
61
|
if (isDir)
|
|
63
62
|
await mkdir(outputPath, { recursive: true });
|
|
64
|
-
|
|
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
|
+
}
|
|
65
73
|
const source = block.source;
|
|
66
74
|
const target = isDir ? join(outputPath, outputName(block.type, source.type === "url" ? source.url : undefined, i)) : outputPath;
|
|
67
75
|
if (source.type === "url") {
|
|
@@ -69,11 +77,17 @@ async function saveOutputs(output, outputPath) {
|
|
|
69
77
|
if (!response.ok)
|
|
70
78
|
throw new Error(`Failed to download ${source.url}: HTTP ${response.status}`);
|
|
71
79
|
await writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
80
|
+
savedPaths.push(target);
|
|
72
81
|
}
|
|
73
82
|
else if (source.type === "base64") {
|
|
74
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}`);
|
|
75
88
|
}
|
|
76
89
|
}
|
|
90
|
+
return savedPaths;
|
|
77
91
|
}
|
|
78
92
|
function outputName(type, url, index) {
|
|
79
93
|
const fromUrl = url ? basename(new URL(url).pathname) : "";
|
|
@@ -97,61 +111,42 @@ function printGeneration(output) {
|
|
|
97
111
|
export function registerGenerations(program) {
|
|
98
112
|
program
|
|
99
113
|
.command("generate")
|
|
100
|
-
.description("Generate multimodal
|
|
114
|
+
.description("Generate multimodal outputs")
|
|
101
115
|
.argument("<prompt>", "Prompt text")
|
|
102
|
-
.requiredOption("--model <model>", "
|
|
103
|
-
.option("--image <path-or-url>", "Image input", collect, [])
|
|
104
|
-
.option("--video <path-or-url>", "Video input", collect, [])
|
|
105
|
-
.option("--audio <path-or-url>", "Audio input", collect, [])
|
|
106
|
-
.option("--param <key=value>", "Generation parameter", collect, [])
|
|
107
|
-
.option("--parameters <json>", "Generation parameters JSON")
|
|
108
|
-
.option("--metadata <json>", "Metadata JSON")
|
|
109
|
-
.option("--output <path>", "Save generated file
|
|
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")
|
|
110
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
|
+
`)
|
|
111
132
|
.action(async (prompt, opts) => {
|
|
112
|
-
const token = resolveToken();
|
|
113
|
-
if (!token)
|
|
114
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
115
133
|
try {
|
|
116
134
|
const content = [{ type: "text", text: prompt }];
|
|
117
135
|
content.push(...await Promise.all(opts.image.map((value) => contentFromPathOrUrl("image", value))));
|
|
118
136
|
content.push(...await Promise.all(opts.video.map((value) => contentFromPathOrUrl("video", value))));
|
|
119
137
|
content.push(...await Promise.all(opts.audio.map((value) => contentFromPathOrUrl("audio", value))));
|
|
120
|
-
const generation = await createClient(
|
|
138
|
+
const generation = await createClient().generations.create({
|
|
121
139
|
model: opts.model,
|
|
122
140
|
content,
|
|
123
141
|
parameters: parseParams(opts.param, opts.parameters),
|
|
124
142
|
metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
|
|
125
143
|
});
|
|
126
|
-
|
|
127
|
-
await saveOutputs(generation.output, opts.output);
|
|
144
|
+
const savedPaths = opts.output && generation.output ? await saveOutputs(generation.output, opts.output) : [];
|
|
128
145
|
if (opts.json)
|
|
129
|
-
return outJson(generation);
|
|
146
|
+
return outJson(savedPaths.length > 0 ? { ...generation, savedPaths } : generation);
|
|
130
147
|
printGeneration(generation.output ?? []);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
handleHttp(e);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
const cmd = program.command("generations").description("Generation model declarations");
|
|
137
|
-
cmd
|
|
138
|
-
.command("ls")
|
|
139
|
-
.alias("list")
|
|
140
|
-
.description("List generation declarations")
|
|
141
|
-
.option("--json", "Output as JSON")
|
|
142
|
-
.action(async (opts) => {
|
|
143
|
-
const token = resolveToken();
|
|
144
|
-
if (!token)
|
|
145
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
146
|
-
try {
|
|
147
|
-
const response = await createClient(token).generations.listDeclarations();
|
|
148
|
-
if (opts.json)
|
|
149
|
-
return outJson(response);
|
|
150
|
-
table(response.declarations, [
|
|
151
|
-
{ key: "model", label: "Model" },
|
|
152
|
-
{ key: "title", label: "Title" },
|
|
153
|
-
{ key: "description", label: "Description" },
|
|
154
|
-
]);
|
|
148
|
+
if (savedPaths.length > 0)
|
|
149
|
+
ok(`Saved to ${savedPaths.join(", ")}`);
|
|
155
150
|
}
|
|
156
151
|
catch (e) {
|
|
157
152
|
handleHttp(e);
|
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,104 @@
|
|
|
1
|
+
import { createClient } from "../client.js";
|
|
2
|
+
import { table, json as outJson, error, handleHttp } from "../output.js";
|
|
3
|
+
const DEFAULT_LIMIT = 20;
|
|
4
|
+
const MAX_TITLE_LENGTH = 72;
|
|
5
|
+
const MAX_CONTEXT_LENGTH = 42;
|
|
6
|
+
const SEARCH_TYPES = new Set(["turn", "session", "space"]);
|
|
7
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
|
+
function clampLimit(value) {
|
|
9
|
+
const parsed = Number(value ?? DEFAULT_LIMIT);
|
|
10
|
+
if (!Number.isFinite(parsed))
|
|
11
|
+
return DEFAULT_LIMIT;
|
|
12
|
+
return Math.min(Math.max(Math.floor(parsed), 1), 50);
|
|
13
|
+
}
|
|
14
|
+
function truncate(value, maxLength) {
|
|
15
|
+
const text = (value ?? "").replace(/\s+/g, " ").trim();
|
|
16
|
+
if (text.length <= maxLength)
|
|
17
|
+
return text;
|
|
18
|
+
return `${text.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
19
|
+
}
|
|
20
|
+
function contextFor(item) {
|
|
21
|
+
if (item.type === "space")
|
|
22
|
+
return item.spaceName ?? "";
|
|
23
|
+
if (item.type === "session")
|
|
24
|
+
return item.spaceName ?? "";
|
|
25
|
+
return item.sessionTitle || item.spaceName || "";
|
|
26
|
+
}
|
|
27
|
+
function parseTypes(value) {
|
|
28
|
+
const types = value
|
|
29
|
+
?.split(",")
|
|
30
|
+
.map((type) => type.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
if (!types?.length)
|
|
33
|
+
return undefined;
|
|
34
|
+
const invalidType = types.find((type) => !SEARCH_TYPES.has(type));
|
|
35
|
+
if (invalidType)
|
|
36
|
+
throw new Error(`Invalid search type: ${invalidType}`);
|
|
37
|
+
return [...new Set(types)];
|
|
38
|
+
}
|
|
39
|
+
function parseSearchInput(opts) {
|
|
40
|
+
const types = parseTypes(opts.types);
|
|
41
|
+
const spaceId = opts.spaceId?.trim();
|
|
42
|
+
if (spaceId && !UUID_PATTERN.test(spaceId))
|
|
43
|
+
throw new Error("Invalid space id");
|
|
44
|
+
return { types, spaceId: spaceId || undefined };
|
|
45
|
+
}
|
|
46
|
+
function rowsFor(items) {
|
|
47
|
+
return items.map((item) => ({
|
|
48
|
+
type: item.type,
|
|
49
|
+
title: truncate(item.title || item.excerpt, MAX_TITLE_LENGTH),
|
|
50
|
+
context: truncate(contextFor(item), MAX_CONTEXT_LENGTH),
|
|
51
|
+
match: item.matchedField,
|
|
52
|
+
updated: item.updatedAt ? item.updatedAt.slice(0, 10) : "",
|
|
53
|
+
href: item.href,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
export function registerSearch(program) {
|
|
57
|
+
program
|
|
58
|
+
.command("search")
|
|
59
|
+
.description("Search spaces, chats, and turns")
|
|
60
|
+
.argument("<query>", "Search query")
|
|
61
|
+
.option("--limit <n>", "Maximum results, 1-50", String(DEFAULT_LIMIT))
|
|
62
|
+
.option("--types <types>", "Comma-separated result types: turn,session,space")
|
|
63
|
+
.option("--space-id <id>", "Limit search to a space")
|
|
64
|
+
.option("--json", "Output as JSON")
|
|
65
|
+
.addHelpText("after", `
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
cohub search "release notes"
|
|
69
|
+
cohub search "failing tests" --limit 10
|
|
70
|
+
cohub search "bug" --types turn,session --space-id <spaceId>
|
|
71
|
+
cohub search "design review" --json
|
|
72
|
+
`)
|
|
73
|
+
.action(async (query, opts) => {
|
|
74
|
+
const client = createClient();
|
|
75
|
+
try {
|
|
76
|
+
const input = parseSearchInput(opts);
|
|
77
|
+
const result = await client.search.query({
|
|
78
|
+
q: query,
|
|
79
|
+
limit: clampLimit(opts.limit),
|
|
80
|
+
types: input.types,
|
|
81
|
+
spaceId: input.spaceId,
|
|
82
|
+
});
|
|
83
|
+
if (opts.json)
|
|
84
|
+
return outJson(result);
|
|
85
|
+
if (result.degraded) {
|
|
86
|
+
process.stderr.write(" Search is temporarily degraded; results may be incomplete.\n");
|
|
87
|
+
}
|
|
88
|
+
table(rowsFor(result.items), [
|
|
89
|
+
{ key: "type", label: "Type" },
|
|
90
|
+
{ key: "title", label: "Title" },
|
|
91
|
+
{ key: "context", label: "Context" },
|
|
92
|
+
{ key: "match", label: "Match" },
|
|
93
|
+
{ key: "updated", label: "Updated" },
|
|
94
|
+
{ key: "href", label: "Href" },
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
if (e instanceof Error && (e.message === "Invalid space id" || e.message.startsWith("Invalid search type:"))) {
|
|
99
|
+
return error(e.message);
|
|
100
|
+
}
|
|
101
|
+
handleHttp(e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -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}`);
|