@2kw/ai 6.0.0-dev.4 → 6.1.0-dev.7

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.
@@ -97,6 +97,40 @@ export function makeBillingCommand() {
97
97
  }
98
98
  });
99
99
  });
100
+ cmd
101
+ .command("has-available")
102
+ .description("Check whether a metered resource has enough available capacity")
103
+ .argument("<type>", "extraction-credits, conversion-credits, or gateway-tokens")
104
+ .option("--required <n>", "Amount required (default: 1)")
105
+ .action(async (type, opts, command) => {
106
+ await runAction(command, async () => {
107
+ const client = getClient(command);
108
+ const typeMap = {
109
+ "extraction-credits": "EXTRACTION_CREDITS",
110
+ "conversion-credits": "CONVERSION_CREDITS",
111
+ "gateway-tokens": "GATEWAY_TOKENS",
112
+ };
113
+ const apiType = typeMap[type] ?? type.toUpperCase();
114
+ const { data } = await client.GET("/v1/billing/has-available", {
115
+ params: {
116
+ query: {
117
+ type: apiType,
118
+ required: opts.required !== undefined ? Number(opts.required) : undefined,
119
+ },
120
+ },
121
+ });
122
+ const result = data?.result;
123
+ if (isJsonOutput(command)) {
124
+ console.log(JSON.stringify({ type: apiType, hasAvailable: result }));
125
+ }
126
+ else if (result) {
127
+ console.log(chalk.green(`${apiType} has available capacity.`));
128
+ }
129
+ else {
130
+ console.log(chalk.yellow(`${apiType} does NOT have available capacity.`));
131
+ }
132
+ });
133
+ });
100
134
  return cmd;
101
135
  }
102
136
  //# sourceMappingURL=billing.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function makeConversationsCommand(): Command;
3
+ //# sourceMappingURL=conversations.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { Command } from "commander";
2
+ import { getClient, runAction } from "../lib/client.js";
3
+ import { formatDetail, formatSuccess } from "../lib/output.js";
4
+ export function makeConversationsCommand() {
5
+ const cmd = new Command("conversations").description("Manage conversations");
6
+ cmd
7
+ .command("create")
8
+ .description("Create a conversation")
9
+ .option("--items <json>", "Initial items as a JSON array")
10
+ .option("--metadata <json>", "Metadata as JSON")
11
+ .action(async (opts, command) => {
12
+ await runAction(command, async () => {
13
+ const client = getClient(command);
14
+ const { data } = await client.POST("/v1/conversations", {
15
+ body: {
16
+ items: opts.items ? JSON.parse(opts.items) : undefined,
17
+ metadata: opts.metadata ? JSON.parse(opts.metadata) : undefined,
18
+ },
19
+ });
20
+ formatDetail(data, command);
21
+ });
22
+ });
23
+ cmd
24
+ .command("get")
25
+ .description("Retrieve a conversation by id")
26
+ .argument("<conversationId>", "Conversation ID")
27
+ .action(async (conversationId, _opts, command) => {
28
+ await runAction(command, async () => {
29
+ const client = getClient(command);
30
+ const { data } = await client.GET("/v1/conversations/{conversationId}", {
31
+ params: { path: { conversationId } },
32
+ });
33
+ formatDetail(data, command);
34
+ });
35
+ });
36
+ cmd
37
+ .command("update")
38
+ .description("Replace a conversation's metadata")
39
+ .argument("<conversationId>", "Conversation ID")
40
+ .requiredOption("--metadata <json>", "New metadata as JSON")
41
+ .action(async (conversationId, opts, command) => {
42
+ await runAction(command, async () => {
43
+ const client = getClient(command);
44
+ const { data } = await client.POST("/v1/conversations/{conversationId}", {
45
+ params: { path: { conversationId } },
46
+ body: { metadata: JSON.parse(opts.metadata) },
47
+ });
48
+ formatDetail(data, command);
49
+ });
50
+ });
51
+ cmd
52
+ .command("delete")
53
+ .description("Delete a conversation and its recorded responses")
54
+ .argument("<conversationId>", "Conversation ID")
55
+ .action(async (conversationId, _opts, command) => {
56
+ await runAction(command, async () => {
57
+ const client = getClient(command);
58
+ await client.DELETE("/v1/conversations/{conversationId}", {
59
+ params: { path: { conversationId } },
60
+ });
61
+ formatSuccess(`Conversation ${conversationId} deleted.`, command);
62
+ });
63
+ });
64
+ const items = new Command("items").description("List a conversation's items");
65
+ items
66
+ .command("list")
67
+ .description("List a conversation's items, oldest first")
68
+ .argument("<conversationId>", "Conversation ID")
69
+ .action(async (conversationId, _opts, command) => {
70
+ await runAction(command, async () => {
71
+ const client = getClient(command);
72
+ const { data } = await client.GET("/v1/conversations/{conversationId}/items", {
73
+ params: { path: { conversationId } },
74
+ });
75
+ formatDetail(data, command);
76
+ });
77
+ });
78
+ cmd.addCommand(items);
79
+ return cmd;
80
+ }
81
+ //# sourceMappingURL=conversations.js.map
@@ -1,7 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
3
  import { resolveLatestVersionId } from "../lib/datasets.js";
4
- import { formatPage, formatDetail } from "../lib/output.js";
4
+ import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
5
5
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
6
6
  export function makeDatasetVersionsCommand() {
7
7
  const cmd = new Command("versions").description("Manage dataset versions");
@@ -85,6 +85,49 @@ export function makeDatasetVersionsCommand() {
85
85
  });
86
86
  });
87
87
  cmd.addCommand(items);
88
+ cmd
89
+ .command("delete-item")
90
+ .description("Soft-delete an item in a dataset version (defaults to latest version)")
91
+ .argument("<itemId>", "Dataset item ID")
92
+ .requiredOption("--dataset <id>", "Dataset ID")
93
+ .option("--version-id <id>", "Version ID (defaults to latest)")
94
+ .action(async (itemId, opts, command) => {
95
+ await runAction(command, async () => {
96
+ const client = getClient(command);
97
+ const versionId = opts.versionId ?? (await resolveLatestVersionId(client, opts.dataset));
98
+ await client.DELETE("/v1/datasets/{id}/versions/{versionId}/items/{itemId}", {
99
+ params: {
100
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
101
+ path: { id: opts.dataset, versionId, itemId },
102
+ },
103
+ });
104
+ formatSuccess(`Item ${itemId} deleted.`, command);
105
+ });
106
+ });
107
+ cmd
108
+ .command("update-item-expected-output")
109
+ .description("Update the expected output of a dataset item (defaults to latest version)")
110
+ .argument("<itemId>", "Dataset item ID")
111
+ .requiredOption("--dataset <id>", "Dataset ID")
112
+ .option("--version-id <id>", "Version ID (defaults to latest)")
113
+ .option("--expected-output <json>", "New expected output (JSON); omit to clear it")
114
+ .action(async (itemId, opts, command) => {
115
+ await runAction(command, async () => {
116
+ const client = getClient(command);
117
+ const versionId = opts.versionId ?? (await resolveLatestVersionId(client, opts.dataset));
118
+ const body = {};
119
+ if (opts.expectedOutput !== undefined)
120
+ body.expectedOutput = JSON.parse(opts.expectedOutput);
121
+ const { data } = await client.PATCH("/v1/datasets/{id}/versions/{versionId}/items/{itemId}/expected-output", {
122
+ params: {
123
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
124
+ path: { id: opts.dataset, versionId, itemId },
125
+ },
126
+ body: body,
127
+ });
128
+ formatDetail(data, command);
129
+ });
130
+ });
88
131
  return cmd;
89
132
  }
90
133
  //# sourceMappingURL=dataset-versions.js.map
@@ -1,7 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { getClient, runAction } from "../lib/client.js";
3
3
  import { resolveLatestVersionId } from "../lib/datasets.js";
4
- import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
4
+ import { formatPage, formatDetail, formatList, formatSuccess } from "../lib/output.js";
5
5
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
6
6
  import { makeDatasetVersionsCommand } from "./dataset-versions.js";
7
7
  export function makeDatasetsCommand() {
@@ -144,6 +144,28 @@ export function makeDatasetsCommand() {
144
144
  formatDetail(data, command);
145
145
  });
146
146
  });
147
+ cmd
148
+ .command("add-items")
149
+ .description("Add multiple items to a dataset in a single request (uses the latest version)")
150
+ .requiredOption("--dataset <id>", "Dataset ID")
151
+ .requiredOption("--items <json>", "Array of items (JSON), each with input and optional expectedOutput/tags/metadata")
152
+ .action(async (opts, command) => {
153
+ await runAction(command, async () => {
154
+ const client = getClient(command);
155
+ const versionId = await resolveLatestVersionId(client, opts.dataset);
156
+ const items = JSON.parse(opts.items);
157
+ const { data } = await client.POST("/v1/datasets/{id}/versions/{versionId}/items/bulk", {
158
+ params: {
159
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
160
+ path: { id: opts.dataset, versionId },
161
+ },
162
+ body: items,
163
+ });
164
+ formatList(data, command, [
165
+ "id", "input", "expectedOutput", "tags",
166
+ ]);
167
+ });
168
+ });
147
169
  cmd.addCommand(makeDatasetVersionsCommand());
148
170
  return cmd;
149
171
  }
@@ -1,11 +1,25 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import ora from "ora";
4
+ import Table from "cli-table3";
4
5
  import { getClient, runAction } from "../lib/client.js";
5
6
  import { formatPage, formatDetail, formatList, formatSuccess, withSpinner } from "../lib/output.js";
6
7
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
7
8
  import { isJsonOutput } from "../lib/config.js";
8
9
  const TERMINAL_STATUSES = new Set(["COMPLETED", "FAILED"]);
10
+ /** Summarize a single cell of the comparison matrix (a variant's result for one item). */
11
+ function formatComparisonCell(result) {
12
+ if (!result)
13
+ return chalk.dim("—");
14
+ if (result.error)
15
+ return chalk.red("ERROR");
16
+ const scores = result.scores ?? [];
17
+ if (scores.length > 0) {
18
+ const avg = scores.reduce((sum, s) => sum + (s.score ?? 0), 0) / scores.length;
19
+ return avg.toFixed(2);
20
+ }
21
+ return result.durationMs != null ? `${result.durationMs}ms` : chalk.dim("done");
22
+ }
9
23
  export function makeExperimentsCommand() {
10
24
  const cmd = new Command("experiments").description("Manage experiments");
11
25
  // ── list ──────────────────────────────────────────────────────
@@ -158,6 +172,53 @@ export function makeExperimentsCommand() {
158
172
  formatList(data, command, ["id", "name", "taskType", "description", "sortOrder"]);
159
173
  });
160
174
  });
175
+ // ── update-variant ───────────────────────────────────────────────
176
+ cmd
177
+ .command("update-variant")
178
+ .description("Update a variant")
179
+ .argument("<variantId>", "Variant ID")
180
+ .requiredOption("--experiment <id>", "Experiment ID")
181
+ .option("-n, --name <name>", "Variant name")
182
+ .option("--task-type <type>", "Task type")
183
+ .option("--config <json>", "Variant configuration (JSON)")
184
+ .option("-d, --description <text>", "Variant description")
185
+ .option("--sort-order <n>", "Sort order", parseInt)
186
+ .action(async (variantId, opts, command) => {
187
+ await runAction(command, async () => {
188
+ const client = getClient(command);
189
+ const body = {};
190
+ if (opts.name !== undefined)
191
+ body.name = opts.name;
192
+ if (opts.taskType !== undefined)
193
+ body.taskType = opts.taskType;
194
+ if (opts.config)
195
+ body.configuration = JSON.parse(opts.config);
196
+ if (opts.description !== undefined)
197
+ body.description = opts.description;
198
+ if (opts.sortOrder !== undefined)
199
+ body.sortOrder = opts.sortOrder;
200
+ const { data } = await client.PUT("/v1/experiments/{id}/variants/{variantId}", {
201
+ params: { path: { id: opts.experiment, variantId } },
202
+ body: body,
203
+ });
204
+ formatDetail(data, command);
205
+ });
206
+ });
207
+ // ── delete-variant ───────────────────────────────────────────────
208
+ cmd
209
+ .command("delete-variant")
210
+ .description("Delete a variant")
211
+ .argument("<variantId>", "Variant ID")
212
+ .requiredOption("--experiment <id>", "Experiment ID")
213
+ .action(async (variantId, opts, command) => {
214
+ await runAction(command, async () => {
215
+ const client = getClient(command);
216
+ await client.DELETE("/v1/experiments/{id}/variants/{variantId}", {
217
+ params: { path: { id: opts.experiment, variantId } },
218
+ });
219
+ formatSuccess(`Variant ${variantId} deleted.`, command);
220
+ });
221
+ });
161
222
  // ── run ───────────────────────────────────────────────────────
162
223
  cmd
163
224
  .command("run")
@@ -250,6 +311,154 @@ export function makeExperimentsCommand() {
250
311
  });
251
312
  });
252
313
  cmd.addCommand(results);
314
+ // ── set-baseline ─────────────────────────────────────────────────
315
+ cmd
316
+ .command("set-baseline")
317
+ .description("Mark a run as the baseline for its variant")
318
+ .requiredOption("--experiment <id>", "Experiment ID")
319
+ .requiredOption("--run <id>", "Run ID")
320
+ .action(async (opts, command) => {
321
+ await runAction(command, async () => {
322
+ const client = getClient(command);
323
+ const { data } = await client.PUT("/v1/experiments/{id}/runs/{runId}/baseline", {
324
+ params: { path: { id: opts.experiment, runId: opts.run } },
325
+ });
326
+ formatDetail(data, command);
327
+ });
328
+ });
329
+ // ── clear-baseline ───────────────────────────────────────────────
330
+ cmd
331
+ .command("clear-baseline")
332
+ .description("Clear the baseline flag on a run")
333
+ .requiredOption("--experiment <id>", "Experiment ID")
334
+ .requiredOption("--run <id>", "Run ID")
335
+ .action(async (opts, command) => {
336
+ await runAction(command, async () => {
337
+ const client = getClient(command);
338
+ const { data } = await client.DELETE("/v1/experiments/{id}/runs/{runId}/baseline", {
339
+ params: { path: { id: opts.experiment, runId: opts.run } },
340
+ });
341
+ formatDetail(data, command);
342
+ });
343
+ });
344
+ // ── comparison ───────────────────────────────────────────────────
345
+ const comparison = new Command("comparison").description("Get the comparison matrix for an experiment (dataset items × variants)");
346
+ addPaginationOptions(comparison);
347
+ comparison.argument("<id>", "Experiment ID");
348
+ comparison.action(async (id, opts, command) => {
349
+ await runAction(command, async () => {
350
+ const client = getClient(command);
351
+ const { data } = await client.GET("/v1/experiments/{id}/comparison", {
352
+ params: {
353
+ path: { id },
354
+ query: paginationParams(opts),
355
+ },
356
+ });
357
+ if (isJsonOutput(command)) {
358
+ console.log(JSON.stringify(data, null, 2));
359
+ return;
360
+ }
361
+ const result = data;
362
+ const aggregates = result?.aggregates ?? [];
363
+ const page = result?.items;
364
+ const variantNames = new Map(aggregates.map((a) => [a.variantId, a.variantName ?? a.variantId]));
365
+ const variantIds = [...variantNames.keys()];
366
+ if (!page?.content || page.content.length === 0) {
367
+ console.log(chalk.dim("No comparison data found."));
368
+ }
369
+ else {
370
+ const table = new Table({
371
+ head: ["Item", ...variantIds.map((v) => variantNames.get(v) ?? v)].map((h) => chalk.cyan(h)),
372
+ wordWrap: true,
373
+ });
374
+ for (const item of page.content) {
375
+ const resultsByVariant = item.resultsByVariantId ?? {};
376
+ table.push([
377
+ item.datasetItemId,
378
+ ...variantIds.map((v) => formatComparisonCell(resultsByVariant[v])),
379
+ ]);
380
+ }
381
+ console.log(table.toString());
382
+ console.log(chalk.dim(`Page ${(page.number ?? 0) + 1} of ${page.totalPages ?? 0} (${page.totalElements ?? 0} total)`));
383
+ }
384
+ if (aggregates.length > 0) {
385
+ const aggTable = new Table({
386
+ head: ["Variant", "Items", "Avg Duration", "Avg Tokens", "Total Cost"].map((h) => chalk.cyan(h)),
387
+ wordWrap: true,
388
+ });
389
+ for (const agg of aggregates) {
390
+ aggTable.push([
391
+ agg.variantName ?? agg.variantId,
392
+ agg.itemCount ?? "—",
393
+ agg.avgDurationMs != null ? `${Math.round(agg.avgDurationMs)}ms` : "—",
394
+ agg.avgTokens != null ? Math.round(agg.avgTokens) : "—",
395
+ agg.totalCost != null ? `$${agg.totalCost}` : "—",
396
+ ]);
397
+ }
398
+ console.log(chalk.bold("\nAggregates:"));
399
+ console.log(aggTable.toString());
400
+ }
401
+ });
402
+ });
403
+ cmd.addCommand(comparison);
404
+ // ── regression ───────────────────────────────────────────────────
405
+ const regression = new Command("regression").description("Compare a run against its baseline and report regressions/improvements");
406
+ regression.requiredOption("--experiment <id>", "Experiment ID");
407
+ regression.requiredOption("--run <id>", "Run ID");
408
+ regression.option("--baseline-run <id>", "Baseline run ID (defaults to the run marked baseline, then the previous COMPLETED run)");
409
+ regression.option("--threshold <number>", "Score delta threshold for classifying a regression", parseFloat);
410
+ regression.action(async (opts, command) => {
411
+ await runAction(command, async () => {
412
+ const client = getClient(command);
413
+ const { data } = await client.GET("/v1/experiments/{id}/runs/{runId}/regression", {
414
+ params: {
415
+ path: { id: opts.experiment, runId: opts.run },
416
+ query: {
417
+ baselineRunId: opts.baselineRun,
418
+ threshold: opts.threshold,
419
+ },
420
+ },
421
+ });
422
+ if (isJsonOutput(command)) {
423
+ console.log(JSON.stringify(data, null, 2));
424
+ return;
425
+ }
426
+ const report = data;
427
+ const summary = report?.summary ?? {};
428
+ console.log(chalk.bold(`Regression report for run ${report?.runId}`));
429
+ console.log(chalk.dim(`Baseline: ${report?.baselineRunId ?? "—"} (${report?.baselineSource ?? "NONE"})` +
430
+ (report?.threshold != null ? `, threshold ${report.threshold}` : "")));
431
+ const summaryTable = new Table();
432
+ for (const [key, value] of Object.entries(summary)) {
433
+ summaryTable.push({ [chalk.cyan(key)]: value == null ? chalk.dim("—") : String(value) });
434
+ }
435
+ console.log(summaryTable.toString());
436
+ const groups = [
437
+ ["Regressed", report?.regressed ?? []],
438
+ ["Improved", report?.improved ?? []],
439
+ ["Ground Truth Changed", report?.gtChanged ?? []],
440
+ ];
441
+ for (const [label, items] of groups) {
442
+ if (items.length === 0)
443
+ continue;
444
+ console.log(chalk.bold(`\n${label}:`));
445
+ const table = new Table({
446
+ head: ["Item", "Baseline", "Current", "Delta"].map((h) => chalk.cyan(h)),
447
+ wordWrap: true,
448
+ });
449
+ for (const item of items) {
450
+ table.push([
451
+ item.datasetItemId,
452
+ item.baselineScore ?? "—",
453
+ item.currentScore ?? "—",
454
+ item.delta ?? "—",
455
+ ]);
456
+ }
457
+ console.log(table.toString());
458
+ }
459
+ });
460
+ });
461
+ cmd.addCommand(regression);
253
462
  return cmd;
254
463
  }
255
464
  //# sourceMappingURL=experiments.js.map
@@ -2,7 +2,7 @@ import { Command } from "commander";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { extname } from "node:path";
4
4
  import { getClient, runAction } from "../lib/client.js";
5
- import { formatPage, formatDetail, withSpinner } from "../lib/output.js";
5
+ import { formatPage, formatDetail, formatSuccess, withSpinner } from "../lib/output.js";
6
6
  import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
7
7
  const IMAGE_MIME_TYPES = {
8
8
  ".png": "image/png",
@@ -74,6 +74,19 @@ export function makeExtractionsCommand() {
74
74
  formatDetail(data, command);
75
75
  });
76
76
  });
77
+ cmd
78
+ .command("delete")
79
+ .description("Delete an extraction")
80
+ .argument("<id>", "Extraction ID")
81
+ .action(async (id, _opts, command) => {
82
+ await runAction(command, async () => {
83
+ const client = getClient(command);
84
+ await client.DELETE("/v1/extractions/{id}", {
85
+ params: { path: { id } },
86
+ });
87
+ formatSuccess(`Extraction ${id} deleted.`, command);
88
+ });
89
+ });
77
90
  const list = new Command("list").description("List extractions");
78
91
  addPaginationOptions(list);
79
92
  list.option("-s, --search <term>", "Search text");
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function makeFilesCommand(): Command;
3
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1,143 @@
1
+ import { Command } from "commander";
2
+ import { writeFileSync } from "node:fs";
3
+ import chalk from "chalk";
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 { formatPage, formatDetail, formatSuccess, withSpinner } from "../lib/output.js";
8
+ import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
9
+ import { fileToBlob } from "../lib/multipart.js";
10
+ /**
11
+ * Upload a single file with purpose=agent_input|knowledge. Bypasses the typed
12
+ * client the same way convert.ts and transcribe.ts do: the generated types
13
+ * describe the multipart body as an opaque `file: string`, which is not
14
+ * something FormData can produce a match for.
15
+ */
16
+ async function uploadFile(command, filePath, purpose) {
17
+ const config = resolveConfig(command);
18
+ const authHeader = await resolveAuthHeader(config);
19
+ const { blob, filename } = fileToBlob(filePath);
20
+ const formData = new FormData();
21
+ formData.append("file", blob, filename);
22
+ const baseUrl = config.baseUrl.replace(/\/+$/, "");
23
+ const url = `${baseUrl}/v1/files?purpose=${encodeURIComponent(purpose)}`;
24
+ const res = await fetch(url, {
25
+ method: "POST",
26
+ headers: { Authorization: authHeader },
27
+ body: formData,
28
+ });
29
+ if (!res.ok) {
30
+ let body;
31
+ try {
32
+ body = await res.json();
33
+ }
34
+ catch {
35
+ body = {
36
+ error: res.statusText,
37
+ message: `HTTP ${res.status}: ${res.statusText}`,
38
+ status: res.status,
39
+ timestamp: new Date().toISOString(),
40
+ };
41
+ }
42
+ throw new BackboneApiError(body);
43
+ }
44
+ return (await res.json());
45
+ }
46
+ /** Stream a file's bytes from /v1/files/{fileId}/content via a raw fetch. */
47
+ async function downloadFile(command, fileId) {
48
+ const config = resolveConfig(command);
49
+ const authHeader = await resolveAuthHeader(config);
50
+ const baseUrl = config.baseUrl.replace(/\/+$/, "");
51
+ const url = `${baseUrl}/v1/files/${encodeURIComponent(fileId)}/content`;
52
+ const res = await fetch(url, {
53
+ headers: { Authorization: authHeader },
54
+ });
55
+ if (!res.ok) {
56
+ let body;
57
+ try {
58
+ body = await res.json();
59
+ }
60
+ catch {
61
+ body = {
62
+ error: res.statusText,
63
+ message: `HTTP ${res.status}: ${res.statusText}`,
64
+ status: res.status,
65
+ timestamp: new Date().toISOString(),
66
+ };
67
+ }
68
+ throw new BackboneApiError(body);
69
+ }
70
+ const disposition = res.headers.get("content-disposition") ?? "";
71
+ const match = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition);
72
+ const filename = match ? decodeURIComponent(match[1]) : undefined;
73
+ const bytes = Buffer.from(await res.arrayBuffer());
74
+ return { bytes, filename, contentType: res.headers.get("content-type") ?? undefined };
75
+ }
76
+ export function makeFilesCommand() {
77
+ const cmd = new Command("files").description("Manage uploaded files");
78
+ const list = new Command("list").description("List files");
79
+ addPaginationOptions(list);
80
+ list.option("--purpose <purpose>", "Filter by purpose (agent_input, knowledge)");
81
+ list.action(async (opts, command) => {
82
+ await runAction(command, async () => {
83
+ const client = getClient(command);
84
+ const { data } = await client.GET("/v1/files", {
85
+ params: { query: { purpose: opts.purpose, ...paginationParams(opts) } },
86
+ });
87
+ formatPage(data, command, ["id", "filename", "purpose", "bytes", "created_at"]);
88
+ });
89
+ });
90
+ cmd.addCommand(list);
91
+ cmd
92
+ .command("get")
93
+ .description("Retrieve a file's metadata")
94
+ .argument("<fileId>", "File ID")
95
+ .action(async (fileId, _opts, command) => {
96
+ await runAction(command, async () => {
97
+ const client = getClient(command);
98
+ const { data } = await client.GET("/v1/files/{fileId}", {
99
+ params: { path: { fileId } },
100
+ });
101
+ formatDetail(data, command);
102
+ });
103
+ });
104
+ cmd
105
+ .command("upload")
106
+ .description("Upload a file")
107
+ .argument("<path>", "Local file path")
108
+ .requiredOption("--purpose <purpose>", "File purpose (agent_input, knowledge)")
109
+ .action(async (path, opts, command) => {
110
+ await runAction(command, async () => {
111
+ const data = await withSpinner("Uploading...", () => uploadFile(command, path, opts.purpose));
112
+ formatDetail(data, command);
113
+ });
114
+ });
115
+ cmd
116
+ .command("download")
117
+ .description("Download a file's content")
118
+ .argument("<fileId>", "File ID")
119
+ .option("-o, --output <path>", "Write bytes to this path instead of the file's own name")
120
+ .action(async (fileId, opts, command) => {
121
+ await runAction(command, async () => {
122
+ const { bytes, filename } = await withSpinner("Downloading...", () => downloadFile(command, fileId));
123
+ const outputPath = opts.output ?? filename ?? fileId;
124
+ writeFileSync(outputPath, bytes);
125
+ console.error(chalk.green(`Wrote ${bytes.length} bytes to ${outputPath}`));
126
+ });
127
+ });
128
+ cmd
129
+ .command("delete")
130
+ .description("Delete an agent_input file (knowledge files are not deletable through this API)")
131
+ .argument("<fileId>", "File ID")
132
+ .action(async (fileId, _opts, command) => {
133
+ await runAction(command, async () => {
134
+ const client = getClient(command);
135
+ await client.DELETE("/v1/files/{fileId}", {
136
+ params: { path: { fileId } },
137
+ });
138
+ formatSuccess(`File ${fileId} deleted.`, command);
139
+ });
140
+ });
141
+ return cmd;
142
+ }
143
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function makeInstallationsCommand(): Command;
3
+ //# sourceMappingURL=installations.d.ts.map