@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,85 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
+
import { formatDetail, formatList } from "../lib/output.js";
|
|
4
|
+
export function makeTracingCommand() {
|
|
5
|
+
const cmd = new Command("tracing").description("Traces and per-org tracing settings (prompt / completion capture)");
|
|
6
|
+
// ── settings ──────────────────────────────────────────────────
|
|
7
|
+
const settings = new Command("settings").description("Manage per-org tracing settings (includePrompts / includeCompletions)");
|
|
8
|
+
settings
|
|
9
|
+
.command("get")
|
|
10
|
+
.description("Show current tracing settings")
|
|
11
|
+
.action(async (_opts, command) => {
|
|
12
|
+
await runAction(command, async () => {
|
|
13
|
+
const client = getClient(command);
|
|
14
|
+
const { data } = await client.GET("/v1/tracing/settings");
|
|
15
|
+
formatDetail(data, command);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
settings
|
|
19
|
+
.command("set")
|
|
20
|
+
.description("Update tracing settings. Both toggles control PII capture — default is false for both.")
|
|
21
|
+
.option("--include-prompts <bool>", "Retain prompt content on ingested spans (true/false)")
|
|
22
|
+
.option("--include-completions <bool>", "Retain completion content on ingested spans (true/false)")
|
|
23
|
+
.action(async (opts, command) => {
|
|
24
|
+
await runAction(command, async () => {
|
|
25
|
+
const client = getClient(command);
|
|
26
|
+
// Fetch current state so the user can flip a single toggle without
|
|
27
|
+
// resetting the other. Otherwise an unset flag defaults to false
|
|
28
|
+
// and silently disables capture the admin had already enabled.
|
|
29
|
+
const { data: current } = await client.GET("/v1/tracing/settings");
|
|
30
|
+
const body = {
|
|
31
|
+
includePrompts: opts.includePrompts !== undefined
|
|
32
|
+
? opts.includePrompts === "true"
|
|
33
|
+
: (current?.includePrompts ?? false),
|
|
34
|
+
includeCompletions: opts.includeCompletions !== undefined
|
|
35
|
+
? opts.includeCompletions === "true"
|
|
36
|
+
: (current?.includeCompletions ?? false),
|
|
37
|
+
};
|
|
38
|
+
const { data } = await client.PUT("/v1/tracing/settings", {
|
|
39
|
+
body,
|
|
40
|
+
});
|
|
41
|
+
formatDetail(data, command);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
cmd.addCommand(settings);
|
|
45
|
+
// ── traces ────────────────────────────────────────────────────
|
|
46
|
+
const list = new Command("list").description("List recent traces");
|
|
47
|
+
list.option("--source-service <name>", "Filter by source_service");
|
|
48
|
+
list.option("--status <code>", "Filter by status (OK, ERROR, UNSET — comma-separated for multiple)");
|
|
49
|
+
list.option("--search <query>", "Free-text search across trace id, span name, service, operation");
|
|
50
|
+
list.option("--page <n>", "Page index (0-based)", "0");
|
|
51
|
+
list.option("--size <n>", "Page size", "20");
|
|
52
|
+
list.action(async (opts, command) => {
|
|
53
|
+
await runAction(command, async () => {
|
|
54
|
+
const client = getClient(command);
|
|
55
|
+
const { data } = await client.GET("/v1/traces", {
|
|
56
|
+
params: {
|
|
57
|
+
query: {
|
|
58
|
+
sourceService: opts.sourceService,
|
|
59
|
+
statusCode: opts.status,
|
|
60
|
+
search: opts.search,
|
|
61
|
+
page: parseInt(opts.page, 10),
|
|
62
|
+
size: parseInt(opts.size, 10),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const page = data;
|
|
67
|
+
formatList((page?.content ?? []), command, ["traceId", "rootSpanName", "statusCode", "durationMs", "spanCount", "sourceServices"]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
cmd.addCommand(list);
|
|
71
|
+
const get = new Command("get").description("Get all spans of a trace by id");
|
|
72
|
+
get.argument("<traceId>", "Trace id");
|
|
73
|
+
get.action(async (traceId, _opts, command) => {
|
|
74
|
+
await runAction(command, async () => {
|
|
75
|
+
const client = getClient(command);
|
|
76
|
+
const { data } = await client.GET("/v1/traces/{traceId}", {
|
|
77
|
+
params: { path: { traceId } },
|
|
78
|
+
});
|
|
79
|
+
formatDetail(data, command);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
cmd.addCommand(get);
|
|
83
|
+
return cmd;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tracing.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolveConfig, isJsonOutput } from "../lib/config.js";
|
|
3
|
+
import { handleError } from "../lib/errors.js";
|
|
4
|
+
import { formatDetail, withSpinner } from "../lib/output.js";
|
|
5
|
+
import { fileToBlob } from "../lib/multipart.js";
|
|
6
|
+
import { BackboneApiError } from "../lib/errors.js";
|
|
7
|
+
export function makeTranscribeCommand() {
|
|
8
|
+
const cmd = new Command("transcribe")
|
|
9
|
+
.description("Transcribe an audio file")
|
|
10
|
+
.argument("<file>", "Audio file path")
|
|
11
|
+
.requiredOption("-m, --model <model>", "Model (e.g., openai/whisper-1)")
|
|
12
|
+
.option("--language <code>", "Language code (e.g., en, de)")
|
|
13
|
+
.option("--prompt <text>", "Optional prompt to guide transcription")
|
|
14
|
+
.option("--format <fmt>", "Response format (json, text, srt, verbose_json, vtt)")
|
|
15
|
+
.option("--temperature <n>", "Temperature")
|
|
16
|
+
.action(async (file, opts, command) => {
|
|
17
|
+
const json = isJsonOutput(command);
|
|
18
|
+
try {
|
|
19
|
+
const config = resolveConfig(command);
|
|
20
|
+
const { blob, filename } = fileToBlob(file);
|
|
21
|
+
const formData = new FormData();
|
|
22
|
+
formData.append("file", blob, filename);
|
|
23
|
+
formData.append("model", opts.model);
|
|
24
|
+
if (opts.language)
|
|
25
|
+
formData.append("language", opts.language);
|
|
26
|
+
if (opts.prompt)
|
|
27
|
+
formData.append("prompt", opts.prompt);
|
|
28
|
+
if (opts.format)
|
|
29
|
+
formData.append("response_format", opts.format);
|
|
30
|
+
if (opts.temperature)
|
|
31
|
+
formData.append("temperature", opts.temperature);
|
|
32
|
+
const baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
33
|
+
const url = `${baseUrl}/v1/audio/transcriptions`;
|
|
34
|
+
const data = await withSpinner("Transcribing...", async () => {
|
|
35
|
+
const res = await fetch(url, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
38
|
+
body: formData,
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
let body;
|
|
42
|
+
try {
|
|
43
|
+
body = await res.json();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
body = {
|
|
47
|
+
error: res.statusText,
|
|
48
|
+
message: `HTTP ${res.status}: ${res.statusText}`,
|
|
49
|
+
status: res.status,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
throw new BackboneApiError(body);
|
|
54
|
+
}
|
|
55
|
+
return res.json();
|
|
56
|
+
});
|
|
57
|
+
formatDetail(data, command);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
handleError(err, json);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return cmd;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=transcribe.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { makeAuthCommand } from "./commands/auth.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const pkg = require("../package.json");
|
|
7
|
+
import { makeConfigCommand } from "./commands/config.js";
|
|
8
|
+
import { makeSchemasCommand } from "./commands/schemas.js";
|
|
9
|
+
import { makePromptsCommand } from "./commands/prompts.js";
|
|
10
|
+
import { makeExtractionsCommand } from "./commands/extractions.js";
|
|
11
|
+
import { makeConvertCommand } from "./commands/convert.js";
|
|
12
|
+
import { makeAiCommand } from "./commands/ai.js";
|
|
13
|
+
import { makeTranscribeCommand } from "./commands/transcribe.js";
|
|
14
|
+
import { makeProvidersCommand } from "./commands/providers.js";
|
|
15
|
+
import { makeAnalyticsCommand } from "./commands/analytics.js";
|
|
16
|
+
import { makeBillingCommand } from "./commands/billing.js";
|
|
17
|
+
import { makeDatasetsCommand } from "./commands/datasets.js";
|
|
18
|
+
import { makeDocsCommand } from "./commands/docs.js";
|
|
19
|
+
import { makeContextCommand } from "./commands/context.js";
|
|
20
|
+
import { makeExperimentsCommand } from "./commands/experiments.js";
|
|
21
|
+
import { makeTracingCommand } from "./commands/tracing.js";
|
|
22
|
+
import { makeEvaluatorsCommand } from "./commands/evaluators.js";
|
|
23
|
+
import { makeScoresCommand } from "./commands/scores.js";
|
|
24
|
+
import { checkForUpdates } from "./lib/update-notifier.js";
|
|
25
|
+
const updater = checkForUpdates(pkg.version);
|
|
26
|
+
const program = new Command();
|
|
27
|
+
program
|
|
28
|
+
.name("2kw")
|
|
29
|
+
.description("CLI for the 2kw.ai platform (also installed as `backbone` and `bb`)")
|
|
30
|
+
.version(pkg.version)
|
|
31
|
+
.option("--api-key <key>", "API key (overrides config)")
|
|
32
|
+
.option("--base-url <url>", "Base URL (overrides config)")
|
|
33
|
+
.option("--json", "Output as JSON")
|
|
34
|
+
.option("--no-color", "Disable colored output");
|
|
35
|
+
program.addCommand(makeAuthCommand());
|
|
36
|
+
program.addCommand(makeConfigCommand());
|
|
37
|
+
program.addCommand(makeContextCommand());
|
|
38
|
+
program.addCommand(makeSchemasCommand());
|
|
39
|
+
program.addCommand(makePromptsCommand());
|
|
40
|
+
program.addCommand(makeExtractionsCommand());
|
|
41
|
+
program.addCommand(makeConvertCommand());
|
|
42
|
+
program.addCommand(makeAiCommand());
|
|
43
|
+
program.addCommand(makeTranscribeCommand());
|
|
44
|
+
program.addCommand(makeProvidersCommand());
|
|
45
|
+
program.addCommand(makeAnalyticsCommand());
|
|
46
|
+
program.addCommand(makeBillingCommand());
|
|
47
|
+
program.addCommand(makeDatasetsCommand());
|
|
48
|
+
program.addCommand(makeExperimentsCommand());
|
|
49
|
+
program.addCommand(makeEvaluatorsCommand());
|
|
50
|
+
program.addCommand(makeScoresCommand());
|
|
51
|
+
program.addCommand(makeTracingCommand());
|
|
52
|
+
program.addCommand(makeDocsCommand());
|
|
53
|
+
program.parseAsync().then(() => updater.notify());
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { paths } from "../generated/openapi.js";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
/**
|
|
4
|
+
* Create a typed openapi-fetch client from the resolved config.
|
|
5
|
+
* Attaches Bearer auth and error middleware automatically.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getClient(command: Command): import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
8
|
+
/**
|
|
9
|
+
* Convenience: run an async action with consistent error handling.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runAction(command: Command, action: () => Promise<void>): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
import { resolveConfig, isJsonOutput } from "./config.js";
|
|
3
|
+
import { BackboneApiError, handleError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Error-handling middleware: intercepts non-ok responses and throws BackboneApiError.
|
|
6
|
+
*/
|
|
7
|
+
const errorMiddleware = {
|
|
8
|
+
async onResponse({ response }) {
|
|
9
|
+
if (response.ok)
|
|
10
|
+
return undefined;
|
|
11
|
+
let body;
|
|
12
|
+
try {
|
|
13
|
+
body = await response.clone().json();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
body = {};
|
|
17
|
+
}
|
|
18
|
+
throw new BackboneApiError({
|
|
19
|
+
status: body.status ?? response.status,
|
|
20
|
+
error: body.title ?? body.error ?? response.statusText,
|
|
21
|
+
message: body.detail ?? body.message ?? `HTTP ${response.status}: ${response.statusText}`,
|
|
22
|
+
timestamp: body.timestamp ?? new Date().toISOString(),
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Create a typed openapi-fetch client from the resolved config.
|
|
28
|
+
* Attaches Bearer auth and error middleware automatically.
|
|
29
|
+
*/
|
|
30
|
+
export function getClient(command) {
|
|
31
|
+
const config = resolveConfig(command);
|
|
32
|
+
const client = createClient({
|
|
33
|
+
baseUrl: config.baseUrl.replace(/\/+$/, ""),
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
client.use(errorMiddleware);
|
|
39
|
+
return client;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Convenience: run an async action with consistent error handling.
|
|
43
|
+
*/
|
|
44
|
+
export async function runAction(command, action) {
|
|
45
|
+
try {
|
|
46
|
+
await action();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
handleError(err, isJsonOutput(command));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
export interface ContextEntry {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
}
|
|
7
|
+
export interface BackboneConfigStore {
|
|
8
|
+
activeContext: string;
|
|
9
|
+
contexts: Record<string, ContextEntry>;
|
|
10
|
+
}
|
|
11
|
+
export interface BackboneConfig {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Fallback base URL when the user has no context, env var, or local config.
|
|
17
|
+
* Override at runtime by setting AI_2KW_DEFAULT_BASE_URL (or the legacy
|
|
18
|
+
* BACKBONE_DEFAULT_BASE_URL) — e.g. for builds targeting a different
|
|
19
|
+
* deployment.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_BASE_URL: string;
|
|
22
|
+
declare const store: Conf<BackboneConfigStore>;
|
|
23
|
+
export { store };
|
|
24
|
+
export declare function validateContextName(name: string): void;
|
|
25
|
+
export declare function getActiveContextName(): string;
|
|
26
|
+
export declare function getActiveContext(): ContextEntry | undefined;
|
|
27
|
+
export declare function getAllContexts(): Record<string, ContextEntry>;
|
|
28
|
+
export declare function getContextCount(): number;
|
|
29
|
+
export declare function setContext(name: string, entry: ContextEntry): void;
|
|
30
|
+
export declare function deleteContext(name: string): void;
|
|
31
|
+
export declare function renameContext(oldName: string, newName: string): void;
|
|
32
|
+
export declare function setActiveContext(name: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve configuration with priority:
|
|
35
|
+
* 1. CLI flags (--api-key, --base-url)
|
|
36
|
+
* 2. Environment variables (AI_2KW_API_KEY then legacy BACKBONE_API_KEY,
|
|
37
|
+
* same for AI_2KW_BASE_URL / BACKBONE_BASE_URL)
|
|
38
|
+
* 3. Local .2kw file (or legacy .backbone)
|
|
39
|
+
* 4. Active context from config store
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveConfig(command: Command): BackboneConfig;
|
|
42
|
+
/** Check if --json flag is set on root command */
|
|
43
|
+
export declare function isJsonOutput(command: Command): boolean;
|
|
44
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Fallback base URL when the user has no context, env var, or local config.
|
|
6
|
+
* Override at runtime by setting AI_2KW_DEFAULT_BASE_URL (or the legacy
|
|
7
|
+
* BACKBONE_DEFAULT_BASE_URL) — e.g. for builds targeting a different
|
|
8
|
+
* deployment.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_BASE_URL = process.env.AI_2KW_DEFAULT_BASE_URL ??
|
|
11
|
+
process.env.BACKBONE_DEFAULT_BASE_URL ??
|
|
12
|
+
"https://backbone.manfred-kunze.dev/api";
|
|
13
|
+
// One-shot flag so we only print the deprecation warning once per process,
|
|
14
|
+
// even if resolveConfig is called multiple times across commands.
|
|
15
|
+
let legacyEnvWarned = false;
|
|
16
|
+
function warnLegacyEnv(legacyName, canonicalName) {
|
|
17
|
+
if (legacyEnvWarned)
|
|
18
|
+
return;
|
|
19
|
+
legacyEnvWarned = true;
|
|
20
|
+
// Write to stderr so JSON output on stdout stays clean.
|
|
21
|
+
process.stderr.write(`[deprecation] ${legacyName} is read for backwards compatibility. ` +
|
|
22
|
+
`Switch to ${canonicalName} — the BACKBONE_* names will be removed ` +
|
|
23
|
+
`in a future release.\n`);
|
|
24
|
+
}
|
|
25
|
+
const store = new Conf({
|
|
26
|
+
projectName: "2kw",
|
|
27
|
+
projectSuffix: "",
|
|
28
|
+
defaults: {
|
|
29
|
+
activeContext: "default",
|
|
30
|
+
contexts: {},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
// One-shot migration: copy any legacy ~/.config/backbone/ store into the new
|
|
34
|
+
// ~/.config/2kw/ store on first run. Triggered only if the new store has no
|
|
35
|
+
// contexts AND the legacy file exists. The legacy file is preserved so users
|
|
36
|
+
// can roll back by deleting the new store.
|
|
37
|
+
//
|
|
38
|
+
// Also handles the very-old flat format ({ apiKey, baseUrl }) that predates
|
|
39
|
+
// contexts — folds it into a "default" context.
|
|
40
|
+
(function migrateFromLegacyStore() {
|
|
41
|
+
// Already configured in the new location? Nothing to do.
|
|
42
|
+
if (Object.keys(store.get("contexts") ?? {}).length > 0)
|
|
43
|
+
return;
|
|
44
|
+
// Conf instantiation never creates the file proactively (it writes on first
|
|
45
|
+
// mutation), so spinning up a second instance pointed at the legacy
|
|
46
|
+
// projectName is a safe way to compute the legacy path.
|
|
47
|
+
const legacy = new Conf({
|
|
48
|
+
projectName: "backbone",
|
|
49
|
+
projectSuffix: "",
|
|
50
|
+
defaults: { activeContext: "default", contexts: {} },
|
|
51
|
+
});
|
|
52
|
+
if (!existsSync(legacy.path))
|
|
53
|
+
return;
|
|
54
|
+
let contexts = {};
|
|
55
|
+
let activeContext = "default";
|
|
56
|
+
try {
|
|
57
|
+
const raw = JSON.parse(readFileSync(legacy.path, "utf-8"));
|
|
58
|
+
if (raw.contexts && Object.keys(raw.contexts).length > 0) {
|
|
59
|
+
contexts = raw.contexts;
|
|
60
|
+
activeContext = raw.activeContext ?? "default";
|
|
61
|
+
}
|
|
62
|
+
else if (typeof raw.apiKey === "string") {
|
|
63
|
+
// Pre-context flat format — promote into a single "default" context.
|
|
64
|
+
contexts = {
|
|
65
|
+
default: {
|
|
66
|
+
apiKey: raw.apiKey,
|
|
67
|
+
baseUrl: raw.baseUrl ?? DEFAULT_BASE_URL,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Unparseable legacy file — leave both stores untouched and let the
|
|
74
|
+
// user re-run "2kw auth login".
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (Object.keys(contexts).length === 0)
|
|
78
|
+
return;
|
|
79
|
+
store.store = { activeContext, contexts };
|
|
80
|
+
process.stderr.write(`[migration] Copied legacy backbone CLI config to ${store.path}. ` +
|
|
81
|
+
`The original at ${legacy.path} is preserved.\n`);
|
|
82
|
+
})();
|
|
83
|
+
export { store };
|
|
84
|
+
const CONTEXT_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
85
|
+
export function validateContextName(name) {
|
|
86
|
+
if (!CONTEXT_NAME_RE.test(name)) {
|
|
87
|
+
throw new Error("Context name must contain only letters, numbers, hyphens, and underscores.");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function getActiveContextName() {
|
|
91
|
+
return store.get("activeContext") ?? "default";
|
|
92
|
+
}
|
|
93
|
+
export function getActiveContext() {
|
|
94
|
+
const name = getActiveContextName();
|
|
95
|
+
const contexts = store.get("contexts") ?? {};
|
|
96
|
+
return contexts[name];
|
|
97
|
+
}
|
|
98
|
+
export function getAllContexts() {
|
|
99
|
+
return store.get("contexts") ?? {};
|
|
100
|
+
}
|
|
101
|
+
export function getContextCount() {
|
|
102
|
+
return Object.keys(getAllContexts()).length;
|
|
103
|
+
}
|
|
104
|
+
export function setContext(name, entry) {
|
|
105
|
+
validateContextName(name);
|
|
106
|
+
const contexts = getAllContexts();
|
|
107
|
+
contexts[name] = entry;
|
|
108
|
+
store.set("contexts", contexts);
|
|
109
|
+
}
|
|
110
|
+
export function deleteContext(name) {
|
|
111
|
+
const contexts = getAllContexts();
|
|
112
|
+
if (!contexts[name]) {
|
|
113
|
+
throw new Error(`Context "${name}" does not exist.`);
|
|
114
|
+
}
|
|
115
|
+
if (Object.keys(contexts).length === 1) {
|
|
116
|
+
throw new Error("Cannot delete the last remaining context.");
|
|
117
|
+
}
|
|
118
|
+
delete contexts[name];
|
|
119
|
+
store.set("contexts", contexts);
|
|
120
|
+
if (getActiveContextName() === name) {
|
|
121
|
+
const remaining = Object.keys(contexts)[0];
|
|
122
|
+
store.set("activeContext", remaining);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function renameContext(oldName, newName) {
|
|
126
|
+
validateContextName(newName);
|
|
127
|
+
const contexts = getAllContexts();
|
|
128
|
+
if (!contexts[oldName]) {
|
|
129
|
+
throw new Error(`Context "${oldName}" does not exist.`);
|
|
130
|
+
}
|
|
131
|
+
if (contexts[newName]) {
|
|
132
|
+
throw new Error(`Context "${newName}" already exists.`);
|
|
133
|
+
}
|
|
134
|
+
contexts[newName] = contexts[oldName];
|
|
135
|
+
delete contexts[oldName];
|
|
136
|
+
store.set("contexts", contexts);
|
|
137
|
+
if (getActiveContextName() === oldName) {
|
|
138
|
+
store.set("activeContext", newName);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function setActiveContext(name) {
|
|
142
|
+
const contexts = getAllContexts();
|
|
143
|
+
if (!contexts[name]) {
|
|
144
|
+
throw new Error(`Context "${name}" does not exist. Available contexts: ${Object.keys(contexts).join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
store.set("activeContext", name);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read local config file from current directory.
|
|
150
|
+
* Prefers .2kw, falls back to legacy .backbone for existing setups.
|
|
151
|
+
*/
|
|
152
|
+
function readLocalFile() {
|
|
153
|
+
const candidates = [".2kw", ".backbone"];
|
|
154
|
+
for (const name of candidates) {
|
|
155
|
+
const p = resolve(process.cwd(), name);
|
|
156
|
+
if (!existsSync(p))
|
|
157
|
+
continue;
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
160
|
+
return { apiKey: parsed.apiKey, baseUrl: parsed.baseUrl };
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Try the next candidate
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
function readEnv(canonical, legacy) {
|
|
169
|
+
const v = process.env[canonical];
|
|
170
|
+
if (v)
|
|
171
|
+
return v;
|
|
172
|
+
const legacyV = process.env[legacy];
|
|
173
|
+
if (legacyV) {
|
|
174
|
+
warnLegacyEnv(legacy, canonical);
|
|
175
|
+
return legacyV;
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Resolve configuration with priority:
|
|
181
|
+
* 1. CLI flags (--api-key, --base-url)
|
|
182
|
+
* 2. Environment variables (AI_2KW_API_KEY then legacy BACKBONE_API_KEY,
|
|
183
|
+
* same for AI_2KW_BASE_URL / BACKBONE_BASE_URL)
|
|
184
|
+
* 3. Local .2kw file (or legacy .backbone)
|
|
185
|
+
* 4. Active context from config store
|
|
186
|
+
*/
|
|
187
|
+
export function resolveConfig(command) {
|
|
188
|
+
const root = getRootCommand(command);
|
|
189
|
+
const opts = root.opts();
|
|
190
|
+
const local = readLocalFile();
|
|
191
|
+
const activeCtx = getActiveContext();
|
|
192
|
+
const apiKey = opts.apiKey ??
|
|
193
|
+
readEnv("AI_2KW_API_KEY", "BACKBONE_API_KEY") ??
|
|
194
|
+
local.apiKey ??
|
|
195
|
+
activeCtx?.apiKey;
|
|
196
|
+
const baseUrl = opts.baseUrl ??
|
|
197
|
+
readEnv("AI_2KW_BASE_URL", "BACKBONE_BASE_URL") ??
|
|
198
|
+
local.baseUrl ??
|
|
199
|
+
activeCtx?.baseUrl ??
|
|
200
|
+
DEFAULT_BASE_URL;
|
|
201
|
+
if (!apiKey) {
|
|
202
|
+
throw new Error('No API key configured. Run "2kw auth login" (or "backbone auth login") or set AI_2KW_API_KEY.');
|
|
203
|
+
}
|
|
204
|
+
return { apiKey, baseUrl };
|
|
205
|
+
}
|
|
206
|
+
/** Walk up Commander's parent chain to find root program */
|
|
207
|
+
function getRootCommand(cmd) {
|
|
208
|
+
let root = cmd;
|
|
209
|
+
while (root.parent)
|
|
210
|
+
root = root.parent;
|
|
211
|
+
return root;
|
|
212
|
+
}
|
|
213
|
+
/** Check if --json flag is set on root command */
|
|
214
|
+
export function isJsonOutput(command) {
|
|
215
|
+
return getRootCommand(command).opts().json === true;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type createClient from "openapi-fetch";
|
|
2
|
+
import type { paths } from "../generated/openapi.js";
|
|
3
|
+
type BackboneClient = ReturnType<typeof createClient<paths>>;
|
|
4
|
+
export declare function resolveLatestVersionId(client: BackboneClient, datasetId: string): Promise<string>;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=datasets.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function resolveLatestVersionId(client, datasetId) {
|
|
2
|
+
const { data } = await client.GET("/v1/datasets/{id}/versions/latest", { params: { path: { id: datasetId } } });
|
|
3
|
+
const versionId = data?.id;
|
|
4
|
+
if (!versionId) {
|
|
5
|
+
throw new Error(`Could not resolve latest version for dataset ${datasetId}. ` +
|
|
6
|
+
`Create a version first with 'bb datasets versions create --dataset ${datasetId}'.`);
|
|
7
|
+
}
|
|
8
|
+
return versionId;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=datasets.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ApiErrorBody {
|
|
2
|
+
status: number;
|
|
3
|
+
error: string;
|
|
4
|
+
message: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class BackboneApiError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
readonly errorType: string;
|
|
10
|
+
readonly timestamp: string;
|
|
11
|
+
constructor(body: ApiErrorBody);
|
|
12
|
+
}
|
|
13
|
+
export declare function handleError(err: unknown, json: boolean): void;
|
|
14
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
export class BackboneApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
errorType;
|
|
5
|
+
timestamp;
|
|
6
|
+
constructor(body) {
|
|
7
|
+
super(body.message);
|
|
8
|
+
this.name = "BackboneApiError";
|
|
9
|
+
this.status = body.status;
|
|
10
|
+
this.errorType = body.error;
|
|
11
|
+
this.timestamp = body.timestamp;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const HINTS = {
|
|
15
|
+
401: 'Invalid or missing API key. Run "backbone auth login" to configure credentials.',
|
|
16
|
+
402: "Billing limit reached. Check your plan limits or upgrade at the dashboard.",
|
|
17
|
+
403: "You don't have permission for this action. Check your organization role.",
|
|
18
|
+
404: "Resource not found. Verify the ID is correct.",
|
|
19
|
+
409: "Conflict — the resource may already exist or was modified concurrently.",
|
|
20
|
+
422: "Validation error. Check the input values.",
|
|
21
|
+
429: "Rate limit exceeded. Wait a moment and try again.",
|
|
22
|
+
};
|
|
23
|
+
export function handleError(err, json) {
|
|
24
|
+
if (err instanceof BackboneApiError) {
|
|
25
|
+
if (json) {
|
|
26
|
+
console.error(JSON.stringify({
|
|
27
|
+
error: err.errorType,
|
|
28
|
+
status: err.status,
|
|
29
|
+
message: err.message,
|
|
30
|
+
timestamp: err.timestamp,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.error(chalk.red(`Error ${err.status}: ${err.message}`));
|
|
35
|
+
const hint = HINTS[err.status];
|
|
36
|
+
if (hint) {
|
|
37
|
+
console.error(chalk.yellow(`Hint: ${hint}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (err instanceof Error) {
|
|
42
|
+
if (json) {
|
|
43
|
+
console.error(JSON.stringify({ error: err.name, message: err.message }));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.error(chalk.red(`Unknown error: ${String(err)}`));
|
|
51
|
+
}
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getMimeType(filename: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* Read a local file and return a Blob suitable for FormData.
|
|
4
|
+
*/
|
|
5
|
+
export declare function fileToBlob(filePath: string): {
|
|
6
|
+
blob: Blob;
|
|
7
|
+
filename: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=multipart.d.ts.map
|