@enter-pro/enter-cli 0.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.
Files changed (51) hide show
  1. package/README.md +0 -0
  2. package/dist/auth.d.ts +12 -0
  3. package/dist/auth.js +39 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/client.d.ts +11 -0
  6. package/dist/client.js +140 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/commands/config.d.ts +2 -0
  9. package/dist/commands/config.js +37 -0
  10. package/dist/commands/config.js.map +1 -0
  11. package/dist/commands/domain.d.ts +2 -0
  12. package/dist/commands/domain.js +65 -0
  13. package/dist/commands/domain.js.map +1 -0
  14. package/dist/commands/login.d.ts +2 -0
  15. package/dist/commands/login.js +143 -0
  16. package/dist/commands/login.js.map +1 -0
  17. package/dist/commands/logout.d.ts +2 -0
  18. package/dist/commands/logout.js +9 -0
  19. package/dist/commands/logout.js.map +1 -0
  20. package/dist/commands/models.d.ts +2 -0
  21. package/dist/commands/models.js +27 -0
  22. package/dist/commands/project.d.ts +2 -0
  23. package/dist/commands/project.js +452 -0
  24. package/dist/commands/project.js.map +1 -0
  25. package/dist/commands/skill.d.ts +2 -0
  26. package/dist/commands/skill.js +118 -0
  27. package/dist/commands/thread.d.ts +2 -0
  28. package/dist/commands/thread.js +578 -0
  29. package/dist/commands/thread.js.map +1 -0
  30. package/dist/commands/whoami.d.ts +2 -0
  31. package/dist/commands/whoami.js +28 -0
  32. package/dist/commands/whoami.js.map +1 -0
  33. package/dist/commands/workspace.d.ts +2 -0
  34. package/dist/commands/workspace.js +178 -0
  35. package/dist/commands/workspace.js.map +1 -0
  36. package/dist/config.d.ts +13 -0
  37. package/dist/config.js +68 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/errors.d.ts +13 -0
  40. package/dist/errors.js +33 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +45 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/lifecycle.d.ts +9 -0
  45. package/dist/lifecycle.js +47 -0
  46. package/dist/output.d.ts +26 -0
  47. package/dist/output.js +89 -0
  48. package/dist/output.js.map +1 -0
  49. package/dist/poll.d.ts +9 -0
  50. package/dist/poll.js +24 -0
  51. package/package.json +41 -0
@@ -0,0 +1,452 @@
1
+ import { Command } from "commander";
2
+ import { writeFileSync } from "fs";
3
+ import * as client from "../client.js";
4
+ import { print, printMessage, printResult, printTable, pickList, getFormat } from "../output.js";
5
+ import { pollUntil, TimeoutError } from "../poll.js";
6
+ import { resolveLifecycleStatus } from "../lifecycle.js";
7
+ export const projectCmd = new Command("project")
8
+ .alias("proj")
9
+ .description("Manage projects");
10
+ // `/v1/projects/{id}/detail` sometimes wraps the project under `.project` and
11
+ // sometimes returns the bare project shape — depending on the field. Normalize.
12
+ function unwrapProject(detail) {
13
+ const d = detail;
14
+ return (d.project ?? d);
15
+ }
16
+ function readPublishStatus(project) {
17
+ const ps = project.publish_status;
18
+ const lastPublishedCommit = String(ps?.last_published_commit_id ?? "");
19
+ const lastPublishedAt = String(ps?.last_published_at ?? "");
20
+ const unpublishedChanges = Number(ps?.unpublished_changes ?? 0);
21
+ return {
22
+ lastPublishedCommit,
23
+ lastPublishedAt,
24
+ unpublishedChanges,
25
+ isPublished: Boolean(lastPublishedCommit) && unpublishedChanges === 0,
26
+ };
27
+ }
28
+ projectCmd
29
+ .command("list <workspace_id>")
30
+ .description("List projects in a workspace")
31
+ .option("--page <number>", "Page number", "1")
32
+ .option("--page-size <number>", "Page size", "20")
33
+ .action(async (id, opts, cmd) => {
34
+ const params = {
35
+ page: opts.page,
36
+ page_size: opts.pageSize,
37
+ };
38
+ const data = await client.get(`/v1/workspaces/${id}/projects`, params);
39
+ const resp = data;
40
+ const items = pickList(resp.projects || [], [
41
+ "project_id", "name", "status", "visibility", "preview_url", "updated_at",
42
+ ]);
43
+ const result = { items, total: resp.total, page: resp.page, page_size: resp.page_size };
44
+ const format = getFormat(cmd);
45
+ if (format !== "table") {
46
+ print(format, result);
47
+ return;
48
+ }
49
+ const rows = items.map((p) => [
50
+ String(p.project_id ?? ""),
51
+ String(p.name ?? ""),
52
+ String(p.status ?? ""),
53
+ String(p.visibility ?? ""),
54
+ String(p.updated_at ?? ""),
55
+ ]);
56
+ printTable(["Project ID", "Name", "Status", "Visibility", "Updated"], rows);
57
+ });
58
+ projectCmd
59
+ .command("get <project_id>")
60
+ .description("Get project details")
61
+ .action(async (id, _opts, cmd) => {
62
+ const data = await client.get(`/v1/projects/${id}/detail`);
63
+ const p = data;
64
+ const lifecycle = resolveLifecycleStatus(p);
65
+ const enriched = { ...p, lifecycle_status: lifecycle };
66
+ const format = getFormat(cmd);
67
+ if (format !== "table") {
68
+ print(format, enriched);
69
+ return;
70
+ }
71
+ printTable(["Project ID", "Name", "Visibility", "Status", "Lifecycle", "Preview URL", "Workspace", "Updated"], [[
72
+ String(p.project_id ?? ""),
73
+ String(p.name ?? ""),
74
+ String(p.visibility ?? ""),
75
+ String(p.status ?? ""),
76
+ lifecycle,
77
+ String(p.preview_url ?? ""),
78
+ String(p.workspace_id ?? ""),
79
+ String(p.updated_at ?? ""),
80
+ ]]);
81
+ });
82
+ projectCmd
83
+ .command("create <workspace_id>")
84
+ .description("Create a new project in a workspace")
85
+ .option("--name <name>", "Project name")
86
+ .option("--prompt <text>", "Initial prompt for the project")
87
+ .option("--model <id>", "Pin AI model (see `enter-cli models list`). Invalid IDs silently fall back to 'auto'.")
88
+ .option("--plan-mode", "Start the project in Plan Mode (first turn produces a plan instead of code)")
89
+ .option("--wait", "Wait until the first build completes")
90
+ .option("--timeout <seconds>", "Timeout for --wait in seconds", "300")
91
+ .action(async (id, opts, cmd) => {
92
+ const body = {};
93
+ if (opts.name)
94
+ body.name = opts.name;
95
+ if (opts.prompt)
96
+ body.prompt = opts.prompt;
97
+ if (opts.model)
98
+ body.model_id = opts.model;
99
+ if (opts.planMode)
100
+ body.plan_mode = true;
101
+ const data = await client.post(`/v1/workspaces/${id}/projects`, body);
102
+ if (!opts.wait) {
103
+ print(getFormat(cmd), data);
104
+ return;
105
+ }
106
+ const created = data;
107
+ const projectId = String(created.project_id ?? created.id ?? "");
108
+ if (!projectId) {
109
+ print(getFormat(cmd), data);
110
+ return;
111
+ }
112
+ console.error("Waiting for first build to complete...");
113
+ const timeoutMs = parseInt(opts.timeout, 10) * 1000;
114
+ const buildingStatuses = new Set(["initializing", "building"]);
115
+ try {
116
+ const result = await pollUntil(() => client.get(`/v1/projects/${projectId}/detail`), (d) => !buildingStatuses.has(String(d.status ?? "")), {
117
+ intervalMs: 3000,
118
+ timeoutMs,
119
+ onTick: (elapsed) => {
120
+ process.stderr.write(`\rWaiting... ${Math.round(elapsed / 1000)}s elapsed`);
121
+ },
122
+ });
123
+ process.stderr.write("\n");
124
+ const enriched = { ...result, lifecycle_status: resolveLifecycleStatus(result) };
125
+ print(getFormat(cmd), enriched);
126
+ }
127
+ catch (err) {
128
+ if (err instanceof TimeoutError) {
129
+ console.error(`\nTimed out after ${opts.timeout}s. Project may still be building.`);
130
+ process.exit(1);
131
+ }
132
+ throw err;
133
+ }
134
+ });
135
+ projectCmd
136
+ .command("rename <project_id> <new_name>")
137
+ .description("Rename a project")
138
+ .action(async (id, name, _opts, cmd) => {
139
+ await client.post(`/v1/projects/${id}/rename`, { name });
140
+ printResult(getFormat(cmd), { project_id: id, name }, `Project renamed to "${name}".`);
141
+ });
142
+ projectCmd
143
+ .command("delete <project_id>")
144
+ .description("Delete a project")
145
+ .action(async (id, _opts, cmd) => {
146
+ await client.del(`/v1/projects/${id}/delete`);
147
+ printResult(getFormat(cmd), { project_id: id, deleted: true }, "Project deleted successfully.");
148
+ });
149
+ projectCmd
150
+ .command("download <project_id>")
151
+ .description("Download project as zip file")
152
+ .option("--out <path>", "Output file path")
153
+ .action(async (id, opts) => {
154
+ const data = await client.getRaw(`/v1/projects/${id}/download`);
155
+ const outPath = opts.out || `${id}.zip`;
156
+ writeFileSync(outPath, Buffer.from(await data.arrayBuffer()));
157
+ printMessage(`Project downloaded to ${outPath}`);
158
+ });
159
+ projectCmd
160
+ .command("publish <project_id>")
161
+ .description("Publish a project (synchronous: waits for completion and verifies URL)")
162
+ .option("--timeout <seconds>", "Timeout in seconds", "300")
163
+ .action(async (id, opts, cmd) => {
164
+ const initialDetail = await client.get(`/v1/projects/${id}/detail`);
165
+ const initialProject = unwrapProject(initialDetail);
166
+ const buildStatus = initialProject.build_status;
167
+ const commitId = String(buildStatus?.commit_id ?? initialProject.commit ?? "");
168
+ if (!commitId) {
169
+ console.error("Error: project has no committed build yet (build_status.commit_id is empty).");
170
+ console.error("Wait for the latest turn to finish, then retry.");
171
+ process.exit(1);
172
+ }
173
+ await client.post(`/v1/projects/${id}/publish`);
174
+ console.error("Waiting for publish to complete...");
175
+ const timeoutMs = parseInt(opts.timeout, 10) * 1000;
176
+ try {
177
+ const result = await pollUntil(() => client.get(`/v1/projects/${id}/detail`), (d) => {
178
+ const view = readPublishStatus(unwrapProject(d));
179
+ // Done when the latest committed build has been published and nothing is queued.
180
+ return view.lastPublishedCommit === commitId && view.unpublishedChanges === 0;
181
+ }, {
182
+ intervalMs: 3000,
183
+ timeoutMs,
184
+ onTick: (elapsed) => {
185
+ process.stderr.write(`\rWaiting... ${Math.round(elapsed / 1000)}s elapsed`);
186
+ },
187
+ });
188
+ process.stderr.write("\n");
189
+ const project = unwrapProject(result);
190
+ const view = readPublishStatus(project);
191
+ const publishUrl = String(project.publish_url ?? "");
192
+ const reachable = publishUrl ? await verifyUrlReachable(publishUrl) : false;
193
+ if (publishUrl && !reachable) {
194
+ console.error(`Warning: ${publishUrl} did not return 200 within retries. The publish completed server-side but the URL may still be propagating.`);
195
+ }
196
+ printMessage(`Published commit ${commitId.slice(0, 7)} at ${view.lastPublishedAt}. URL: ${publishUrl}${reachable ? " (200 OK)" : ""}`);
197
+ print(getFormat(cmd), { ...result, lifecycle_status: resolveLifecycleStatus(project) });
198
+ }
199
+ catch (err) {
200
+ if (err instanceof TimeoutError) {
201
+ console.error(`\nTimed out after ${opts.timeout}s. Publish may still be in progress.`);
202
+ console.error(`Run "proj urls ${id}" to check current status.`);
203
+ process.exit(1);
204
+ }
205
+ throw err;
206
+ }
207
+ });
208
+ // HEAD-first URL probe with a small wall-clock budget. Falls back to GET when
209
+ // HEAD isn't allowed (some hosts return 405). Used by `proj publish` (after
210
+ // publish completes) and `proj urls` (to determine the recommended URL).
211
+ async function verifyUrlReachable(url, attempts = 3, perAttemptMs = 2000) {
212
+ for (let i = 0; i < attempts; i++) {
213
+ const ctrl = new AbortController();
214
+ const t = setTimeout(() => ctrl.abort(), perAttemptMs);
215
+ try {
216
+ let res = await fetch(url, { method: "HEAD", signal: ctrl.signal });
217
+ // Some hosts reject HEAD (405 / 501) — retry once with GET on this attempt.
218
+ if (res.status === 405 || res.status === 501) {
219
+ res = await fetch(url, { method: "GET", signal: ctrl.signal });
220
+ }
221
+ clearTimeout(t);
222
+ if (res.status === 200)
223
+ return true;
224
+ }
225
+ catch {
226
+ clearTimeout(t);
227
+ }
228
+ if (i < attempts - 1)
229
+ await new Promise((r) => setTimeout(r, 1000));
230
+ }
231
+ return false;
232
+ }
233
+ projectCmd
234
+ .command("publish-status <project_id>")
235
+ .description("Get publish status and URLs for a project")
236
+ .action(async (id, _opts, cmd) => {
237
+ const project = unwrapProject(await client.get(`/v1/projects/${id}/detail`));
238
+ const view = readPublishStatus(project);
239
+ const result = {
240
+ status: String(project.status ?? ""),
241
+ lifecycle_status: resolveLifecycleStatus(project),
242
+ published: view.isPublished,
243
+ unpublished_changes: view.unpublishedChanges,
244
+ last_published_commit_id: view.lastPublishedCommit || null,
245
+ last_published_at: view.lastPublishedAt || null,
246
+ publish_url: project.publish_url ?? null,
247
+ preview_url: project.preview_url ?? null,
248
+ publish_commit: project.publish_commit ?? null,
249
+ updated_at: project.updated_at ?? null,
250
+ };
251
+ print(getFormat(cmd), result);
252
+ });
253
+ projectCmd
254
+ .command("urls <project_id>")
255
+ .description("Get all URLs for a project with a recommended share URL")
256
+ .action(async (id, _opts, cmd) => {
257
+ const project = unwrapProject(await client.get(`/v1/projects/${id}/detail`));
258
+ const view = readPublishStatus(project);
259
+ const publishUrl = project.publish_url || "";
260
+ const previewUrl = project.preview_url || "";
261
+ const visibility = project.visibility ?? null;
262
+ const publishCandidate = publishUrl && Boolean(view.lastPublishedCommit);
263
+ // Probe both in parallel — independent network calls.
264
+ const [publishReachable, previewReachable] = await Promise.all([
265
+ publishCandidate ? verifyUrlReachable(publishUrl) : Promise.resolve(false),
266
+ previewUrl ? verifyUrlReachable(previewUrl) : Promise.resolve(false),
267
+ ]);
268
+ let recommended = null;
269
+ let environment = "none";
270
+ if (publishReachable) {
271
+ recommended = publishUrl;
272
+ environment = "production";
273
+ }
274
+ else if (previewReachable) {
275
+ recommended = previewUrl;
276
+ environment = "preview";
277
+ }
278
+ const result = {
279
+ preview_url: previewUrl || null,
280
+ preview_reachable: previewReachable,
281
+ publish_url: publishUrl || null,
282
+ publish_reachable: publishReachable,
283
+ last_published_commit_id: view.lastPublishedCommit || null,
284
+ last_published_at: view.lastPublishedAt || null,
285
+ recommended_share_url: recommended,
286
+ environment,
287
+ visibility,
288
+ };
289
+ print(getFormat(cmd), result);
290
+ });
291
+ projectCmd
292
+ .command("remix <project_id>")
293
+ .description("Remix a project")
294
+ .option("--workspace-id <id>", "Target workspace ID for remix")
295
+ .action(async (id, opts, cmd) => {
296
+ const body = {};
297
+ if (opts.workspaceId)
298
+ body.workspace_id = opts.workspaceId;
299
+ const data = await client.post(`/v1/projects/${id}/remix`, body);
300
+ print(getFormat(cmd), data);
301
+ });
302
+ projectCmd
303
+ .command("visibility <project_id> <public|private>")
304
+ .description("Update project visibility")
305
+ .action(async (id, visibility, _opts, cmd) => {
306
+ if (visibility !== "public" && visibility !== "private") {
307
+ throw new Error("Visibility must be 'public' or 'private'");
308
+ }
309
+ await client.post(`/v1/projects/${id}/update_visibility`, { visibility });
310
+ printResult(getFormat(cmd), { project_id: id, visibility }, `Project visibility set to ${visibility}.`);
311
+ });
312
+ projectCmd
313
+ .command("plan-mode <project_id> <enable|disable>")
314
+ .description("Enable or disable plan mode for a project")
315
+ .action(async (id, action, _opts, cmd) => {
316
+ if (action !== "enable" && action !== "disable") {
317
+ throw new Error("Action must be 'enable' or 'disable'");
318
+ }
319
+ const data = await client.post(`/v1/projects/${id}/plan-mode/${action}`);
320
+ const project = unwrapProject(data);
321
+ printResult(getFormat(cmd), project, `Plan mode ${action}d.`);
322
+ });
323
+ projectCmd
324
+ .command("source-code <project_id>")
325
+ .description("Get the full source code of a project")
326
+ .action(async (id, _opts, cmd) => {
327
+ const data = await client.get(`/v1/projects/${id}/source-code`);
328
+ print(getFormat(cmd), data);
329
+ });
330
+ projectCmd
331
+ .command("edit-file <project_id>")
332
+ .description("Directly edit source files in the project (creates a new turn)")
333
+ .requiredOption("--commit <id>", "Current commit ID (from proj get)")
334
+ .requiredOption("--path <file_path>", "File path to edit")
335
+ .requiredOption("--code <content>", "New file content")
336
+ .action(async (id, opts, cmd) => {
337
+ const data = await client.post(`/v1/projects/${id}/edit-code`, {
338
+ commit: opts.commit,
339
+ source_codes: [{ file_path: opts.path, code: opts.code }],
340
+ });
341
+ print(getFormat(cmd), data);
342
+ });
343
+ projectCmd
344
+ .command("model <project_id> <model>")
345
+ .description("Set the AI model for a project (takes effect on next turn)")
346
+ .action(async (id, model, _opts, cmd) => {
347
+ const data = await client.post(`/v1/projects/${id}/model`, { model });
348
+ print(getFormat(cmd), data);
349
+ });
350
+ // MCP subcommand group
351
+ const mcpCmd = new Command("mcp").description("Manage MCP servers for a project");
352
+ mcpCmd
353
+ .command("list <project_id>")
354
+ .description("List MCP servers")
355
+ .action(async (id, _opts, cmd) => {
356
+ const data = await client.get(`/v1/projects/${id}/mcp/servers`);
357
+ const resp = data;
358
+ const format = getFormat(cmd);
359
+ if (format !== "table") {
360
+ print(format, data);
361
+ return;
362
+ }
363
+ const rows = (resp.servers || []).map((s) => [
364
+ String(s.id ?? ""),
365
+ String(s.name ?? s.display_name ?? ""),
366
+ String(s.transport ?? ""),
367
+ String(s.enabled ?? ""),
368
+ String(s.url ?? ""),
369
+ ]);
370
+ printTable(["ID", "Name", "Transport", "Enabled", "URL"], rows);
371
+ });
372
+ mcpCmd
373
+ .command("get <project_id> <server_id>")
374
+ .description("Get an MCP server")
375
+ .action(async (id, serverId, _opts, cmd) => {
376
+ const data = await client.get(`/v1/projects/${id}/mcp/servers/${serverId}`);
377
+ print(getFormat(cmd), data);
378
+ });
379
+ mcpCmd
380
+ .command("create <project_id>")
381
+ .description("Add MCP server(s) to a project")
382
+ .requiredOption("--name <name>", "Server name (key in mcpServers map)")
383
+ .requiredOption("--url <url>", "Server URL")
384
+ .option("--transport <type>", "Transport type: sse, http, stdio")
385
+ .action(async (id, opts, cmd) => {
386
+ const serverConfig = { url: opts.url };
387
+ if (opts.transport)
388
+ serverConfig.transport = opts.transport;
389
+ const data = await client.post(`/v1/projects/${id}/mcp/servers`, {
390
+ mcpServers: { [opts.name]: serverConfig },
391
+ });
392
+ print(getFormat(cmd), data);
393
+ });
394
+ mcpCmd
395
+ .command("update <project_id> <server_id>")
396
+ .description("Update an MCP server")
397
+ .option("--display-name <name>", "New display name")
398
+ .option("--enabled <bool>", "Enable or disable (true/false)")
399
+ .option("--allowed-tools <tools>", "Comma-separated list of allowed tools")
400
+ .action(async (id, serverId, opts, cmd) => {
401
+ const body = {};
402
+ if (opts.displayName)
403
+ body.display_name = opts.displayName;
404
+ if (opts.enabled !== undefined)
405
+ body.enabled = opts.enabled === "true";
406
+ if (opts.allowedTools)
407
+ body.allowed_tools = opts.allowedTools.split(",").map((t) => t.trim());
408
+ const data = await client.patch(`/v1/projects/${id}/mcp/servers/${serverId}`, body);
409
+ print(getFormat(cmd), data ?? { message: "MCP server updated." });
410
+ });
411
+ mcpCmd
412
+ .command("delete <project_id> <server_id>")
413
+ .description("Delete an MCP server")
414
+ .action(async (id, serverId) => {
415
+ await client.del(`/v1/projects/${id}/mcp/servers/${serverId}`);
416
+ printMessage(`MCP server ${serverId} deleted.`);
417
+ });
418
+ projectCmd.addCommand(mcpCmd);
419
+ // Project skills subcommand group
420
+ const projSkillsCmd = new Command("skills").description("Manage project skills");
421
+ projSkillsCmd
422
+ .command("list <project_id>")
423
+ .description("List skills installed on a project")
424
+ .action(async (id, _opts, cmd) => {
425
+ const data = await client.get(`/v1/projects/${id}/skills`);
426
+ const resp = data;
427
+ const format = getFormat(cmd);
428
+ if (format !== "table") {
429
+ print(format, data);
430
+ return;
431
+ }
432
+ const rows = (resp.skills || []).map((s) => [
433
+ String(s.skill_key ?? ""),
434
+ String(s.name ?? ""),
435
+ String(s.type ?? ""),
436
+ String(s.state ?? ""),
437
+ String(s.version ?? ""),
438
+ ]);
439
+ printTable(["Key", "Name", "Type", "State", "Version"], rows);
440
+ });
441
+ projSkillsCmd
442
+ .command("update <project_id>")
443
+ .description("Enable or disable a project skill")
444
+ .requiredOption("--skill-key <key>", "Skill key")
445
+ .requiredOption("--enabled <bool>", "true to enable, false to disable")
446
+ .action(async (id, opts, cmd) => {
447
+ const data = await client.patch(`/v1/projects/${id}/skills`, {
448
+ updates: [{ skill_key: opts.skillKey, enabled: opts.enabled === "true" }],
449
+ });
450
+ print(getFormat(cmd), data);
451
+ });
452
+ projectCmd.addCommand(projSkillsCmd);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,KAAK,MAAM,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAa,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEpF,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;KAC7C,KAAK,CAAC,MAAM,CAAC;KACb,WAAW,CAAC,iBAAiB,CAAC,CAAC;AAElC,SAAS,SAAS,CAAC,GAAY;IAC7B,OAAO,GAAG,CAAC,eAAe,EAAE,CAAC,MAAM,IAAI,MAAM,CAAC;AAChD,CAAC;AAED,UAAU;KACP,OAAO,CAAC,qBAAqB,CAAC;KAC9B,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,iBAAiB,EAAE,aAAa,EAAE,GAAG,CAAC;KAC7C,MAAM,CAAC,sBAAsB,EAAE,WAAW,EAAE,IAAI,CAAC;KACjD,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;IACtC,MAAM,MAAM,GAA2B;QACrC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,SAAS,EAAE,IAAI,CAAC,QAAQ;KACzB,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,kBAAkB,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,IAA+F,CAAC;IAC7G,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE;QAC1C,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY;KAC1E,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IAClG,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtB,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC;QAC1B,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QACpB,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;QACtB,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC;QAC1B,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC;KAC3B,CAAC,CAAC;IACH,UAAU,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,kBAAkB,CAAC;KAC3B,WAAW,CAAC,qBAAqB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACvC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACpB,OAAO;IACT,CAAC;IAED,MAAM,CAAC,GAAG,IAQT,CAAC;IAEF,UAAU,CACR;QACE,YAAY;QACZ,MAAM;QACN,YAAY;QACZ,QAAQ;QACR,aAAa;QACb,WAAW;QACX,SAAS;KACV,EACD;QACE;YACE,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,WAAW;YACb,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC;YACtB,CAAC,CAAC,UAAU;SACb;KACF,CACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,uBAAuB,CAAC;KAChC,WAAW,CAAC,qCAAqC,CAAC;KAClD,MAAM,CAAC,eAAe,EAAE,cAAc,CAAC;KACvC,MAAM,CAAC,iBAAiB,EAAE,gCAAgC,CAAC;KAC3D,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;IACtC,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,IAAI,IAAI,CAAC,IAAI;QAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACrC,IAAI,IAAI,CAAC,MAAM;QAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3C,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAC5B,kBAAkB,EAAE,WAAW,EAC/B,IAAI,CACL,CAAC;IACF,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,gCAAgC,CAAC;KACzC,WAAW,CAAC,kBAAkB,CAAC;KAC/B,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,IAAY,EAAE,EAAE;IACzC,MAAM,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,YAAY,CAAC,uBAAuB,IAAI,IAAI,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,qBAAqB,CAAC;KAC9B,WAAW,CAAC,kBAAkB,CAAC;KAC/B,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,EAAE;IAC3B,MAAM,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAC9C,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,uBAAuB,CAAC;KAChC,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAAC;KAC1C,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,IAAI,EAAE,EAAE;IACjC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,EAAE,MAAM,CAAC;IACxC,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC9D,YAAY,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,sBAAsB,CAAC;KAC/B,WAAW,CAAC,mBAAmB,CAAC;KAChC,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACvC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IAC7D,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,oBAAoB,CAAC;KAC7B,WAAW,CAAC,iBAAiB,CAAC;KAC9B,MAAM,CAAC,qBAAqB,EAAE,+BAA+B,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;IACtC,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,IAAI,IAAI,CAAC,WAAW;QAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC;IAC3D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IACjE,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL,UAAU;KACP,OAAO,CAAC,0CAA0C,CAAC;KACnD,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,KAAK,EAAE,EAAU,EAAE,UAAkB,EAAE,EAAE;IAC/C,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,oBAAoB,EAAE;QACxD,UAAU;KACX,CAAC,CAAC;IACH,YAAY,CAAC,6BAA6B,UAAU,GAAG,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const skillCmd: Command;
@@ -0,0 +1,118 @@
1
+ import { Command } from "commander";
2
+ import * as client from "../client.js";
3
+ import { print, printMessage, printTable, pickList } from "../output.js";
4
+ export const skillCmd = new Command("skill").description("Manage skills and agent skill installations");
5
+ function getFormat(cmd) {
6
+ return cmd.optsWithGlobals().output || "json";
7
+ }
8
+ skillCmd
9
+ .command("list")
10
+ .description("List available skills")
11
+ .option("--role <role>", "Filter by minimum role: owner, admin, developer, viewer")
12
+ .action(async (opts, cmd) => {
13
+ const params = {};
14
+ if (opts.role)
15
+ params.role = opts.role;
16
+ const data = await client.workGet("/v1/skills", params);
17
+ const resp = data;
18
+ const items = pickList(resp.skills || [], [
19
+ "slug", "name", "description", "is_public", "latest_version", "published_version", "role",
20
+ ]);
21
+ const format = getFormat(cmd);
22
+ if (format !== "table") {
23
+ print(format, { items, total: items.length });
24
+ return;
25
+ }
26
+ const rows = items.map((s) => [
27
+ String(s.slug ?? ""),
28
+ String(s.name ?? ""),
29
+ String(s.is_public ?? ""),
30
+ String(s.published_version ?? ""),
31
+ String(s.role ?? ""),
32
+ ]);
33
+ printTable(["Slug", "Name", "Public", "Version", "Role"], rows);
34
+ });
35
+ skillCmd
36
+ .command("search <keyword>")
37
+ .description("Search skills by keyword")
38
+ .action(async (keyword, _opts, cmd) => {
39
+ const data = await client.workGet("/v1/skills/search", { keyword });
40
+ const resp = data;
41
+ const items = pickList(resp.skills || [], [
42
+ "slug", "name", "description", "is_public", "published_version", "role",
43
+ ]);
44
+ const format = getFormat(cmd);
45
+ if (format !== "table") {
46
+ print(format, { items, total: items.length });
47
+ return;
48
+ }
49
+ const rows = items.map((s) => [
50
+ String(s.slug ?? ""),
51
+ String(s.name ?? ""),
52
+ String(s.is_public ?? ""),
53
+ String(s.published_version ?? ""),
54
+ ]);
55
+ printTable(["Slug", "Name", "Public", "Version"], rows);
56
+ });
57
+ skillCmd
58
+ .command("get <slug>")
59
+ .description("Get skill details")
60
+ .action(async (slug, _opts, cmd) => {
61
+ const data = await client.workGet(`/v1/skills/${slug}`);
62
+ print(getFormat(cmd), data);
63
+ });
64
+ skillCmd
65
+ .command("delete <slug>")
66
+ .description("Delete a skill")
67
+ .action(async (slug) => {
68
+ await client.workDel(`/v1/skills/${slug}`);
69
+ printMessage(`Skill "${slug}" deleted.`);
70
+ });
71
+ skillCmd
72
+ .command("visibility <slug> <public|private>")
73
+ .description("Set skill visibility")
74
+ .action(async (slug, visibility) => {
75
+ if (visibility !== "public" && visibility !== "private") {
76
+ throw new Error("Visibility must be 'public' or 'private'");
77
+ }
78
+ await client.workPatch(`/v1/skills/${slug}/visibility`, {
79
+ is_public: visibility === "public",
80
+ });
81
+ printMessage(`Skill "${slug}" visibility set to ${visibility}.`);
82
+ });
83
+ skillCmd
84
+ .command("install <slug>")
85
+ .description("Install a skill to an agent")
86
+ .requiredOption("--agent <name>", "Agent name to install the skill to")
87
+ .option("--version <n>", "Skill version to install (default: latest published)")
88
+ .action(async (slug, opts, cmd) => {
89
+ const body = { agent_name: opts.agent };
90
+ if (opts.version)
91
+ body.version = parseInt(opts.version, 10);
92
+ const data = await client.workPost(`/v1/skills/${slug}/install`, body);
93
+ print(getFormat(cmd), data ?? { message: "Skill installed successfully." });
94
+ });
95
+ skillCmd
96
+ .command("uninstall")
97
+ .description("Uninstall a skill from an agent")
98
+ .requiredOption("--agent <name>", "Agent name")
99
+ .requiredOption("--skill <name>", "Skill directory name (from 'skill agents' output)")
100
+ .action(async (opts) => {
101
+ await client.workDel(`/v1/user/agents/${opts.agent}/skills/${opts.skill}`);
102
+ printMessage(`Skill "${opts.skill}" uninstalled from agent "${opts.agent}".`);
103
+ });
104
+ skillCmd
105
+ .command("agents")
106
+ .description("List skills installed on agents")
107
+ .option("--agent <name>", "Show skills for a specific agent only")
108
+ .action(async (opts, cmd) => {
109
+ const format = getFormat(cmd);
110
+ if (opts.agent) {
111
+ const data = await client.workGet(`/v1/user/agents/${opts.agent}/skills`);
112
+ print(format, data);
113
+ }
114
+ else {
115
+ const data = await client.workGet("/v1/user/agents/skills");
116
+ print(format, data);
117
+ }
118
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const threadCmd: Command;