@2kw/ai 6.1.0-dev.8 → 6.2.0-dev.13

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.
@@ -20,6 +20,29 @@ export function makeConversationsCommand() {
20
20
  formatDetail(data, command);
21
21
  });
22
22
  });
23
+ cmd
24
+ .command("list")
25
+ .description("List conversations, most recently active first")
26
+ .option("--agent-id <id>", "Only conversations bound to this agent")
27
+ .option("--scope <scope>", "mine (default) or org; org needs MEMBER or above", "mine")
28
+ .option("--page <n>", "Page number, starting at 0", "0")
29
+ .option("--size <n>", "Page size, 1 to 100", "30")
30
+ .action(async (opts, command) => {
31
+ await runAction(command, async () => {
32
+ const client = getClient(command);
33
+ const { data } = await client.GET("/v1/conversations", {
34
+ params: {
35
+ query: {
36
+ agent_id: opts.agentId,
37
+ scope: opts.scope,
38
+ page: Number(opts.page),
39
+ size: Number(opts.size),
40
+ },
41
+ },
42
+ });
43
+ formatDetail(data, command);
44
+ });
45
+ });
23
46
  cmd
24
47
  .command("get")
25
48
  .description("Retrieve a conversation by id")
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ /** Install, sync and detach Claude Code plugins (#640). */
3
+ export declare function makePluginsCommand(): Command;
4
+ //# sourceMappingURL=plugins.d.ts.map
@@ -0,0 +1,88 @@
1
+ import { Command, Option } from "commander";
2
+ import { getClient, runAction } from "../lib/client.js";
3
+ import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
4
+ import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
5
+ const REF_POLICY_HELP = "track:<branch-or-tag> to follow it, pin:<sha> to freeze";
6
+ /** Install, sync and detach Claude Code plugins (#640). */
7
+ export function makePluginsCommand() {
8
+ const cmd = new Command("plugins").description("Manage Claude Code plugins imported from git");
9
+ const list = new Command("list").description("List installed plugins");
10
+ addPaginationOptions(list);
11
+ list.addOption(new Option("--status <status>", "Filter by status").choices(["ACTIVE", "DISABLED"]));
12
+ list.action(async (opts, command) => {
13
+ await runAction(command, async () => {
14
+ const client = getClient(command);
15
+ const { data } = await client.GET("/v1/plugins", {
16
+ params: { query: { status: opts.status, ...paginationParams(opts) } },
17
+ });
18
+ formatPage(data, command, ["id", "name", "gitUrl", "refPolicy", "status", "lastSyncedSha"]);
19
+ });
20
+ });
21
+ cmd.addCommand(list);
22
+ cmd
23
+ .command("get")
24
+ .description("Get an installed plugin, including its last sync report")
25
+ .argument("<id>", "Plugin ID")
26
+ .action(async (id, _opts, command) => {
27
+ await runAction(command, async () => {
28
+ const client = getClient(command);
29
+ const { data } = await client.GET("/v1/plugins/{id}", { params: { path: { id } } });
30
+ formatDetail(data, command);
31
+ });
32
+ });
33
+ cmd
34
+ .command("install")
35
+ .description("Install a plugin from a git repository and run its first sync (admin)")
36
+ .argument("<git-url>", "https remote of a repository in Claude Code plugin layout")
37
+ .option("-n, --name <name>", "Org-local name (default: the manifest name, slugified)")
38
+ .option("--ref-policy <policy>", REF_POLICY_HELP)
39
+ .action(async (gitUrl, opts, command) => {
40
+ await runAction(command, async () => {
41
+ const client = getClient(command);
42
+ const { data } = await client.POST("/v1/plugins", {
43
+ body: { gitUrl, name: opts.name, refPolicy: opts.refPolicy },
44
+ });
45
+ formatDetail(data, command);
46
+ });
47
+ });
48
+ cmd
49
+ .command("update")
50
+ .description("Change a plugin's ref policy or status (admin)")
51
+ .argument("<id>", "Plugin ID")
52
+ .option("--ref-policy <policy>", REF_POLICY_HELP)
53
+ .addOption(new Option("--status <status>", "ACTIVE or DISABLED").choices(["ACTIVE", "DISABLED"]))
54
+ .action(async (id, opts, command) => {
55
+ await runAction(command, async () => {
56
+ const client = getClient(command);
57
+ const { data } = await client.PATCH("/v1/plugins/{id}", {
58
+ params: { path: { id } },
59
+ body: { refPolicy: opts.refPolicy, status: opts.status },
60
+ });
61
+ formatDetail(data, command);
62
+ });
63
+ });
64
+ cmd
65
+ .command("sync")
66
+ .description("Sync a plugin now; unchanged is a no-op (admin)")
67
+ .argument("<id>", "Plugin ID")
68
+ .action(async (id, _opts, command) => {
69
+ await runAction(command, async () => {
70
+ const client = getClient(command);
71
+ const { data } = await client.POST("/v1/plugins/{id}/sync", { params: { path: { id } } });
72
+ formatDetail(data, command);
73
+ });
74
+ });
75
+ cmd
76
+ .command("delete")
77
+ .description("Detach a plugin; imported skills stay (admin)")
78
+ .argument("<id>", "Plugin ID")
79
+ .action(async (id, _opts, command) => {
80
+ await runAction(command, async () => {
81
+ const client = getClient(command);
82
+ await client.DELETE("/v1/plugins/{id}", { params: { path: { id } } });
83
+ formatSuccess(`Plugin ${id} detached`, command);
84
+ });
85
+ });
86
+ return cmd;
87
+ }
88
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1,11 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Annotation queues let a human reviewer work through a curated list of
4
+ * subjects (run results, traces, spans, dataset items). This command group
5
+ * covers the queue itself plus the items inside it: creating a queue,
6
+ * bulk-adding subjects, claiming the next eligible item, and recording the
7
+ * outcome — the full loop an agent or a human reviewer needs to run review
8
+ * end to end from the CLI.
9
+ */
10
+ export declare function makeQueuesCommand(): Command;
11
+ //# sourceMappingURL=queues.d.ts.map
@@ -0,0 +1,244 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { getClient, runAction } from "../lib/client.js";
4
+ import { isJsonOutput } from "../lib/config.js";
5
+ import { formatList, formatDetail, formatSuccess } from "../lib/output.js";
6
+ /**
7
+ * Annotation queues let a human reviewer work through a curated list of
8
+ * subjects (run results, traces, spans, dataset items). This command group
9
+ * covers the queue itself plus the items inside it: creating a queue,
10
+ * bulk-adding subjects, claiming the next eligible item, and recording the
11
+ * outcome — the full loop an agent or a human reviewer needs to run review
12
+ * end to end from the CLI.
13
+ */
14
+ export function makeQueuesCommand() {
15
+ const cmd = new Command("queues").description("Manage annotation queues for human review");
16
+ // ── list ──────────────────────────────────────────────────────
17
+ cmd
18
+ .command("list")
19
+ .description("List annotation queues")
20
+ .option("--archived", "List archived queues instead of active ones", false)
21
+ .action(async (opts, command) => {
22
+ await runAction(command, async () => {
23
+ const client = getClient(command);
24
+ const { data } = await client.GET("/v1/annotation-queues", {
25
+ params: { query: { archived: opts.archived } },
26
+ });
27
+ formatList(data, command, [
28
+ "id",
29
+ "name",
30
+ "description",
31
+ "archivedAt",
32
+ "createdAt",
33
+ ]);
34
+ });
35
+ });
36
+ // ── get ───────────────────────────────────────────────────────
37
+ cmd
38
+ .command("get")
39
+ .description("Get an annotation queue by ID")
40
+ .argument("<id>", "Queue ID")
41
+ .action(async (id, _opts, command) => {
42
+ await runAction(command, async () => {
43
+ const client = getClient(command);
44
+ const { data } = await client.GET("/v1/annotation-queues/{id}", {
45
+ params: { path: { id } },
46
+ });
47
+ formatDetail(data, command);
48
+ });
49
+ });
50
+ // ── create ────────────────────────────────────────────────────
51
+ cmd
52
+ .command("create")
53
+ .description("Create a new annotation queue")
54
+ .requiredOption("-n, --name <name>", "Queue name")
55
+ .option("-d, --description <text>", "Queue description")
56
+ .action(async (opts, command) => {
57
+ await runAction(command, async () => {
58
+ const client = getClient(command);
59
+ const { data } = await client.POST("/v1/annotation-queues", {
60
+ body: { name: opts.name, description: opts.description },
61
+ });
62
+ formatDetail(data, command);
63
+ });
64
+ });
65
+ // ── update ────────────────────────────────────────────────────
66
+ cmd
67
+ .command("update")
68
+ .description("Update a queue's name or description")
69
+ .argument("<id>", "Queue ID")
70
+ .option("-n, --name <name>", "New name")
71
+ .option("-d, --description <text>", "New description")
72
+ .action(async (id, opts, command) => {
73
+ await runAction(command, async () => {
74
+ const client = getClient(command);
75
+ const body = {};
76
+ if (opts.name !== undefined)
77
+ body.name = opts.name;
78
+ if (opts.description !== undefined)
79
+ body.description = opts.description;
80
+ const { data } = await client.PATCH("/v1/annotation-queues/{id}", {
81
+ params: { path: { id } },
82
+ body: body,
83
+ });
84
+ formatDetail(data, command);
85
+ });
86
+ });
87
+ // ── archive ───────────────────────────────────────────────────
88
+ cmd
89
+ .command("archive")
90
+ .description("Archive a queue (idempotent)")
91
+ .argument("<id>", "Queue ID")
92
+ .action(async (id, _opts, command) => {
93
+ await runAction(command, async () => {
94
+ const client = getClient(command);
95
+ await client.POST("/v1/annotation-queues/{id}/archive", {
96
+ params: { path: { id } },
97
+ });
98
+ formatSuccess(`Queue ${id} archived.`, command);
99
+ });
100
+ });
101
+ // ── by-subject ────────────────────────────────────────────────
102
+ cmd
103
+ .command("by-subject")
104
+ .description("Find queue items referencing a subject, across every queue")
105
+ .requiredOption("--subject-type <type>", "RUN_RESULT | TRACE | SPAN | DATASET_ITEM")
106
+ .requiredOption("--subject-id <id>", "Subject ID")
107
+ .action(async (opts, command) => {
108
+ await runAction(command, async () => {
109
+ const client = getClient(command);
110
+ const { data } = await client.GET("/v1/annotation-queues/by-subject", {
111
+ params: {
112
+ query: { subjectType: opts.subjectType, subjectId: opts.subjectId },
113
+ },
114
+ });
115
+ formatList(data, command, [
116
+ "id",
117
+ "queueId",
118
+ "subjectType",
119
+ "subjectId",
120
+ "status",
121
+ "assignedTo",
122
+ ]);
123
+ });
124
+ });
125
+ // ── next ──────────────────────────────────────────────────────
126
+ cmd
127
+ .command("next")
128
+ .description("Claim the next eligible item in a queue for the caller")
129
+ .argument("<id>", "Queue ID")
130
+ .action(async (id, _opts, command) => {
131
+ await runAction(command, async () => {
132
+ const client = getClient(command);
133
+ const { data } = await client.GET("/v1/annotation-queues/{id}/items/next", {
134
+ params: { path: { id } },
135
+ });
136
+ if (!data) {
137
+ // 204 No Content: the queue has nothing left for this caller. A
138
+ // normal end state, not an error — printed plainly either way.
139
+ if (isJsonOutput(command)) {
140
+ console.log(JSON.stringify(null));
141
+ }
142
+ else {
143
+ console.log(chalk.dim("No items available to claim — the queue is empty for you."));
144
+ }
145
+ return;
146
+ }
147
+ formatDetail(data, command);
148
+ });
149
+ });
150
+ // ── items ─────────────────────────────────────────────────────
151
+ const items = new Command("items").description("Manage items within an annotation queue");
152
+ items
153
+ .command("list")
154
+ .description("List items in a queue")
155
+ .requiredOption("--queue <id>", "Queue ID")
156
+ .option("--status <status>", "PENDING | IN_PROGRESS | DONE | SKIPPED")
157
+ .option("--assigned-to <userId>", "Filter by assignee")
158
+ .option("-p, --page <number>", "Page number (0-indexed)", "0")
159
+ .option("--size <number>", "Page size", "50")
160
+ .action(async (opts, command) => {
161
+ await runAction(command, async () => {
162
+ const client = getClient(command);
163
+ const { data } = await client.GET("/v1/annotation-queues/{id}/items", {
164
+ params: {
165
+ path: { id: opts.queue },
166
+ query: {
167
+ status: opts.status,
168
+ assignedTo: opts.assignedTo,
169
+ page: opts.page !== undefined ? parseInt(opts.page, 10) : undefined,
170
+ size: opts.size !== undefined ? parseInt(opts.size, 10) : undefined,
171
+ },
172
+ },
173
+ });
174
+ formatList(data, command, [
175
+ "id",
176
+ "subjectType",
177
+ "subjectId",
178
+ "status",
179
+ "assignedTo",
180
+ "createdAt",
181
+ ]);
182
+ });
183
+ });
184
+ items
185
+ .command("add")
186
+ .description("Bulk-add subjects to a queue (duplicates skipped)")
187
+ .requiredOption("--queue <id>", "Queue ID")
188
+ .requiredOption("--items <json>", 'JSON array of {"subjectType","subjectId"} objects')
189
+ .action(async (opts, command) => {
190
+ await runAction(command, async () => {
191
+ const client = getClient(command);
192
+ const parsedItems = JSON.parse(opts.items);
193
+ const { data } = await client.POST("/v1/annotation-queues/{id}/items", {
194
+ params: { path: { id: opts.queue } },
195
+ body: { items: parsedItems },
196
+ });
197
+ formatList(data, command, [
198
+ "id",
199
+ "subjectType",
200
+ "subjectId",
201
+ "status",
202
+ ]);
203
+ });
204
+ });
205
+ items
206
+ .command("get")
207
+ .description("Get a single queue item")
208
+ .argument("<itemId>", "Item ID")
209
+ .requiredOption("--queue <id>", "Queue ID")
210
+ .action(async (itemId, opts, command) => {
211
+ await runAction(command, async () => {
212
+ const client = getClient(command);
213
+ const { data } = await client.GET("/v1/annotation-queues/{id}/items/{itemId}", {
214
+ params: { path: { id: opts.queue, itemId } },
215
+ });
216
+ formatDetail(data, command);
217
+ });
218
+ });
219
+ items
220
+ .command("update")
221
+ .description("Update a queue item's status or assignee — the outcome of a review")
222
+ .argument("<itemId>", "Item ID")
223
+ .requiredOption("--queue <id>", "Queue ID")
224
+ .option("--status <status>", "PENDING | IN_PROGRESS | DONE | SKIPPED")
225
+ .option("--assigned-to <userId>", "New assignee")
226
+ .action(async (itemId, opts, command) => {
227
+ await runAction(command, async () => {
228
+ const client = getClient(command);
229
+ const body = {};
230
+ if (opts.status !== undefined)
231
+ body.status = opts.status;
232
+ if (opts.assignedTo !== undefined)
233
+ body.assignedTo = opts.assignedTo;
234
+ const { data } = await client.PATCH("/v1/annotation-queues/{id}/items/{itemId}", {
235
+ params: { path: { id: opts.queue, itemId } },
236
+ body: body,
237
+ });
238
+ formatDetail(data, command);
239
+ });
240
+ });
241
+ cmd.addCommand(items);
242
+ return cmd;
243
+ }
244
+ //# sourceMappingURL=queues.js.map
@@ -1,14 +1,18 @@
1
1
  import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
- import { formatDetail } from "../lib/output.js";
3
+ import { formatDetail, formatList } from "../lib/output.js";
4
4
  export function makeScoresCommand() {
5
- const cmd = new Command("scores").description("Record evaluation scores on experiment run results");
5
+ const cmd = new Command("scores").description("Record and review evaluation scores on experiment run results and other subjects");
6
6
  cmd
7
7
  .command("human")
8
- .description("Record a human score for a run result. Upserts by (runResultId, evaluatorId, annotatorId).")
9
- .requiredOption("--run-result <id>", "Run result ID")
8
+ .description("Record a human score. Upserts by (subject, evaluatorId, annotatorId) — pass either " +
9
+ "--run-result, or --subject-type/--subject-id for a trace, span, or dataset item.")
10
+ .option("--run-result <id>", "Run result ID (legacy shape)")
11
+ .option("--subject-type <type>", "RUN_RESULT | TRACE | SPAN | DATASET_ITEM")
12
+ .option("--subject-id <id>", "Subject ID")
13
+ .option("--queue-item <id>", "Annotation queue item ID this score resolves")
10
14
  .requiredOption("--evaluator <id>", "Evaluator identifier (e.g. 'helpfulness' or a template id)")
11
- .requiredOption("--score <number>", "Score between 0.0 and 1.0", parseFloat)
15
+ .option("--score <number>", "Score between 0.0 and 1.0", parseFloat)
12
16
  .option("--label <text>", "Short label (e.g. 'pass', 'fail', 'good')")
13
17
  .option("--comment <text>", "Optional reviewer note")
14
18
  .action(async (opts, command) => {
@@ -17,6 +21,9 @@ export function makeScoresCommand() {
17
21
  const { data } = await client.POST("/v1/evaluation-scores/human", {
18
22
  body: {
19
23
  runResultId: opts.runResult,
24
+ subjectType: opts.subjectType,
25
+ subjectId: opts.subjectId,
26
+ annotationQueueItemId: opts.queueItem,
20
27
  evaluatorId: opts.evaluator,
21
28
  score: opts.score,
22
29
  label: opts.label,
@@ -26,6 +33,37 @@ export function makeScoresCommand() {
26
33
  formatDetail(data, command);
27
34
  });
28
35
  });
36
+ cmd
37
+ .command("list")
38
+ .description("List human scores filed against a subject. Defaults to the caller's own scores — " +
39
+ "pass --annotator to see another reviewer's.")
40
+ .requiredOption("--subject-type <type>", "RUN_RESULT | TRACE | SPAN | DATASET_ITEM")
41
+ .requiredOption("--subject-id <id>", "Subject ID")
42
+ .option("--annotator <id>", "Annotator ID (defaults to the caller)")
43
+ .action(async (opts, command) => {
44
+ await runAction(command, async () => {
45
+ const client = getClient(command);
46
+ const { data } = await client.GET("/v1/evaluation-scores/human", {
47
+ params: {
48
+ query: {
49
+ subjectType: opts.subjectType,
50
+ subjectId: opts.subjectId,
51
+ annotatorId: opts.annotator,
52
+ },
53
+ },
54
+ });
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ formatList(data, command, [
57
+ "id",
58
+ "evaluatorId",
59
+ "score",
60
+ "label",
61
+ "annotatorId",
62
+ "comment",
63
+ "createdAt",
64
+ ]);
65
+ });
66
+ });
29
67
  return cmd;
30
68
  }
31
69
  //# sourceMappingURL=scores.js.map
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ /** Manage skill labels (#642). */
3
+ export declare function makeSkillLabelsCommand(): Command;
4
+ //# sourceMappingURL=skill-labels.d.ts.map
@@ -0,0 +1,64 @@
1
+ import { Command } from "commander";
2
+ import { getClient, runAction } from "../lib/client.js";
3
+ import { formatList, formatDetail, formatSuccess } from "../lib/output.js";
4
+ /** Manage skill labels (#642). */
5
+ export function makeSkillLabelsCommand() {
6
+ const cmd = new Command("labels").description("Manage skill labels");
7
+ cmd
8
+ .command("list")
9
+ .description("List labels for a skill")
10
+ .argument("<id>", "Skill ID")
11
+ .action(async (id, _opts, command) => {
12
+ await runAction(command, async () => {
13
+ const client = getClient(command);
14
+ const { data } = await client.GET("/v1/skills/{id}/labels", { params: { path: { id } } });
15
+ formatList(data, command, ["name", "skillVersionId", "versionNumber"]);
16
+ });
17
+ });
18
+ cmd
19
+ .command("create")
20
+ .description("Create a label for a skill version")
21
+ .argument("<id>", "Skill ID")
22
+ .argument("<name>", "Label name")
23
+ .requiredOption("--version-id <id>", "Skill version ID to label")
24
+ .action(async (id, name, opts, command) => {
25
+ await runAction(command, async () => {
26
+ const client = getClient(command);
27
+ const { data } = await client.POST("/v1/skills/{id}/labels", {
28
+ params: { path: { id } },
29
+ body: { name, skillVersionId: opts.versionId },
30
+ });
31
+ formatDetail(data, command);
32
+ });
33
+ });
34
+ cmd
35
+ .command("update")
36
+ .description("Update a label to point to a different version")
37
+ .argument("<id>", "Skill ID")
38
+ .argument("<name>", "Label name")
39
+ .requiredOption("--version-id <id>", "New skill version ID")
40
+ .action(async (id, name, opts, command) => {
41
+ await runAction(command, async () => {
42
+ const client = getClient(command);
43
+ const { data } = await client.PUT("/v1/skills/{id}/labels/{name}", {
44
+ params: { path: { id, name } },
45
+ body: { skillVersionId: opts.versionId },
46
+ });
47
+ formatDetail(data, command);
48
+ });
49
+ });
50
+ cmd
51
+ .command("delete")
52
+ .description("Delete a skill label")
53
+ .argument("<id>", "Skill ID")
54
+ .argument("<name>", "Label name")
55
+ .action(async (id, name, _opts, command) => {
56
+ await runAction(command, async () => {
57
+ const client = getClient(command);
58
+ await client.DELETE("/v1/skills/{id}/labels/{name}", { params: { path: { id, name } } });
59
+ formatSuccess(`Label "${name}" deleted.`, command);
60
+ });
61
+ });
62
+ return cmd;
63
+ }
64
+ //# sourceMappingURL=skill-labels.js.map
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ /** Manage skill versions (#642). */
3
+ export declare function makeSkillVersionsCommand(): Command;
4
+ //# sourceMappingURL=skill-versions.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { Command } from "commander";
2
+ import { readFileSync } from "node:fs";
3
+ import { getClient, runAction } from "../lib/client.js";
4
+ import { formatPage, formatDetail } from "../lib/output.js";
5
+ import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
6
+ /** Manage skill versions (#642). */
7
+ export function makeSkillVersionsCommand() {
8
+ const cmd = new Command("versions").description("Manage skill versions");
9
+ const list = new Command("list").description("List versions of a skill").argument("<id>", "Skill ID");
10
+ addPaginationOptions(list);
11
+ list.action(async (id, opts, command) => {
12
+ await runAction(command, async () => {
13
+ const client = getClient(command);
14
+ const { data } = await client.GET("/v1/skills/{id}/versions", {
15
+ params: { path: { id }, query: paginationParams(opts) },
16
+ });
17
+ formatPage(data, command, ["id", "versionNumber", "description", "contentHash"]);
18
+ });
19
+ });
20
+ cmd.addCommand(list);
21
+ cmd
22
+ .command("get")
23
+ .description("Get a specific skill version")
24
+ .argument("<id>", "Skill ID")
25
+ .argument("<versionNumber>", "Version number")
26
+ .action(async (id, versionNumber, _opts, command) => {
27
+ await runAction(command, async () => {
28
+ const client = getClient(command);
29
+ const { data } = await client.GET("/v1/skills/{id}/versions/{versionNumber}", {
30
+ params: { path: { id, versionNumber: Number(versionNumber) } },
31
+ });
32
+ formatDetail(data, command);
33
+ });
34
+ });
35
+ cmd
36
+ .command("create")
37
+ .description("Create a skill version from a markdown body file")
38
+ .argument("<id>", "Skill ID")
39
+ .requiredOption("--description <text>", "Version description")
40
+ .requiredOption("--body-file <path>", "Markdown body file")
41
+ .action(async (id, opts, command) => {
42
+ await runAction(command, async () => {
43
+ const client = getClient(command);
44
+ const { data } = await client.POST("/v1/skills/{id}/versions", {
45
+ params: { path: { id } },
46
+ body: { description: opts.description, body: readFileSync(opts.bodyFile, "utf-8") },
47
+ });
48
+ formatDetail(data, command);
49
+ });
50
+ });
51
+ return cmd;
52
+ }
53
+ //# sourceMappingURL=skill-versions.js.map
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ /** Manage skills, versions and labels (#642). */
3
+ export declare function makeSkillsCommand(): Command;
4
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1,161 @@
1
+ import { Command, Option } from "commander";
2
+ import { statSync, writeFileSync } from "node:fs";
3
+ import { basename, extname } from "node:path";
4
+ import { getClient, resolveAuthHeader, runAction } from "../lib/client.js";
5
+ import { resolveConfig } from "../lib/config.js";
6
+ import { BackboneApiError } from "../lib/errors.js";
7
+ import { fileToBlob } from "../lib/multipart.js";
8
+ import { formatPage, formatDetail, formatSuccess, withSpinner } from "../lib/output.js";
9
+ import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
10
+ import { makeSkillVersionsCommand } from "./skill-versions.js";
11
+ import { makeSkillLabelsCommand } from "./skill-labels.js";
12
+ async function checkResponse(res) {
13
+ if (res.ok)
14
+ return;
15
+ const body = await res.json().catch(() => ({}));
16
+ throw new BackboneApiError({
17
+ status: body.status ?? res.status,
18
+ error: body.title ?? body.error ?? res.statusText,
19
+ message: body.detail ?? body.message ?? `HTTP ${res.status}: ${res.statusText}`,
20
+ timestamp: body.timestamp ?? new Date().toISOString(),
21
+ });
22
+ }
23
+ /** Manage skills, versions and labels (#642). */
24
+ export function makeSkillsCommand() {
25
+ const cmd = new Command("skills").description("Manage skills");
26
+ const list = new Command("list").description("List skills");
27
+ addPaginationOptions(list);
28
+ list.option("-s, --search <term>", "Filter by name");
29
+ list.addOption(new Option("--status <status>", "Filter by status").choices(["ACTIVE", "ARCHIVED"]));
30
+ list.action(async (opts, command) => {
31
+ await runAction(command, async () => {
32
+ const client = getClient(command);
33
+ const { data } = await client.GET("/v1/skills", {
34
+ params: { query: { search: opts.search, status: opts.status, ...paginationParams(opts) } },
35
+ });
36
+ formatPage(data, command, ["id", "name", "status", "latestVersionNumber"]);
37
+ });
38
+ });
39
+ cmd.addCommand(list);
40
+ cmd
41
+ .command("get")
42
+ .description("Get a skill by ID")
43
+ .argument("<id>", "Skill ID")
44
+ .action(async (id, _opts, command) => {
45
+ await runAction(command, async () => {
46
+ const client = getClient(command);
47
+ const { data } = await client.GET("/v1/skills/{id}", { params: { path: { id } } });
48
+ formatDetail(data, command);
49
+ });
50
+ });
51
+ cmd
52
+ .command("create")
53
+ .description("Create a skill (add content with versions create or import)")
54
+ .argument("<name>", "Skill name")
55
+ .action(async (name, _opts, command) => {
56
+ await runAction(command, async () => {
57
+ const client = getClient(command);
58
+ const { data } = await client.POST("/v1/skills", { body: { name } });
59
+ formatDetail(data, command);
60
+ });
61
+ });
62
+ cmd
63
+ .command("update")
64
+ .description("Update a skill's status")
65
+ .argument("<id>", "Skill ID")
66
+ .addOption(new Option("--status <status>", "New status").choices(["ACTIVE", "ARCHIVED"]).makeOptionMandatory())
67
+ .action(async (id, opts, command) => {
68
+ await runAction(command, async () => {
69
+ const client = getClient(command);
70
+ const { data } = await client.PUT("/v1/skills/{id}", {
71
+ params: { path: { id } },
72
+ body: { status: opts.status },
73
+ });
74
+ formatDetail(data, command);
75
+ });
76
+ });
77
+ cmd
78
+ .command("delete")
79
+ .description("Delete a skill")
80
+ .argument("<id>", "Skill ID")
81
+ .action(async (id, _opts, command) => {
82
+ await runAction(command, async () => {
83
+ const client = getClient(command);
84
+ await client.DELETE("/v1/skills/{id}", { params: { path: { id } } });
85
+ formatSuccess(`Skill ${id} deleted.`, command);
86
+ });
87
+ });
88
+ cmd
89
+ .command("resolve")
90
+ .description("Resolve a skill by name, name@label or name@version")
91
+ .argument("<ref>", "Skill reference")
92
+ .action(async (ref, _opts, command) => {
93
+ await runAction(command, async () => {
94
+ const client = getClient(command);
95
+ const { data } = await client.GET("/v1/skills/resolve", { params: { query: { ref } } });
96
+ formatDetail(data, command);
97
+ });
98
+ });
99
+ cmd
100
+ .command("import")
101
+ .description("Import a local SKILL.md or zip archive")
102
+ .argument("<path>", "Local SKILL.md or .zip file")
103
+ .action(async (path, _opts, command) => {
104
+ await runAction(command, async () => {
105
+ if (statSync(path).isDirectory())
106
+ throw new Error("Cannot import a directory; zip it first.");
107
+ if (basename(path) !== "SKILL.md" && extname(path).toLowerCase() !== ".zip") {
108
+ throw new Error("Import requires a SKILL.md or .zip file.");
109
+ }
110
+ const config = resolveConfig(command);
111
+ const authHeader = await resolveAuthHeader(config);
112
+ const { blob, filename } = fileToBlob(path);
113
+ const formData = new FormData();
114
+ formData.append("file", blob, filename);
115
+ const data = await withSpinner("Importing skill...", async () => {
116
+ const res = await fetch(`${config.baseUrl.replace(/\/+$/, "")}/v1/skills/import`, {
117
+ method: "POST",
118
+ headers: { Authorization: authHeader },
119
+ body: formData,
120
+ });
121
+ await checkResponse(res);
122
+ return res.json();
123
+ });
124
+ formatDetail(data, command);
125
+ });
126
+ });
127
+ cmd
128
+ .command("export")
129
+ .description("Export a skill version as a zip archive")
130
+ .argument("<id>", "Skill ID")
131
+ .argument("<versionNumber>", "Version number")
132
+ .option("-o, --output <file>", "Output path (default: <skill-name>-v<n>.zip)")
133
+ .action(async (id, versionNumber, opts, command) => {
134
+ await runAction(command, async () => {
135
+ let outputPath = opts.output;
136
+ if (!outputPath) {
137
+ const client = getClient(command);
138
+ const { data } = await client.GET("/v1/skills/{id}", { params: { path: { id } } });
139
+ if (!data?.name)
140
+ throw new Error("Skill name missing; specify an output path with -o.");
141
+ outputPath = `${basename(data.name)}-v${versionNumber}.zip`;
142
+ }
143
+ const config = resolveConfig(command);
144
+ const authHeader = await resolveAuthHeader(config);
145
+ const bytes = await withSpinner("Exporting skill...", async () => {
146
+ const baseUrl = config.baseUrl.replace(/\/+$/, "");
147
+ const res = await fetch(`${baseUrl}/v1/skills/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionNumber)}/export`, {
148
+ headers: { Authorization: authHeader },
149
+ });
150
+ await checkResponse(res);
151
+ return Buffer.from(await res.arrayBuffer());
152
+ });
153
+ writeFileSync(outputPath, bytes);
154
+ formatSuccess(`Wrote ${bytes.length} bytes to ${outputPath}`, command);
155
+ });
156
+ });
157
+ cmd.addCommand(makeSkillVersionsCommand());
158
+ cmd.addCommand(makeSkillLabelsCommand());
159
+ return cmd;
160
+ }
161
+ //# sourceMappingURL=skills.js.map
package/dist/index.js CHANGED
@@ -7,6 +7,8 @@ const pkg = require("../package.json");
7
7
  import { makeConfigCommand } from "./commands/config.js";
8
8
  import { makeSchemasCommand } from "./commands/schemas.js";
9
9
  import { makePromptsCommand } from "./commands/prompts.js";
10
+ import { makeSkillsCommand } from "./commands/skills.js";
11
+ import { makePluginsCommand } from "./commands/plugins.js";
10
12
  import { makeExtractionsCommand } from "./commands/extractions.js";
11
13
  import { makeConvertCommand } from "./commands/convert.js";
12
14
  import { makeAiCommand } from "./commands/ai.js";
@@ -21,6 +23,7 @@ import { makeExperimentsCommand } from "./commands/experiments.js";
21
23
  import { makeTracingCommand } from "./commands/tracing.js";
22
24
  import { makeEvaluatorsCommand } from "./commands/evaluators.js";
23
25
  import { makeScoresCommand } from "./commands/scores.js";
26
+ import { makeQueuesCommand } from "./commands/queues.js";
24
27
  import { makeAgentsCommand } from "./commands/agents.js";
25
28
  import { makeConversationsCommand } from "./commands/conversations.js";
26
29
  import { makeKnowledgeCommand } from "./commands/knowledge.js";
@@ -42,6 +45,8 @@ program.addCommand(makeConfigCommand());
42
45
  program.addCommand(makeContextCommand());
43
46
  program.addCommand(makeSchemasCommand());
44
47
  program.addCommand(makePromptsCommand());
48
+ program.addCommand(makeSkillsCommand());
49
+ program.addCommand(makePluginsCommand());
45
50
  program.addCommand(makeExtractionsCommand());
46
51
  program.addCommand(makeConvertCommand());
47
52
  program.addCommand(makeAiCommand());
@@ -53,6 +58,7 @@ program.addCommand(makeDatasetsCommand());
53
58
  program.addCommand(makeExperimentsCommand());
54
59
  program.addCommand(makeEvaluatorsCommand());
55
60
  program.addCommand(makeScoresCommand());
61
+ program.addCommand(makeQueuesCommand());
56
62
  program.addCommand(makeTracingCommand());
57
63
  program.addCommand(makeAgentsCommand());
58
64
  program.addCommand(makeConversationsCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "6.1.0-dev.8",
3
+ "version": "6.2.0-dev.13",
4
4
  "description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
5
5
  "keywords": [
6
6
  "cli",
@@ -54,7 +54,7 @@
54
54
  "ora": "^8.2.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@types/node": "^22.19.17",
57
+ "@types/node": "^22.20.1",
58
58
  "openapi-typescript": "^7.13.0",
59
59
  "tsx": "^4.21.1",
60
60
  "typescript": "^5.9.3",