@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/spaces.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { readdir, stat } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
2
5
|
import { createClient } from "../client.js";
|
|
3
6
|
import { table, json as outJson, ok, error, handleHttp } from "../output.js";
|
|
4
7
|
function requireSpace(program) {
|
|
@@ -11,6 +14,151 @@ function requireSpace(program) {
|
|
|
11
14
|
}
|
|
12
15
|
return error("Missing required option", "Add -s, --space <id> to target a space");
|
|
13
16
|
}
|
|
17
|
+
const slashPath = (value) => value.split(sep).join("/");
|
|
18
|
+
const walkUploadPath = async (input, root, prefix = "") => {
|
|
19
|
+
const localPath = resolve(input);
|
|
20
|
+
const info = await stat(localPath);
|
|
21
|
+
const name = basename(localPath);
|
|
22
|
+
const relativePath = slashPath(prefix ? `${prefix}/${name}` : relative(root, localPath) || name);
|
|
23
|
+
if (info.isDirectory()) {
|
|
24
|
+
const children = await readdir(localPath);
|
|
25
|
+
const nested = await Promise.all(children.map((child) => walkUploadPath(resolve(localPath, child), root, relativePath)));
|
|
26
|
+
return nested.flat();
|
|
27
|
+
}
|
|
28
|
+
if (!info.isFile())
|
|
29
|
+
return [];
|
|
30
|
+
return [{
|
|
31
|
+
id: randomUploadEntryId(),
|
|
32
|
+
localPath,
|
|
33
|
+
relativePath,
|
|
34
|
+
name,
|
|
35
|
+
size: info.size,
|
|
36
|
+
mimeType: null,
|
|
37
|
+
}];
|
|
38
|
+
};
|
|
39
|
+
const randomUploadEntryId = () => randomUUID();
|
|
40
|
+
async function collectUploadFiles(paths) {
|
|
41
|
+
if (paths.length === 0)
|
|
42
|
+
return error("No files provided", "Pass one or more local files or directories.");
|
|
43
|
+
const roots = paths.map((path) => {
|
|
44
|
+
const resolved = resolve(path);
|
|
45
|
+
return dirname(resolved);
|
|
46
|
+
});
|
|
47
|
+
const nested = await Promise.all(paths.map((path, index) => walkUploadPath(path, roots[index] ?? process.cwd())));
|
|
48
|
+
const files = nested.flat();
|
|
49
|
+
if (files.length === 0)
|
|
50
|
+
return error("No regular files found");
|
|
51
|
+
return files;
|
|
52
|
+
}
|
|
53
|
+
async function putUploadEntry(entry, uploadUrl, headers) {
|
|
54
|
+
const response = await fetch(uploadUrl, {
|
|
55
|
+
method: "PUT",
|
|
56
|
+
headers,
|
|
57
|
+
body: createReadStream(entry.localPath),
|
|
58
|
+
duplex: "half",
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const detail = await response.text().catch(() => "");
|
|
62
|
+
throw new Error(`Failed to upload ${entry.relativePath}: HTTP ${response.status}${detail ? ` — ${detail}` : ""}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function uploadFiles(command, paths, opts) {
|
|
66
|
+
const spaceId = requireSpace(command);
|
|
67
|
+
const client = createClient();
|
|
68
|
+
try {
|
|
69
|
+
const files = await collectUploadFiles(paths);
|
|
70
|
+
const plan = await client.space(spaceId).files.createUpload({
|
|
71
|
+
targetDir: opts.dir,
|
|
72
|
+
entries: files.map((file) => ({
|
|
73
|
+
id: file.id,
|
|
74
|
+
name: file.name,
|
|
75
|
+
relativePath: file.relativePath,
|
|
76
|
+
size: file.size,
|
|
77
|
+
mimeType: file.mimeType,
|
|
78
|
+
})),
|
|
79
|
+
});
|
|
80
|
+
const byId = new Map(files.map((file) => [file.id, file]));
|
|
81
|
+
for (const entry of plan.entries) {
|
|
82
|
+
const file = byId.get(entry.id);
|
|
83
|
+
if (!file)
|
|
84
|
+
throw new Error(`Missing upload entry: ${entry.id}`);
|
|
85
|
+
await putUploadEntry(file, entry.uploadUrl, entry.headers);
|
|
86
|
+
}
|
|
87
|
+
const result = await client.space(spaceId).files.completeUpload(plan.uploadId, {
|
|
88
|
+
entries: plan.entries.map((entry) => ({ id: entry.id })),
|
|
89
|
+
});
|
|
90
|
+
if (opts.json)
|
|
91
|
+
return outJson({ ...result, uploadId: plan.uploadId, files: files.length });
|
|
92
|
+
ok(`Uploaded ${files.length} file${files.length === 1 ? "" : "s"} — taskRunId: ${result.taskRunId}`);
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
handleHttp(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function readPromptContent(words) {
|
|
99
|
+
let content = words.join(" ");
|
|
100
|
+
if (!content && !process.stdin.isTTY) {
|
|
101
|
+
const chunks = [];
|
|
102
|
+
for await (const chunk of process.stdin)
|
|
103
|
+
chunks.push(chunk);
|
|
104
|
+
content = Buffer.concat(chunks).toString().trim();
|
|
105
|
+
}
|
|
106
|
+
if (!content)
|
|
107
|
+
return error("No content", "Pass as argument or pipe via stdin");
|
|
108
|
+
return content;
|
|
109
|
+
}
|
|
110
|
+
async function sendPrompt(command, words, opts) {
|
|
111
|
+
const content = await readPromptContent(words);
|
|
112
|
+
const scheduleFlags = [opts.delayMs, opts.at, opts.cron].filter((value) => value !== undefined);
|
|
113
|
+
if (scheduleFlags.length > 1)
|
|
114
|
+
return error("Conflicting schedule", "Use only one of --delay-ms, --at, or --cron");
|
|
115
|
+
if (opts.cron && !opts.timezone)
|
|
116
|
+
return error("Missing timezone", "--timezone is required with --cron");
|
|
117
|
+
const spaceId = requireSpace(command);
|
|
118
|
+
const client = createClient();
|
|
119
|
+
try {
|
|
120
|
+
const schedule = opts.delayMs
|
|
121
|
+
? { mode: "delay", delayMs: Number.parseInt(opts.delayMs, 10) }
|
|
122
|
+
: opts.at
|
|
123
|
+
? { mode: "at", sendAt: opts.at }
|
|
124
|
+
: opts.cron
|
|
125
|
+
? { mode: "repeat", cronExpression: opts.cron, timezone: opts.timezone }
|
|
126
|
+
: undefined;
|
|
127
|
+
const result = await client.space(spaceId).prompt({
|
|
128
|
+
sessionId: opts.session,
|
|
129
|
+
title: opts.title,
|
|
130
|
+
content: [{ type: "text", text: content }],
|
|
131
|
+
model: opts.model,
|
|
132
|
+
provider: opts.provider,
|
|
133
|
+
schedule,
|
|
134
|
+
});
|
|
135
|
+
if (opts.json)
|
|
136
|
+
return outJson(result);
|
|
137
|
+
if (result.mode === "immediate")
|
|
138
|
+
return ok(`Prompt sent — sessionId: ${result.sessionId}, turnId: ${result.turnId}`);
|
|
139
|
+
if (result.mode === "repeat")
|
|
140
|
+
return ok(`Prompt scheduled — cronJobId: ${result.cronJobId}, nextRunAt: ${result.nextRunAt}`);
|
|
141
|
+
return ok(`Prompt scheduled — taskRunId: ${result.taskRunId}, scheduledAt: ${result.scheduledAt}`);
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
handleHttp(e);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function registerPrompt(program) {
|
|
148
|
+
program
|
|
149
|
+
.command("prompt [content...]")
|
|
150
|
+
.description("Send or schedule a prompt in a space")
|
|
151
|
+
.option("--session <id>", "Target session ID")
|
|
152
|
+
.option("--title <title>", "Title for a newly created session or schedule")
|
|
153
|
+
.option("-m, --model <model>", "Model name")
|
|
154
|
+
.option("-p, --provider <provider>", "Provider name")
|
|
155
|
+
.option("--delay-ms <ms>", "Delay sending by milliseconds")
|
|
156
|
+
.option("--at <iso>", "Send once at an ISO 8601 time with timezone")
|
|
157
|
+
.option("--cron <expression>", "Repeat using a 5-field cron expression")
|
|
158
|
+
.option("--timezone <tz>", "IANA timezone for --cron, e.g. Asia/Shanghai")
|
|
159
|
+
.option("--json", "Output as JSON")
|
|
160
|
+
.action((words, opts) => sendPrompt(program, words, opts));
|
|
161
|
+
}
|
|
14
162
|
export function registerSpaces(program) {
|
|
15
163
|
const spacesCmd = program.command("spaces").description("Space management");
|
|
16
164
|
// ── spaces ls ──
|
|
@@ -20,8 +168,7 @@ export function registerSpaces(program) {
|
|
|
20
168
|
.description("List all spaces")
|
|
21
169
|
.option("--json", "Output as JSON")
|
|
22
170
|
.action(async (opts) => {
|
|
23
|
-
const
|
|
24
|
-
const client = createClient(token);
|
|
171
|
+
const client = createClient();
|
|
25
172
|
try {
|
|
26
173
|
const items = await client.spaces.list();
|
|
27
174
|
if (opts.json)
|
|
@@ -42,8 +189,7 @@ export function registerSpaces(program) {
|
|
|
42
189
|
.description("Show space details")
|
|
43
190
|
.option("--json", "Output as JSON")
|
|
44
191
|
.action(async (id, opts) => {
|
|
45
|
-
const
|
|
46
|
-
const client = createClient(token);
|
|
192
|
+
const client = createClient();
|
|
47
193
|
try {
|
|
48
194
|
const space = await client.spaces.get(id);
|
|
49
195
|
if (opts.json)
|
|
@@ -68,8 +214,7 @@ export function registerSpaces(program) {
|
|
|
68
214
|
.option("-d, --description <desc>", "Space description")
|
|
69
215
|
.option("--json", "Output as JSON")
|
|
70
216
|
.action(async (opts) => {
|
|
71
|
-
const
|
|
72
|
-
const client = createClient(token);
|
|
217
|
+
const client = createClient();
|
|
73
218
|
try {
|
|
74
219
|
const result = await client.spaces.create({
|
|
75
220
|
name: opts.name,
|
|
@@ -93,8 +238,7 @@ export function registerSpaces(program) {
|
|
|
93
238
|
.command("rename <id> <name>")
|
|
94
239
|
.description("Rename a space")
|
|
95
240
|
.action(async (id, name) => {
|
|
96
|
-
const
|
|
97
|
-
const client = createClient(token);
|
|
241
|
+
const client = createClient();
|
|
98
242
|
try {
|
|
99
243
|
await client.space(id).rename(name);
|
|
100
244
|
ok(`Space renamed to "${name}"`);
|
|
@@ -105,7 +249,8 @@ export function registerSpaces(program) {
|
|
|
105
249
|
});
|
|
106
250
|
// ── spaces prompt ──
|
|
107
251
|
spacesCmd
|
|
108
|
-
.command("prompt [content...]")
|
|
252
|
+
.command("prompt [content...]", { hidden: true })
|
|
253
|
+
.alias("send")
|
|
109
254
|
.description("Send or schedule a prompt in the target space")
|
|
110
255
|
.option("--session <id>", "Target session ID")
|
|
111
256
|
.option("--title <title>", "Title for a newly created session or schedule")
|
|
@@ -116,52 +261,7 @@ export function registerSpaces(program) {
|
|
|
116
261
|
.option("--cron <expression>", "Repeat using a 5-field cron expression")
|
|
117
262
|
.option("--timezone <tz>", "IANA timezone for --cron, e.g. Asia/Shanghai")
|
|
118
263
|
.option("--json", "Output as JSON")
|
|
119
|
-
.action(
|
|
120
|
-
const token = resolveToken() ?? missingAuth();
|
|
121
|
-
let content = words.join(" ");
|
|
122
|
-
if (!content && !process.stdin.isTTY) {
|
|
123
|
-
const chunks = [];
|
|
124
|
-
for await (const chunk of process.stdin)
|
|
125
|
-
chunks.push(chunk);
|
|
126
|
-
content = Buffer.concat(chunks).toString().trim();
|
|
127
|
-
}
|
|
128
|
-
if (!content)
|
|
129
|
-
return error("No content", "Pass as argument or pipe via stdin");
|
|
130
|
-
const scheduleFlags = [opts.delayMs, opts.at, opts.cron].filter((value) => value !== undefined);
|
|
131
|
-
if (scheduleFlags.length > 1)
|
|
132
|
-
return error("Conflicting schedule", "Use only one of --delay-ms, --at, or --cron");
|
|
133
|
-
if (opts.cron && !opts.timezone)
|
|
134
|
-
return error("Missing timezone", "--timezone is required with --cron");
|
|
135
|
-
const spaceId = requireSpace(spacesCmd);
|
|
136
|
-
const client = createClient(token);
|
|
137
|
-
try {
|
|
138
|
-
const schedule = opts.delayMs
|
|
139
|
-
? { mode: "delay", delayMs: Number.parseInt(opts.delayMs, 10) }
|
|
140
|
-
: opts.at
|
|
141
|
-
? { mode: "at", sendAt: opts.at }
|
|
142
|
-
: opts.cron
|
|
143
|
-
? { mode: "repeat", cronExpression: opts.cron, timezone: opts.timezone }
|
|
144
|
-
: undefined;
|
|
145
|
-
const result = await client.space(spaceId).prompt({
|
|
146
|
-
sessionId: opts.session,
|
|
147
|
-
title: opts.title,
|
|
148
|
-
content: [{ type: "text", text: content }],
|
|
149
|
-
model: opts.model,
|
|
150
|
-
provider: opts.provider,
|
|
151
|
-
schedule,
|
|
152
|
-
});
|
|
153
|
-
if (opts.json)
|
|
154
|
-
return outJson(result);
|
|
155
|
-
if (result.mode === "immediate")
|
|
156
|
-
return ok(`Prompt sent — sessionId: ${result.sessionId}, turnId: ${result.turnId}`);
|
|
157
|
-
if (result.mode === "repeat")
|
|
158
|
-
return ok(`Prompt scheduled — cronJobId: ${result.cronJobId}, nextRunAt: ${result.nextRunAt}`);
|
|
159
|
-
return ok(`Prompt scheduled — taskRunId: ${result.taskRunId}, scheduledAt: ${result.scheduledAt}`);
|
|
160
|
-
}
|
|
161
|
-
catch (e) {
|
|
162
|
-
handleHttp(e);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
264
|
+
.action((words, opts) => sendPrompt(spacesCmd, words, opts));
|
|
165
265
|
// ── spaces files ──
|
|
166
266
|
registerFiles(spacesCmd);
|
|
167
267
|
// ── spaces sessions ──
|
|
@@ -178,9 +278,8 @@ export function registerSpaces(program) {
|
|
|
178
278
|
.description("Space usage statistics (default: 30 days)")
|
|
179
279
|
.option("--json", "Output as JSON")
|
|
180
280
|
.action(async (days, opts) => {
|
|
181
|
-
const token = resolveToken() ?? missingAuth();
|
|
182
281
|
const spaceId = requireSpace(spacesCmd);
|
|
183
|
-
const client = createClient(
|
|
282
|
+
const client = createClient();
|
|
184
283
|
try {
|
|
185
284
|
const usage = await client.space(spaceId).usage.get(Number.parseInt(days ?? "30", 10));
|
|
186
285
|
if (opts.json)
|
|
@@ -211,9 +310,8 @@ function registerFiles(spacesCmd) {
|
|
|
211
310
|
.description("List directory tree")
|
|
212
311
|
.option("--json", "Output as JSON")
|
|
213
312
|
.action(async (path, opts) => {
|
|
214
|
-
const token = resolveToken() ?? missingAuth();
|
|
215
313
|
const spaceId = requireSpace(spacesCmd);
|
|
216
|
-
const client = createClient(
|
|
314
|
+
const client = createClient();
|
|
217
315
|
try {
|
|
218
316
|
const tree = await client.space(spaceId).files.list(path ?? "");
|
|
219
317
|
if (opts.json)
|
|
@@ -237,11 +335,15 @@ function registerFiles(spacesCmd) {
|
|
|
237
335
|
.command("cat <path>")
|
|
238
336
|
.description("Read file content")
|
|
239
337
|
.action(async (path) => {
|
|
240
|
-
const token = resolveToken() ?? missingAuth();
|
|
241
338
|
const spaceId = requireSpace(spacesCmd);
|
|
242
|
-
const client = createClient(
|
|
339
|
+
const client = createClient();
|
|
243
340
|
try {
|
|
244
341
|
const file = await client.space(spaceId).files.read(path);
|
|
342
|
+
if (!("content" in file))
|
|
343
|
+
return error("File is being prepared. Please retry shortly.");
|
|
344
|
+
if (file.delivery === "url" && file.url) {
|
|
345
|
+
console.log(`[CDN] ${file.url}`);
|
|
346
|
+
}
|
|
245
347
|
console.log(file.content);
|
|
246
348
|
}
|
|
247
349
|
catch (e) {
|
|
@@ -254,7 +356,6 @@ function registerFiles(spacesCmd) {
|
|
|
254
356
|
.option("-c, --content <text>", "File content")
|
|
255
357
|
.option("-e, --encoding <enc>", "Encoding (utf-8 or base64)", "utf-8")
|
|
256
358
|
.action(async (path, opts) => {
|
|
257
|
-
const token = resolveToken() ?? missingAuth();
|
|
258
359
|
let content = opts.content ?? "";
|
|
259
360
|
if (!content && !process.stdin.isTTY) {
|
|
260
361
|
const chunks = [];
|
|
@@ -265,7 +366,7 @@ function registerFiles(spacesCmd) {
|
|
|
265
366
|
if (!content)
|
|
266
367
|
return error("No content provided", "Use -c or pipe via stdin");
|
|
267
368
|
const spaceId = requireSpace(spacesCmd);
|
|
268
|
-
const client = createClient(
|
|
369
|
+
const client = createClient();
|
|
269
370
|
try {
|
|
270
371
|
const result = await client.space(spaceId).files.write({
|
|
271
372
|
path,
|
|
@@ -278,13 +379,18 @@ function registerFiles(spacesCmd) {
|
|
|
278
379
|
handleHttp(e);
|
|
279
380
|
}
|
|
280
381
|
});
|
|
382
|
+
filesCmd
|
|
383
|
+
.command("upload <paths...>")
|
|
384
|
+
.description("Upload local files or directories")
|
|
385
|
+
.option("--dir <path>", "Target directory in the space")
|
|
386
|
+
.option("--json", "Output as JSON")
|
|
387
|
+
.action((paths, opts) => uploadFiles(spacesCmd, paths, opts));
|
|
281
388
|
filesCmd
|
|
282
389
|
.command("mkdir <path>")
|
|
283
390
|
.description("Create a directory")
|
|
284
391
|
.action(async (path) => {
|
|
285
|
-
const token = resolveToken() ?? missingAuth();
|
|
286
392
|
const spaceId = requireSpace(spacesCmd);
|
|
287
|
-
const client = createClient(
|
|
393
|
+
const client = createClient();
|
|
288
394
|
try {
|
|
289
395
|
await client.space(spaceId).files.createDir(path);
|
|
290
396
|
ok(`Directory created: ${path}`);
|
|
@@ -298,9 +404,8 @@ function registerFiles(spacesCmd) {
|
|
|
298
404
|
.description("Delete a file or directory")
|
|
299
405
|
.option("-r, --recursive", "Delete recursively")
|
|
300
406
|
.action(async (path, opts) => {
|
|
301
|
-
const token = resolveToken() ?? missingAuth();
|
|
302
407
|
const spaceId = requireSpace(spacesCmd);
|
|
303
|
-
const client = createClient(
|
|
408
|
+
const client = createClient();
|
|
304
409
|
try {
|
|
305
410
|
await client.space(spaceId).files.delete(path, opts.recursive ?? false);
|
|
306
411
|
ok(`Deleted: ${path}`);
|
|
@@ -313,9 +418,8 @@ function registerFiles(spacesCmd) {
|
|
|
313
418
|
.command("mv <from> <to>")
|
|
314
419
|
.description("Move or rename")
|
|
315
420
|
.action(async (from, to) => {
|
|
316
|
-
const token = resolveToken() ?? missingAuth();
|
|
317
421
|
const spaceId = requireSpace(spacesCmd);
|
|
318
|
-
const client = createClient(
|
|
422
|
+
const client = createClient();
|
|
319
423
|
try {
|
|
320
424
|
await client.space(spaceId).files.move({ fromPath: from, toPath: to });
|
|
321
425
|
ok(`Moved: ${from} → ${to}`);
|
|
@@ -324,19 +428,12 @@ function registerFiles(spacesCmd) {
|
|
|
324
428
|
handleHttp(e);
|
|
325
429
|
}
|
|
326
430
|
});
|
|
327
|
-
filesCmd
|
|
328
|
-
.command("upload <files...>")
|
|
329
|
-
.description("Upload files to a directory")
|
|
330
|
-
.option("--dir <dir>", "Target directory", "")
|
|
331
|
-
.action(async (_files) => {
|
|
332
|
-
error("Upload requires browser File API", "Use the web interface for now");
|
|
333
|
-
});
|
|
334
431
|
}
|
|
335
432
|
// ── Session operations ──
|
|
336
433
|
function registerSessions(spacesCmd) {
|
|
337
434
|
const sessionsCmd = spacesCmd
|
|
338
435
|
.command("sessions")
|
|
339
|
-
.description("
|
|
436
|
+
.description("Browse sessions and turns")
|
|
340
437
|
.hook("preAction", () => { requireSpace(spacesCmd); });
|
|
341
438
|
sessionsCmd
|
|
342
439
|
.command("ls")
|
|
@@ -344,9 +441,8 @@ function registerSessions(spacesCmd) {
|
|
|
344
441
|
.description("List sessions")
|
|
345
442
|
.option("--json", "Output as JSON")
|
|
346
443
|
.action(async (opts) => {
|
|
347
|
-
const token = resolveToken() ?? missingAuth();
|
|
348
444
|
const spaceId = requireSpace(spacesCmd);
|
|
349
|
-
const client = createClient(
|
|
445
|
+
const client = createClient();
|
|
350
446
|
try {
|
|
351
447
|
const result = await client.space(spaceId).sessions.list();
|
|
352
448
|
if (opts.json)
|
|
@@ -371,9 +467,8 @@ function registerSessions(spacesCmd) {
|
|
|
371
467
|
.description("Create a session")
|
|
372
468
|
.option("--json", "Output as JSON")
|
|
373
469
|
.action(async (title, opts) => {
|
|
374
|
-
const token = resolveToken() ?? missingAuth();
|
|
375
470
|
const spaceId = requireSpace(spacesCmd);
|
|
376
|
-
const client = createClient(
|
|
471
|
+
const client = createClient();
|
|
377
472
|
try {
|
|
378
473
|
const result = await client.space(spaceId).sessions.create({ title });
|
|
379
474
|
if (opts.json)
|
|
@@ -393,9 +488,8 @@ function registerSessions(spacesCmd) {
|
|
|
393
488
|
.description("Session details")
|
|
394
489
|
.option("--json", "Output as JSON")
|
|
395
490
|
.action(async (id, opts) => {
|
|
396
|
-
const token = resolveToken() ?? missingAuth();
|
|
397
491
|
const spaceId = requireSpace(spacesCmd);
|
|
398
|
-
const client = createClient(
|
|
492
|
+
const client = createClient();
|
|
399
493
|
try {
|
|
400
494
|
const result = await client.space(spaceId).session(id).get();
|
|
401
495
|
if (opts.json)
|
|
@@ -416,9 +510,8 @@ function registerSessions(spacesCmd) {
|
|
|
416
510
|
.command("rename <id> <name>")
|
|
417
511
|
.description("Rename a session")
|
|
418
512
|
.action(async (id, name) => {
|
|
419
|
-
const token = resolveToken() ?? missingAuth();
|
|
420
513
|
const spaceId = requireSpace(spacesCmd);
|
|
421
|
-
const client = createClient(
|
|
514
|
+
const client = createClient();
|
|
422
515
|
try {
|
|
423
516
|
await client.space(spaceId).session(id).rename(name);
|
|
424
517
|
ok(`Session renamed to "${name}"`);
|
|
@@ -427,17 +520,14 @@ function registerSessions(spacesCmd) {
|
|
|
427
520
|
handleHttp(e);
|
|
428
521
|
}
|
|
429
522
|
});
|
|
430
|
-
// ── sessions messages ──
|
|
431
|
-
registerMessages(sessionsCmd);
|
|
432
523
|
// ── sessions tail ──
|
|
433
524
|
sessionsCmd
|
|
434
525
|
.command("tail <id>")
|
|
435
526
|
.description("Stream realtime session events")
|
|
436
527
|
.option("--json", "Output as JSON")
|
|
437
528
|
.action(async (id, opts) => {
|
|
438
|
-
const token = resolveToken() ?? missingAuth();
|
|
439
529
|
const spaceId = requireSpace(spacesCmd);
|
|
440
|
-
const client = createClient(
|
|
530
|
+
const client = createClient();
|
|
441
531
|
const session = client.space(spaceId).session(id);
|
|
442
532
|
process.stdout.write(" Listening for events...\n\n");
|
|
443
533
|
let lastAppendPath = null;
|
|
@@ -474,71 +564,202 @@ function registerSessions(spacesCmd) {
|
|
|
474
564
|
process.exit(1);
|
|
475
565
|
});
|
|
476
566
|
});
|
|
567
|
+
// ── sessions turns ──
|
|
568
|
+
registerTurns(sessionsCmd);
|
|
569
|
+
// ── sessions access ──
|
|
570
|
+
registerSessionAccess(sessionsCmd);
|
|
477
571
|
}
|
|
478
|
-
// ──
|
|
479
|
-
function
|
|
480
|
-
const
|
|
481
|
-
|
|
572
|
+
// ── Turn operations ──
|
|
573
|
+
function registerTurns(sessionsCmd) {
|
|
574
|
+
const turnsCmd = sessionsCmd.command("turns").description("Inspect session turns");
|
|
575
|
+
turnsCmd
|
|
482
576
|
.command("ls <sessionId>")
|
|
483
577
|
.alias("list")
|
|
484
|
-
.description("List
|
|
578
|
+
.description("List recent turns")
|
|
579
|
+
.option("--cursor <sequence>", "Turn sequence cursor")
|
|
580
|
+
.option("--direction <older|newer>", "Page direction", "older")
|
|
581
|
+
.option("--limit <n>", "Page size", "30")
|
|
485
582
|
.option("--json", "Output as JSON")
|
|
486
|
-
.option("--limit <n>", "Page size", "50")
|
|
487
583
|
.action(async (sessionId, opts) => {
|
|
488
|
-
const token = resolveToken() ?? missingAuth();
|
|
489
584
|
const spaceId = requireSpace(sessionsCmd);
|
|
490
|
-
const client = createClient(
|
|
585
|
+
const client = createClient();
|
|
491
586
|
try {
|
|
492
|
-
const result = await client.space(spaceId).session(sessionId).
|
|
493
|
-
|
|
587
|
+
const result = await client.space(spaceId).session(sessionId).turns.listPaginated({
|
|
588
|
+
cursor: opts.cursor === undefined ? undefined : Number.parseInt(opts.cursor, 10),
|
|
589
|
+
direction: opts.direction,
|
|
590
|
+
limit: Number.parseInt(opts.limit ?? "30", 10),
|
|
494
591
|
});
|
|
495
592
|
if (opts.json)
|
|
496
593
|
return outJson(result);
|
|
497
|
-
if (result.
|
|
498
|
-
console.log("
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
table(result.messages, [
|
|
594
|
+
if (result.turns.length === 0)
|
|
595
|
+
return console.log(" No turns found");
|
|
596
|
+
table(result.turns, [
|
|
597
|
+
{ key: "sequence", label: "Seq" },
|
|
502
598
|
{ key: "id", label: "ID" },
|
|
503
|
-
{ key: "
|
|
504
|
-
{ key: "
|
|
599
|
+
{ key: "status", label: "Status" },
|
|
600
|
+
{ key: "userText", label: "User" },
|
|
601
|
+
{ key: "assistantText", label: "Assistant" },
|
|
602
|
+
{ key: "updatedAt", label: "Updated" },
|
|
505
603
|
]);
|
|
506
|
-
if (result.hasMore)
|
|
507
|
-
console.log(`\n
|
|
508
|
-
}
|
|
604
|
+
if (result.hasMore)
|
|
605
|
+
console.log(`\n More turns available — next cursor: ${result.nextCursor}`);
|
|
509
606
|
}
|
|
510
607
|
catch (e) {
|
|
511
608
|
handleHttp(e);
|
|
512
609
|
}
|
|
513
610
|
});
|
|
514
|
-
|
|
515
|
-
.command("
|
|
516
|
-
.description("
|
|
517
|
-
.option("-m, --model <model>", "Model name")
|
|
518
|
-
.option("-p, --provider <provider>", "Provider name")
|
|
611
|
+
turnsCmd
|
|
612
|
+
.command("get <sessionId> <turnId>")
|
|
613
|
+
.description("Show turn details")
|
|
519
614
|
.option("--json", "Output as JSON")
|
|
520
|
-
.action(async (sessionId,
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
615
|
+
.action(async (sessionId, turnId, opts) => {
|
|
616
|
+
const spaceId = requireSpace(sessionsCmd);
|
|
617
|
+
const client = createClient();
|
|
618
|
+
try {
|
|
619
|
+
const result = await client.space(spaceId).session(sessionId).turns.get(turnId);
|
|
620
|
+
if (opts.json)
|
|
621
|
+
return outJson(result);
|
|
622
|
+
table([result.turn], [
|
|
623
|
+
{ key: "sequence", label: "Seq" },
|
|
624
|
+
{ key: "id", label: "ID" },
|
|
625
|
+
{ key: "status", label: "Status" },
|
|
626
|
+
{ key: "provider", label: "Provider" },
|
|
627
|
+
{ key: "model", label: "Model" },
|
|
628
|
+
{ key: "stopReason", label: "Stop" },
|
|
629
|
+
{ key: "errorMessage", label: "Error" },
|
|
630
|
+
]);
|
|
631
|
+
if (result.turn.userText)
|
|
632
|
+
console.log(`\nUser:\n${result.turn.userText}`);
|
|
633
|
+
if (result.turn.assistantText)
|
|
634
|
+
console.log(`\nAssistant:\n${result.turn.assistantText}`);
|
|
528
635
|
}
|
|
529
|
-
|
|
530
|
-
|
|
636
|
+
catch (e) {
|
|
637
|
+
handleHttp(e);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
turnsCmd
|
|
641
|
+
.command("index <sessionId>", { hidden: true })
|
|
642
|
+
.description("List lightweight turn index")
|
|
643
|
+
.option("--cursor <sequence>", "Turn sequence cursor")
|
|
644
|
+
.option("--limit <n>", "Page size", "100")
|
|
645
|
+
.option("--json", "Output as JSON")
|
|
646
|
+
.action(async (sessionId, opts) => {
|
|
647
|
+
const spaceId = requireSpace(sessionsCmd);
|
|
648
|
+
const client = createClient();
|
|
649
|
+
try {
|
|
650
|
+
const result = await client.space(spaceId).session(sessionId).turns.index({
|
|
651
|
+
cursor: opts.cursor === undefined ? undefined : Number.parseInt(opts.cursor, 10),
|
|
652
|
+
limit: Number.parseInt(opts.limit ?? "100", 10),
|
|
653
|
+
});
|
|
654
|
+
if (opts.json)
|
|
655
|
+
return outJson(result);
|
|
656
|
+
if (result.turns.length === 0)
|
|
657
|
+
return console.log(" No turns found");
|
|
658
|
+
table(result.turns, [
|
|
659
|
+
{ key: "sequence", label: "Seq" },
|
|
660
|
+
{ key: "id", label: "ID" },
|
|
661
|
+
{ key: "status", label: "Status" },
|
|
662
|
+
{ key: "userPreview", label: "User" },
|
|
663
|
+
{ key: "assistantPreview", label: "Assistant" },
|
|
664
|
+
]);
|
|
665
|
+
if (result.hasMore)
|
|
666
|
+
console.log(`\n More turns available — next cursor: ${result.nextCursor}`);
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
handleHttp(e);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
turnsCmd
|
|
673
|
+
.command("window <sessionId>", { hidden: true })
|
|
674
|
+
.description("Load turns around a sequence or turn ID")
|
|
675
|
+
.option("--sequence <n>", "Anchor turn sequence")
|
|
676
|
+
.option("--turn <id>", "Anchor turn ID")
|
|
677
|
+
.option("--before <n>", "Turns before anchor", "10")
|
|
678
|
+
.option("--after <n>", "Turns after anchor", "20")
|
|
679
|
+
.option("--json", "Output as JSON")
|
|
680
|
+
.action(async (sessionId, opts) => {
|
|
531
681
|
const spaceId = requireSpace(sessionsCmd);
|
|
532
|
-
|
|
682
|
+
if (!opts.sequence && !opts.turn)
|
|
683
|
+
return error("Missing anchor", "Use --sequence <n> or --turn <id>");
|
|
684
|
+
const client = createClient();
|
|
533
685
|
try {
|
|
534
|
-
const result = await client.space(spaceId).session(sessionId).
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
686
|
+
const result = await client.space(spaceId).session(sessionId).turns.window({
|
|
687
|
+
sequence: opts.sequence === undefined ? undefined : Number.parseInt(opts.sequence, 10),
|
|
688
|
+
turnId: opts.turn,
|
|
689
|
+
before: Number.parseInt(opts.before ?? "10", 10),
|
|
690
|
+
after: Number.parseInt(opts.after ?? "20", 10),
|
|
538
691
|
});
|
|
539
692
|
if (opts.json)
|
|
540
693
|
return outJson(result);
|
|
541
|
-
|
|
694
|
+
if (result.turns.length === 0)
|
|
695
|
+
return console.log(" No turns found");
|
|
696
|
+
table(result.turns, [
|
|
697
|
+
{ key: "sequence", label: "Seq" },
|
|
698
|
+
{ key: "id", label: "ID" },
|
|
699
|
+
{ key: "status", label: "Status" },
|
|
700
|
+
{ key: "userText", label: "User" },
|
|
701
|
+
{ key: "assistantText", label: "Assistant" },
|
|
702
|
+
]);
|
|
703
|
+
console.log(`\n Window — older: ${result.hasMoreOlder ? "yes" : "no"}, newer: ${result.hasMoreNewer ? "yes" : "no"}`);
|
|
704
|
+
}
|
|
705
|
+
catch (e) {
|
|
706
|
+
handleHttp(e);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
// ── Session access operations ──
|
|
711
|
+
function registerSessionAccess(sessionsCmd) {
|
|
712
|
+
const accessCmd = sessionsCmd.command("access").description("Session access control");
|
|
713
|
+
accessCmd
|
|
714
|
+
.command("get <id>")
|
|
715
|
+
.description("Get session access policy")
|
|
716
|
+
.option("--json", "Output as JSON")
|
|
717
|
+
.action(async (id, opts) => {
|
|
718
|
+
const client = createClient();
|
|
719
|
+
try {
|
|
720
|
+
const policy = await client.sessionAccess.get(id);
|
|
721
|
+
if (opts.json)
|
|
722
|
+
return outJson(policy);
|
|
723
|
+
table([policy], [
|
|
724
|
+
{ key: "signed_in_user", label: "Signed-in" },
|
|
725
|
+
{ key: "anonymous_user", label: "Anonymous" },
|
|
726
|
+
]);
|
|
727
|
+
}
|
|
728
|
+
catch (e) {
|
|
729
|
+
handleHttp(e);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
accessCmd
|
|
733
|
+
.command("set <id>")
|
|
734
|
+
.description("Set session anonymous access")
|
|
735
|
+
.option("--anonymous <role>", "Anonymous role (host|builder|guest|null)")
|
|
736
|
+
.option("--json", "Output as JSON")
|
|
737
|
+
.action(async (id, opts) => {
|
|
738
|
+
const client = createClient();
|
|
739
|
+
try {
|
|
740
|
+
const policy = await client.sessionAccess.set(id, {
|
|
741
|
+
anonymous_user: (opts.anonymous ?? null),
|
|
742
|
+
});
|
|
743
|
+
if (opts.json)
|
|
744
|
+
return outJson(policy);
|
|
745
|
+
ok("Session access updated");
|
|
746
|
+
table([policy], [
|
|
747
|
+
{ key: "signed_in_user", label: "Signed-in" },
|
|
748
|
+
{ key: "anonymous_user", label: "Anonymous" },
|
|
749
|
+
]);
|
|
750
|
+
}
|
|
751
|
+
catch (e) {
|
|
752
|
+
handleHttp(e);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
accessCmd
|
|
756
|
+
.command("remove <id>")
|
|
757
|
+
.description("Remove session access override")
|
|
758
|
+
.action(async (id) => {
|
|
759
|
+
const client = createClient();
|
|
760
|
+
try {
|
|
761
|
+
await client.sessionAccess.remove(id);
|
|
762
|
+
ok(`Session access override removed: ${id}`);
|
|
542
763
|
}
|
|
543
764
|
catch (e) {
|
|
544
765
|
handleHttp(e);
|
|
@@ -557,9 +778,8 @@ function registerMembers(spacesCmd) {
|
|
|
557
778
|
.description("List space members")
|
|
558
779
|
.option("--json", "Output as JSON")
|
|
559
780
|
.action(async (opts) => {
|
|
560
|
-
const token = resolveToken() ?? missingAuth();
|
|
561
781
|
const spaceId = requireSpace(spacesCmd);
|
|
562
|
-
const client = createClient(
|
|
782
|
+
const client = createClient();
|
|
563
783
|
try {
|
|
564
784
|
const result = await client.space(spaceId).members.list();
|
|
565
785
|
if (opts.json)
|
|
@@ -582,9 +802,8 @@ function registerMembers(spacesCmd) {
|
|
|
582
802
|
.command("update <userId> <role>")
|
|
583
803
|
.description("Change member role (host | builder | guest)")
|
|
584
804
|
.action(async (userId, role) => {
|
|
585
|
-
const token = resolveToken() ?? missingAuth();
|
|
586
805
|
const spaceId = requireSpace(spacesCmd);
|
|
587
|
-
const client = createClient(
|
|
806
|
+
const client = createClient();
|
|
588
807
|
try {
|
|
589
808
|
await client.space(spaceId).members.update(userId, role);
|
|
590
809
|
ok(`${userId} → ${role}`);
|
|
@@ -597,9 +816,8 @@ function registerMembers(spacesCmd) {
|
|
|
597
816
|
.command("remove <userId>")
|
|
598
817
|
.description("Remove a member")
|
|
599
818
|
.action(async (userId) => {
|
|
600
|
-
const token = resolveToken() ?? missingAuth();
|
|
601
819
|
const spaceId = requireSpace(spacesCmd);
|
|
602
|
-
const client = createClient(
|
|
820
|
+
const client = createClient();
|
|
603
821
|
try {
|
|
604
822
|
await client.space(spaceId).members.remove(userId);
|
|
605
823
|
ok(`${userId} removed`);
|
|
@@ -620,9 +838,8 @@ function registerAccess(spacesCmd) {
|
|
|
620
838
|
.description("Get access policy")
|
|
621
839
|
.option("--json", "Output as JSON")
|
|
622
840
|
.action(async (opts) => {
|
|
623
|
-
const token = resolveToken() ?? missingAuth();
|
|
624
841
|
const spaceId = requireSpace(spacesCmd);
|
|
625
|
-
const client = createClient(
|
|
842
|
+
const client = createClient();
|
|
626
843
|
try {
|
|
627
844
|
const policy = await client.space(spaceId).access.get();
|
|
628
845
|
if (opts.json)
|
|
@@ -643,9 +860,8 @@ function registerAccess(spacesCmd) {
|
|
|
643
860
|
.option("--anonymous <role>", "Role for anonymous users (host|builder|guest|null)")
|
|
644
861
|
.option("--json", "Output as JSON")
|
|
645
862
|
.action(async (opts) => {
|
|
646
|
-
const token = resolveToken() ?? missingAuth();
|
|
647
863
|
const spaceId = requireSpace(spacesCmd);
|
|
648
|
-
const client = createClient(
|
|
864
|
+
const client = createClient();
|
|
649
865
|
try {
|
|
650
866
|
const policy = await client.space(spaceId).access.set({
|
|
651
867
|
signed_in_user: (opts.signedIn ?? null),
|
|
@@ -676,9 +892,8 @@ function registerCheckpoints(spacesCmd) {
|
|
|
676
892
|
.description("List checkpoints")
|
|
677
893
|
.option("--json", "Output as JSON")
|
|
678
894
|
.action(async (opts) => {
|
|
679
|
-
const token = resolveToken() ?? missingAuth();
|
|
680
895
|
const spaceId = requireSpace(spacesCmd);
|
|
681
|
-
const client = createClient(
|
|
896
|
+
const client = createClient();
|
|
682
897
|
try {
|
|
683
898
|
const result = await client.space(spaceId).checkpoints.list();
|
|
684
899
|
if (opts.json)
|
|
@@ -703,9 +918,8 @@ function registerCheckpoints(spacesCmd) {
|
|
|
703
918
|
.description("Checkpoint details")
|
|
704
919
|
.option("--json", "Output as JSON")
|
|
705
920
|
.action(async (id, opts) => {
|
|
706
|
-
const token = resolveToken() ?? missingAuth();
|
|
707
921
|
const spaceId = requireSpace(spacesCmd);
|
|
708
|
-
const client = createClient(
|
|
922
|
+
const client = createClient();
|
|
709
923
|
try {
|
|
710
924
|
const result = await client.space(spaceId).checkpoints.get(id);
|
|
711
925
|
if (opts.json)
|
|
@@ -727,9 +941,8 @@ function registerCheckpoints(spacesCmd) {
|
|
|
727
941
|
.description("Create a checkpoint")
|
|
728
942
|
.option("--json", "Output as JSON")
|
|
729
943
|
.action(async (description, opts) => {
|
|
730
|
-
const token = resolveToken() ?? missingAuth();
|
|
731
944
|
const spaceId = requireSpace(spacesCmd);
|
|
732
|
-
const client = createClient(
|
|
945
|
+
const client = createClient();
|
|
733
946
|
try {
|
|
734
947
|
const result = await client.space(spaceId).checkpoints.create(description ?? null);
|
|
735
948
|
if (opts.json)
|
|
@@ -741,6 +954,3 @@ function registerCheckpoints(spacesCmd) {
|
|
|
741
954
|
}
|
|
742
955
|
});
|
|
743
956
|
}
|
|
744
|
-
function missingAuth() {
|
|
745
|
-
return error("Not authenticated", "Run 'cohub auth login <token>'");
|
|
746
|
-
}
|