@2kw/ai 5.1.0 → 5.2.0-dev.8
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/README.md +53 -32
- package/dist/commands/auth.js +18 -1
- package/dist/lib/config.js +10 -0
- package/dist/lib/errors.d.ts +17 -0
- package/dist/lib/errors.js +47 -0
- package/dist/lib/update-notifier.js +1 -1
- package/package.json +27 -4
package/README.md
CHANGED
|
@@ -1,31 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 2kw.ai CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The command-line interface for [2kw.ai](https://2kw.ai) — the EU-hosted AI integration platform. Run [schema-driven document extraction](https://2kw.ai/extraction), route LLM calls through an [OpenAI-compatible gateway](https://2kw.ai/gateway), transcribe audio, and manage prompts, datasets, and experiments — from your terminal or inside agentic workflows.
|
|
4
|
+
|
|
5
|
+
Installs three equivalent binaries: `2kw`, `backbone`, and `bb`.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
# Latest stable release
|
|
9
|
-
npm install -g @
|
|
11
|
+
npm install -g @2kw/ai
|
|
10
12
|
|
|
11
13
|
# Pre-release (dev channel)
|
|
12
|
-
npm install -g @
|
|
14
|
+
npm install -g @2kw/ai@dev
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
The CLI
|
|
17
|
+
The CLI notifies you when a newer version is available.
|
|
16
18
|
|
|
17
19
|
## Quick Start
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
# Authenticate
|
|
21
|
-
|
|
22
|
+
# Authenticate (get an API key from your 2kw.ai dashboard — 7-day free trial)
|
|
23
|
+
2kw auth login
|
|
22
24
|
|
|
23
25
|
# Define a schema and run an extraction
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
2kw schemas create -n "Invoice"
|
|
27
|
+
2kw extractions create --schema <id> -m gpt-4o --text "Invoice #123, Total: €500"
|
|
28
|
+
|
|
29
|
+
# Convert and transcribe
|
|
30
|
+
2kw convert file report.docx --format md
|
|
31
|
+
2kw transcribe call.mp3 --format srt
|
|
32
|
+
|
|
33
|
+
# Chat through the gateway
|
|
34
|
+
2kw ai chat -m gpt-4o --message "Draft a summary of this quarter's extraction volume"
|
|
26
35
|
```
|
|
27
36
|
|
|
28
|
-
|
|
37
|
+
All commands work identically as `bb` for quick access — e.g. `bb schemas list`.
|
|
29
38
|
|
|
30
39
|
## Authentication
|
|
31
40
|
|
|
@@ -34,27 +43,24 @@ The CLI supports multiple authentication methods (highest priority first):
|
|
|
34
43
|
| Method | Example |
|
|
35
44
|
|--------|---------|
|
|
36
45
|
| CLI flags | `--api-key sk_... --base-url https://...` |
|
|
37
|
-
| Environment variables | `
|
|
38
|
-
| Local `.
|
|
39
|
-
| Config store |
|
|
46
|
+
| Environment variables | `AI_2KW_API_KEY`, `AI_2KW_BASE_URL` (legacy: `BACKBONE_*`) |
|
|
47
|
+
| Local `.2kw` file | JSON file in the current directory (legacy: `.backbone`) |
|
|
48
|
+
| Config store | Set via `2kw auth login` |
|
|
40
49
|
|
|
41
50
|
```bash
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
2kw auth login # Interactive setup
|
|
52
|
+
2kw auth status # Verify credentials
|
|
53
|
+
2kw auth logout # Clear stored credentials
|
|
45
54
|
```
|
|
46
55
|
|
|
47
56
|
## Contexts
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
kubectl-style contexts switch between organizations and environments:
|
|
50
59
|
|
|
51
60
|
```bash
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
backbone context create staging # Create a new context
|
|
56
|
-
backbone context rename old new # Rename a context
|
|
57
|
-
backbone context delete old # Remove a context
|
|
61
|
+
2kw context list # List all contexts
|
|
62
|
+
2kw context use acme-corp # Switch active context
|
|
63
|
+
2kw context create staging # Create a new context
|
|
58
64
|
```
|
|
59
65
|
|
|
60
66
|
## Commands
|
|
@@ -63,15 +69,20 @@ backbone context delete old # Remove a context
|
|
|
63
69
|
|---------|-------------|
|
|
64
70
|
| `auth` | Login, logout, and check auth status |
|
|
65
71
|
| `config` | Get, set, and list configuration values |
|
|
66
|
-
| `context` | Manage
|
|
72
|
+
| `context` | Manage contexts for multiple orgs/environments |
|
|
67
73
|
| `schemas` | Manage schemas, versions, labels, validation, and testing |
|
|
68
|
-
| `prompts` |
|
|
74
|
+
| `prompts` | Versioned prompt management, labels, compilation, testing |
|
|
69
75
|
| `extractions` | Create extractions (text, file, images), list, re-run, estimate tokens |
|
|
70
|
-
| `convert` | Convert
|
|
71
|
-
| `ai` | Chat completions and model listing via the
|
|
72
|
-
| `transcribe` | Transcribe audio
|
|
76
|
+
| `convert` | Convert PDF, DOCX, XLSX, images, and 20+ formats to Markdown/text/HTML/JSON |
|
|
77
|
+
| `ai` | Chat completions and model listing via the OpenAI-compatible gateway |
|
|
78
|
+
| `transcribe` | Transcribe audio (FLAC, MP3, MP4, OGG, WAV, WebM) |
|
|
79
|
+
| `datasets` | Build datasets and dataset versions for evaluation |
|
|
80
|
+
| `experiments` | Run experiments against datasets |
|
|
81
|
+
| `evaluators` | Manage evaluator templates |
|
|
82
|
+
| `scores` | Record and inspect quality scores |
|
|
83
|
+
| `tracing` | Inspect request traces and tracing settings |
|
|
73
84
|
| `providers` | Manage BYOK AI providers |
|
|
74
|
-
| `analytics` |
|
|
85
|
+
| `analytics` | Usage analytics: spend, quality, providers, errors |
|
|
75
86
|
| `billing` | Check subscription tier and usage limits |
|
|
76
87
|
| `docs` | Browse API documentation from the terminal |
|
|
77
88
|
|
|
@@ -80,11 +91,21 @@ backbone context delete old # Remove a context
|
|
|
80
91
|
| Flag | Description |
|
|
81
92
|
|------|-------------|
|
|
82
93
|
| `--api-key <key>` | Override the API key |
|
|
83
|
-
| `--base-url <url>` | Override the base URL (
|
|
84
|
-
| `--json` | Output raw JSON instead of formatted tables |
|
|
94
|
+
| `--base-url <url>` | Override the base URL (defaults to the 2kw.ai cloud API) |
|
|
95
|
+
| `--json` | Output raw JSON instead of formatted tables — built for scripting and agents |
|
|
85
96
|
| `--no-color` | Disable colored output |
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
## Why 2kw.ai
|
|
99
|
+
|
|
100
|
+
- **EU data residency** — built-in models run GDPR-compliant and EU-hosted, operated by Manfred Kunze Development GmbH (Germany)
|
|
101
|
+
- **Grounding scores** — every extracted value links back to where it came from in the source document
|
|
102
|
+
- **One platform** — gateway, extraction, transcription, and observability share one API and one bill
|
|
103
|
+
|
|
104
|
+
## Related
|
|
105
|
+
|
|
106
|
+
- [2kw.ai](https://2kw.ai) — platform overview and pricing
|
|
107
|
+
- [Documentation](https://docs.2kw.ai) — full command and API reference
|
|
108
|
+
- [`@2kw/ai-mcp-server`](https://www.npmjs.com/package/@2kw/ai-mcp-server) — the 2kw.ai MCP server for Claude Code, Cursor, and Windsurf
|
|
88
109
|
|
|
89
110
|
## License
|
|
90
111
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -2,7 +2,15 @@ import { Command } from "commander";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { store, resolveConfig, isJsonOutput, getActiveContextName, getActiveContext, getContextCount, setContext, setActiveContext, deleteContext, DEFAULT_BASE_URL, } from "../lib/config.js";
|
|
4
4
|
import { getClient, runAction } from "../lib/client.js";
|
|
5
|
+
import { classifyAuthFailure } from "../lib/errors.js";
|
|
5
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
// Human-readable, actionable line per failure kind for the non-JSON output.
|
|
8
|
+
const FAILURE_HINTS = {
|
|
9
|
+
UNAUTHORIZED: 'Key rejected. Run "2kw auth login" to re-authenticate.',
|
|
10
|
+
FORBIDDEN: "Key is valid but lacks access. Check the key's organization in the 2kw UI.",
|
|
11
|
+
UNREACHABLE: "Could not reach the host. Check the base URL and your connection.",
|
|
12
|
+
UNKNOWN: 'Validation failed. Run "2kw auth login" and, if it persists, check the base URL.',
|
|
13
|
+
};
|
|
6
14
|
export function makeAuthCommand() {
|
|
7
15
|
const cmd = new Command("auth").description("Manage authentication");
|
|
8
16
|
cmd
|
|
@@ -71,6 +79,8 @@ export function makeAuthCommand() {
|
|
|
71
79
|
config = resolveConfig(command);
|
|
72
80
|
}
|
|
73
81
|
catch {
|
|
82
|
+
// No credentials configured at all — distinct from a rejected key.
|
|
83
|
+
process.exitCode = 1;
|
|
74
84
|
if (json) {
|
|
75
85
|
console.log(JSON.stringify({ authenticated: false }));
|
|
76
86
|
}
|
|
@@ -108,13 +118,19 @@ export function makeAuthCommand() {
|
|
|
108
118
|
console.log(` Models: ${modelCount} available`);
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
|
-
catch {
|
|
121
|
+
catch (err) {
|
|
122
|
+
// Credentials are present but the check failed. Surface WHY, so
|
|
123
|
+
// callers stop guessing among a bad key, missing permissions, and an
|
|
124
|
+
// unreachable host.
|
|
125
|
+
const failureKind = classifyAuthFailure(err);
|
|
126
|
+
process.exitCode = 1;
|
|
112
127
|
if (json) {
|
|
113
128
|
console.log(JSON.stringify({
|
|
114
129
|
authenticated: false,
|
|
115
130
|
...(multiContext ? { context: contextName } : {}),
|
|
116
131
|
baseUrl,
|
|
117
132
|
apiKeyPreview: keyPreview,
|
|
133
|
+
failureKind,
|
|
118
134
|
error: "Failed to validate credentials",
|
|
119
135
|
}));
|
|
120
136
|
}
|
|
@@ -125,6 +141,7 @@ export function makeAuthCommand() {
|
|
|
125
141
|
}
|
|
126
142
|
console.log(` Base URL: ${baseUrl}`);
|
|
127
143
|
console.log(` API Key: ${keyPreview}`);
|
|
144
|
+
console.log(chalk.yellow(` ${FAILURE_HINTS[failureKind]}`));
|
|
128
145
|
}
|
|
129
146
|
}
|
|
130
147
|
});
|
package/dist/lib/config.js
CHANGED
|
@@ -22,6 +22,14 @@ function warnLegacyEnv(legacyName, canonicalName) {
|
|
|
22
22
|
`Switch to ${canonicalName} — the BACKBONE_* names will be removed ` +
|
|
23
23
|
`in a future release.\n`);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Optional override for where the config store lives. Set AI_2KW_CONFIG_DIR to
|
|
27
|
+
* relocate the store — e.g. to isolate an environment, or so the test suite
|
|
28
|
+
* never touches the real user profile. When set, it applies to both the active
|
|
29
|
+
* store and the legacy store the migration reads, so a redirected run stays
|
|
30
|
+
* fully self-contained.
|
|
31
|
+
*/
|
|
32
|
+
const CONFIG_DIR_OVERRIDE = process.env.AI_2KW_CONFIG_DIR;
|
|
25
33
|
const store = new Conf({
|
|
26
34
|
projectName: "2kw",
|
|
27
35
|
projectSuffix: "",
|
|
@@ -29,6 +37,7 @@ const store = new Conf({
|
|
|
29
37
|
activeContext: "default",
|
|
30
38
|
contexts: {},
|
|
31
39
|
},
|
|
40
|
+
...(CONFIG_DIR_OVERRIDE ? { cwd: CONFIG_DIR_OVERRIDE } : {}),
|
|
32
41
|
});
|
|
33
42
|
// One-shot migration: copy any legacy ~/.config/backbone/ store into the new
|
|
34
43
|
// ~/.config/2kw/ store on first run. Triggered only if the new store has no
|
|
@@ -48,6 +57,7 @@ const store = new Conf({
|
|
|
48
57
|
projectName: "backbone",
|
|
49
58
|
projectSuffix: "",
|
|
50
59
|
defaults: { activeContext: "default", contexts: {} },
|
|
60
|
+
...(CONFIG_DIR_OVERRIDE ? { cwd: CONFIG_DIR_OVERRIDE } : {}),
|
|
51
61
|
});
|
|
52
62
|
if (!existsSync(legacy.path))
|
|
53
63
|
return;
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -10,5 +10,22 @@ export declare class BackboneApiError extends Error {
|
|
|
10
10
|
readonly timestamp: string;
|
|
11
11
|
constructor(body: ApiErrorBody);
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Discriminated cause of a failed credential check. Lets callers give a
|
|
15
|
+
* precise message instead of guessing, because `2kw auth status` otherwise
|
|
16
|
+
* collapses every failure into one string.
|
|
17
|
+
*/
|
|
18
|
+
export type AuthFailureKind = "UNAUTHORIZED" | "FORBIDDEN" | "UNREACHABLE" | "UNKNOWN";
|
|
19
|
+
/**
|
|
20
|
+
* Classify why a credential check failed, from the thrown error alone.
|
|
21
|
+
*
|
|
22
|
+
* - An HTTP rejection arrives as {@link BackboneApiError}: 401 → UNAUTHORIZED,
|
|
23
|
+
* 403 → FORBIDDEN, anything else → UNKNOWN.
|
|
24
|
+
* - A network fault (fetch throws before any response) → UNREACHABLE, detected
|
|
25
|
+
* by undici's `fetch failed` TypeError or a known network error code on the
|
|
26
|
+
* error or its `cause`.
|
|
27
|
+
* - Everything else → UNKNOWN.
|
|
28
|
+
*/
|
|
29
|
+
export declare function classifyAuthFailure(err: unknown): AuthFailureKind;
|
|
13
30
|
export declare function handleError(err: unknown, json: boolean): void;
|
|
14
31
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/lib/errors.js
CHANGED
|
@@ -11,6 +11,53 @@ export class BackboneApiError extends Error {
|
|
|
11
11
|
this.timestamp = body.timestamp;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
// Node/undici surface network faults through the error's `code`. A DNS or
|
|
15
|
+
// connection failure means the host was never reached — distinct from the
|
|
16
|
+
// API rejecting the key.
|
|
17
|
+
const NETWORK_ERROR_CODES = new Set([
|
|
18
|
+
"ENOTFOUND",
|
|
19
|
+
"ECONNREFUSED",
|
|
20
|
+
"EAI_AGAIN",
|
|
21
|
+
"ETIMEDOUT",
|
|
22
|
+
"ECONNRESET",
|
|
23
|
+
"ENETUNREACH",
|
|
24
|
+
"EHOSTUNREACH",
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Classify why a credential check failed, from the thrown error alone.
|
|
28
|
+
*
|
|
29
|
+
* - An HTTP rejection arrives as {@link BackboneApiError}: 401 → UNAUTHORIZED,
|
|
30
|
+
* 403 → FORBIDDEN, anything else → UNKNOWN.
|
|
31
|
+
* - A network fault (fetch throws before any response) → UNREACHABLE, detected
|
|
32
|
+
* by undici's `fetch failed` TypeError or a known network error code on the
|
|
33
|
+
* error or its `cause`.
|
|
34
|
+
* - Everything else → UNKNOWN.
|
|
35
|
+
*/
|
|
36
|
+
export function classifyAuthFailure(err) {
|
|
37
|
+
if (err instanceof BackboneApiError) {
|
|
38
|
+
if (err.status === 401)
|
|
39
|
+
return "UNAUTHORIZED";
|
|
40
|
+
if (err.status === 403)
|
|
41
|
+
return "FORBIDDEN";
|
|
42
|
+
return "UNKNOWN";
|
|
43
|
+
}
|
|
44
|
+
if (isNetworkError(err))
|
|
45
|
+
return "UNREACHABLE";
|
|
46
|
+
return "UNKNOWN";
|
|
47
|
+
}
|
|
48
|
+
function isNetworkError(err) {
|
|
49
|
+
if (!(err instanceof Error))
|
|
50
|
+
return false;
|
|
51
|
+
// undici throws `TypeError: fetch failed` when the request never completes.
|
|
52
|
+
if (err instanceof TypeError && /fetch failed/i.test(err.message))
|
|
53
|
+
return true;
|
|
54
|
+
const code = err.code;
|
|
55
|
+
if (typeof code === "string" && NETWORK_ERROR_CODES.has(code))
|
|
56
|
+
return true;
|
|
57
|
+
const cause = err.cause;
|
|
58
|
+
const causeCode = cause?.code;
|
|
59
|
+
return typeof causeCode === "string" && NETWORK_ERROR_CODES.has(causeCode);
|
|
60
|
+
}
|
|
14
61
|
const HINTS = {
|
|
15
62
|
401: 'Invalid or missing API key. Run "backbone auth login" to configure credentials.',
|
|
16
63
|
402: "Billing limit reached. Check your plan limits or upgrade at the dashboard.",
|
|
@@ -2,7 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
const PKG_NAME = "@
|
|
5
|
+
const PKG_NAME = "@2kw/ai";
|
|
6
6
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 1 day
|
|
7
7
|
const CACHE_DIR = join(homedir(), ".config", "backbone");
|
|
8
8
|
const CACHE_FILE = join(CACHE_DIR, "update-check.json");
|
package/package.json
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai",
|
|
3
|
-
"version": "5.
|
|
4
|
-
"description": "CLI for
|
|
3
|
+
"version": "5.2.0-dev.8",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"llm-gateway",
|
|
8
|
+
"ai-gateway",
|
|
9
|
+
"document-extraction",
|
|
10
|
+
"structured-data-extraction",
|
|
11
|
+
"pdf-extraction",
|
|
12
|
+
"openai-compatible",
|
|
13
|
+
"transcription",
|
|
14
|
+
"prompt-management",
|
|
15
|
+
"llm-evaluation",
|
|
16
|
+
"llm-observability",
|
|
17
|
+
"agentic-workflows",
|
|
18
|
+
"gdpr",
|
|
19
|
+
"eu-data-residency",
|
|
20
|
+
"2kw",
|
|
21
|
+
"backbone"
|
|
22
|
+
],
|
|
5
23
|
"type": "module",
|
|
6
24
|
"main": "dist/index.js",
|
|
7
25
|
"bin": {
|
|
@@ -17,7 +35,11 @@
|
|
|
17
35
|
"generate:types": "openapi-typescript openapi/openapi.json -o src/generated/openapi.d.ts",
|
|
18
36
|
"generate": "npm run generate:spec && npm run generate:types",
|
|
19
37
|
"check:spec": "tsx --env-file=.env openapi/scripts/check-drift.ts",
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"test:run": "vitest run",
|
|
41
|
+
"test:unit": "vitest run tests/unit",
|
|
42
|
+
"test:contract": "vitest run tests/contract"
|
|
21
43
|
},
|
|
22
44
|
"dependencies": {
|
|
23
45
|
"chalk": "^5.6.2",
|
|
@@ -31,7 +53,8 @@
|
|
|
31
53
|
"@types/node": "^22.19.17",
|
|
32
54
|
"openapi-typescript": "^7.13.0",
|
|
33
55
|
"tsx": "^4.21.1",
|
|
34
|
-
"typescript": "^5.9.3"
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"vitest": "^4.1.10"
|
|
35
58
|
},
|
|
36
59
|
"engines": {
|
|
37
60
|
"node": ">=18.20.8"
|