@neta-art/cohub-cli 2.2.8 → 2.2.10
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.js +11 -0
- package/dist/commands/spaces.js +231 -2
- package/dist/index.js +1 -0
- package/dist/output.js +10 -3
- package/package.json +3 -3
|
@@ -333,6 +333,17 @@ Examples:
|
|
|
333
333
|
if (jsonRequested(opts))
|
|
334
334
|
return outJson(savedPaths.length > 0 ? { ...result, taskRunId: created.taskRunId, savedPaths } : { ...result, taskRunId: created.taskRunId });
|
|
335
335
|
printGeneration(result.output);
|
|
336
|
+
if (result.requestId || result.cost !== undefined || result.billing) {
|
|
337
|
+
const details = [
|
|
338
|
+
result.requestId ? `request ID: ${result.requestId}` : null,
|
|
339
|
+
result.cost !== undefined ? `cost: ${result.cost}` : null,
|
|
340
|
+
result.billing
|
|
341
|
+
? `billing: ${result.billing.status}${result.billing.amountUsd > 0 ? ` ${result.billing.amountUsd}` : ""}${result.billing.reason ? ` (${result.billing.reason})` : ""}`
|
|
342
|
+
: null,
|
|
343
|
+
].filter(Boolean).join(", ");
|
|
344
|
+
if (details)
|
|
345
|
+
process.stderr.write(` ${details}\n`);
|
|
346
|
+
}
|
|
336
347
|
if (savedPaths.length > 0)
|
|
337
348
|
ok(`Saved to ${savedPaths.join(", ")}`);
|
|
338
349
|
}
|
package/dist/commands/spaces.js
CHANGED
|
@@ -252,6 +252,85 @@ async function sendPrompt(command, words, opts) {
|
|
|
252
252
|
handleHttp(e);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
+
async function runCompletionCommand(command, words, opts) {
|
|
256
|
+
const spaceId = resolveSpace(command);
|
|
257
|
+
const content = words.join(" ").trim();
|
|
258
|
+
if (!content && process.stdin.isTTY) {
|
|
259
|
+
return error("Message required", "Pass content args or pipe via stdin");
|
|
260
|
+
}
|
|
261
|
+
let text = content;
|
|
262
|
+
if (!text && !process.stdin.isTTY) {
|
|
263
|
+
const chunks = [];
|
|
264
|
+
for await (const chunk of process.stdin)
|
|
265
|
+
chunks.push(chunk);
|
|
266
|
+
text = Buffer.concat(chunks).toString().trim();
|
|
267
|
+
}
|
|
268
|
+
if (!text)
|
|
269
|
+
return error("Message required", "Pass content args or pipe via stdin");
|
|
270
|
+
const temperature = opts.temperature !== undefined ? Number(opts.temperature) : undefined;
|
|
271
|
+
if (temperature !== undefined && !Number.isFinite(temperature)) {
|
|
272
|
+
return error("Invalid temperature", "--temperature must be a number");
|
|
273
|
+
}
|
|
274
|
+
const maxTokens = opts.maxTokens !== undefined ? Number(opts.maxTokens) : undefined;
|
|
275
|
+
if (maxTokens !== undefined && (!Number.isFinite(maxTokens) || maxTokens <= 0)) {
|
|
276
|
+
return error("Invalid max tokens", "--max-tokens must be a positive number");
|
|
277
|
+
}
|
|
278
|
+
const thinkingLevel = opts.thinkingLevel?.trim() || undefined;
|
|
279
|
+
if (thinkingLevel
|
|
280
|
+
&& !new Set(["off", "minimal", "low", "medium", "high", "xhigh"]).has(thinkingLevel)) {
|
|
281
|
+
return error("Invalid thinking level", "Use off|minimal|low|medium|high|xhigh");
|
|
282
|
+
}
|
|
283
|
+
const client = createClient();
|
|
284
|
+
const input = {
|
|
285
|
+
provider: opts.provider,
|
|
286
|
+
model: opts.model,
|
|
287
|
+
systemPromptPath: opts.systemPrompt,
|
|
288
|
+
messages: [{ role: "user", content: [{ type: "text", text }] }],
|
|
289
|
+
temperature,
|
|
290
|
+
maxTokens,
|
|
291
|
+
thinkingLevel: thinkingLevel,
|
|
292
|
+
};
|
|
293
|
+
try {
|
|
294
|
+
if (opts.stream) {
|
|
295
|
+
const iterator = client.space(spaceId).streamCompletion(input);
|
|
296
|
+
let step = await iterator.next();
|
|
297
|
+
let finalResult = null;
|
|
298
|
+
while (!step.done) {
|
|
299
|
+
const event = step.value;
|
|
300
|
+
if (event.type === "delta") {
|
|
301
|
+
if (!jsonRequested(opts))
|
|
302
|
+
process.stdout.write(event.text);
|
|
303
|
+
}
|
|
304
|
+
else if (event.type === "thinking_delta" && !jsonRequested(opts)) {
|
|
305
|
+
process.stderr.write(event.text);
|
|
306
|
+
}
|
|
307
|
+
step = await iterator.next();
|
|
308
|
+
}
|
|
309
|
+
finalResult = step.value;
|
|
310
|
+
if (jsonRequested(opts))
|
|
311
|
+
return outJson(finalResult);
|
|
312
|
+
process.stdout.write("\n");
|
|
313
|
+
if (finalResult.usage?.totalTokens != null) {
|
|
314
|
+
process.stderr.write(` tokens: ${finalResult.usage.totalTokens}\n`);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const result = await client.space(spaceId).completion(input);
|
|
319
|
+
if (jsonRequested(opts))
|
|
320
|
+
return outJson(result);
|
|
321
|
+
const textOut = result.message.content
|
|
322
|
+
.filter((block) => block.type === "text")
|
|
323
|
+
.map((block) => block.text)
|
|
324
|
+
.join("");
|
|
325
|
+
console.log(textOut);
|
|
326
|
+
if (result.usage?.totalTokens != null) {
|
|
327
|
+
process.stderr.write(`\n tokens: ${result.usage.totalTokens}\n`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
331
|
+
handleHttp(e);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
255
334
|
export function registerPrompt(program) {
|
|
256
335
|
program
|
|
257
336
|
.command("prompt [content...]")
|
|
@@ -272,6 +351,18 @@ export function registerPrompt(program) {
|
|
|
272
351
|
.option("--image <path>", "Attach an image", collectOption, [])
|
|
273
352
|
.option("--json", "Output as JSON")
|
|
274
353
|
.action((words, opts) => sendPrompt(program, words, opts));
|
|
354
|
+
program
|
|
355
|
+
.command("completion [content...]")
|
|
356
|
+
.description("Raw LLM completion with full message control")
|
|
357
|
+
.option("-m, --model <model>", "Model name")
|
|
358
|
+
.option("-p, --provider <provider>", "Provider name")
|
|
359
|
+
.option("--system-prompt <path>", "Space-relative system prompt markdown path")
|
|
360
|
+
.option("--stream", "Stream tokens via SSE")
|
|
361
|
+
.option("--temperature <n>", "Sampling temperature")
|
|
362
|
+
.option("--max-tokens <n>", "Maximum output tokens")
|
|
363
|
+
.option("--thinking-level <level>", "Thinking level: off|minimal|low|medium|high|xhigh")
|
|
364
|
+
.option("--json", "Output as JSON")
|
|
365
|
+
.action((words, opts) => runCompletionCommand(program, words, opts));
|
|
275
366
|
}
|
|
276
367
|
export function registerSpaces(program) {
|
|
277
368
|
const spacesCmd = program.command("spaces").description("Space management");
|
|
@@ -436,7 +527,7 @@ export function registerSpaces(program) {
|
|
|
436
527
|
const result = await client.space(id).updateConfig({ sandbox: { ...(autoDestroy ? { autoDestroy } : {}), ...(spec ? { spec } : {}) } });
|
|
437
528
|
if (jsonRequested(opts))
|
|
438
529
|
return outJson(result);
|
|
439
|
-
ok(`Space config updated${autoDestroy ? ` — sandbox auto destroy: ${formatAutoDestroy(autoDestroy)}` : ""}${spec ? ` — sandbox spec: ${spec}` : ""}`);
|
|
530
|
+
ok(`Space config updated${autoDestroy ? ` — sandbox auto destroy: ${formatAutoDestroy(autoDestroy)}` : ""}${spec ? ` — sandbox spec: ${spec}` : ""}${result.sandbox?.pendingRestart ? " — restart the sandbox to apply" : ""}`);
|
|
440
531
|
return;
|
|
441
532
|
}
|
|
442
533
|
const result = await client.space(id).getConfig();
|
|
@@ -476,6 +567,19 @@ export function registerSpaces(program) {
|
|
|
476
567
|
.option("--image <path>", "Attach an image", collectOption, [])
|
|
477
568
|
.option("--json", "Output as JSON")
|
|
478
569
|
.action((words, opts) => sendPrompt(spacesCmd, words, opts));
|
|
570
|
+
// ── spaces completion ──
|
|
571
|
+
spacesCmd
|
|
572
|
+
.command("completion [content...]")
|
|
573
|
+
.description("Raw LLM completion with full message control")
|
|
574
|
+
.option("-m, --model <model>", "Model name")
|
|
575
|
+
.option("-p, --provider <provider>", "Provider name")
|
|
576
|
+
.option("--system-prompt <path>", "Space-relative system prompt markdown path")
|
|
577
|
+
.option("--stream", "Stream tokens via SSE")
|
|
578
|
+
.option("--temperature <n>", "Sampling temperature")
|
|
579
|
+
.option("--max-tokens <n>", "Maximum output tokens")
|
|
580
|
+
.option("--thinking-level <level>", "Thinking level: off|minimal|low|medium|high|xhigh")
|
|
581
|
+
.option("--json", "Output as JSON")
|
|
582
|
+
.action((words, opts) => runCompletionCommand(spacesCmd, words, opts));
|
|
479
583
|
// ── spaces files ──
|
|
480
584
|
registerFiles(spacesCmd);
|
|
481
585
|
// ── spaces sessions ──
|
|
@@ -839,7 +943,73 @@ function registerMods(spacesCmd) {
|
|
|
839
943
|
}
|
|
840
944
|
});
|
|
841
945
|
}
|
|
842
|
-
|
|
946
|
+
function formatDiffBytes(value) {
|
|
947
|
+
if (value === null || value === undefined)
|
|
948
|
+
return "—";
|
|
949
|
+
if (value < 1024)
|
|
950
|
+
return `${value} B`;
|
|
951
|
+
if (value < 1024 * 1024)
|
|
952
|
+
return `${(value / 1024).toFixed(value >= 10 * 1024 ? 0 : 1)} KB`;
|
|
953
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
954
|
+
}
|
|
955
|
+
function printDiffFile(file) {
|
|
956
|
+
if (file.kind !== "text") {
|
|
957
|
+
const size = file.oldSize !== undefined || file.newSize !== undefined
|
|
958
|
+
? ` (${formatDiffBytes(file.oldSize)} → ${formatDiffBytes(file.newSize)})`
|
|
959
|
+
: "";
|
|
960
|
+
console.log(`${file.status ?? "?"} ${file.path} (${file.kind})${size}`);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (file.lines.length === 0) {
|
|
964
|
+
console.log(`${file.status ?? "?"} ${file.path} (no textual changes)`);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
for (const line of file.lines) {
|
|
968
|
+
if (line.type === "add")
|
|
969
|
+
console.log(`+${line.text}`);
|
|
970
|
+
else if (line.type === "del")
|
|
971
|
+
console.log(`-${line.text}`);
|
|
972
|
+
else if (line.type === "hunk")
|
|
973
|
+
console.log(line.text);
|
|
974
|
+
else if (line.type === "meta")
|
|
975
|
+
continue;
|
|
976
|
+
else
|
|
977
|
+
console.log(` ${line.text}`);
|
|
978
|
+
}
|
|
979
|
+
if (file.truncated)
|
|
980
|
+
console.log(" … truncated");
|
|
981
|
+
}
|
|
982
|
+
function printDiffSummary(summary, options) {
|
|
983
|
+
if (summary.files.length === 0) {
|
|
984
|
+
console.log(options?.emptyLabel ?? " (no changes)");
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (summary.stats) {
|
|
988
|
+
const parts = [`${summary.stats.changedFileCount} file(s)`];
|
|
989
|
+
if (summary.stats.additions > 0)
|
|
990
|
+
parts.push(`+${summary.stats.additions}`);
|
|
991
|
+
if (summary.stats.deletions > 0)
|
|
992
|
+
parts.push(`-${summary.stats.deletions}`);
|
|
993
|
+
console.log(` ${parts.join(" · ")}`);
|
|
994
|
+
}
|
|
995
|
+
table(summary.files.map((file) => ({
|
|
996
|
+
status: file.status,
|
|
997
|
+
path: file.oldPath ? `${file.oldPath} → ${file.path}` : file.path,
|
|
998
|
+
plus: file.additions ?? (file.binary || file.asset ? "-" : "0"),
|
|
999
|
+
minus: file.deletions ?? (file.binary || file.asset ? "-" : "0"),
|
|
1000
|
+
})), [
|
|
1001
|
+
{ key: "status", label: "St" },
|
|
1002
|
+
{ key: "path", label: "Path" },
|
|
1003
|
+
{ key: "plus", label: "+" },
|
|
1004
|
+
{ key: "minus", label: "-" },
|
|
1005
|
+
]);
|
|
1006
|
+
if (options?.footer)
|
|
1007
|
+
console.log(options.footer);
|
|
1008
|
+
else if (summary.incomplete)
|
|
1009
|
+
console.log(" … partial scan");
|
|
1010
|
+
else if (summary.truncated)
|
|
1011
|
+
console.log(" … truncated");
|
|
1012
|
+
}
|
|
843
1013
|
function registerFiles(spacesCmd) {
|
|
844
1014
|
const filesCmd = spacesCmd
|
|
845
1015
|
.command("files")
|
|
@@ -969,6 +1139,37 @@ function registerFiles(spacesCmd) {
|
|
|
969
1139
|
handleHttp(e);
|
|
970
1140
|
}
|
|
971
1141
|
});
|
|
1142
|
+
filesCmd
|
|
1143
|
+
.command("diff [path]")
|
|
1144
|
+
.description("Show pending workspace changes vs last checkpoint")
|
|
1145
|
+
.option("--json", "Output as JSON")
|
|
1146
|
+
.action(async (path, opts) => {
|
|
1147
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1148
|
+
const client = createClient();
|
|
1149
|
+
try {
|
|
1150
|
+
if (path) {
|
|
1151
|
+
const file = await client.space(spaceId).files.diffFile(path);
|
|
1152
|
+
if (jsonRequested(opts))
|
|
1153
|
+
return outJson(file);
|
|
1154
|
+
printDiffFile(file);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const summary = await client.space(spaceId).files.diff();
|
|
1158
|
+
if (jsonRequested(opts))
|
|
1159
|
+
return outJson(summary);
|
|
1160
|
+
printDiffSummary(summary, {
|
|
1161
|
+
emptyLabel: " (no pending changes)",
|
|
1162
|
+
footer: summary.incomplete
|
|
1163
|
+
? " … partial scan"
|
|
1164
|
+
: summary.truncated
|
|
1165
|
+
? " … truncated"
|
|
1166
|
+
: null,
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
catch (e) {
|
|
1170
|
+
handleHttp(e);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
972
1173
|
}
|
|
973
1174
|
function registerSessions(spacesCmd) {
|
|
974
1175
|
const sessionsCmd = spacesCmd
|
|
@@ -1585,4 +1786,32 @@ function registerCheckpoints(spacesCmd) {
|
|
|
1585
1786
|
handleHttp(e);
|
|
1586
1787
|
}
|
|
1587
1788
|
});
|
|
1789
|
+
cpCmd
|
|
1790
|
+
.command("diff <checkpointId> [path]")
|
|
1791
|
+
.description("Show checkpoint diff vs parent (or --base)")
|
|
1792
|
+
.option("--base <checkpointId>", "Compare against another checkpoint")
|
|
1793
|
+
.option("--json", "Output as JSON")
|
|
1794
|
+
.action(async (checkpointId, path, opts) => {
|
|
1795
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
1796
|
+
const client = createClient();
|
|
1797
|
+
try {
|
|
1798
|
+
if (path) {
|
|
1799
|
+
const file = await client.space(spaceId).checkpoints(checkpointId).diff.file(path, { base: opts.base ?? null });
|
|
1800
|
+
if (jsonRequested(opts))
|
|
1801
|
+
return outJson(file);
|
|
1802
|
+
printDiffFile(file);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
const summary = await client.space(spaceId).checkpoints(checkpointId).diff.summary({ base: opts.base ?? null });
|
|
1806
|
+
if (jsonRequested(opts))
|
|
1807
|
+
return outJson(summary);
|
|
1808
|
+
printDiffSummary(summary, {
|
|
1809
|
+
emptyLabel: " (no changes)",
|
|
1810
|
+
footer: summary.truncated ? " … truncated" : null,
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
catch (e) {
|
|
1814
|
+
handleHttp(e);
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1588
1817
|
}
|
package/dist/index.js
CHANGED
|
@@ -41,6 +41,7 @@ Common commands:
|
|
|
41
41
|
cohub profile avatar ./avatar.png
|
|
42
42
|
cohub spaces ls
|
|
43
43
|
cohub -s <space-id> prompt "Fix the failing tests"
|
|
44
|
+
cohub -s <space-id> completion "Summarize AGENTS.md" --system-prompt AGENTS.md --stream
|
|
44
45
|
cohub -s <space-id> run -- git status
|
|
45
46
|
cohub sandbox up ./my-project
|
|
46
47
|
cohub search "release notes"
|
package/dist/output.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
+
import { extractBillingPayload } from "@neta-art/cohub";
|
|
2
3
|
function colWidth(rows, key, label) {
|
|
3
4
|
const maxVal = rows.reduce((m, r) => {
|
|
4
5
|
const v = r[key] ?? "";
|
|
@@ -55,8 +56,6 @@ function errorMessageFromBody(body) {
|
|
|
55
56
|
const errorBody = body;
|
|
56
57
|
if (typeof errorBody.message === "string" && errorBody.message.trim())
|
|
57
58
|
return errorBody.message;
|
|
58
|
-
if (typeof errorBody.error?.message === "string" && errorBody.error.message.trim())
|
|
59
|
-
return errorBody.error.message;
|
|
60
59
|
return null;
|
|
61
60
|
}
|
|
62
61
|
function debugErrorMetaFromBody(body) {
|
|
@@ -64,7 +63,7 @@ function debugErrorMetaFromBody(body) {
|
|
|
64
63
|
return [];
|
|
65
64
|
const errorBody = body;
|
|
66
65
|
const items = [];
|
|
67
|
-
const code = typeof errorBody.code === "string" ? errorBody.code :
|
|
66
|
+
const code = typeof errorBody.code === "string" ? errorBody.code : null;
|
|
68
67
|
if (code)
|
|
69
68
|
items.push(code);
|
|
70
69
|
if (typeof errorBody.requestId === "string")
|
|
@@ -101,6 +100,14 @@ export function handleHttp(e) {
|
|
|
101
100
|
}
|
|
102
101
|
const status = e.status;
|
|
103
102
|
const body = e.body;
|
|
103
|
+
if (status === 402) {
|
|
104
|
+
const conversion = extractBillingPayload(body)?.conversion;
|
|
105
|
+
if (conversion) {
|
|
106
|
+
return error(conversion.title || "Upgrade required", [conversion.message, "Manage billing at your Cohub account settings."]
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.join(" · "));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
104
111
|
const presentation = errorPresentationFromHttpError(e);
|
|
105
112
|
const message = presentation?.message ?? errorMessageFromBody(body) ?? (e instanceof Error ? e.message : String(e));
|
|
106
113
|
const fetchDetail = fetchFailureDetail(e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.10",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@neta-art/generation": "^0.1.
|
|
16
|
+
"@neta-art/generation": "^0.1.12",
|
|
17
17
|
"commander": "^14.0.3",
|
|
18
18
|
"sharp": "^0.34.5",
|
|
19
|
-
"@neta-art/cohub": "2.
|
|
19
|
+
"@neta-art/cohub": "2.9.0"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|