@2kw/ai 4.0.0-dev.2
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/LICENSE +19 -0
- package/README.md +91 -0
- package/dist/commands/ai.d.ts +3 -0
- package/dist/commands/ai.js +72 -0
- package/dist/commands/analytics.d.ts +3 -0
- package/dist/commands/analytics.js +161 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +134 -0
- package/dist/commands/billing.d.ts +3 -0
- package/dist/commands/billing.js +102 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.js +90 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +149 -0
- package/dist/commands/convert.d.ts +3 -0
- package/dist/commands/convert.js +291 -0
- package/dist/commands/dataset-versions.d.ts +3 -0
- package/dist/commands/dataset-versions.js +90 -0
- package/dist/commands/datasets.d.ts +3 -0
- package/dist/commands/datasets.js +150 -0
- package/dist/commands/docs.d.ts +3 -0
- package/dist/commands/docs.js +166 -0
- package/dist/commands/evaluators.d.ts +3 -0
- package/dist/commands/evaluators.js +124 -0
- package/dist/commands/experiments.d.ts +3 -0
- package/dist/commands/experiments.js +255 -0
- package/dist/commands/extractions.d.ts +3 -0
- package/dist/commands/extractions.js +134 -0
- package/dist/commands/prompt-labels.d.ts +3 -0
- package/dist/commands/prompt-labels.js +67 -0
- package/dist/commands/prompt-versions.d.ts +3 -0
- package/dist/commands/prompt-versions.js +65 -0
- package/dist/commands/prompts.d.ts +3 -0
- package/dist/commands/prompts.js +159 -0
- package/dist/commands/providers.d.ts +3 -0
- package/dist/commands/providers.js +115 -0
- package/dist/commands/schema-labels.d.ts +3 -0
- package/dist/commands/schema-labels.js +67 -0
- package/dist/commands/schema-versions.d.ts +3 -0
- package/dist/commands/schema-versions.js +66 -0
- package/dist/commands/schemas.d.ts +3 -0
- package/dist/commands/schemas.js +178 -0
- package/dist/commands/scores.d.ts +3 -0
- package/dist/commands/scores.js +31 -0
- package/dist/commands/tracing.d.ts +3 -0
- package/dist/commands/tracing.js +85 -0
- package/dist/commands/transcribe.d.ts +3 -0
- package/dist/commands/transcribe.js +65 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +54 -0
- package/dist/lib/client.d.ts +12 -0
- package/dist/lib/client.js +52 -0
- package/dist/lib/config.d.ts +44 -0
- package/dist/lib/config.js +217 -0
- package/dist/lib/datasets.d.ts +6 -0
- package/dist/lib/datasets.js +10 -0
- package/dist/lib/errors.d.ts +14 -0
- package/dist/lib/errors.js +54 -0
- package/dist/lib/multipart.d.ts +10 -0
- package/dist/lib/multipart.js +59 -0
- package/dist/lib/output.d.ts +29 -0
- package/dist/lib/output.js +107 -0
- package/dist/lib/pagination.d.ts +21 -0
- package/dist/lib/pagination.js +21 -0
- package/dist/lib/schema-compat.d.ts +11 -0
- package/dist/lib/schema-compat.js +51 -0
- package/dist/lib/update-notifier.d.ts +9 -0
- package/dist/lib/update-notifier.js +96 -0
- package/package.json +49 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { store, isJsonOutput, getActiveContext, getActiveContextName, getContextCount, setContext, DEFAULT_BASE_URL, } from "../lib/config.js";
|
|
4
|
+
const ALLOWED_KEYS = ["apiKey", "baseUrl"];
|
|
5
|
+
const KEY_ALIASES = {
|
|
6
|
+
"api-key": "apiKey",
|
|
7
|
+
"base-url": "baseUrl",
|
|
8
|
+
};
|
|
9
|
+
function validateKey(key) {
|
|
10
|
+
if (key === "project") {
|
|
11
|
+
throw new Error('The "project" config key is no longer supported — resources are scoped by organization.');
|
|
12
|
+
}
|
|
13
|
+
const normalized = KEY_ALIASES[key] ?? key;
|
|
14
|
+
if (!ALLOWED_KEYS.includes(normalized)) {
|
|
15
|
+
throw new Error(`Unknown config key: "${key}". Allowed keys: ${ALLOWED_KEYS.join(", ")}`);
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
export function makeConfigCommand() {
|
|
20
|
+
const cmd = new Command("config").description("Manage CLI configuration");
|
|
21
|
+
cmd
|
|
22
|
+
.command("set")
|
|
23
|
+
.description("Set a configuration value")
|
|
24
|
+
.argument("<key>", `Config key (${ALLOWED_KEYS.join(", ")})`)
|
|
25
|
+
.argument("<value>", "Config value")
|
|
26
|
+
.action((key, value, _opts, command) => {
|
|
27
|
+
const validKey = validateKey(key);
|
|
28
|
+
// Update the active context
|
|
29
|
+
const contextName = getActiveContextName();
|
|
30
|
+
const ctx = getActiveContext() ?? {
|
|
31
|
+
apiKey: "",
|
|
32
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
33
|
+
};
|
|
34
|
+
ctx[validKey] = value;
|
|
35
|
+
setContext(contextName, ctx);
|
|
36
|
+
if (isJsonOutput(command)) {
|
|
37
|
+
console.log(JSON.stringify({ key: validKey, value }));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log(chalk.green(`Set ${validKey} = ${validKey === "apiKey" ? "****" : value}`));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
cmd
|
|
44
|
+
.command("get")
|
|
45
|
+
.description("Get a configuration value")
|
|
46
|
+
.argument("<key>", `Config key (${ALLOWED_KEYS.join(", ")})`)
|
|
47
|
+
.action((key, _opts, command) => {
|
|
48
|
+
const validKey = validateKey(key);
|
|
49
|
+
const ctx = getActiveContext();
|
|
50
|
+
const value = ctx?.[validKey];
|
|
51
|
+
if (isJsonOutput(command)) {
|
|
52
|
+
console.log(JSON.stringify({ key: validKey, value: value ?? null }));
|
|
53
|
+
}
|
|
54
|
+
else if (value) {
|
|
55
|
+
console.log(validKey === "apiKey" ? "****" : value);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(chalk.dim("(not set)"));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
cmd
|
|
62
|
+
.command("list")
|
|
63
|
+
.description("Show all configuration values")
|
|
64
|
+
.action((_opts, command) => {
|
|
65
|
+
const ctx = getActiveContext();
|
|
66
|
+
const values = {
|
|
67
|
+
apiKey: ctx?.apiKey,
|
|
68
|
+
baseUrl: ctx?.baseUrl,
|
|
69
|
+
};
|
|
70
|
+
if (isJsonOutput(command)) {
|
|
71
|
+
console.log(JSON.stringify(values, null, 2));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
for (const [key, value] of Object.entries(values)) {
|
|
75
|
+
const display = !value
|
|
76
|
+
? chalk.dim("(not set)")
|
|
77
|
+
: key === "apiKey"
|
|
78
|
+
? value.slice(0, 7) + "..." + value.slice(-4)
|
|
79
|
+
: value;
|
|
80
|
+
console.log(`${chalk.cyan(key)}: ${display}`);
|
|
81
|
+
}
|
|
82
|
+
if (getContextCount() > 1) {
|
|
83
|
+
console.log(chalk.dim(`\nContext: ${getActiveContextName()} (use "backbone context list" to see all)`));
|
|
84
|
+
}
|
|
85
|
+
console.log(chalk.dim(`\nConfig file: ${store.path}`));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return cmd;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import Table from "cli-table3";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { store, isJsonOutput, getAllContexts, getActiveContextName, getContextCount, setContext, deleteContext, renameContext, setActiveContext, validateContextName, DEFAULT_BASE_URL, } from "../lib/config.js";
|
|
6
|
+
function maskApiKey(key) {
|
|
7
|
+
if (key.length <= 11)
|
|
8
|
+
return "****";
|
|
9
|
+
return key.slice(0, 7) + "..." + key.slice(-4);
|
|
10
|
+
}
|
|
11
|
+
export function makeContextCommand() {
|
|
12
|
+
const cmd = new Command("context").description("Manage CLI contexts for multiple environments");
|
|
13
|
+
cmd
|
|
14
|
+
.command("list")
|
|
15
|
+
.description("List all contexts")
|
|
16
|
+
.action((_opts, command) => {
|
|
17
|
+
const contexts = getAllContexts();
|
|
18
|
+
const activeName = getActiveContextName();
|
|
19
|
+
if (isJsonOutput(command)) {
|
|
20
|
+
console.log(JSON.stringify({ activeContext: activeName, contexts }, null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const names = Object.keys(contexts);
|
|
24
|
+
if (names.length === 0) {
|
|
25
|
+
console.log(chalk.dim('No contexts configured. Run "backbone auth login" to get started.'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const table = new Table({
|
|
29
|
+
head: ["", "NAME", "BASE URL", "API KEY"].map((h) => chalk.cyan(h)),
|
|
30
|
+
});
|
|
31
|
+
for (const name of names) {
|
|
32
|
+
const ctx = contexts[name];
|
|
33
|
+
table.push([
|
|
34
|
+
name === activeName ? chalk.green("*") : "",
|
|
35
|
+
name,
|
|
36
|
+
ctx.baseUrl,
|
|
37
|
+
maskApiKey(ctx.apiKey),
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
console.log(table.toString());
|
|
41
|
+
});
|
|
42
|
+
cmd
|
|
43
|
+
.command("use")
|
|
44
|
+
.description("Switch active context")
|
|
45
|
+
.argument("<name>", "Context name")
|
|
46
|
+
.action((name, _opts, command) => {
|
|
47
|
+
setActiveContext(name);
|
|
48
|
+
if (isJsonOutput(command)) {
|
|
49
|
+
console.log(JSON.stringify({ activeContext: name }));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(chalk.green(`Switched to context "${name}".`));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
cmd
|
|
56
|
+
.command("create")
|
|
57
|
+
.description("Create a new context")
|
|
58
|
+
.argument("<name>", "Context name")
|
|
59
|
+
.option("--base-url <url>", "Base URL")
|
|
60
|
+
.option("--api-key <key>", "API key")
|
|
61
|
+
.action(async (name, opts) => {
|
|
62
|
+
validateContextName(name);
|
|
63
|
+
const contexts = getAllContexts();
|
|
64
|
+
if (contexts[name]) {
|
|
65
|
+
throw new Error(`Context "${name}" already exists. Delete it first or choose a different name.`);
|
|
66
|
+
}
|
|
67
|
+
let baseUrl = opts.baseUrl;
|
|
68
|
+
let apiKey = opts.apiKey;
|
|
69
|
+
if (!baseUrl || !apiKey) {
|
|
70
|
+
const rl = createInterface({
|
|
71
|
+
input: process.stdin,
|
|
72
|
+
output: process.stdout,
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
if (!baseUrl) {
|
|
76
|
+
baseUrl = await rl.question(`Base URL [${DEFAULT_BASE_URL}]: `);
|
|
77
|
+
if (!baseUrl)
|
|
78
|
+
baseUrl = DEFAULT_BASE_URL;
|
|
79
|
+
}
|
|
80
|
+
if (!apiKey) {
|
|
81
|
+
apiKey = await rl.question("API Key (sk_...): ");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
rl.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!apiKey) {
|
|
89
|
+
console.error(chalk.red("API key is required."));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const entry = { baseUrl, apiKey };
|
|
93
|
+
setContext(name, entry);
|
|
94
|
+
// Auto-set as active if it's the first context
|
|
95
|
+
if (getContextCount() === 1) {
|
|
96
|
+
setActiveContext(name);
|
|
97
|
+
}
|
|
98
|
+
console.log(chalk.green(`Context "${name}" created.`));
|
|
99
|
+
console.log(chalk.dim(`Config stored at: ${store.path}`));
|
|
100
|
+
});
|
|
101
|
+
cmd
|
|
102
|
+
.command("current")
|
|
103
|
+
.description("Show the active context")
|
|
104
|
+
.action((_opts, command) => {
|
|
105
|
+
const name = getActiveContextName();
|
|
106
|
+
const contexts = getAllContexts();
|
|
107
|
+
const ctx = contexts[name];
|
|
108
|
+
if (isJsonOutput(command)) {
|
|
109
|
+
console.log(JSON.stringify({ name, ...(ctx ?? {}) }, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!ctx) {
|
|
113
|
+
console.log(chalk.yellow('No active context configured. Run "backbone auth login" to get started.'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(`${chalk.cyan("Context")}: ${name}`);
|
|
117
|
+
console.log(`${chalk.cyan("Base URL")}: ${ctx.baseUrl}`);
|
|
118
|
+
console.log(`${chalk.cyan("API Key")}: ${maskApiKey(ctx.apiKey)}`);
|
|
119
|
+
});
|
|
120
|
+
cmd
|
|
121
|
+
.command("delete")
|
|
122
|
+
.description("Delete a context")
|
|
123
|
+
.argument("<name>", "Context name")
|
|
124
|
+
.action((name, _opts, command) => {
|
|
125
|
+
deleteContext(name);
|
|
126
|
+
if (isJsonOutput(command)) {
|
|
127
|
+
console.log(JSON.stringify({ deleted: name }));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.log(chalk.green(`Context "${name}" deleted.`));
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
cmd
|
|
134
|
+
.command("rename")
|
|
135
|
+
.description("Rename a context")
|
|
136
|
+
.argument("<old>", "Current name")
|
|
137
|
+
.argument("<new>", "New name")
|
|
138
|
+
.action((oldName, newName, _opts, command) => {
|
|
139
|
+
renameContext(oldName, newName);
|
|
140
|
+
if (isJsonOutput(command)) {
|
|
141
|
+
console.log(JSON.stringify({ from: oldName, to: newName }));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
console.log(chalk.green(`Context "${oldName}" renamed to "${newName}".`));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return cmd;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
5
|
+
import { resolveConfig, isJsonOutput } from "../lib/config.js";
|
|
6
|
+
import { BackboneApiError } from "../lib/errors.js";
|
|
7
|
+
import { formatDetail, withSpinner } from "../lib/output.js";
|
|
8
|
+
import { fileToBlob, getMimeType } from "../lib/multipart.js";
|
|
9
|
+
function mapFormat(format) {
|
|
10
|
+
const map = {
|
|
11
|
+
markdown: "MD",
|
|
12
|
+
md: "MD",
|
|
13
|
+
text: "TEXT",
|
|
14
|
+
txt: "TEXT",
|
|
15
|
+
json: "JSON",
|
|
16
|
+
html: "HTML",
|
|
17
|
+
};
|
|
18
|
+
return map[format.toLowerCase()] ?? format.toUpperCase();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extract the primary content from a document based on the requested formats.
|
|
22
|
+
* Prefers markdown > text > html.
|
|
23
|
+
*/
|
|
24
|
+
function getPrimaryContent(doc, formats) {
|
|
25
|
+
const upper = formats.map((f) => f.toUpperCase());
|
|
26
|
+
if (upper.includes("MD") && doc.mdContent)
|
|
27
|
+
return doc.mdContent;
|
|
28
|
+
if (upper.includes("TEXT") && doc.textContent)
|
|
29
|
+
return doc.textContent;
|
|
30
|
+
if (upper.includes("HTML") && doc.htmlContent)
|
|
31
|
+
return doc.htmlContent;
|
|
32
|
+
if (upper.includes("JSON") && doc.jsonContent)
|
|
33
|
+
return JSON.stringify(doc.jsonContent.content ?? doc.jsonContent, null, 2);
|
|
34
|
+
return doc.mdContent ?? doc.textContent ?? doc.htmlContent ?? "";
|
|
35
|
+
}
|
|
36
|
+
function formatBytes(bytes) {
|
|
37
|
+
if (bytes < 1024)
|
|
38
|
+
return `${bytes} B`;
|
|
39
|
+
if (bytes < 1024 * 1024)
|
|
40
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
41
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Output conversion result: --json, --output, or human-readable.
|
|
45
|
+
*/
|
|
46
|
+
function outputConvertResult(data, command, opts) {
|
|
47
|
+
if (isJsonOutput(command)) {
|
|
48
|
+
console.log(JSON.stringify(data, null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const result = data;
|
|
52
|
+
const docs = result.documents ?? [];
|
|
53
|
+
const formats = opts.format.map(mapFormat);
|
|
54
|
+
// --output: write content to file
|
|
55
|
+
if (opts.output) {
|
|
56
|
+
// For JSON format, produce a valid JSON array instead of ---‑separated objects
|
|
57
|
+
const isJson = formats.some((f) => f.toUpperCase() === "JSON");
|
|
58
|
+
let content;
|
|
59
|
+
if (isJson) {
|
|
60
|
+
const jsonParts = docs
|
|
61
|
+
.map((doc) => doc.jsonContent?.content ?? doc.jsonContent)
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
content = JSON.stringify(jsonParts.length === 1 ? jsonParts[0] : jsonParts, null, 2);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const parts = docs.map((doc) => getPrimaryContent(doc, formats));
|
|
67
|
+
content = parts.join("\n\n---\n\n");
|
|
68
|
+
}
|
|
69
|
+
writeFileSync(opts.output, content, "utf-8");
|
|
70
|
+
const size = Buffer.byteLength(content, "utf-8");
|
|
71
|
+
console.error(chalk.green(`Wrote ${formatBytes(size)} to ${opts.output}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Human-readable output
|
|
75
|
+
const timeStr = result.processingTime
|
|
76
|
+
? ` (${result.processingTime.toFixed(1)}s)`
|
|
77
|
+
: "";
|
|
78
|
+
const count = docs.length;
|
|
79
|
+
console.log(chalk.green(`Converted ${count} document${count !== 1 ? "s" : ""}${timeStr}`));
|
|
80
|
+
if (result.errors?.length) {
|
|
81
|
+
for (const err of result.errors) {
|
|
82
|
+
console.log(chalk.yellow(` Warning: ${err.filename ?? "unknown"} — ${err.errorMessage ?? "error"}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const doc of docs) {
|
|
86
|
+
const content = getPrimaryContent(doc, formats);
|
|
87
|
+
const size = formatBytes(Buffer.byteLength(content, "utf-8"));
|
|
88
|
+
console.log(chalk.dim(`\n--- ${doc.filename ?? "document"} (${size}) ---\n`));
|
|
89
|
+
console.log(content);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Poll an async task until terminal status, then fetch and return the result.
|
|
94
|
+
*/
|
|
95
|
+
async function waitForTask(client, taskId, waitSeconds) {
|
|
96
|
+
const terminalStatuses = new Set(["SUCCESS", "PARTIAL_SUCCESS", "FAILURE", "COMPLETED", "FAILED"]);
|
|
97
|
+
// Poll until done
|
|
98
|
+
let status;
|
|
99
|
+
// eslint-disable-next-line no-constant-condition
|
|
100
|
+
while (true) {
|
|
101
|
+
const { data } = await client.GET("/v1/convert/tasks/{taskId}", {
|
|
102
|
+
params: {
|
|
103
|
+
path: { taskId },
|
|
104
|
+
query: { wait: waitSeconds },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
status = data;
|
|
108
|
+
if (status?.taskStatus && terminalStatuses.has(status.taskStatus.toUpperCase())) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Fetch final result
|
|
113
|
+
const { data } = await client.GET("/v1/convert/tasks/{taskId}/result", {
|
|
114
|
+
params: { path: { taskId } },
|
|
115
|
+
});
|
|
116
|
+
return data;
|
|
117
|
+
}
|
|
118
|
+
function buildFormData(paths, opts) {
|
|
119
|
+
const formData = new FormData();
|
|
120
|
+
const outputFormats = opts.format.map(mapFormat);
|
|
121
|
+
for (const p of paths) {
|
|
122
|
+
if (p === "-") {
|
|
123
|
+
const name = opts.filename ?? "document";
|
|
124
|
+
const buffer = readFileSync(0);
|
|
125
|
+
const blob = new Blob([buffer], { type: getMimeType(name) });
|
|
126
|
+
formData.append("files", blob, name);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const { blob, filename } = fileToBlob(p);
|
|
130
|
+
formData.append("files", blob, filename);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const pipelineOptions = { outputFormats };
|
|
134
|
+
if (opts.imageMode)
|
|
135
|
+
pipelineOptions.imageExportMode = opts.imageMode;
|
|
136
|
+
formData.append("options", new Blob([JSON.stringify(pipelineOptions)], { type: "application/json" }));
|
|
137
|
+
return formData;
|
|
138
|
+
}
|
|
139
|
+
async function multipartConvert(command, formData, endpoint, pipeline) {
|
|
140
|
+
const config = resolveConfig(command);
|
|
141
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
142
|
+
const url = pipeline
|
|
143
|
+
? `${baseUrl}${endpoint}?pipeline=${encodeURIComponent(pipeline)}`
|
|
144
|
+
: `${baseUrl}${endpoint}`;
|
|
145
|
+
const res = await fetch(url, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
148
|
+
body: formData,
|
|
149
|
+
});
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
let body;
|
|
152
|
+
try {
|
|
153
|
+
body = await res.json();
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
body = {
|
|
157
|
+
error: res.statusText,
|
|
158
|
+
message: `HTTP ${res.status}: ${res.statusText}`,
|
|
159
|
+
status: res.status,
|
|
160
|
+
timestamp: new Date().toISOString(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
throw new BackboneApiError(body);
|
|
164
|
+
}
|
|
165
|
+
return (await res.json());
|
|
166
|
+
}
|
|
167
|
+
export function makeConvertCommand() {
|
|
168
|
+
const cmd = new Command("convert").description("Convert documents");
|
|
169
|
+
cmd
|
|
170
|
+
.command("file")
|
|
171
|
+
.description("Convert local files (use - to read from stdin)")
|
|
172
|
+
.argument("<paths...>", "File paths to convert (use - for stdin)")
|
|
173
|
+
.option("--format <formats...>", "Output formats (md, text, html, json)", ["md"])
|
|
174
|
+
.option("--pipeline <name>", "Pipeline: fast (default), ocr, vlm. For compound files (MSG/EML), applies to attachments; email body is always plain text")
|
|
175
|
+
.option("--image-mode <mode>", "Image export mode (placeholder, embedded)")
|
|
176
|
+
.option("-o, --output <path>", "Write content to file instead of stdout")
|
|
177
|
+
.option("--filename <name>", "Filename for MIME detection when reading from stdin")
|
|
178
|
+
.option("--async", "Run asynchronously")
|
|
179
|
+
.option("-w, --wait [seconds]", "With --async: wait for completion (default: 30s)", parseFloat)
|
|
180
|
+
.action(async (paths, opts, command) => {
|
|
181
|
+
await runAction(command, async () => {
|
|
182
|
+
const formData = buildFormData(paths, opts);
|
|
183
|
+
if (opts.async) {
|
|
184
|
+
const data = await withSpinner("Submitting conversion...", () => multipartConvert(command, formData, "/v1/convert/file/async", opts.pipeline));
|
|
185
|
+
if (opts.wait !== undefined) {
|
|
186
|
+
const waitSeconds = opts.wait === true ? 30 : opts.wait;
|
|
187
|
+
const taskId = data?.taskId;
|
|
188
|
+
if (!taskId)
|
|
189
|
+
throw new Error("No taskId returned from async submission");
|
|
190
|
+
const client = getClient(command);
|
|
191
|
+
const result = await withSpinner("Waiting for completion...", () => waitForTask(client, taskId, waitSeconds));
|
|
192
|
+
outputConvertResult(result, command, opts);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
formatDetail(data, command);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const data = await withSpinner("Converting...", () => multipartConvert(command, formData, "/v1/convert/file", opts.pipeline));
|
|
200
|
+
outputConvertResult(data, command, opts);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
cmd
|
|
205
|
+
.command("url")
|
|
206
|
+
.description("Convert documents from URLs")
|
|
207
|
+
.argument("<urls...>", "URLs to convert")
|
|
208
|
+
.option("--format <formats...>", "Output formats (md, text, html, json)", ["md"])
|
|
209
|
+
.option("--pipeline <name>", "Pipeline: fast (default), ocr, vlm. For compound files (MSG/EML), applies to attachments; email body is always plain text")
|
|
210
|
+
.option("--image-mode <mode>", "Image export mode (placeholder, embedded)")
|
|
211
|
+
.option("-o, --output <path>", "Write content to file instead of stdout")
|
|
212
|
+
.option("--async", "Run asynchronously")
|
|
213
|
+
.option("-w, --wait [seconds]", "With --async: wait for completion (default: 30s)", parseFloat)
|
|
214
|
+
.action(async (urls, opts, command) => {
|
|
215
|
+
await runAction(command, async () => {
|
|
216
|
+
const client = getClient(command);
|
|
217
|
+
const sources = urls.map((url) => ({
|
|
218
|
+
kind: "http",
|
|
219
|
+
url,
|
|
220
|
+
}));
|
|
221
|
+
const outputFormats = opts.format.map(mapFormat);
|
|
222
|
+
const body = {
|
|
223
|
+
sources,
|
|
224
|
+
options: {
|
|
225
|
+
pipeline: opts.pipeline,
|
|
226
|
+
options: {
|
|
227
|
+
outputFormats,
|
|
228
|
+
...(opts.imageMode && { imageExportMode: opts.imageMode }),
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
if (opts.async) {
|
|
233
|
+
const { data } = await withSpinner("Submitting conversion...", () =>
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
235
|
+
client.POST("/v1/convert/source/async", { body }));
|
|
236
|
+
if (opts.wait !== undefined) {
|
|
237
|
+
const waitSeconds = opts.wait === true ? 30 : opts.wait;
|
|
238
|
+
const taskId = data?.taskId;
|
|
239
|
+
if (!taskId)
|
|
240
|
+
throw new Error("No taskId returned from async submission");
|
|
241
|
+
const result = await withSpinner("Waiting for completion...", () => waitForTask(client, taskId, waitSeconds));
|
|
242
|
+
outputConvertResult(result, command, opts);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
formatDetail(data, command);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const { data } = await withSpinner("Converting...", () =>
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
|
+
client.POST("/v1/convert/source", { body }));
|
|
252
|
+
outputConvertResult(data, command, opts);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
cmd
|
|
257
|
+
.command("status")
|
|
258
|
+
.description("Check async conversion task status")
|
|
259
|
+
.argument("<taskId>", "Task ID")
|
|
260
|
+
.option("-w, --wait [seconds]", "Wait for completion (default: 30s)", parseFloat)
|
|
261
|
+
.action(async (taskId, opts, command) => {
|
|
262
|
+
await runAction(command, async () => {
|
|
263
|
+
const client = getClient(command);
|
|
264
|
+
const wait = opts.wait === true ? 30 : opts.wait;
|
|
265
|
+
const { data } = await client.GET("/v1/convert/tasks/{taskId}", {
|
|
266
|
+
params: {
|
|
267
|
+
path: { taskId },
|
|
268
|
+
query: { wait },
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
formatDetail(data, command);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
cmd
|
|
275
|
+
.command("result")
|
|
276
|
+
.description("Get async conversion task result")
|
|
277
|
+
.argument("<taskId>", "Task ID")
|
|
278
|
+
.option("-o, --output <path>", "Write content to file instead of stdout")
|
|
279
|
+
.option("--format <formats...>", "Preferred format for --output (md, text, html)", ["md"])
|
|
280
|
+
.action(async (taskId, opts, command) => {
|
|
281
|
+
await runAction(command, async () => {
|
|
282
|
+
const client = getClient(command);
|
|
283
|
+
const { data } = await client.GET("/v1/convert/tasks/{taskId}/result", {
|
|
284
|
+
params: { path: { taskId } },
|
|
285
|
+
});
|
|
286
|
+
outputConvertResult(data, command, opts);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
return cmd;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
+
import { resolveLatestVersionId } from "../lib/datasets.js";
|
|
4
|
+
import { formatPage, formatDetail } from "../lib/output.js";
|
|
5
|
+
import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
|
|
6
|
+
export function makeDatasetVersionsCommand() {
|
|
7
|
+
const cmd = new Command("versions").description("Manage dataset versions");
|
|
8
|
+
const list = new Command("list").description("List versions of a dataset");
|
|
9
|
+
addPaginationOptions(list);
|
|
10
|
+
list.requiredOption("--dataset <id>", "Dataset ID");
|
|
11
|
+
list.action(async (opts, command) => {
|
|
12
|
+
await runAction(command, async () => {
|
|
13
|
+
const client = getClient(command);
|
|
14
|
+
const { data } = await client.GET("/v1/datasets/{id}/versions", {
|
|
15
|
+
params: {
|
|
16
|
+
path: { id: opts.dataset },
|
|
17
|
+
query: paginationParams(opts),
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
formatPage(data, command, ["id", "versionNumber", "itemCount", "changeDescription", "createdAt"]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
cmd.addCommand(list);
|
|
24
|
+
cmd
|
|
25
|
+
.command("get")
|
|
26
|
+
.description("Get a specific dataset version")
|
|
27
|
+
.argument("<versionId>", "Version ID")
|
|
28
|
+
.requiredOption("--dataset <id>", "Dataset ID")
|
|
29
|
+
.action(async (versionId, opts, command) => {
|
|
30
|
+
await runAction(command, async () => {
|
|
31
|
+
const client = getClient(command);
|
|
32
|
+
const { data } = await client.GET("/v1/datasets/{id}/versions/{versionId}", {
|
|
33
|
+
params: {
|
|
34
|
+
path: { id: opts.dataset, versionId },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
formatDetail(data, command);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
cmd
|
|
41
|
+
.command("create")
|
|
42
|
+
.description("Create a new dataset version")
|
|
43
|
+
.requiredOption("--dataset <id>", "Dataset ID")
|
|
44
|
+
.option("-m, --message <text>", "Change description")
|
|
45
|
+
.action(async (opts, command) => {
|
|
46
|
+
await runAction(command, async () => {
|
|
47
|
+
const client = getClient(command);
|
|
48
|
+
const { data } = await client.POST("/v1/datasets/{id}/versions", {
|
|
49
|
+
params: { path: { id: opts.dataset } },
|
|
50
|
+
body: { changeDescription: opts.message },
|
|
51
|
+
});
|
|
52
|
+
formatDetail(data, command);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
cmd
|
|
56
|
+
.command("latest")
|
|
57
|
+
.description("Get the latest version of a dataset")
|
|
58
|
+
.requiredOption("--dataset <id>", "Dataset ID")
|
|
59
|
+
.action(async (opts, command) => {
|
|
60
|
+
await runAction(command, async () => {
|
|
61
|
+
const client = getClient(command);
|
|
62
|
+
const { data } = await client.GET("/v1/datasets/{id}/versions/latest", {
|
|
63
|
+
params: {
|
|
64
|
+
path: { id: opts.dataset },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
formatDetail(data, command);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
const items = new Command("items").description("List items in a dataset version (defaults to latest version)");
|
|
71
|
+
addPaginationOptions(items);
|
|
72
|
+
items.requiredOption("--dataset <id>", "Dataset ID");
|
|
73
|
+
items.option("--version-id <id>", "Version ID (defaults to latest)");
|
|
74
|
+
items.action(async (opts, command) => {
|
|
75
|
+
await runAction(command, async () => {
|
|
76
|
+
const client = getClient(command);
|
|
77
|
+
const versionId = opts.versionId ?? (await resolveLatestVersionId(client, opts.dataset));
|
|
78
|
+
const { data } = await client.GET("/v1/datasets/{id}/versions/{versionId}/items", {
|
|
79
|
+
params: {
|
|
80
|
+
path: { id: opts.dataset, versionId },
|
|
81
|
+
query: paginationParams(opts),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
formatPage(data, command, ["id", "input", "expectedOutput", "tags", "createdAt"]);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
cmd.addCommand(items);
|
|
88
|
+
return cmd;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=dataset-versions.js.map
|