@neta-art/cohub-cli 3.2.0 → 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
|
|
|
@@ -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
|
+
}
|
package/dist/commands/spaces.js
CHANGED
|
@@ -8,6 +8,7 @@ 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";
|
|
11
12
|
import { registerSpaceTurns } from "./space-turns.js";
|
|
12
13
|
const cliEnv = resolveCohubEnvironment();
|
|
13
14
|
const defaultIdleTtlSeconds = cliEnv === "prod" ? 12 * 60 * 60 : 10 * 60;
|
|
@@ -377,6 +378,7 @@ export function registerPrompt(program) {
|
|
|
377
378
|
}
|
|
378
379
|
export function registerSpaces(program) {
|
|
379
380
|
const spacesCmd = program.command("spaces").description("Space management");
|
|
381
|
+
registerSpaceInvitations(spacesCmd);
|
|
380
382
|
// ── spaces ls ──
|
|
381
383
|
spacesCmd
|
|
382
384
|
.command("ls")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "3.
|
|
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.
|
|
22
|
+
"@neta-art/cohub": "4.5.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|