@neta-art/cohub-cli 3.1.6 → 3.3.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 CHANGED
@@ -65,6 +65,9 @@ COHUB_SPACE_ID=<spaceId> cohub spaces get
65
65
  cohub spaces create --name "<name>" --description "<description>" --json
66
66
  cohub spaces update <spaceId> --slug <space-slug>
67
67
  cohub spaces rename <spaceId> "<new name>"
68
+ cohub -s <spaceId> spaces invites create --role builder --days 7
69
+ cohub -s <spaceId> spaces invites ls
70
+ cohub -s <spaceId> spaces invites revoke <code> --yes
68
71
  cohub -s <spaceId> run -- git status
69
72
  ```
70
73
 
@@ -121,6 +124,25 @@ cohub -s <spaceId> spaces sessions rename <sessionId> "<new title>"
121
124
 
122
125
  Use `spaces prompt --session <sessionId>` to send to a Chat.
123
126
 
127
+ ## Space turns
128
+
129
+ List recent turns across all visible Sessions in a Space:
130
+
131
+ ```bash
132
+ cohub -s <spaceId> spaces turns ls
133
+ cohub -s <spaceId> spaces turns ls --author others --limit 50 --json
134
+ cohub -s <spaceId> spaces turns ls --session <sessionId>
135
+ ```
136
+
137
+ Use `pageInfo.nextCursor` with `--cursor` to load older pages. Use a previous
138
+ `snapshotCursor` with `--after` and an explicit `--before` boundary to query
139
+ newer turns:
140
+
141
+ ```bash
142
+ cohub -s <spaceId> spaces turns ls --cursor <nextCursor> --json
143
+ cohub -s <spaceId> spaces turns ls --after <snapshotCursor> --before <snapshotAt> --json
144
+ ```
145
+
124
146
  ## Boards
125
147
 
126
148
  Board commands use the selected Space and support `-h` at every level:
@@ -0,0 +1,28 @@
1
+ import { type CreateInvitationInput, type CreateInvitationResponse, type SpaceInvitationListResponse } from "@neta-art/cohub";
2
+ import type { Command } from "commander";
3
+ export type SpaceInvitationCreateCliOptions = {
4
+ role?: string;
5
+ days?: string;
6
+ maxUses?: string;
7
+ json?: boolean;
8
+ };
9
+ type SpaceInvitationCommandClient = {
10
+ space(spaceId: string): {
11
+ invitations: {
12
+ list(): Promise<SpaceInvitationListResponse>;
13
+ create(input: CreateInvitationInput): Promise<CreateInvitationResponse>;
14
+ revoke(token: string): Promise<{
15
+ ok: true;
16
+ }>;
17
+ };
18
+ };
19
+ };
20
+ export declare class InvalidSpaceInvitationCliOptionsError extends Error {
21
+ readonly detail: string;
22
+ constructor(message: string, detail: string);
23
+ }
24
+ export declare function parseSpaceInvitationCreateOptions(options: SpaceInvitationCreateCliOptions): Required<Pick<CreateInvitationInput, "role" | "ttlSeconds" | "maxUses">>;
25
+ export declare function registerSpaceInvitations(spacesCommand: Command, dependencies?: {
26
+ createClient: () => SpaceInvitationCommandClient;
27
+ }): Command;
28
+ export {};
@@ -0,0 +1,168 @@
1
+ import { buildSpaceInvitePath, } from "@neta-art/cohub";
2
+ import { createClient } from "../client.js";
3
+ import { error, handleHttp, json as outJson, jsonRequested, ok, table, } from "../output.js";
4
+ import { resolveSpace } from "../space.js";
5
+ const SPACE_ROLES = ["host", "builder", "guest"];
6
+ const DEFAULT_DAYS = 7;
7
+ const MAX_DAYS = 30;
8
+ const MAX_USES = 10_000;
9
+ export class InvalidSpaceInvitationCliOptionsError extends Error {
10
+ detail;
11
+ constructor(message, detail) {
12
+ super(message);
13
+ this.detail = detail;
14
+ this.name = "InvalidSpaceInvitationCliOptionsError";
15
+ }
16
+ }
17
+ function parseInteger(value, label, min, max) {
18
+ if (!/^\d+$/.test(value.trim())) {
19
+ throw new InvalidSpaceInvitationCliOptionsError(`Invalid ${label}`, `${label} must be an integer from ${min} to ${max}`);
20
+ }
21
+ const parsed = Number.parseInt(value, 10);
22
+ if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) {
23
+ throw new InvalidSpaceInvitationCliOptionsError(`Invalid ${label}`, `${label} must be an integer from ${min} to ${max}`);
24
+ }
25
+ return parsed;
26
+ }
27
+ export function parseSpaceInvitationCreateOptions(options) {
28
+ const role = (options.role ?? "builder");
29
+ if (!SPACE_ROLES.includes(role)) {
30
+ throw new InvalidSpaceInvitationCliOptionsError("Invalid role", `Use one of: ${SPACE_ROLES.join(", ")}`);
31
+ }
32
+ const days = parseInteger(options.days ?? String(DEFAULT_DAYS), "days", 1, MAX_DAYS);
33
+ const maxUses = parseInteger(options.maxUses ?? "0", "max uses", 0, MAX_USES);
34
+ return { role, ttlSeconds: days * 24 * 60 * 60, maxUses };
35
+ }
36
+ function invitationUrl(invitation) {
37
+ const origin = process.env.COHUB_WEB_URL?.replace(/\/+$/, "") ?? "https://cohub.run";
38
+ return `${origin}${buildSpaceInvitePath({
39
+ spaceId: invitation.spaceId,
40
+ ownerUsername: invitation.ownerUsername,
41
+ spaceSlug: invitation.spaceSlug,
42
+ inviteCode: invitation.token,
43
+ })}`;
44
+ }
45
+ async function confirmRevoke(options) {
46
+ if (options.yes)
47
+ return;
48
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
49
+ return error("Confirmation required", "Pass --yes to revoke the invite link.");
50
+ }
51
+ process.stdout.write("This invite link will stop working. Continue? [y/N] ");
52
+ const chunks = [];
53
+ for await (const chunk of process.stdin) {
54
+ chunks.push(chunk);
55
+ break;
56
+ }
57
+ const answer = Buffer.concat(chunks).toString().trim().toLowerCase();
58
+ if (answer !== "y" && answer !== "yes")
59
+ return error("Cancelled");
60
+ }
61
+ export function registerSpaceInvitations(spacesCommand, dependencies = {
62
+ createClient,
63
+ }) {
64
+ const invitations = spacesCommand
65
+ .command("invites")
66
+ .description("Create and manage space invite links");
67
+ invitations
68
+ .command("create")
69
+ .description("Create an invite link")
70
+ .option("--role <role>", "Member role: host, builder, or guest", "builder")
71
+ .option("--days <days>", "Validity in days, from 1 to 30", String(DEFAULT_DAYS))
72
+ .option("--max-uses <count>", "Usage limit, or 0 for unlimited", "0")
73
+ .option("--json", "Output as JSON")
74
+ .action(async (options) => {
75
+ const spaceId = resolveSpace(spacesCommand);
76
+ let input;
77
+ try {
78
+ input = parseSpaceInvitationCreateOptions(options);
79
+ }
80
+ catch (cause) {
81
+ if (cause instanceof InvalidSpaceInvitationCliOptionsError) {
82
+ return error(cause.message, cause.detail);
83
+ }
84
+ throw cause;
85
+ }
86
+ try {
87
+ const created = await dependencies
88
+ .createClient()
89
+ .space(spaceId)
90
+ .invitations.create(input);
91
+ const output = {
92
+ ...created,
93
+ url: invitationUrl({
94
+ ...created,
95
+ spaceId: created.spaceId || spaceId,
96
+ ownerUsername: created.ownerUsername ?? null,
97
+ spaceSlug: created.spaceSlug ?? null,
98
+ }),
99
+ };
100
+ if (jsonRequested(options))
101
+ return outJson(output);
102
+ ok(`Invite link created: ${output.url}`);
103
+ }
104
+ catch (cause) {
105
+ handleHttp(cause);
106
+ }
107
+ });
108
+ invitations
109
+ .command("ls")
110
+ .alias("list")
111
+ .description("List invite links")
112
+ .option("--json", "Output as JSON")
113
+ .action(async (options) => {
114
+ const spaceId = resolveSpace(spacesCommand);
115
+ try {
116
+ const result = await dependencies
117
+ .createClient()
118
+ .space(spaceId)
119
+ .invitations.list();
120
+ const items = result.items.map((item) => ({
121
+ ...item,
122
+ url: invitationUrl({
123
+ ...result,
124
+ token: item.token,
125
+ spaceId: result.spaceId || spaceId,
126
+ ownerUsername: result.ownerUsername ?? null,
127
+ spaceSlug: result.spaceSlug ?? null,
128
+ }),
129
+ uses: item.maxUses ? `${item.useCount}/${item.maxUses}` : String(item.useCount),
130
+ }));
131
+ if (jsonRequested(options))
132
+ return outJson({ ...result, items });
133
+ table(items, [
134
+ { key: "token", label: "Code" },
135
+ { key: "role", label: "Role" },
136
+ { key: "status", label: "Status" },
137
+ { key: "uses", label: "Uses" },
138
+ { key: "expiresInSeconds", label: "Expires in" },
139
+ { key: "url", label: "URL" },
140
+ ]);
141
+ }
142
+ catch (cause) {
143
+ handleHttp(cause);
144
+ }
145
+ });
146
+ invitations
147
+ .command("revoke <code>")
148
+ .description("Revoke an invite link")
149
+ .option("-y, --yes", "Confirm revocation")
150
+ .option("--json", "Output as JSON")
151
+ .action(async (code, options) => {
152
+ await confirmRevoke(options);
153
+ const spaceId = resolveSpace(spacesCommand);
154
+ try {
155
+ const result = await dependencies
156
+ .createClient()
157
+ .space(spaceId)
158
+ .invitations.revoke(code);
159
+ if (jsonRequested(options))
160
+ return outJson(result);
161
+ ok("Invite link revoked");
162
+ }
163
+ catch (cause) {
164
+ handleHttp(cause);
165
+ }
166
+ });
167
+ return invitations;
168
+ }
@@ -0,0 +1,29 @@
1
+ import type { SpaceTurnListItem, SpaceTurnListOptions, SpaceTurnsResponse } from "@neta-art/cohub";
2
+ import type { Command } from "commander";
3
+ import { type Row } from "../output.js";
4
+ export type SpaceTurnListCliOptions = {
5
+ author?: string;
6
+ after?: string;
7
+ before?: string;
8
+ cursor?: string;
9
+ limit?: string;
10
+ session?: string;
11
+ json?: boolean;
12
+ };
13
+ type SpaceTurnsCommandClient = {
14
+ space(spaceId: string): {
15
+ turns: {
16
+ list(options: SpaceTurnListOptions): Promise<SpaceTurnsResponse>;
17
+ };
18
+ };
19
+ };
20
+ export declare class InvalidSpaceTurnCliOptionsError extends Error {
21
+ readonly detail: string;
22
+ constructor(message: string, detail: string);
23
+ }
24
+ export declare function parseSpaceTurnListOptions(options: SpaceTurnListCliOptions): SpaceTurnListOptions;
25
+ export declare function toSpaceTurnRows(turns: SpaceTurnListItem[]): Row[];
26
+ export declare function registerSpaceTurns(spacesCmd: Command, dependencies?: {
27
+ createClient?: () => SpaceTurnsCommandClient;
28
+ }): Command;
29
+ export {};
@@ -0,0 +1,121 @@
1
+ import { createClient } from "../client.js";
2
+ import { error, handleHttp, json as outJson, jsonRequested, table, } from "../output.js";
3
+ import { resolveSpace } from "../space.js";
4
+ const SPACE_TURN_AUTHORS = ["any", "self", "others"];
5
+ export class InvalidSpaceTurnCliOptionsError extends Error {
6
+ detail;
7
+ constructor(message, detail) {
8
+ super(message);
9
+ this.detail = detail;
10
+ this.name = "InvalidSpaceTurnCliOptionsError";
11
+ }
12
+ }
13
+ function parseAuthor(value) {
14
+ if (value === undefined)
15
+ return undefined;
16
+ if (SPACE_TURN_AUTHORS.includes(value)) {
17
+ return value;
18
+ }
19
+ throw new InvalidSpaceTurnCliOptionsError("Invalid author", `Use one of: ${SPACE_TURN_AUTHORS.join(", ")}`);
20
+ }
21
+ function parseLimit(value) {
22
+ if (value === undefined)
23
+ return undefined;
24
+ if (!/^\d+$/.test(value.trim())) {
25
+ throw new InvalidSpaceTurnCliOptionsError("Invalid limit", "limit must be an integer from 1 to 100");
26
+ }
27
+ const limit = Number.parseInt(value, 10);
28
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
29
+ throw new InvalidSpaceTurnCliOptionsError("Invalid limit", "limit must be an integer from 1 to 100");
30
+ }
31
+ return limit;
32
+ }
33
+ function parseBefore(value) {
34
+ if (value === undefined)
35
+ return undefined;
36
+ if (!value.trim() || Number.isNaN(new Date(value).getTime())) {
37
+ throw new InvalidSpaceTurnCliOptionsError("Invalid before", "before must be an ISO 8601 timestamp");
38
+ }
39
+ return value;
40
+ }
41
+ export function parseSpaceTurnListOptions(options) {
42
+ return {
43
+ author: parseAuthor(options.author),
44
+ after: options.after,
45
+ before: parseBefore(options.before),
46
+ cursor: options.cursor,
47
+ limit: parseLimit(options.limit),
48
+ sessionId: options.session,
49
+ };
50
+ }
51
+ export function toSpaceTurnRows(turns) {
52
+ return turns.map((turn) => ({
53
+ createdAt: turn.createdAt,
54
+ author: turn.authorProfile?.displayName ?? turn.userUuid ?? "system",
55
+ sessionTitle: turn.session.title ?? "",
56
+ sessionId: turn.session.id,
57
+ sequence: turn.sequence,
58
+ id: turn.id,
59
+ status: turn.status,
60
+ userPreview: turn.userPreview ?? "",
61
+ assistantPreview: turn.assistantPreview ?? "",
62
+ }));
63
+ }
64
+ export function registerSpaceTurns(spacesCmd, dependencies = {}) {
65
+ const turnsCmd = spacesCmd
66
+ .command("turns")
67
+ .description("Browse turns across the space")
68
+ .hook("preAction", () => {
69
+ resolveSpace(spacesCmd);
70
+ });
71
+ turnsCmd
72
+ .command("ls")
73
+ .alias("list")
74
+ .description("List recent turns across sessions")
75
+ .option("--author <any|self|others>", "Filter turns by author")
76
+ .option("--after <cursor>", "Only turns after a snapshot cursor")
77
+ .option("--before <timestamp>", "Only turns at or before an ISO 8601 timestamp")
78
+ .option("--cursor <cursor>", "Older-page cursor from a previous result")
79
+ .option("--limit <n>", "Page size, from 1 to 100")
80
+ .option("--session <id>", "Only turns from this session")
81
+ .option("--json", "Output as JSON")
82
+ .action(async (options) => {
83
+ let query;
84
+ try {
85
+ query = parseSpaceTurnListOptions(options);
86
+ }
87
+ catch (cause) {
88
+ if (cause instanceof InvalidSpaceTurnCliOptionsError) {
89
+ return error(cause.message, cause.detail);
90
+ }
91
+ throw cause;
92
+ }
93
+ const spaceId = resolveSpace(spacesCmd);
94
+ const client = dependencies.createClient?.() ?? createClient();
95
+ try {
96
+ const result = await client.space(spaceId).turns.list(query);
97
+ if (jsonRequested(options))
98
+ return outJson(result);
99
+ if (result.turns.length === 0)
100
+ return console.log(" No turns found");
101
+ table(toSpaceTurnRows(result.turns), [
102
+ { key: "createdAt", label: "Created" },
103
+ { key: "author", label: "Author" },
104
+ { key: "sessionTitle", label: "Session" },
105
+ { key: "sessionId", label: "Session ID" },
106
+ { key: "sequence", label: "Seq" },
107
+ { key: "id", label: "Turn ID" },
108
+ { key: "status", label: "Status" },
109
+ { key: "userPreview", label: "User" },
110
+ { key: "assistantPreview", label: "Assistant" },
111
+ ]);
112
+ if (result.pageInfo.hasMore && result.pageInfo.nextCursor) {
113
+ console.log(`\n More turns available - next cursor: ${result.pageInfo.nextCursor}`);
114
+ }
115
+ }
116
+ catch (cause) {
117
+ handleHttp(cause);
118
+ }
119
+ });
120
+ return turnsCmd;
121
+ }
@@ -8,6 +8,8 @@ import { createClient } from "../client.js";
8
8
  import { table, json as outJson, jsonRequested, ok, error, handleHttp } from "../output.js";
9
9
  import { resolveSpace } from "../space.js";
10
10
  import { registerSpaceCommerce } from "./space-commerce.js";
11
+ import { registerSpaceInvitations } from "./space-invitations.js";
12
+ import { registerSpaceTurns } from "./space-turns.js";
11
13
  const cliEnv = resolveCohubEnvironment();
12
14
  const defaultIdleTtlSeconds = cliEnv === "prod" ? 12 * 60 * 60 : 10 * 60;
13
15
  const SPACE_ROLES = ["host", "builder", "guest"];
@@ -376,6 +378,7 @@ export function registerPrompt(program) {
376
378
  }
377
379
  export function registerSpaces(program) {
378
380
  const spacesCmd = program.command("spaces").description("Space management");
381
+ registerSpaceInvitations(spacesCmd);
379
382
  // ── spaces ls ──
380
383
  spacesCmd
381
384
  .command("ls")
@@ -609,6 +612,8 @@ export function registerSpaces(program) {
609
612
  registerFiles(spacesCmd);
610
613
  // ── spaces sessions ──
611
614
  registerSessions(spacesCmd);
615
+ // ── spaces turns ──
616
+ registerSpaceTurns(spacesCmd);
612
617
  // ── spaces members ──
613
618
  registerMembers(spacesCmd);
614
619
  // ── spaces access ──
package/dist/index.js CHANGED
@@ -50,6 +50,7 @@ Common commands:
50
50
  cohub sandbox up ./my-project
51
51
  cohub search "release notes"
52
52
  cohub -s <space-id> boards inspect <board-id>
53
+ cohub -s <space-id> spaces turns ls --author others
53
54
  cohub -s <space-id> spaces sessions turns ls <session-id>
54
55
  cohub -s <space-id> spaces files ls
55
56
  cohub -s <space-id> works publish demo --file dist/index.html
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "3.1.6",
3
+ "version": "3.3.0",
4
4
  "description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -19,7 +19,7 @@
19
19
  "commander": "^15.0.0",
20
20
  "pixi.js": "^8.19.0",
21
21
  "sharp": "^0.35.3",
22
- "@neta-art/cohub": "4.3.0"
22
+ "@neta-art/cohub": "4.5.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"