@orchagent/cli 0.2.15 → 0.2.16
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/index.js +2 -0
- package/dist/commands/workspace.js +219 -98
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const delete_1 = require("./delete");
|
|
|
18
18
|
const github_1 = require("./github");
|
|
19
19
|
const doctor_1 = require("./doctor");
|
|
20
20
|
const status_1 = require("./status");
|
|
21
|
+
const workspace_1 = require("./workspace");
|
|
21
22
|
function registerCommands(program) {
|
|
22
23
|
(0, login_1.registerLoginCommand)(program);
|
|
23
24
|
(0, whoami_1.registerWhoamiCommand)(program);
|
|
@@ -36,4 +37,5 @@ function registerCommands(program) {
|
|
|
36
37
|
(0, github_1.registerGitHubCommand)(program);
|
|
37
38
|
(0, doctor_1.registerDoctorCommand)(program);
|
|
38
39
|
(0, status_1.registerStatusCommand)(program);
|
|
40
|
+
(0, workspace_1.registerWorkspaceCommand)(program);
|
|
39
41
|
}
|
|
@@ -1,133 +1,254 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.registerWorkspaceCommand = registerWorkspaceCommand;
|
|
7
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
4
9
|
const config_1 = require("../lib/config");
|
|
5
10
|
const api_1 = require("../lib/api");
|
|
11
|
+
const errors_1 = require("../lib/errors");
|
|
12
|
+
const analytics_1 = require("../lib/analytics");
|
|
13
|
+
function deriveSlug(name) {
|
|
14
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
15
|
+
}
|
|
16
|
+
async function resolveWorkspaceId(config, slug) {
|
|
17
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
18
|
+
const targetSlug = slug ?? configFile.workspace;
|
|
19
|
+
if (!targetSlug) {
|
|
20
|
+
throw new errors_1.CliError('No workspace specified. Use --workspace <slug> or run `orchagent workspace use <slug>` first.');
|
|
21
|
+
}
|
|
22
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
23
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
24
|
+
if (!workspace) {
|
|
25
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
26
|
+
}
|
|
27
|
+
return workspace.id;
|
|
28
|
+
}
|
|
29
|
+
async function listWorkspaces(config, options) {
|
|
30
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
31
|
+
const workspaces = response.workspaces;
|
|
32
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
33
|
+
const currentSlug = configFile.workspace;
|
|
34
|
+
await (0, analytics_1.track)('cli_workspace_list');
|
|
35
|
+
if (options.json) {
|
|
36
|
+
process.stdout.write(`${JSON.stringify(workspaces, null, 2)}\n`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (workspaces.length === 0) {
|
|
40
|
+
process.stdout.write('No workspaces found.\n');
|
|
41
|
+
process.stdout.write('\nCreate one with: orchagent workspace create <name>\n');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const table = new cli_table3_1.default({
|
|
45
|
+
head: [
|
|
46
|
+
'',
|
|
47
|
+
chalk_1.default.bold('Name'),
|
|
48
|
+
chalk_1.default.bold('Slug'),
|
|
49
|
+
chalk_1.default.bold('Type'),
|
|
50
|
+
chalk_1.default.bold('Role'),
|
|
51
|
+
chalk_1.default.bold('Members'),
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
workspaces.forEach((workspace) => {
|
|
55
|
+
const isCurrent = workspace.slug === currentSlug;
|
|
56
|
+
const marker = isCurrent ? chalk_1.default.green('\u2192') : '';
|
|
57
|
+
table.push([
|
|
58
|
+
marker,
|
|
59
|
+
workspace.name,
|
|
60
|
+
workspace.slug,
|
|
61
|
+
workspace.type,
|
|
62
|
+
workspace.role,
|
|
63
|
+
workspace.member_count.toString(),
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
process.stdout.write(`${table.toString()}\n`);
|
|
67
|
+
}
|
|
68
|
+
async function createWorkspace(config, name, options) {
|
|
69
|
+
const slug = options.slug ?? deriveSlug(name);
|
|
70
|
+
const response = await (0, api_1.request)(config, 'POST', '/workspaces', {
|
|
71
|
+
body: JSON.stringify({ name, slug }),
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
});
|
|
74
|
+
await (0, analytics_1.track)('cli_workspace_create', { slug });
|
|
75
|
+
process.stdout.write(chalk_1.default.green('\u2713') + ` Created workspace: ${response.workspace.name} (${response.workspace.slug})\n`);
|
|
76
|
+
}
|
|
77
|
+
async function useWorkspace(slug) {
|
|
78
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
79
|
+
// Verify workspace exists
|
|
80
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
81
|
+
const workspace = response.workspaces.find((w) => w.slug === slug);
|
|
82
|
+
if (!workspace) {
|
|
83
|
+
throw new errors_1.CliError(`Workspace '${slug}' not found.`);
|
|
84
|
+
}
|
|
85
|
+
// Save to config
|
|
86
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
87
|
+
configFile.workspace = slug;
|
|
88
|
+
await (0, config_1.saveConfig)(configFile);
|
|
89
|
+
await (0, analytics_1.track)('cli_workspace_use', { slug });
|
|
90
|
+
process.stdout.write(chalk_1.default.green('\u2713') + ` Now using workspace: ${workspace.name} (${workspace.slug})\n`);
|
|
91
|
+
}
|
|
92
|
+
async function listMembers(config, workspaceSlug, options) {
|
|
93
|
+
const workspaceId = await resolveWorkspaceId(config, workspaceSlug);
|
|
94
|
+
const response = await (0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/members`);
|
|
95
|
+
await (0, analytics_1.track)('cli_workspace_members');
|
|
96
|
+
if (options.json) {
|
|
97
|
+
process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Members table
|
|
101
|
+
if (response.members.length > 0) {
|
|
102
|
+
process.stdout.write('Members:\n');
|
|
103
|
+
const membersTable = new cli_table3_1.default({
|
|
104
|
+
head: [
|
|
105
|
+
chalk_1.default.bold('Name'),
|
|
106
|
+
chalk_1.default.bold('Email'),
|
|
107
|
+
chalk_1.default.bold('Role'),
|
|
108
|
+
chalk_1.default.bold('Joined'),
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
response.members.forEach((member) => {
|
|
112
|
+
membersTable.push([
|
|
113
|
+
member.name ?? '-',
|
|
114
|
+
member.email ?? '-',
|
|
115
|
+
member.role,
|
|
116
|
+
member.accepted_at ? new Date(member.accepted_at).toLocaleDateString() : '-',
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
process.stdout.write(`${membersTable.toString()}\n`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
process.stdout.write('No members found.\n');
|
|
123
|
+
}
|
|
124
|
+
// Pending invites table (the gateway only returns pending invites)
|
|
125
|
+
if (response.invites.length > 0) {
|
|
126
|
+
process.stdout.write('\nPending Invites:\n');
|
|
127
|
+
const invitesTable = new cli_table3_1.default({
|
|
128
|
+
head: [
|
|
129
|
+
chalk_1.default.bold('Email'),
|
|
130
|
+
chalk_1.default.bold('Role'),
|
|
131
|
+
chalk_1.default.bold('Sent'),
|
|
132
|
+
],
|
|
133
|
+
});
|
|
134
|
+
response.invites.forEach((invite) => {
|
|
135
|
+
invitesTable.push([
|
|
136
|
+
invite.email,
|
|
137
|
+
invite.role,
|
|
138
|
+
new Date(invite.created_at).toLocaleDateString(),
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
process.stdout.write(`${invitesTable.toString()}\n`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function inviteMember(config, email, options) {
|
|
145
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
146
|
+
const role = options.role ?? 'member';
|
|
147
|
+
const response = await (0, api_1.request)(config, 'POST', `/workspaces/${workspaceId}/invites`, {
|
|
148
|
+
body: JSON.stringify({ email, role }),
|
|
149
|
+
headers: { 'Content-Type': 'application/json' },
|
|
150
|
+
});
|
|
151
|
+
await (0, analytics_1.track)('cli_workspace_invite', { role });
|
|
152
|
+
process.stdout.write(chalk_1.default.green('\u2713') + ` Invited ${email} as ${role}\n`);
|
|
153
|
+
if (response.invite.invite_url) {
|
|
154
|
+
process.stdout.write(`\nInvite URL: ${response.invite.invite_url}\n`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function leaveWorkspace(config, workspaceSlug) {
|
|
158
|
+
const workspaceId = await resolveWorkspaceId(config, workspaceSlug);
|
|
159
|
+
// Get current user's email and members list to find our clerk_user_id
|
|
160
|
+
const [userResponse, membersResponse] = await Promise.all([
|
|
161
|
+
(0, api_1.request)(config, 'GET', '/users/me'),
|
|
162
|
+
(0, api_1.request)(config, 'GET', `/workspaces/${workspaceId}/members`),
|
|
163
|
+
]);
|
|
164
|
+
const currentUserEmail = userResponse.user.email;
|
|
165
|
+
const currentMember = membersResponse.members.find((m) => m.email === currentUserEmail);
|
|
166
|
+
if (!currentMember) {
|
|
167
|
+
throw new errors_1.CliError('Could not find your membership in this workspace.');
|
|
168
|
+
}
|
|
169
|
+
await (0, api_1.request)(config, 'DELETE', `/workspaces/${workspaceId}/members/${currentMember.clerk_user_id}`);
|
|
170
|
+
await (0, analytics_1.track)('cli_workspace_leave');
|
|
171
|
+
// Clear workspace from config if it was the current one
|
|
172
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
173
|
+
if (configFile.workspace) {
|
|
174
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
175
|
+
const leftWorkspace = response.workspaces.find((w) => w.id === workspaceId);
|
|
176
|
+
if (leftWorkspace && configFile.workspace === leftWorkspace.slug) {
|
|
177
|
+
delete configFile.workspace;
|
|
178
|
+
await (0, config_1.saveConfig)(configFile);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
process.stdout.write(chalk_1.default.green('\u2713') + ' Left workspace\n');
|
|
182
|
+
}
|
|
6
183
|
function registerWorkspaceCommand(program) {
|
|
7
184
|
const workspace = program
|
|
8
185
|
.command('workspace')
|
|
9
186
|
.description('Manage workspaces');
|
|
10
|
-
//
|
|
187
|
+
// workspace list
|
|
11
188
|
workspace
|
|
12
189
|
.command('list')
|
|
13
190
|
.description('List all workspaces')
|
|
14
|
-
.
|
|
191
|
+
.option('--json', 'Output raw JSON')
|
|
192
|
+
.action(async (options) => {
|
|
15
193
|
const config = await (0, config_1.getResolvedConfig)();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (workspaces.length === 0) {
|
|
19
|
-
process.stdout.write('No workspaces found.\n');
|
|
20
|
-
return;
|
|
194
|
+
if (!config.apiKey) {
|
|
195
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
21
196
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
197
|
+
await listWorkspaces(config, options);
|
|
198
|
+
});
|
|
199
|
+
// workspace create <name>
|
|
200
|
+
workspace
|
|
201
|
+
.command('create <name>')
|
|
202
|
+
.description('Create a new workspace')
|
|
203
|
+
.option('--slug <slug>', 'Workspace slug (default: derived from name)')
|
|
204
|
+
.action(async (name, options) => {
|
|
205
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
206
|
+
if (!config.apiKey) {
|
|
207
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
29
208
|
}
|
|
209
|
+
await createWorkspace(config, name, options);
|
|
30
210
|
});
|
|
31
|
-
//
|
|
211
|
+
// workspace use <slug>
|
|
32
212
|
workspace
|
|
33
213
|
.command('use <slug>')
|
|
34
|
-
.description('
|
|
214
|
+
.description('Set the current workspace')
|
|
35
215
|
.action(async (slug) => {
|
|
36
|
-
|
|
37
|
-
const workspaces = await (0, api_1.listWorkspaces)(config);
|
|
38
|
-
const target = workspaces.find(ws => ws.slug === slug || ws.id === slug);
|
|
39
|
-
if (!target) {
|
|
40
|
-
process.stderr.write(`Workspace "${slug}" not found.\n`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
const fileConfig = await (0, config_1.loadConfig)();
|
|
44
|
-
fileConfig.current_workspace = target.id;
|
|
45
|
-
await (0, config_1.saveConfig)(fileConfig);
|
|
46
|
-
process.stdout.write(`Switched to workspace: ${target.name} (${target.slug})\n`);
|
|
216
|
+
await useWorkspace(slug);
|
|
47
217
|
});
|
|
48
|
-
//
|
|
218
|
+
// workspace members [workspace]
|
|
49
219
|
workspace
|
|
50
|
-
.command('
|
|
51
|
-
.description('
|
|
52
|
-
.option('--
|
|
53
|
-
.action(async (
|
|
220
|
+
.command('members [workspace]')
|
|
221
|
+
.description('List workspace members and pending invites')
|
|
222
|
+
.option('--json', 'Output raw JSON')
|
|
223
|
+
.action(async (workspaceSlug, options) => {
|
|
54
224
|
const config = await (0, config_1.getResolvedConfig)();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.toLowerCase()
|
|
58
|
-
.replace(/\s+/g, '-')
|
|
59
|
-
.replace(/[^a-z0-9-]/g, '')
|
|
60
|
-
.replace(/-+/g, '-')
|
|
61
|
-
.slice(0, 30);
|
|
62
|
-
try {
|
|
63
|
-
const workspace = await (0, api_1.createWorkspace)(config, { name, slug });
|
|
64
|
-
// Switch to the new workspace
|
|
65
|
-
const fileConfig = await (0, config_1.loadConfig)();
|
|
66
|
-
fileConfig.current_workspace = workspace.id;
|
|
67
|
-
await (0, config_1.saveConfig)(fileConfig);
|
|
68
|
-
process.stdout.write(`Created workspace: ${workspace.name} (${workspace.slug})\n`);
|
|
69
|
-
process.stdout.write(`Switched to new workspace.\n`);
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
process.stderr.write(`Failed to create workspace: ${err instanceof Error ? err.message : 'Unknown error'}\n`);
|
|
73
|
-
process.exit(1);
|
|
225
|
+
if (!config.apiKey) {
|
|
226
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
74
227
|
}
|
|
228
|
+
await listMembers(config, workspaceSlug, options);
|
|
75
229
|
});
|
|
76
|
-
//
|
|
230
|
+
// workspace invite <email>
|
|
77
231
|
workspace
|
|
78
232
|
.command('invite <email>')
|
|
79
|
-
.description('Invite a
|
|
80
|
-
.option('--role <role>', 'Role
|
|
233
|
+
.description('Invite a user to the workspace')
|
|
234
|
+
.option('--role <role>', 'Role for the invited user (default: member)', 'member')
|
|
235
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
81
236
|
.action(async (email, options) => {
|
|
82
237
|
const config = await (0, config_1.getResolvedConfig)();
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
process.stderr.write('No workspace selected. Run `orch workspace use <slug>` first.\n');
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
88
|
-
const role = options.role;
|
|
89
|
-
if (role !== 'owner' && role !== 'member') {
|
|
90
|
-
process.stderr.write('Role must be "owner" or "member".\n');
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
await (0, api_1.inviteToWorkspace)(config, fileConfig.current_workspace, { email, role });
|
|
95
|
-
process.stdout.write(`Invited ${email} as ${role}.\n`);
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
process.stderr.write(`Failed to send invite: ${err instanceof Error ? err.message : 'Unknown error'}\n`);
|
|
99
|
-
process.exit(1);
|
|
238
|
+
if (!config.apiKey) {
|
|
239
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
100
240
|
}
|
|
241
|
+
await inviteMember(config, email, options);
|
|
101
242
|
});
|
|
102
|
-
//
|
|
243
|
+
// workspace leave [workspace]
|
|
103
244
|
workspace
|
|
104
|
-
.command('
|
|
105
|
-
.description('
|
|
106
|
-
.action(async () => {
|
|
245
|
+
.command('leave [workspace]')
|
|
246
|
+
.description('Leave a workspace')
|
|
247
|
+
.action(async (workspaceSlug) => {
|
|
107
248
|
const config = await (0, config_1.getResolvedConfig)();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
process.stderr.write('No workspace selected. Run `orch workspace use <slug>` first.\n');
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const { members, invites } = await (0, api_1.getWorkspaceMembers)(config, fileConfig.current_workspace);
|
|
115
|
-
process.stdout.write('Members:\n');
|
|
116
|
-
for (const member of members) {
|
|
117
|
-
const roleLabel = member.role === 'owner' ? '[owner]' : '[member]';
|
|
118
|
-
process.stdout.write(` ${member.clerk_user_id} ${roleLabel}\n`);
|
|
119
|
-
}
|
|
120
|
-
if (invites.length > 0) {
|
|
121
|
-
process.stdout.write('\nPending invites:\n');
|
|
122
|
-
for (const invite of invites) {
|
|
123
|
-
const expires = new Date(invite.expires_at).toLocaleDateString();
|
|
124
|
-
process.stdout.write(` ${invite.email} (${invite.role}) - expires ${expires}\n`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
process.stderr.write(`Failed to list members: ${err instanceof Error ? err.message : 'Unknown error'}\n`);
|
|
130
|
-
process.exit(1);
|
|
249
|
+
if (!config.apiKey) {
|
|
250
|
+
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
131
251
|
}
|
|
252
|
+
await leaveWorkspace(config, workspaceSlug);
|
|
132
253
|
});
|
|
133
254
|
}
|