@neta-art/cohub-cli 1.1.4 → 1.2.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/dist/commands/generations.d.ts +2 -0
- package/dist/commands/generations.js +164 -0
- package/dist/index.js +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, join } from "node:path";
|
|
3
|
+
import { resolveToken } from "../auth.js";
|
|
4
|
+
import { createClient } from "../client.js";
|
|
5
|
+
import { table, json as outJson, error, handleHttp } from "../output.js";
|
|
6
|
+
const mimeByExt = {
|
|
7
|
+
".png": "image/png",
|
|
8
|
+
".jpg": "image/jpeg",
|
|
9
|
+
".jpeg": "image/jpeg",
|
|
10
|
+
".webp": "image/webp",
|
|
11
|
+
".gif": "image/gif",
|
|
12
|
+
".mp4": "video/mp4",
|
|
13
|
+
".webm": "video/webm",
|
|
14
|
+
".mov": "video/quicktime",
|
|
15
|
+
".mp3": "audio/mpeg",
|
|
16
|
+
".wav": "audio/wav",
|
|
17
|
+
".ogg": "audio/ogg",
|
|
18
|
+
};
|
|
19
|
+
function parseValue(value) {
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
if (trimmed === "true")
|
|
22
|
+
return true;
|
|
23
|
+
if (trimmed === "false")
|
|
24
|
+
return false;
|
|
25
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed))
|
|
26
|
+
return Number(trimmed);
|
|
27
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(trimmed);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function parseParams(param, parameters) {
|
|
38
|
+
const result = parameters ? JSON.parse(parameters) : {};
|
|
39
|
+
for (const item of param ?? []) {
|
|
40
|
+
const index = item.indexOf("=");
|
|
41
|
+
if (index <= 0)
|
|
42
|
+
throw new Error(`Invalid --param value: ${item}`);
|
|
43
|
+
result[item.slice(0, index)] = parseValue(item.slice(index + 1));
|
|
44
|
+
}
|
|
45
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
46
|
+
}
|
|
47
|
+
async function contentFromPathOrUrl(type, value) {
|
|
48
|
+
if (/^https?:\/\//.test(value))
|
|
49
|
+
return { type, source: { type: "url", url: value } };
|
|
50
|
+
const data = await readFile(value);
|
|
51
|
+
const media_type = mimeByExt[extname(value).toLowerCase()] ?? "application/octet-stream";
|
|
52
|
+
return { type, source: { type: "base64", media_type, data: data.toString("base64") } };
|
|
53
|
+
}
|
|
54
|
+
async function saveOutputs(output, outputPath) {
|
|
55
|
+
const files = output.filter((block) => block.type !== "text");
|
|
56
|
+
if (files.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
const info = await stat(outputPath).catch(() => null);
|
|
59
|
+
const isDir = info?.isDirectory() ?? (!extname(outputPath) && files.length > 1);
|
|
60
|
+
if (files.length > 1 && !isDir)
|
|
61
|
+
throw new Error("--output must be a directory when generation returns multiple files");
|
|
62
|
+
if (isDir)
|
|
63
|
+
await mkdir(outputPath, { recursive: true });
|
|
64
|
+
for (const [i, block] of files.entries()) {
|
|
65
|
+
const source = block.source;
|
|
66
|
+
const target = isDir ? join(outputPath, outputName(block.type, source.type === "url" ? source.url : undefined, i)) : outputPath;
|
|
67
|
+
if (source.type === "url") {
|
|
68
|
+
const response = await fetch(source.url);
|
|
69
|
+
if (!response.ok)
|
|
70
|
+
throw new Error(`Failed to download ${source.url}: HTTP ${response.status}`);
|
|
71
|
+
await writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
72
|
+
}
|
|
73
|
+
else if (source.type === "base64") {
|
|
74
|
+
await writeFile(target, Buffer.from(source.data, "base64"));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function outputName(type, url, index) {
|
|
79
|
+
const fromUrl = url ? basename(new URL(url).pathname) : "";
|
|
80
|
+
if (fromUrl && fromUrl.includes("."))
|
|
81
|
+
return `generation-${index + 1}-${fromUrl}`;
|
|
82
|
+
const ext = type === "video" ? "mp4" : type === "audio" ? "bin" : "png";
|
|
83
|
+
return `generation-${index + 1}.${ext}`;
|
|
84
|
+
}
|
|
85
|
+
function printGeneration(output) {
|
|
86
|
+
for (const block of output) {
|
|
87
|
+
if (block.type === "text")
|
|
88
|
+
console.log(block.text);
|
|
89
|
+
else if (block.source.type === "url")
|
|
90
|
+
console.log(`${block.type}: ${block.source.url}`);
|
|
91
|
+
else if (block.source.type === "base64")
|
|
92
|
+
console.log(`${block.type}: base64 ${block.source.media_type} (${block.source.data.length} chars)`);
|
|
93
|
+
else
|
|
94
|
+
console.log(`${block.type}: ${block.source.space_id}:${block.source.path}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function registerGenerations(program) {
|
|
98
|
+
program
|
|
99
|
+
.command("generate")
|
|
100
|
+
.description("Generate multimodal content")
|
|
101
|
+
.argument("<prompt>", "Prompt text")
|
|
102
|
+
.requiredOption("--model <model>", "Generation 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 output")
|
|
110
|
+
.option("--json", "Output as JSON")
|
|
111
|
+
.action(async (prompt, opts) => {
|
|
112
|
+
const token = resolveToken();
|
|
113
|
+
if (!token)
|
|
114
|
+
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
115
|
+
try {
|
|
116
|
+
const content = [{ type: "text", text: prompt }];
|
|
117
|
+
content.push(...await Promise.all(opts.image.map((value) => contentFromPathOrUrl("image", value))));
|
|
118
|
+
content.push(...await Promise.all(opts.video.map((value) => contentFromPathOrUrl("video", value))));
|
|
119
|
+
content.push(...await Promise.all(opts.audio.map((value) => contentFromPathOrUrl("audio", value))));
|
|
120
|
+
const generation = await createClient(token).generations.create({
|
|
121
|
+
model: opts.model,
|
|
122
|
+
content,
|
|
123
|
+
parameters: parseParams(opts.param, opts.parameters),
|
|
124
|
+
metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
|
|
125
|
+
});
|
|
126
|
+
if (opts.output && generation.output)
|
|
127
|
+
await saveOutputs(generation.output, opts.output);
|
|
128
|
+
if (opts.json)
|
|
129
|
+
return outJson(generation);
|
|
130
|
+
printGeneration(generation.output ?? []);
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
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
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
handleHttp(e);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function collect(value, previous) {
|
|
162
|
+
previous.push(value);
|
|
163
|
+
return previous;
|
|
164
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { registerAuth } from "./commands/auth.js";
|
|
5
5
|
import { registerChannels } from "./commands/channels.js";
|
|
6
6
|
import { registerCronJobs } from "./commands/cron-jobs.js";
|
|
7
|
+
import { registerGenerations } from "./commands/generations.js";
|
|
7
8
|
import { registerModels } from "./commands/models.js";
|
|
8
9
|
import { registerPrompts } from "./commands/prompts.js";
|
|
9
10
|
import { registerSessionAccess } from "./commands/session-access.js";
|
|
@@ -28,6 +29,7 @@ program
|
|
|
28
29
|
registerAuth(program);
|
|
29
30
|
registerSpaces(program);
|
|
30
31
|
registerChannels(program);
|
|
32
|
+
registerGenerations(program);
|
|
31
33
|
registerModels(program);
|
|
32
34
|
registerPrompts(program);
|
|
33
35
|
registerTasks(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"commander": "^13.1.0",
|
|
17
|
-
"@neta-art/cohub": "1.
|
|
17
|
+
"@neta-art/cohub": "1.8.0"
|
|
18
18
|
},
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|