@neta-art/cohub-cli 1.8.0 → 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 +6 -0
- package/dist/commands/generations.js +65 -22
- package/dist/output.d.ts +1 -0
- package/dist/output.js +22 -4
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -142,6 +142,12 @@ cohub generate "restyle this image" \
|
|
|
142
142
|
--image ./input.png \
|
|
143
143
|
--param size=1024x1024 \
|
|
144
144
|
--json
|
|
145
|
+
|
|
146
|
+
cohub generate "a calm lake" \
|
|
147
|
+
--model <model> \
|
|
148
|
+
--async
|
|
149
|
+
|
|
150
|
+
cohub tasks get <taskRunId> --json
|
|
145
151
|
```
|
|
146
152
|
|
|
147
153
|
Supported inputs:
|
|
@@ -48,8 +48,8 @@ async function contentFromPathOrUrl(type, value) {
|
|
|
48
48
|
if (/^https?:\/\//.test(value))
|
|
49
49
|
return { type, source: { type: "url", url: value } };
|
|
50
50
|
const data = await readFile(value);
|
|
51
|
-
const
|
|
52
|
-
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") } };
|
|
53
53
|
}
|
|
54
54
|
async function saveOutputs(output, outputPath) {
|
|
55
55
|
const outputs = output.filter((block) => block.type === "text" || block.type === "image" || block.type === "video" || block.type === "audio");
|
|
@@ -72,7 +72,7 @@ async function saveOutputs(output, outputPath) {
|
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
74
|
const source = block.source;
|
|
75
|
-
const target = isDir ? join(outputPath, outputName(block
|
|
75
|
+
const target = isDir ? join(outputPath, outputName(block, source.type === "url" ? source.url : undefined, i)) : outputPath;
|
|
76
76
|
if (source.type === "url") {
|
|
77
77
|
const response = await fetch(source.url);
|
|
78
78
|
if (!response.ok)
|
|
@@ -80,35 +80,69 @@ async function saveOutputs(output, outputPath) {
|
|
|
80
80
|
await writeFile(target, Buffer.from(await response.arrayBuffer()));
|
|
81
81
|
savedPaths.push(target);
|
|
82
82
|
}
|
|
83
|
-
else
|
|
83
|
+
else {
|
|
84
84
|
await writeFile(target, Buffer.from(source.data, "base64"));
|
|
85
85
|
savedPaths.push(target);
|
|
86
86
|
}
|
|
87
|
-
else {
|
|
88
|
-
throw new Error(`Cannot save space file output locally: ${source.space_id}:${source.path}`);
|
|
89
|
-
}
|
|
90
87
|
}
|
|
91
88
|
return savedPaths;
|
|
92
89
|
}
|
|
93
|
-
function outputName(
|
|
90
|
+
function outputName(block, url, index) {
|
|
94
91
|
const fromUrl = url ? basename(new URL(url).pathname) : "";
|
|
92
|
+
const label = slugOutputLabel(block);
|
|
95
93
|
if (fromUrl?.includes("."))
|
|
96
|
-
return `generation-${index + 1}-${fromUrl}`;
|
|
97
|
-
const ext = type === "video" ? "mp4" : type === "audio" ? "bin" : "png";
|
|
98
|
-
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);
|
|
99
119
|
}
|
|
100
120
|
function printGeneration(output) {
|
|
101
121
|
for (const block of output) {
|
|
102
|
-
if (block.type === "text")
|
|
122
|
+
if (block.type === "text") {
|
|
103
123
|
console.log(block.text);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
else
|
|
109
|
-
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
|
+
}
|
|
110
131
|
}
|
|
111
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
|
+
}
|
|
112
146
|
function parseTimeoutMs(value) {
|
|
113
147
|
if (!value)
|
|
114
148
|
return undefined;
|
|
@@ -161,16 +195,25 @@ Examples:
|
|
|
161
195
|
if (opts.async) {
|
|
162
196
|
if (jsonRequested(opts))
|
|
163
197
|
return outJson(created);
|
|
164
|
-
return ok(`Generation queued —
|
|
198
|
+
return ok(`Generation queued — task ID: ${created.taskRunId}\n ${resumeHint(created.taskRunId)}`);
|
|
165
199
|
}
|
|
166
200
|
const spin = spinner();
|
|
167
|
-
|
|
168
|
-
|
|
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
|
+
}
|
|
169
208
|
const result = await client.generations.wait(created.taskRunId, {
|
|
170
209
|
timeoutMs: parseTimeoutMs(opts.timeoutMs),
|
|
210
|
+
onPoll: () => {
|
|
211
|
+
pollCount += 1;
|
|
212
|
+
spin.update(`Generating... ${formatElapsed(Date.now() - waitStartedAt)}, ${pollCount} polls`);
|
|
213
|
+
},
|
|
171
214
|
});
|
|
172
215
|
if (!jsonRequested(opts))
|
|
173
|
-
spin.stop(
|
|
216
|
+
spin.stop(`Generation completed — task ID: ${created.taskRunId}, ${formatElapsed(Date.now() - waitStartedAt)}, ${pollCount} polls`);
|
|
174
217
|
const savedPaths = opts.output ? await saveOutputs(result.output, opts.output) : [];
|
|
175
218
|
if (jsonRequested(opts))
|
|
176
219
|
return outJson(savedPaths.length > 0 ? { ...result, taskRunId: created.taskRunId, savedPaths } : { ...result, taskRunId: created.taskRunId });
|
package/dist/output.d.ts
CHANGED
package/dist/output.js
CHANGED
|
@@ -73,6 +73,18 @@ function debugErrorMetaFromBody(body) {
|
|
|
73
73
|
items.push(`traceId: ${errorBody.traceId}`);
|
|
74
74
|
return items;
|
|
75
75
|
}
|
|
76
|
+
function fetchFailureDetail(e) {
|
|
77
|
+
if (!(e instanceof Error) || e.message !== "fetch failed")
|
|
78
|
+
return null;
|
|
79
|
+
const cause = e.cause;
|
|
80
|
+
const code = typeof cause?.code === "string" ? cause.code : null;
|
|
81
|
+
const hostname = typeof cause?.hostname === "string" ? cause.hostname : null;
|
|
82
|
+
const message = typeof cause?.message === "string" ? cause.message : null;
|
|
83
|
+
const parts = [code, hostname && `host: ${hostname}`, message].filter(Boolean);
|
|
84
|
+
return parts.length > 0
|
|
85
|
+
? `Network request failed (${parts.join(" · ")}). Check DNS/proxy/firewall settings and try again.`
|
|
86
|
+
: "Network request failed. Check DNS/proxy/firewall settings and try again.";
|
|
87
|
+
}
|
|
76
88
|
export function handleHttp(e) {
|
|
77
89
|
if (e instanceof Error && e.name === "AuthRequiredError") {
|
|
78
90
|
return error("not authenticated", "run `cohub auth login`");
|
|
@@ -80,32 +92,38 @@ export function handleHttp(e) {
|
|
|
80
92
|
const status = e.status;
|
|
81
93
|
const body = e.body;
|
|
82
94
|
const message = errorMessageFromBody(body) ?? (e instanceof Error ? e.message : String(e));
|
|
95
|
+
const fetchDetail = fetchFailureDetail(e);
|
|
83
96
|
const detailParts = [];
|
|
84
97
|
if (process.env.COHUB_DEBUG_ERRORS) {
|
|
85
98
|
if (status)
|
|
86
99
|
detailParts.push(`HTTP ${status}`);
|
|
87
100
|
detailParts.push(...debugErrorMetaFromBody(body));
|
|
88
101
|
}
|
|
89
|
-
error(message, detailParts.length > 0 ? detailParts.join(" · ") : undefined);
|
|
102
|
+
error(message, detailParts.length > 0 ? detailParts.join(" · ") : fetchDetail ?? undefined);
|
|
90
103
|
}
|
|
91
104
|
// -- Spinner -----------------------------------------------------------------
|
|
92
105
|
export function spinner() {
|
|
93
106
|
let interval = null;
|
|
94
107
|
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
95
108
|
let i = 0;
|
|
109
|
+
let currentMsg = "";
|
|
96
110
|
return {
|
|
97
111
|
start(msg) {
|
|
112
|
+
currentMsg = msg;
|
|
98
113
|
if (process.env.CI || !process.stderr.isTTY) {
|
|
99
|
-
process.stderr.write(` ${
|
|
114
|
+
process.stderr.write(` ${currentMsg}...\n`);
|
|
100
115
|
return;
|
|
101
116
|
}
|
|
102
|
-
process.stderr.write(` ${
|
|
117
|
+
process.stderr.write(` ${currentMsg} `);
|
|
103
118
|
interval = setInterval(() => {
|
|
104
119
|
process.stderr.clearLine?.(0);
|
|
105
120
|
process.stderr.cursorTo?.(0);
|
|
106
|
-
process.stderr.write(` ${frames[i++ % frames.length] ?? ""} ${
|
|
121
|
+
process.stderr.write(` ${frames[i++ % frames.length] ?? ""} ${currentMsg} `);
|
|
107
122
|
}, 80);
|
|
108
123
|
},
|
|
124
|
+
update(msg) {
|
|
125
|
+
currentMsg = msg;
|
|
126
|
+
},
|
|
109
127
|
stop(msg) {
|
|
110
128
|
if (interval)
|
|
111
129
|
clearInterval(interval);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"@neta-art/generation": "^0.1.2",
|
|
16
17
|
"commander": "^13.1.0",
|
|
17
18
|
"sharp": "^0.34.5",
|
|
18
|
-
"@neta-art/cohub": "1.16.
|
|
19
|
+
"@neta-art/cohub": "1.16.1"
|
|
19
20
|
},
|
|
20
21
|
"publishConfig": {
|
|
21
22
|
"access": "public"
|