@neta-art/cohub-cli 1.0.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.
@@ -0,0 +1,675 @@
1
+ import { resolveToken } from "../auth.js";
2
+ import { createClient } from "../client.js";
3
+ import { table, json as outJson, ok, error, handleHttp } from "../output.js";
4
+ function requireSpace(program) {
5
+ let current = program;
6
+ while (current) {
7
+ const opts = current.opts();
8
+ if (opts.space)
9
+ return String(opts.space);
10
+ current = current.parent ?? null;
11
+ }
12
+ return error("Missing required option", "Add -s, --space <id> to target a space");
13
+ }
14
+ export function registerSpaces(program) {
15
+ const spacesCmd = program.command("spaces").description("Space management");
16
+ // ── spaces ls ──
17
+ spacesCmd
18
+ .command("ls")
19
+ .alias("list")
20
+ .description("List all spaces")
21
+ .option("--json", "Output as JSON")
22
+ .action(async (opts) => {
23
+ const token = resolveToken() ?? missingAuth();
24
+ const client = createClient(token);
25
+ try {
26
+ const items = await client.spaces.list();
27
+ if (opts.json)
28
+ return outJson(items);
29
+ table(items, [
30
+ { key: "id", label: "ID" },
31
+ { key: "name", label: "Name" },
32
+ { key: "createdAt", label: "Created" },
33
+ ]);
34
+ }
35
+ catch (e) {
36
+ handleHttp(e);
37
+ }
38
+ });
39
+ // ── spaces get ──
40
+ spacesCmd
41
+ .command("get <id>")
42
+ .description("Show space details")
43
+ .option("--json", "Output as JSON")
44
+ .action(async (id, opts) => {
45
+ const token = resolveToken() ?? missingAuth();
46
+ const client = createClient(token);
47
+ try {
48
+ const space = await client.spaces.get(id);
49
+ if (opts.json)
50
+ return outJson(space);
51
+ table([space], [
52
+ { key: "id", label: "ID" },
53
+ { key: "name", label: "Name" },
54
+ { key: "description", label: "Description" },
55
+ { key: "status", label: "Status" },
56
+ { key: "createdAt", label: "Created" },
57
+ ]);
58
+ }
59
+ catch (e) {
60
+ handleHttp(e);
61
+ }
62
+ });
63
+ // ── spaces create ──
64
+ spacesCmd
65
+ .command("create")
66
+ .description("Create a new space")
67
+ .option("-n, --name <name>", "Space name")
68
+ .option("-d, --description <desc>", "Space description")
69
+ .option("--json", "Output as JSON")
70
+ .action(async (opts) => {
71
+ const token = resolveToken() ?? missingAuth();
72
+ const client = createClient(token);
73
+ try {
74
+ const result = await client.spaces.create({
75
+ name: opts.name,
76
+ description: opts.description,
77
+ });
78
+ if (opts.json)
79
+ return outJson(result);
80
+ ok(`Space created: ${result.space.id}`);
81
+ table([result.space], [
82
+ { key: "id", label: "ID" },
83
+ { key: "name", label: "Name" },
84
+ { key: "taskRunId", label: "Task" },
85
+ ]);
86
+ }
87
+ catch (e) {
88
+ handleHttp(e);
89
+ }
90
+ });
91
+ // ── spaces rename ──
92
+ spacesCmd
93
+ .command("rename <id> <name>")
94
+ .description("Rename a space")
95
+ .action(async (id, name) => {
96
+ const token = resolveToken() ?? missingAuth();
97
+ const client = createClient(token);
98
+ try {
99
+ await client.space(id).rename(name);
100
+ ok(`Space renamed to "${name}"`);
101
+ }
102
+ catch (e) {
103
+ handleHttp(e);
104
+ }
105
+ });
106
+ // ── spaces files ──
107
+ registerFiles(spacesCmd);
108
+ // ── spaces sessions ──
109
+ registerSessions(spacesCmd);
110
+ // ── spaces members ──
111
+ registerMembers(spacesCmd);
112
+ // ── spaces access ──
113
+ registerAccess(spacesCmd);
114
+ // ── spaces checkpoints ──
115
+ registerCheckpoints(spacesCmd);
116
+ // ── spaces usage ──
117
+ spacesCmd
118
+ .command("usage [days]")
119
+ .description("Space usage statistics (default: 30 days)")
120
+ .option("--json", "Output as JSON")
121
+ .action(async (days, opts) => {
122
+ const token = resolveToken() ?? missingAuth();
123
+ const spaceId = requireSpace(spacesCmd);
124
+ const client = createClient(token);
125
+ try {
126
+ const usage = await client.space(spaceId).usage.get(Number.parseInt(days ?? "30", 10));
127
+ if (opts.json)
128
+ return outJson(usage);
129
+ console.log("\n Summary:");
130
+ table([usage.summary], [
131
+ { key: "totalTokens", label: "Tokens" },
132
+ { key: "costTotal", label: "Cost ($)" },
133
+ { key: "requestCount", label: "Requests" },
134
+ { key: "successCount", label: "Success" },
135
+ { key: "errorCount", label: "Errors" },
136
+ ]);
137
+ }
138
+ catch (e) {
139
+ handleHttp(e);
140
+ }
141
+ });
142
+ }
143
+ // ── File operations ──
144
+ function registerFiles(spacesCmd) {
145
+ const filesCmd = spacesCmd
146
+ .command("files")
147
+ .description("File operations")
148
+ .hook("preAction", () => { requireSpace(spacesCmd); });
149
+ filesCmd
150
+ .command("ls [path]")
151
+ .alias("list")
152
+ .description("List directory tree")
153
+ .option("--json", "Output as JSON")
154
+ .action(async (path, opts) => {
155
+ const token = resolveToken() ?? missingAuth();
156
+ const spaceId = requireSpace(spacesCmd);
157
+ const client = createClient(token);
158
+ try {
159
+ const tree = await client.space(spaceId).files.list(path ?? "");
160
+ if (opts.json)
161
+ return outJson(tree);
162
+ if (tree.entries.length === 0) {
163
+ console.log(" (empty)");
164
+ return;
165
+ }
166
+ table(tree.entries, [
167
+ { key: "name", label: "Name" },
168
+ { key: "type", label: "Type" },
169
+ { key: "size", label: "Size" },
170
+ { key: "mtimeMs", label: "Modified" },
171
+ ]);
172
+ }
173
+ catch (e) {
174
+ handleHttp(e);
175
+ }
176
+ });
177
+ filesCmd
178
+ .command("cat <path>")
179
+ .description("Read file content")
180
+ .action(async (path) => {
181
+ const token = resolveToken() ?? missingAuth();
182
+ const spaceId = requireSpace(spacesCmd);
183
+ const client = createClient(token);
184
+ try {
185
+ const file = await client.space(spaceId).files.read(path);
186
+ console.log(file.content);
187
+ }
188
+ catch (e) {
189
+ handleHttp(e);
190
+ }
191
+ });
192
+ filesCmd
193
+ .command("write <path>")
194
+ .description("Write file content")
195
+ .option("-c, --content <text>", "File content")
196
+ .option("-e, --encoding <enc>", "Encoding (utf-8 or base64)", "utf-8")
197
+ .action(async (path, opts) => {
198
+ const token = resolveToken() ?? missingAuth();
199
+ let content = opts.content ?? "";
200
+ if (!content && !process.stdin.isTTY) {
201
+ const chunks = [];
202
+ for await (const chunk of process.stdin)
203
+ chunks.push(chunk);
204
+ content = Buffer.concat(chunks).toString();
205
+ }
206
+ if (!content)
207
+ return error("No content provided", "Use -c or pipe via stdin");
208
+ const spaceId = requireSpace(spacesCmd);
209
+ const client = createClient(token);
210
+ try {
211
+ const result = await client.space(spaceId).files.write({
212
+ path,
213
+ content,
214
+ encoding: opts.encoding,
215
+ });
216
+ ok(`Written ${result.size} bytes to ${result.path}`);
217
+ }
218
+ catch (e) {
219
+ handleHttp(e);
220
+ }
221
+ });
222
+ filesCmd
223
+ .command("mkdir <path>")
224
+ .description("Create a directory")
225
+ .action(async (path) => {
226
+ const token = resolveToken() ?? missingAuth();
227
+ const spaceId = requireSpace(spacesCmd);
228
+ const client = createClient(token);
229
+ try {
230
+ await client.space(spaceId).files.createDir(path);
231
+ ok(`Directory created: ${path}`);
232
+ }
233
+ catch (e) {
234
+ handleHttp(e);
235
+ }
236
+ });
237
+ filesCmd
238
+ .command("rm <path>")
239
+ .description("Delete a file or directory")
240
+ .option("-r, --recursive", "Delete recursively")
241
+ .action(async (path, opts) => {
242
+ const token = resolveToken() ?? missingAuth();
243
+ const spaceId = requireSpace(spacesCmd);
244
+ const client = createClient(token);
245
+ try {
246
+ await client.space(spaceId).files.delete(path, opts.recursive ?? false);
247
+ ok(`Deleted: ${path}`);
248
+ }
249
+ catch (e) {
250
+ handleHttp(e);
251
+ }
252
+ });
253
+ filesCmd
254
+ .command("mv <from> <to>")
255
+ .description("Move or rename")
256
+ .action(async (from, to) => {
257
+ const token = resolveToken() ?? missingAuth();
258
+ const spaceId = requireSpace(spacesCmd);
259
+ const client = createClient(token);
260
+ try {
261
+ await client.space(spaceId).files.move({ fromPath: from, toPath: to });
262
+ ok(`Moved: ${from} → ${to}`);
263
+ }
264
+ catch (e) {
265
+ handleHttp(e);
266
+ }
267
+ });
268
+ filesCmd
269
+ .command("upload <files...>")
270
+ .description("Upload files to a directory")
271
+ .option("--dir <dir>", "Target directory", "")
272
+ .action(async (_files) => {
273
+ error("Upload requires browser File API", "Use the web interface for now");
274
+ });
275
+ }
276
+ // ── Session operations ──
277
+ function registerSessions(spacesCmd) {
278
+ const sessionsCmd = spacesCmd
279
+ .command("sessions")
280
+ .description("Session operations")
281
+ .hook("preAction", () => { requireSpace(spacesCmd); });
282
+ sessionsCmd
283
+ .command("ls")
284
+ .alias("list")
285
+ .description("List sessions")
286
+ .option("--json", "Output as JSON")
287
+ .action(async (opts) => {
288
+ const token = resolveToken() ?? missingAuth();
289
+ const spaceId = requireSpace(spacesCmd);
290
+ const client = createClient(token);
291
+ try {
292
+ const result = await client.space(spaceId).sessions.list();
293
+ if (opts.json)
294
+ return outJson(result);
295
+ if (result.sessions.length === 0) {
296
+ console.log(" (empty)");
297
+ return;
298
+ }
299
+ table(result.sessions, [
300
+ { key: "id", label: "ID" },
301
+ { key: "title", label: "Title" },
302
+ { key: "totalMessages", label: "Messages" },
303
+ { key: "createdAt", label: "Created" },
304
+ ]);
305
+ }
306
+ catch (e) {
307
+ handleHttp(e);
308
+ }
309
+ });
310
+ sessionsCmd
311
+ .command("create [title]")
312
+ .description("Create a session")
313
+ .option("--json", "Output as JSON")
314
+ .action(async (title, opts) => {
315
+ const token = resolveToken() ?? missingAuth();
316
+ const spaceId = requireSpace(spacesCmd);
317
+ const client = createClient(token);
318
+ try {
319
+ const result = await client.space(spaceId).sessions.create({ title });
320
+ if (opts.json)
321
+ return outJson(result);
322
+ ok(`Session created: ${result.session.id}`);
323
+ table([result.session], [
324
+ { key: "id", label: "ID" },
325
+ { key: "title", label: "Title" },
326
+ ]);
327
+ }
328
+ catch (e) {
329
+ handleHttp(e);
330
+ }
331
+ });
332
+ sessionsCmd
333
+ .command("get <id>")
334
+ .description("Session details")
335
+ .option("--json", "Output as JSON")
336
+ .action(async (id, opts) => {
337
+ const token = resolveToken() ?? missingAuth();
338
+ const spaceId = requireSpace(spacesCmd);
339
+ const client = createClient(token);
340
+ try {
341
+ const result = await client.space(spaceId).session(id).get();
342
+ if (opts.json)
343
+ return outJson(result);
344
+ table([result.session], [
345
+ { key: "id", label: "ID" },
346
+ { key: "title", label: "Title" },
347
+ { key: "totalMessages", label: "Messages" },
348
+ { key: "totalToolCalls", label: "Tool Calls" },
349
+ { key: "createdAt", label: "Created" },
350
+ ]);
351
+ }
352
+ catch (e) {
353
+ handleHttp(e);
354
+ }
355
+ });
356
+ sessionsCmd
357
+ .command("rename <id> <name>")
358
+ .description("Rename a session")
359
+ .action(async (id, name) => {
360
+ const token = resolveToken() ?? missingAuth();
361
+ const spaceId = requireSpace(spacesCmd);
362
+ const client = createClient(token);
363
+ try {
364
+ await client.space(spaceId).session(id).rename(name);
365
+ ok(`Session renamed to "${name}"`);
366
+ }
367
+ catch (e) {
368
+ handleHttp(e);
369
+ }
370
+ });
371
+ // ── sessions messages ──
372
+ registerMessages(sessionsCmd);
373
+ // ── sessions tail ──
374
+ sessionsCmd
375
+ .command("tail <id>")
376
+ .description("Stream realtime session events")
377
+ .option("--json", "Output as JSON")
378
+ .action(async (id, opts) => {
379
+ const token = resolveToken() ?? missingAuth();
380
+ const spaceId = requireSpace(spacesCmd);
381
+ const client = createClient(token);
382
+ const session = client.space(spaceId).session(id);
383
+ process.stdout.write(" Listening for events...\n\n");
384
+ session.on("turn.progress", (e) => {
385
+ if (opts.json) {
386
+ console.log(JSON.stringify(e));
387
+ }
388
+ else {
389
+ const blocks = e.payload?.content;
390
+ const text = blocks?.find((b) => b.type === "text")?.text;
391
+ if (text)
392
+ process.stdout.write(text);
393
+ }
394
+ });
395
+ session.on("turn.final", () => {
396
+ process.stdout.write("\n\n ✓ Done\n");
397
+ process.exit(0);
398
+ });
399
+ session.on("turn.error", (e) => {
400
+ process.stderr.write(`\n ✗ Error\n`);
401
+ if (opts.json)
402
+ process.stderr.write(JSON.stringify(e) + "\n");
403
+ process.exit(1);
404
+ });
405
+ });
406
+ }
407
+ // ── Message operations ──
408
+ function registerMessages(sessionsCmd) {
409
+ const msgsCmd = sessionsCmd.command("messages").description("Message operations");
410
+ msgsCmd
411
+ .command("ls <sessionId>")
412
+ .alias("list")
413
+ .description("List session messages")
414
+ .option("--json", "Output as JSON")
415
+ .option("--limit <n>", "Page size", "50")
416
+ .action(async (sessionId, opts) => {
417
+ const token = resolveToken() ?? missingAuth();
418
+ const spaceId = requireSpace(sessionsCmd);
419
+ const client = createClient(token);
420
+ try {
421
+ const result = await client.space(spaceId).session(sessionId).messages.listPaginated({
422
+ limit: Number.parseInt(opts.limit ?? "50", 10),
423
+ });
424
+ if (opts.json)
425
+ return outJson(result);
426
+ if (result.messages.length === 0) {
427
+ console.log(" (empty)");
428
+ return;
429
+ }
430
+ table(result.messages, [
431
+ { key: "id", label: "ID" },
432
+ { key: "role", label: "Role" },
433
+ { key: "createdAt", label: "Created" },
434
+ ]);
435
+ if (result.hasMore) {
436
+ console.log(`\n (more — use --cursor ${result.nextCursor} for next page)`);
437
+ }
438
+ }
439
+ catch (e) {
440
+ handleHttp(e);
441
+ }
442
+ });
443
+ msgsCmd
444
+ .command("send <sessionId> [content...]")
445
+ .description("Send a message to a session")
446
+ .option("-m, --model <model>", "Model name")
447
+ .option("-p, --provider <provider>", "Provider name")
448
+ .option("--json", "Output as JSON")
449
+ .action(async (sessionId, words, opts) => {
450
+ const token = resolveToken() ?? missingAuth();
451
+ let content = words.join(" ");
452
+ if (!content && !process.stdin.isTTY) {
453
+ const chunks = [];
454
+ for await (const chunk of process.stdin)
455
+ chunks.push(chunk);
456
+ content = Buffer.concat(chunks).toString().trim();
457
+ }
458
+ if (!content)
459
+ return error("No content", "Pass as argument or pipe via stdin");
460
+ const spaceId = requireSpace(sessionsCmd);
461
+ const client = createClient(token);
462
+ try {
463
+ const result = await client.space(spaceId).session(sessionId).messages.send({
464
+ content: [{ type: "text", text: content }],
465
+ model: opts.model,
466
+ provider: opts.provider,
467
+ });
468
+ if (opts.json)
469
+ return outJson(result);
470
+ ok(`Message sent — userMessageId: ${result.userMessageId}`);
471
+ }
472
+ catch (e) {
473
+ handleHttp(e);
474
+ }
475
+ });
476
+ }
477
+ // ── Member operations ──
478
+ function registerMembers(spacesCmd) {
479
+ const memCmd = spacesCmd
480
+ .command("members")
481
+ .description("Member management")
482
+ .hook("preAction", () => { requireSpace(spacesCmd); });
483
+ memCmd
484
+ .command("ls")
485
+ .alias("list")
486
+ .description("List space members")
487
+ .option("--json", "Output as JSON")
488
+ .action(async (opts) => {
489
+ const token = resolveToken() ?? missingAuth();
490
+ const spaceId = requireSpace(spacesCmd);
491
+ const client = createClient(token);
492
+ try {
493
+ const result = await client.space(spaceId).members.list();
494
+ if (opts.json)
495
+ return outJson(result);
496
+ if (result.items.length === 0) {
497
+ console.log(" (empty)");
498
+ return;
499
+ }
500
+ table(result.items, [
501
+ { key: "userId", label: "User ID" },
502
+ { key: "role", label: "Role" },
503
+ { key: "createdAt", label: "Since" },
504
+ ]);
505
+ }
506
+ catch (e) {
507
+ handleHttp(e);
508
+ }
509
+ });
510
+ memCmd
511
+ .command("update <userId> <role>")
512
+ .description("Change member role (host | builder | guest)")
513
+ .action(async (userId, role) => {
514
+ const token = resolveToken() ?? missingAuth();
515
+ const spaceId = requireSpace(spacesCmd);
516
+ const client = createClient(token);
517
+ try {
518
+ await client.space(spaceId).members.update(userId, role);
519
+ ok(`${userId} → ${role}`);
520
+ }
521
+ catch (e) {
522
+ handleHttp(e);
523
+ }
524
+ });
525
+ memCmd
526
+ .command("remove <userId>")
527
+ .description("Remove a member")
528
+ .action(async (userId) => {
529
+ const token = resolveToken() ?? missingAuth();
530
+ const spaceId = requireSpace(spacesCmd);
531
+ const client = createClient(token);
532
+ try {
533
+ await client.space(spaceId).members.remove(userId);
534
+ ok(`${userId} removed`);
535
+ }
536
+ catch (e) {
537
+ handleHttp(e);
538
+ }
539
+ });
540
+ }
541
+ // ── Access control ──
542
+ function registerAccess(spacesCmd) {
543
+ const accCmd = spacesCmd
544
+ .command("access")
545
+ .description("Access control")
546
+ .hook("preAction", () => { requireSpace(spacesCmd); });
547
+ accCmd
548
+ .command("get")
549
+ .description("Get access policy")
550
+ .option("--json", "Output as JSON")
551
+ .action(async (opts) => {
552
+ const token = resolveToken() ?? missingAuth();
553
+ const spaceId = requireSpace(spacesCmd);
554
+ const client = createClient(token);
555
+ try {
556
+ const policy = await client.space(spaceId).access.get();
557
+ if (opts.json)
558
+ return outJson(policy);
559
+ table([policy], [
560
+ { key: "signed_in_user", label: "Signed-in" },
561
+ { key: "anonymous_user", label: "Anonymous" },
562
+ ]);
563
+ }
564
+ catch (e) {
565
+ handleHttp(e);
566
+ }
567
+ });
568
+ accCmd
569
+ .command("set")
570
+ .description("Set access policy")
571
+ .option("--signed-in <role>", "Role for signed-in users (host|builder|guest|null)")
572
+ .option("--anonymous <role>", "Role for anonymous users (host|builder|guest|null)")
573
+ .option("--json", "Output as JSON")
574
+ .action(async (opts) => {
575
+ const token = resolveToken() ?? missingAuth();
576
+ const spaceId = requireSpace(spacesCmd);
577
+ const client = createClient(token);
578
+ try {
579
+ const policy = await client.space(spaceId).access.set({
580
+ signed_in_user: (opts.signedIn ?? null),
581
+ anonymous_user: (opts.anonymous ?? null),
582
+ });
583
+ if (opts.json)
584
+ return outJson(policy);
585
+ ok("Access policy updated");
586
+ table([policy], [
587
+ { key: "signed_in_user", label: "Signed-in" },
588
+ { key: "anonymous_user", label: "Anonymous" },
589
+ ]);
590
+ }
591
+ catch (e) {
592
+ handleHttp(e);
593
+ }
594
+ });
595
+ }
596
+ // ── Checkpoint operations ──
597
+ function registerCheckpoints(spacesCmd) {
598
+ const cpCmd = spacesCmd
599
+ .command("checkpoints")
600
+ .description("Checkpoint management")
601
+ .hook("preAction", () => { requireSpace(spacesCmd); });
602
+ cpCmd
603
+ .command("ls")
604
+ .alias("list")
605
+ .description("List checkpoints")
606
+ .option("--json", "Output as JSON")
607
+ .action(async (opts) => {
608
+ const token = resolveToken() ?? missingAuth();
609
+ const spaceId = requireSpace(spacesCmd);
610
+ const client = createClient(token);
611
+ try {
612
+ const result = await client.space(spaceId).checkpoints.list();
613
+ if (opts.json)
614
+ return outJson(result);
615
+ if (result.checkpoints.length === 0) {
616
+ console.log(" (empty)");
617
+ return;
618
+ }
619
+ table(result.checkpoints, [
620
+ { key: "id", label: "ID" },
621
+ { key: "commitHash", label: "Commit" },
622
+ { key: "description", label: "Description" },
623
+ { key: "createdAt", label: "Created" },
624
+ ]);
625
+ }
626
+ catch (e) {
627
+ handleHttp(e);
628
+ }
629
+ });
630
+ cpCmd
631
+ .command("get <id>")
632
+ .description("Checkpoint details")
633
+ .option("--json", "Output as JSON")
634
+ .action(async (id, opts) => {
635
+ const token = resolveToken() ?? missingAuth();
636
+ const spaceId = requireSpace(spacesCmd);
637
+ const client = createClient(token);
638
+ try {
639
+ const result = await client.space(spaceId).checkpoints.get(id);
640
+ if (opts.json)
641
+ return outJson(result);
642
+ table([result.checkpoint], [
643
+ { key: "id", label: "ID" },
644
+ { key: "commitHash", label: "Commit" },
645
+ { key: "description", label: "Description" },
646
+ { key: "forkCount", label: "Forks" },
647
+ { key: "createdAt", label: "Created" },
648
+ ]);
649
+ }
650
+ catch (e) {
651
+ handleHttp(e);
652
+ }
653
+ });
654
+ cpCmd
655
+ .command("create [description]")
656
+ .description("Create a checkpoint")
657
+ .option("--json", "Output as JSON")
658
+ .action(async (description, opts) => {
659
+ const token = resolveToken() ?? missingAuth();
660
+ const spaceId = requireSpace(spacesCmd);
661
+ const client = createClient(token);
662
+ try {
663
+ const result = await client.space(spaceId).checkpoints.create(description ?? null);
664
+ if (opts.json)
665
+ return outJson(result);
666
+ ok(`Checkpoint created — taskRunId: ${result.taskRunId}`);
667
+ }
668
+ catch (e) {
669
+ handleHttp(e);
670
+ }
671
+ });
672
+ }
673
+ function missingAuth() {
674
+ return error("Not authenticated", "Run 'cohub auth login <token>'");
675
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerTasks(program: Command): void;