@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
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025-present Manfred Kunze Dev
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. No part of the Software may be reproduced, distributed, or
|
|
7
|
+
transmitted in any form or by any means, including photocopying, recording, or
|
|
8
|
+
other electronic or mechanical methods, without the prior written permission of
|
|
9
|
+
the copyright holder.
|
|
10
|
+
|
|
11
|
+
The Software is provided "as is", without warranty of any kind, express or implied,
|
|
12
|
+
including but not limited to the warranties of merchantability, fitness for a
|
|
13
|
+
particular purpose, and noninfringement. In no event shall the authors or copyright
|
|
14
|
+
holders be liable for any claim, damages, or other liability, whether in an action
|
|
15
|
+
of contract, tort, or otherwise, arising from, out of, or in connection with the
|
|
16
|
+
Software or the use or other dealings in the Software.
|
|
17
|
+
|
|
18
|
+
Usage of this Software is permitted only in accordance with the terms and conditions
|
|
19
|
+
set forth by Manfred Kunze Dev.
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Backbone CLI
|
|
2
|
+
|
|
3
|
+
A command-line interface for the [Backbone AI](https://backbone.manfred-kunze.dev) platform. Manage schemas, prompts, extractions, documents, and more — straight from your terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Latest stable release
|
|
9
|
+
npm install -g @manfred-kunze-dev/backbone-cli
|
|
10
|
+
|
|
11
|
+
# Pre-release (dev channel)
|
|
12
|
+
npm install -g @manfred-kunze-dev/backbone-cli@dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The CLI will notify you when a newer version is available.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Authenticate
|
|
21
|
+
backbone auth login
|
|
22
|
+
|
|
23
|
+
# Define a schema and run an extraction
|
|
24
|
+
backbone schemas create -n "Invoice"
|
|
25
|
+
backbone extractions create --schema <id> -m gpt-4o --text "Invoice #123, Total: $500"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The CLI is also available as `bb` for quick access — e.g. `bb schemas list`.
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
The CLI supports multiple authentication methods (highest priority first):
|
|
33
|
+
|
|
34
|
+
| Method | Example |
|
|
35
|
+
|--------|---------|
|
|
36
|
+
| CLI flags | `--api-key sk_... --base-url https://...` |
|
|
37
|
+
| Environment variables | `BACKBONE_API_KEY`, `BACKBONE_BASE_URL` |
|
|
38
|
+
| Local `.backbone` file | JSON file in the current directory |
|
|
39
|
+
| Config store | `~/.config/backbone/config.json` (set via `backbone auth login`) |
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
backbone auth login # Interactive setup
|
|
43
|
+
backbone auth status # Verify credentials
|
|
44
|
+
backbone auth logout # Clear stored credentials
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Contexts
|
|
48
|
+
|
|
49
|
+
The CLI supports kubectl-style contexts for switching between organizations and environments:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Contexts are created automatically via `backbone auth login`
|
|
53
|
+
backbone context list # List all contexts
|
|
54
|
+
backbone context use acme-corp # Switch active context
|
|
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
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Commands
|
|
61
|
+
|
|
62
|
+
| Command | Description |
|
|
63
|
+
|---------|-------------|
|
|
64
|
+
| `auth` | Login, logout, and check auth status |
|
|
65
|
+
| `config` | Get, set, and list configuration values |
|
|
66
|
+
| `context` | Manage CLI contexts for multiple environments |
|
|
67
|
+
| `schemas` | Manage schemas, versions, labels, validation, and testing |
|
|
68
|
+
| `prompts` | Manage prompts, versions, labels, compilation, and testing |
|
|
69
|
+
| `extractions` | Create extractions (text, file, images), list, re-run, estimate tokens |
|
|
70
|
+
| `convert` | Convert documents (PDF, DOCX, images) to Markdown/text/HTML/JSON |
|
|
71
|
+
| `ai` | Chat completions and model listing via the LLM Gateway |
|
|
72
|
+
| `transcribe` | Transcribe audio files (mp3, wav, flac, ogg, etc.) |
|
|
73
|
+
| `providers` | Manage BYOK AI providers |
|
|
74
|
+
| `analytics` | View organization usage analytics |
|
|
75
|
+
| `billing` | Check subscription tier and usage limits |
|
|
76
|
+
| `docs` | Browse API documentation from the terminal |
|
|
77
|
+
|
|
78
|
+
### Global Options
|
|
79
|
+
|
|
80
|
+
| Flag | Description |
|
|
81
|
+
|------|-------------|
|
|
82
|
+
| `--api-key <key>` | Override the API key |
|
|
83
|
+
| `--base-url <url>` | Override the base URL (default: `https://backbone.manfred-kunze.dev/api`) |
|
|
84
|
+
| `--json` | Output raw JSON instead of formatted tables |
|
|
85
|
+
| `--no-color` | Disable colored output |
|
|
86
|
+
|
|
87
|
+
For full command documentation, see the [Backbone docs](https://backbone.manfred-kunze.dev).
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
Proprietary — see [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
+
import { isJsonOutput } from "../lib/config.js";
|
|
4
|
+
import { formatList, withSpinner } from "../lib/output.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
export function makeAiCommand() {
|
|
7
|
+
const cmd = new Command("ai").description("AI gateway operations");
|
|
8
|
+
cmd
|
|
9
|
+
.command("chat")
|
|
10
|
+
.description("Send a chat completion request")
|
|
11
|
+
.requiredOption("-m, --model <model>", "Model (provider/model format)")
|
|
12
|
+
.requiredOption("--message <text>", "User message")
|
|
13
|
+
.option("--system <text>", "System message")
|
|
14
|
+
.option("--temperature <n>", "Temperature (0-2)")
|
|
15
|
+
.option("--max-tokens <n>", "Max tokens")
|
|
16
|
+
.option("--top-p <n>", "Top-p sampling")
|
|
17
|
+
.action(async (opts, command) => {
|
|
18
|
+
await runAction(command, async () => {
|
|
19
|
+
const client = getClient(command);
|
|
20
|
+
const messages = [];
|
|
21
|
+
if (opts.system) {
|
|
22
|
+
messages.push({ role: "system", content: opts.system });
|
|
23
|
+
}
|
|
24
|
+
messages.push({ role: "user", content: opts.message });
|
|
25
|
+
const body = {
|
|
26
|
+
model: opts.model,
|
|
27
|
+
messages,
|
|
28
|
+
stream: false,
|
|
29
|
+
temperature: opts.temperature ? parseFloat(opts.temperature) : undefined,
|
|
30
|
+
max_tokens: opts.maxTokens ? parseInt(opts.maxTokens, 10) : undefined,
|
|
31
|
+
top_p: opts.topP ? parseFloat(opts.topP) : undefined,
|
|
32
|
+
};
|
|
33
|
+
const { data } = await withSpinner("Thinking...", () => client.POST("/v1/chat/completions", { body }));
|
|
34
|
+
// Response type is untyped in OpenAPI spec (OpenAI-compatible format)
|
|
35
|
+
const result = data;
|
|
36
|
+
const json = isJsonOutput(command);
|
|
37
|
+
if (json) {
|
|
38
|
+
console.log(JSON.stringify(result, null, 2));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const choices = result?.choices;
|
|
42
|
+
const content = choices?.[0]?.message?.content;
|
|
43
|
+
if (content) {
|
|
44
|
+
console.log(content);
|
|
45
|
+
}
|
|
46
|
+
const usage = result?.usage;
|
|
47
|
+
if (usage) {
|
|
48
|
+
console.log(chalk.dim(`\n[${result?.model} | ${usage.prompt_tokens} in / ${usage.completion_tokens} out / ${usage.total_tokens} total tokens]`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
cmd
|
|
54
|
+
.command("models")
|
|
55
|
+
.description("List available AI models")
|
|
56
|
+
.action(async (_opts, command) => {
|
|
57
|
+
await runAction(command, async () => {
|
|
58
|
+
const client = getClient(command);
|
|
59
|
+
const { data } = await client.GET("/v1/models");
|
|
60
|
+
const json = isJsonOutput(command);
|
|
61
|
+
if (json) {
|
|
62
|
+
console.log(JSON.stringify(data, null, 2));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const models = data?.data ?? [];
|
|
66
|
+
formatList(models, command, ["id", "owned_by"]);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
return cmd;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=ai.js.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
+
import { formatDetail, formatList } from "../lib/output.js";
|
|
4
|
+
function addDateOptions(cmd) {
|
|
5
|
+
return cmd
|
|
6
|
+
.option("--start <date>", "Start date (YYYY-MM-DD)")
|
|
7
|
+
.option("--end <date>", "End date (YYYY-MM-DD)");
|
|
8
|
+
}
|
|
9
|
+
export function makeAnalyticsCommand() {
|
|
10
|
+
const cmd = new Command("analytics").description("Organization analytics");
|
|
11
|
+
const summary = new Command("summary").description("Cross-surface summary (total ops, spend, p95, per-surface breakdown)");
|
|
12
|
+
addDateOptions(summary);
|
|
13
|
+
summary.action(async (opts, command) => {
|
|
14
|
+
await runAction(command, async () => {
|
|
15
|
+
const client = getClient(command);
|
|
16
|
+
const { data } = await client.GET("/v1/analytics/summary", {
|
|
17
|
+
params: {
|
|
18
|
+
query: { startDate: opts.start, endDate: opts.end },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
formatDetail(data, command);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
cmd.addCommand(summary);
|
|
25
|
+
const timeSeries = new Command("time-series").description("Daily / weekly / monthly operations grouped by surface");
|
|
26
|
+
addDateOptions(timeSeries);
|
|
27
|
+
timeSeries.option("--group-by <interval>", "Group by: day, week, month", "day");
|
|
28
|
+
timeSeries.action(async (opts, command) => {
|
|
29
|
+
await runAction(command, async () => {
|
|
30
|
+
const client = getClient(command);
|
|
31
|
+
const { data } = await client.GET("/v1/analytics/time-series", {
|
|
32
|
+
params: {
|
|
33
|
+
query: {
|
|
34
|
+
startDate: opts.start,
|
|
35
|
+
endDate: opts.end,
|
|
36
|
+
groupBy: opts.groupBy,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
// Rows carry `date` + `bySurface` (map keyed by Surface). Nothing
|
|
41
|
+
// sensible to tabulate across rows of different surface sets, so
|
|
42
|
+
// fall through to the JSON view via formatDetail per row.
|
|
43
|
+
formatList(data, command, ["date", "bySurface"]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
cmd.addCommand(timeSeries);
|
|
47
|
+
const schemas = new Command("schemas").description("Top schemas by extraction count (extraction surface only)");
|
|
48
|
+
addDateOptions(schemas);
|
|
49
|
+
schemas.option("--limit <n>", "Max results", "10");
|
|
50
|
+
schemas.action(async (opts, command) => {
|
|
51
|
+
await runAction(command, async () => {
|
|
52
|
+
const client = getClient(command);
|
|
53
|
+
const { data } = await client.GET("/v1/analytics/schemas", {
|
|
54
|
+
params: {
|
|
55
|
+
query: {
|
|
56
|
+
startDate: opts.start,
|
|
57
|
+
endDate: opts.end,
|
|
58
|
+
limit: parseInt(opts.limit, 10),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
formatList(data, command, [
|
|
63
|
+
"schemaVersionId",
|
|
64
|
+
"schemaName",
|
|
65
|
+
"totalExtractions",
|
|
66
|
+
"successfulExtractions",
|
|
67
|
+
"successRate",
|
|
68
|
+
"totalTokens",
|
|
69
|
+
"totalCost",
|
|
70
|
+
"avgDurationMs",
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
cmd.addCommand(schemas);
|
|
75
|
+
const providers = new Command("providers").description("Provider usage breakdown (extraction surface only)");
|
|
76
|
+
addDateOptions(providers);
|
|
77
|
+
providers.action(async (opts, command) => {
|
|
78
|
+
await runAction(command, async () => {
|
|
79
|
+
const client = getClient(command);
|
|
80
|
+
const { data } = await client.GET("/v1/analytics/providers", {
|
|
81
|
+
params: {
|
|
82
|
+
query: { startDate: opts.start, endDate: opts.end },
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
formatList(data, command, [
|
|
86
|
+
"providerId",
|
|
87
|
+
"providerName",
|
|
88
|
+
"totalExtractions",
|
|
89
|
+
"successfulExtractions",
|
|
90
|
+
"successRate",
|
|
91
|
+
"totalTokens",
|
|
92
|
+
"totalCost",
|
|
93
|
+
"avgDurationMs",
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
cmd.addCommand(providers);
|
|
98
|
+
const errors = new Command("errors").description("Top failures grouped by surface + error message");
|
|
99
|
+
addDateOptions(errors);
|
|
100
|
+
errors.option("--limit <n>", "Max results", "10");
|
|
101
|
+
errors.action(async (opts, command) => {
|
|
102
|
+
await runAction(command, async () => {
|
|
103
|
+
const client = getClient(command);
|
|
104
|
+
const { data } = await client.GET("/v1/analytics/errors", {
|
|
105
|
+
params: {
|
|
106
|
+
query: {
|
|
107
|
+
startDate: opts.start,
|
|
108
|
+
endDate: opts.end,
|
|
109
|
+
limit: parseInt(opts.limit, 10),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
formatList(data, command, [
|
|
114
|
+
"surface",
|
|
115
|
+
"errorMessage",
|
|
116
|
+
"count",
|
|
117
|
+
"lastOccurrence",
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
cmd.addCommand(errors);
|
|
122
|
+
const quality = new Command("quality").description("Total experiment runs in range + average evaluator score");
|
|
123
|
+
addDateOptions(quality);
|
|
124
|
+
quality.action(async (opts, command) => {
|
|
125
|
+
await runAction(command, async () => {
|
|
126
|
+
const client = getClient(command);
|
|
127
|
+
const { data } = await client.GET("/v1/analytics/quality", {
|
|
128
|
+
params: {
|
|
129
|
+
query: { startDate: opts.start, endDate: opts.end },
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
formatDetail(data, command);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
cmd.addCommand(quality);
|
|
136
|
+
const qualityTrend = new Command("quality-trend").description("Average evaluator score over time");
|
|
137
|
+
addDateOptions(qualityTrend);
|
|
138
|
+
qualityTrend.option("--group-by <interval>", "Group by: day, week, month", "day");
|
|
139
|
+
qualityTrend.action(async (opts, command) => {
|
|
140
|
+
await runAction(command, async () => {
|
|
141
|
+
const client = getClient(command);
|
|
142
|
+
const { data } = await client.GET("/v1/analytics/quality/trend", {
|
|
143
|
+
params: {
|
|
144
|
+
query: {
|
|
145
|
+
startDate: opts.start,
|
|
146
|
+
endDate: opts.end,
|
|
147
|
+
groupBy: opts.groupBy,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
formatList(data, command, [
|
|
152
|
+
"date",
|
|
153
|
+
"runs",
|
|
154
|
+
"avgScore",
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
cmd.addCommand(qualityTrend);
|
|
159
|
+
return cmd;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { store, resolveConfig, isJsonOutput, getActiveContextName, getActiveContext, getContextCount, setContext, setActiveContext, deleteContext, DEFAULT_BASE_URL, } from "../lib/config.js";
|
|
4
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
export function makeAuthCommand() {
|
|
7
|
+
const cmd = new Command("auth").description("Manage authentication");
|
|
8
|
+
cmd
|
|
9
|
+
.command("login")
|
|
10
|
+
.description("Configure API credentials")
|
|
11
|
+
.option("--api-key <key>", "API key")
|
|
12
|
+
.option("--base-url <url>", "Base URL")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
let apiKey = opts.apiKey;
|
|
15
|
+
let baseUrl = opts.baseUrl;
|
|
16
|
+
const contextName = getActiveContextName();
|
|
17
|
+
const currentCtx = getActiveContext();
|
|
18
|
+
if (!apiKey || !baseUrl) {
|
|
19
|
+
const rl = createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
});
|
|
23
|
+
try {
|
|
24
|
+
if (!baseUrl) {
|
|
25
|
+
const defaultUrl = currentCtx?.baseUrl ?? DEFAULT_BASE_URL;
|
|
26
|
+
baseUrl = await rl.question(`Base URL [${defaultUrl}]: `);
|
|
27
|
+
if (!baseUrl)
|
|
28
|
+
baseUrl = defaultUrl;
|
|
29
|
+
}
|
|
30
|
+
if (!apiKey) {
|
|
31
|
+
apiKey = await rl.question("API Key (sk_...): ");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
rl.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!apiKey) {
|
|
39
|
+
console.error(chalk.red("API key is required."));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
setContext(contextName, { apiKey, baseUrl });
|
|
43
|
+
setActiveContext(contextName);
|
|
44
|
+
console.log(chalk.green("Credentials saved successfully."));
|
|
45
|
+
console.log(chalk.dim(`Config stored at: ${store.path}`));
|
|
46
|
+
});
|
|
47
|
+
cmd
|
|
48
|
+
.command("logout")
|
|
49
|
+
.description("Clear stored credentials")
|
|
50
|
+
.action(() => {
|
|
51
|
+
const contextName = getActiveContextName();
|
|
52
|
+
const count = getContextCount();
|
|
53
|
+
if (count <= 1) {
|
|
54
|
+
// Last context — clear it by resetting the store
|
|
55
|
+
store.store = { activeContext: "default", contexts: {} };
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
deleteContext(contextName);
|
|
59
|
+
console.log(chalk.dim(`Removed context "${contextName}". Switched to "${getActiveContextName()}".`));
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.green("Credentials cleared."));
|
|
62
|
+
});
|
|
63
|
+
cmd
|
|
64
|
+
.command("status")
|
|
65
|
+
.description("Show current authentication status")
|
|
66
|
+
.action(async (_opts, command) => {
|
|
67
|
+
await runAction(command, async () => {
|
|
68
|
+
const json = isJsonOutput(command);
|
|
69
|
+
let config;
|
|
70
|
+
try {
|
|
71
|
+
config = resolveConfig(command);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
if (json) {
|
|
75
|
+
console.log(JSON.stringify({ authenticated: false }));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.log(chalk.yellow("Not authenticated."));
|
|
79
|
+
console.log(chalk.dim('Run "2kw auth login" to configure.'));
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const { apiKey, baseUrl } = config;
|
|
84
|
+
const keyPreview = apiKey.slice(0, 7) + "..." + apiKey.slice(-4);
|
|
85
|
+
const multiContext = getContextCount() > 1;
|
|
86
|
+
const contextName = getActiveContextName();
|
|
87
|
+
// Try to validate by listing models (lightweight call)
|
|
88
|
+
try {
|
|
89
|
+
const client = getClient(command);
|
|
90
|
+
const { data } = await client.GET("/v1/models");
|
|
91
|
+
const modelCount = data?.data?.length ?? 0;
|
|
92
|
+
if (json) {
|
|
93
|
+
console.log(JSON.stringify({
|
|
94
|
+
authenticated: true,
|
|
95
|
+
...(multiContext ? { context: contextName } : {}),
|
|
96
|
+
baseUrl,
|
|
97
|
+
apiKeyPreview: keyPreview,
|
|
98
|
+
modelCount,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(chalk.green("Authenticated"));
|
|
103
|
+
if (multiContext) {
|
|
104
|
+
console.log(` Context: ${contextName}`);
|
|
105
|
+
}
|
|
106
|
+
console.log(` Base URL: ${baseUrl}`);
|
|
107
|
+
console.log(` API Key: ${keyPreview}`);
|
|
108
|
+
console.log(` Models: ${modelCount} available`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
if (json) {
|
|
113
|
+
console.log(JSON.stringify({
|
|
114
|
+
authenticated: false,
|
|
115
|
+
...(multiContext ? { context: contextName } : {}),
|
|
116
|
+
baseUrl,
|
|
117
|
+
apiKeyPreview: keyPreview,
|
|
118
|
+
error: "Failed to validate credentials",
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log(chalk.yellow("Credentials configured but validation failed."));
|
|
123
|
+
if (multiContext) {
|
|
124
|
+
console.log(` Context: ${contextName}`);
|
|
125
|
+
}
|
|
126
|
+
console.log(` Base URL: ${baseUrl}`);
|
|
127
|
+
console.log(` API Key: ${keyPreview}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
return cmd;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
+
import { isJsonOutput } from "../lib/config.js";
|
|
4
|
+
import { formatDetail, formatList } from "../lib/output.js";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
export function makeBillingCommand() {
|
|
7
|
+
const cmd = new Command("billing").description("Billing and subscription management");
|
|
8
|
+
cmd
|
|
9
|
+
.command("tiers")
|
|
10
|
+
.description("List available subscription tiers")
|
|
11
|
+
.action(async (_opts, command) => {
|
|
12
|
+
await runAction(command, async () => {
|
|
13
|
+
const client = getClient(command);
|
|
14
|
+
const { data } = await client.GET("/v1/billing/tiers");
|
|
15
|
+
formatList(data, command, ["name", "displayName", "maxProjects", "maxSchemas", "monthlyExtractionCredits", "monthlyGatewayTokens"]);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
cmd
|
|
19
|
+
.command("tier")
|
|
20
|
+
.description("Show current organization tier")
|
|
21
|
+
.action(async (_opts, command) => {
|
|
22
|
+
await runAction(command, async () => {
|
|
23
|
+
const client = getClient(command);
|
|
24
|
+
const { data } = await client.GET("/v1/billing/tier");
|
|
25
|
+
formatDetail(data, command);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
cmd
|
|
29
|
+
.command("limits")
|
|
30
|
+
.description("Show current usage and limits")
|
|
31
|
+
.action(async (_opts, command) => {
|
|
32
|
+
await runAction(command, async () => {
|
|
33
|
+
const client = getClient(command);
|
|
34
|
+
const { data } = await client.GET("/v1/billing/limits");
|
|
35
|
+
if (isJsonOutput(command)) {
|
|
36
|
+
console.log(JSON.stringify(data, null, 2));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(chalk.cyan(`Tier: ${data?.tier ?? "unknown"}`));
|
|
40
|
+
console.log();
|
|
41
|
+
if (data?.resources?.length) {
|
|
42
|
+
console.log(chalk.bold("Resources:"));
|
|
43
|
+
for (const r of data.resources) {
|
|
44
|
+
const label = r.displayName ?? r.resourceType;
|
|
45
|
+
const bar = r.limit === -1
|
|
46
|
+
? chalk.green("unlimited")
|
|
47
|
+
: `${r.used ?? 0} / ${r.limit ?? 0} (${r.remaining ?? 0} remaining)`;
|
|
48
|
+
console.log(` ${chalk.bold(String(label))}: ${bar}`);
|
|
49
|
+
}
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk.bold("Usage:"));
|
|
53
|
+
console.log(` Schemas: ${data?.currentSchemas ?? 0} / ${data?.maxSchemas ?? 0}`);
|
|
54
|
+
console.log(` Prompts: ${data?.currentPrompts ?? 0} / ${data?.maxPrompts ?? 0}`);
|
|
55
|
+
console.log(` Team members: ${data?.currentTeamMembers ?? 0} / ${data?.maxTeamMembers ?? 0}`);
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk.bold("Features:"));
|
|
58
|
+
console.log(` BYOK: ${data?.byokEnabled ? chalk.green("yes") : chalk.dim("no")}`);
|
|
59
|
+
console.log(` Priority support: ${data?.prioritySupport ? chalk.green("yes") : chalk.dim("no")}`);
|
|
60
|
+
if (data?.periodStart || data?.periodEnd) {
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.dim(`Period: ${data.periodStart?.slice(0, 10) ?? "?"} \u2192 ${data.periodEnd?.slice(0, 10) ?? "?"}`));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
cmd
|
|
68
|
+
.command("check")
|
|
69
|
+
.description("Check if a resource can be created")
|
|
70
|
+
.argument("<resource>", "Resource type: schema, prompt, team-member")
|
|
71
|
+
.action(async (resource, _opts, command) => {
|
|
72
|
+
await runAction(command, async () => {
|
|
73
|
+
const client = getClient(command);
|
|
74
|
+
let response;
|
|
75
|
+
switch (resource) {
|
|
76
|
+
case "schema":
|
|
77
|
+
response = await client.GET("/v1/billing/can-create-schema");
|
|
78
|
+
break;
|
|
79
|
+
case "prompt":
|
|
80
|
+
response = await client.GET("/v1/billing/can-create-prompt");
|
|
81
|
+
break;
|
|
82
|
+
case "team-member":
|
|
83
|
+
response = await client.GET("/v1/billing/can-add-team-member");
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
throw new Error(`Unknown resource: "${resource}". Use: schema, prompt, team-member`);
|
|
87
|
+
}
|
|
88
|
+
const result = response.data?.result;
|
|
89
|
+
if (isJsonOutput(command)) {
|
|
90
|
+
console.log(JSON.stringify({ resource, canCreate: result }));
|
|
91
|
+
}
|
|
92
|
+
else if (result) {
|
|
93
|
+
console.log(chalk.green(`You can create a new ${resource}.`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(chalk.yellow(`You cannot create a new ${resource}. Upgrade your tier or check limits.`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
return cmd;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=billing.js.map
|