@bytevion/cli 0.2.0 → 0.4.0
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/dist/base.js +8 -1
- package/dist/commands/opt/preset.js +8 -5
- package/dist/commands/opt/show.js +20 -6
- package/dist/commands/providers/add.js +10 -2
- package/dist/commands/run.js +1 -1
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +210 -39
- package/dist/commands/usage.js +32 -8
- package/dist/hooks/init/home.js +41 -5
- package/dist/lib/api.d.ts +1 -0
- package/dist/lib/api.js +6 -0
- package/dist/lib/friendly.d.ts +8 -0
- package/dist/lib/friendly.js +86 -0
- package/dist/lib/home.d.ts +16 -0
- package/dist/lib/home.js +97 -0
- package/dist/lib/output.d.ts +3 -0
- package/dist/lib/output.js +43 -0
- package/dist/lib/presets.d.ts +9 -0
- package/dist/lib/presets.js +40 -0
- package/dist/lib/tui.d.ts +105 -0
- package/dist/lib/tui.gate.test.d.ts +1 -0
- package/dist/lib/tui.gate.test.js +96 -0
- package/dist/lib/tui.js +62 -0
- package/dist/lib/ui.js +13 -1
- package/dist/tui/__tests__/home.render.test.d.ts +1 -0
- package/dist/tui/__tests__/home.render.test.js +59 -0
- package/dist/tui/__tests__/state.test.d.ts +1 -0
- package/dist/tui/__tests__/state.test.js +88 -0
- package/dist/tui/components/App.d.ts +7 -0
- package/dist/tui/components/App.js +129 -0
- package/dist/tui/components/Brand.d.ts +9 -0
- package/dist/tui/components/Brand.js +13 -0
- package/dist/tui/components/Card.d.ts +11 -0
- package/dist/tui/components/Card.js +12 -0
- package/dist/tui/components/DoneScreen.d.ts +11 -0
- package/dist/tui/components/DoneScreen.js +30 -0
- package/dist/tui/components/Home.d.ts +12 -0
- package/dist/tui/components/Home.js +144 -0
- package/dist/tui/components/KeyStep.d.ts +13 -0
- package/dist/tui/components/KeyStep.js +44 -0
- package/dist/tui/components/Panel.d.ts +11 -0
- package/dist/tui/components/Panel.js +12 -0
- package/dist/tui/components/Picker.d.ts +19 -0
- package/dist/tui/components/Picker.js +68 -0
- package/dist/tui/components/PresetStep.d.ts +12 -0
- package/dist/tui/components/PresetStep.js +26 -0
- package/dist/tui/components/ProviderStep.d.ts +20 -0
- package/dist/tui/components/ProviderStep.js +159 -0
- package/dist/tui/components/Stepper.d.ts +9 -0
- package/dist/tui/components/Stepper.js +29 -0
- package/dist/tui/contract.d.ts +99 -0
- package/dist/tui/contract.js +5 -0
- package/dist/tui/index.d.ts +3 -0
- package/dist/tui/index.js +78 -0
- package/dist/tui/package.json +1 -0
- package/dist/tui/state.d.ts +77 -0
- package/dist/tui/state.js +84 -0
- package/dist/tui/theme.d.ts +23 -0
- package/dist/tui/theme.js +49 -0
- package/oclif.manifest.json +152 -150
- package/package.json +13 -3
package/dist/base.js
CHANGED
|
@@ -6,6 +6,7 @@ const api_1 = require("./lib/api");
|
|
|
6
6
|
const config_1 = require("./lib/config");
|
|
7
7
|
const credentials_1 = require("./lib/credentials");
|
|
8
8
|
const errors_1 = require("./lib/errors");
|
|
9
|
+
const friendly_1 = require("./lib/friendly");
|
|
9
10
|
class BaseCommand extends core_1.Command {
|
|
10
11
|
static enableJsonFlag = true;
|
|
11
12
|
static baseFlags = {
|
|
@@ -41,7 +42,13 @@ class BaseCommand extends core_1.Command {
|
|
|
41
42
|
}
|
|
42
43
|
async catch(err) {
|
|
43
44
|
if (err instanceof errors_1.ByteError) {
|
|
44
|
-
|
|
45
|
+
// --json callers want the machine-readable oclif error; humans get a panel that
|
|
46
|
+
// says what broke, why, and the exact next command — then we exit with the code.
|
|
47
|
+
if (this.jsonEnabled()) {
|
|
48
|
+
return this.error(err.message, { code: err.code, exit: err.exit });
|
|
49
|
+
}
|
|
50
|
+
this.logToStderr((0, friendly_1.renderErrorPanel)((0, friendly_1.friendlyError)(err)));
|
|
51
|
+
return this.exit(err.exit);
|
|
45
52
|
}
|
|
46
53
|
return super.catch(err);
|
|
47
54
|
}
|
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const base_1 = require("../../base");
|
|
5
|
+
const presets_1 = require("../../lib/presets");
|
|
5
6
|
class OptPreset extends base_1.BaseCommand {
|
|
6
|
-
static description = 'Apply an optimization
|
|
7
|
+
static description = 'Apply an optimization mode for the whole org (maximum, balanced, max_savings, lowest_latency, reliability_first).';
|
|
7
8
|
static args = {
|
|
8
9
|
preset: core_1.Args.string({
|
|
9
|
-
description: '
|
|
10
|
+
description: 'Mode to apply',
|
|
10
11
|
required: true,
|
|
11
|
-
options:
|
|
12
|
+
options: presets_1.PRESET_VALUES,
|
|
12
13
|
}),
|
|
13
14
|
};
|
|
14
15
|
async run() {
|
|
15
16
|
const { args, flags } = await this.parse(OptPreset);
|
|
16
17
|
this.requireToken(flags);
|
|
17
18
|
const res = await this.api(flags).optPatch({ default_mode: args.preset });
|
|
18
|
-
if (!this.jsonEnabled())
|
|
19
|
-
|
|
19
|
+
if (!this.jsonEnabled()) {
|
|
20
|
+
const card = (0, presets_1.presetCard)(args.preset);
|
|
21
|
+
this.log(`Optimization mode set to "${args.preset}".${card ? ` ${card.blurb}` : ''}`);
|
|
22
|
+
}
|
|
20
23
|
return res;
|
|
21
24
|
}
|
|
22
25
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
3
7
|
const base_1 = require("../../base");
|
|
8
|
+
const presets_1 = require("../../lib/presets");
|
|
4
9
|
class OptShow extends base_1.BaseCommand {
|
|
5
|
-
static description = 'Show the current optimization
|
|
10
|
+
static description = 'Show the current optimization mode and every layer running on your requests.';
|
|
6
11
|
async run() {
|
|
7
12
|
const { flags } = await this.parse(OptShow);
|
|
8
13
|
this.requireToken(flags);
|
|
@@ -10,14 +15,23 @@ class OptShow extends base_1.BaseCommand {
|
|
|
10
15
|
if (this.jsonEnabled())
|
|
11
16
|
return res;
|
|
12
17
|
const settings = res.settings ?? res;
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
const mode = settings.default_mode ?? '-';
|
|
19
|
+
const card = (0, presets_1.presetCard)(mode);
|
|
20
|
+
this.log('');
|
|
21
|
+
this.log(` ${picocolors_1.default.bold('Optimization mode')} ${picocolors_1.default.cyan(mode)}${card ? picocolors_1.default.dim(` ${card.hint}`) : ''}`);
|
|
22
|
+
this.log(` ${picocolors_1.default.bold('Cache mode')} ${picocolors_1.default.cyan(settings.cache_mode ?? '-')}`);
|
|
15
23
|
const layers = Array.isArray(res.layers) ? res.layers : [];
|
|
16
24
|
if (layers.length) {
|
|
17
|
-
const
|
|
18
|
-
this.log(`
|
|
25
|
+
const on = layers.filter((l) => l.enabled).length;
|
|
26
|
+
this.log(` ${picocolors_1.default.bold('Active layers')} ${picocolors_1.default.green(String(on))}${picocolors_1.default.dim(`/${layers.length}`)} running on every request`);
|
|
27
|
+
this.log('');
|
|
28
|
+
for (const l of layers) {
|
|
29
|
+
const name = String(l.title ?? l.plain_label ?? l.key ?? 'layer');
|
|
30
|
+
this.log(l.enabled ? ` ${picocolors_1.default.green('●')} ${name}` : ` ${picocolors_1.default.dim('○')} ${picocolors_1.default.dim(name)}`);
|
|
31
|
+
}
|
|
19
32
|
}
|
|
20
|
-
this.log('
|
|
33
|
+
this.log('');
|
|
34
|
+
this.log(picocolors_1.default.dim(' Run with --json for the full settings object.'));
|
|
21
35
|
return res;
|
|
22
36
|
}
|
|
23
37
|
}
|
|
@@ -106,16 +106,24 @@ class ProvidersAdd extends base_1.BaseCommand {
|
|
|
106
106
|
message: 'Model id to use in your tools',
|
|
107
107
|
options: models.slice(0, 50).map((m) => ({ value: m.byte_alias, label: m.byte_alias, hint: m.model_id })),
|
|
108
108
|
});
|
|
109
|
-
|
|
109
|
+
// Enable the chosen model so the gateway can serve it immediately.
|
|
110
|
+
const picked = models.find((m) => m.byte_alias === alias);
|
|
111
|
+
if (picked?.id)
|
|
112
|
+
await api.modelEnable(picked.id).catch(() => undefined);
|
|
113
|
+
await ui.note(`${alias}\n\n${ui.theme.muted('Enabled and ready — use this id in your tools.')}`, 'Model id');
|
|
110
114
|
}
|
|
111
115
|
return res;
|
|
112
116
|
}
|
|
113
117
|
const res = await api.providersAdd(body);
|
|
118
|
+
// Enable the first imported model so the gateway is usable right away.
|
|
119
|
+
const firstModel = (res.fetch_result?.models ?? res.models ?? [])[0];
|
|
120
|
+
if (firstModel?.id)
|
|
121
|
+
await api.modelEnable(firstModel.id).catch(() => undefined);
|
|
114
122
|
if (this.jsonEnabled())
|
|
115
123
|
return res;
|
|
116
124
|
const id = res.id ?? res.connection?.id;
|
|
117
125
|
const imported = res.fetch_result?.imported ?? res.imported ?? 0;
|
|
118
|
-
this.log(`Provider connection added (id ${id}). Models imported: ${imported}.`);
|
|
126
|
+
this.log(`Provider connection added (id ${id}). Models imported: ${imported}.${firstModel?.byte_alias ? ` Enabled ${firstModel.byte_alias}.` : ''}`);
|
|
119
127
|
return res;
|
|
120
128
|
}
|
|
121
129
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -9,7 +9,7 @@ class Run extends base_1.BaseCommand {
|
|
|
9
9
|
prompt: core_1.Args.string({ description: 'The prompt to send', required: true }),
|
|
10
10
|
};
|
|
11
11
|
static flags = {
|
|
12
|
-
model: core_1.Flags.string({ description: 'Model or Byte alias', default: '
|
|
12
|
+
model: core_1.Flags.string({ description: 'Model or Byte alias (default: auto — routes to your enabled model)', default: 'auto' }),
|
|
13
13
|
'max-tokens': core_1.Flags.integer({ description: 'Maximum output tokens' }),
|
|
14
14
|
};
|
|
15
15
|
async run() {
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export default class Setup extends BaseCommand {
|
|
|
14
14
|
connect: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
15
|
};
|
|
16
16
|
run(): Promise<unknown>;
|
|
17
|
+
private buildWizardPorts;
|
|
17
18
|
private connectProvider;
|
|
18
19
|
private runNonInteractive;
|
|
19
20
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -37,6 +37,7 @@ const core_1 = require("@oclif/core");
|
|
|
37
37
|
const base_1 = require("../base");
|
|
38
38
|
const auth_1 = require("../lib/auth");
|
|
39
39
|
const credentials_1 = require("../lib/credentials");
|
|
40
|
+
const presets_1 = require("../lib/presets");
|
|
40
41
|
const providers_1 = require("../lib/providers");
|
|
41
42
|
const tty_1 = require("../lib/tty");
|
|
42
43
|
const ui = __importStar(require("../lib/ui"));
|
|
@@ -50,9 +51,9 @@ class Setup extends base_1.BaseCommand {
|
|
|
50
51
|
'provider-base-url': core_1.Flags.string({ description: 'Custom provider base URL (for --provider custom)' }),
|
|
51
52
|
mode: core_1.Flags.string({ description: 'Gateway mode override (byte_compatible/byte_messages/byte_generative)' }),
|
|
52
53
|
preset: core_1.Flags.string({
|
|
53
|
-
description: 'Optimization
|
|
54
|
-
options: ['balanced', 'max_savings', 'lowest_latency', 'reliability_first'],
|
|
55
|
-
default: '
|
|
54
|
+
description: 'Optimization mode',
|
|
55
|
+
options: ['maximum', 'balanced', 'max_savings', 'lowest_latency', 'reliability_first'],
|
|
56
|
+
default: 'maximum',
|
|
56
57
|
}),
|
|
57
58
|
'key-name': core_1.Flags.string({ description: 'Name for the Byte key', default: 'cli' }),
|
|
58
59
|
'skip-provider': core_1.Flags.boolean({ description: 'Skip connecting a provider' }),
|
|
@@ -65,6 +66,35 @@ class Setup extends base_1.BaseCommand {
|
|
|
65
66
|
if (this.jsonEnabled() || !(0, tty_1.interactive)()) {
|
|
66
67
|
return this.runNonInteractive(flags, profile, base);
|
|
67
68
|
}
|
|
69
|
+
// Full-screen Ink wizard when the terminal supports it. Any failure inside the island
|
|
70
|
+
// returns {status:'fallback'} and we drop through to the clack flow below — no regression.
|
|
71
|
+
const tui = require('../lib/tui');
|
|
72
|
+
if (tui.useInk({ json: this.jsonEnabled() })) {
|
|
73
|
+
const ports = this.buildWizardPorts(flags, profile, base);
|
|
74
|
+
const res = await tui.renderWizardIsland({
|
|
75
|
+
plainColor: Boolean(process.env.NO_COLOR),
|
|
76
|
+
ascii: tui.computeAscii(),
|
|
77
|
+
version: this.config.version,
|
|
78
|
+
initial: { signedIn: Boolean((0, credentials_1.getToken)(profile)), email: undefined, byteKey: (0, credentials_1.getByteKey)(profile) },
|
|
79
|
+
ports,
|
|
80
|
+
providerPresets: providers_1.PROVIDER_PRESETS,
|
|
81
|
+
presetCards: presets_1.PRESET_CARDS,
|
|
82
|
+
});
|
|
83
|
+
if (res.status === 'cancelled')
|
|
84
|
+
return this.exit(130);
|
|
85
|
+
if (res.status === 'done') {
|
|
86
|
+
if (res.summary.connect) {
|
|
87
|
+
await this.config.runCommand('integrate', [
|
|
88
|
+
res.summary.connect,
|
|
89
|
+
'--write',
|
|
90
|
+
'--yes',
|
|
91
|
+
...(res.summary.model ? ['--model', res.summary.model] : []),
|
|
92
|
+
]);
|
|
93
|
+
}
|
|
94
|
+
return res.summary;
|
|
95
|
+
}
|
|
96
|
+
// status === 'fallback' → continue to the clack flow.
|
|
97
|
+
}
|
|
68
98
|
await ui.intro(ui.banner());
|
|
69
99
|
// 1) Sign in
|
|
70
100
|
let token = (0, credentials_1.getToken)(profile);
|
|
@@ -82,37 +112,46 @@ class Setup extends base_1.BaseCommand {
|
|
|
82
112
|
await (0, auth_1.deviceLogin)({ baseUrl: base, profile });
|
|
83
113
|
}
|
|
84
114
|
// 2) Connect a provider
|
|
85
|
-
let
|
|
115
|
+
let provider = { status: 'skipped' };
|
|
86
116
|
if (!flags['skip-provider']) {
|
|
87
117
|
const existing = await api.providersList().catch(() => ({ connections: [] }));
|
|
88
118
|
const conns = existing.connections ?? (Array.isArray(existing) ? existing : []);
|
|
89
119
|
let addNew = true;
|
|
90
120
|
if (conns.length) {
|
|
91
|
-
addNew =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
121
|
+
addNew =
|
|
122
|
+
(await ui.select({
|
|
123
|
+
message: 'Provider',
|
|
124
|
+
options: [
|
|
125
|
+
{ value: 'existing', label: `Use existing connection (${conns.length})` },
|
|
126
|
+
{ value: 'new', label: 'Connect a new provider' },
|
|
127
|
+
],
|
|
128
|
+
})) === 'new';
|
|
129
|
+
if (!addNew)
|
|
130
|
+
provider = { status: 'connected' };
|
|
98
131
|
}
|
|
99
132
|
if (addNew) {
|
|
100
|
-
|
|
133
|
+
provider = await this.connectProvider(api, flags);
|
|
101
134
|
}
|
|
102
135
|
}
|
|
103
|
-
// 3)
|
|
136
|
+
// 3) Choose the optimization mode
|
|
137
|
+
const preset = await ui.select({
|
|
138
|
+
message: 'Optimization mode',
|
|
139
|
+
options: presets_1.PRESET_CARDS.map((c) => ({ value: c.value, label: c.label, hint: c.hint })),
|
|
140
|
+
initialValue: presets_1.PRESET_CARDS.some((c) => c.value === flags.preset) ? flags.preset : 'maximum',
|
|
141
|
+
});
|
|
142
|
+
// 4) Create a Byte key
|
|
104
143
|
let byteKey = (0, credentials_1.getByteKey)(profile);
|
|
105
144
|
if (!byteKey) {
|
|
106
145
|
const name = await ui.text({ message: 'Name this Byte key', defaultValue: flags['key-name'] || 'cli' });
|
|
107
|
-
const res = await api.keysCreate({ name: name || 'cli', preset
|
|
108
|
-
|
|
109
|
-
(0, credentials_1.setByteKey)(profile,
|
|
110
|
-
await ui.revealSecret('Your Byte API key',
|
|
146
|
+
const res = await api.keysCreate({ name: name || 'cli', preset });
|
|
147
|
+
byteKey = String(res.key);
|
|
148
|
+
(0, credentials_1.setByteKey)(profile, byteKey);
|
|
149
|
+
await ui.revealSecret('Your Byte API key', byteKey);
|
|
111
150
|
}
|
|
112
|
-
//
|
|
113
|
-
await api.optPatch({ default_mode:
|
|
114
|
-
await ui.log.success(`Optimizations: ${
|
|
115
|
-
//
|
|
151
|
+
// 5) Apply the optimization mode to the org
|
|
152
|
+
await api.optPatch({ default_mode: preset }).catch(() => undefined);
|
|
153
|
+
await ui.log.success(`Optimizations: ${preset} mode applied — every layer this mode enables now runs on each request.`);
|
|
154
|
+
// 6) Connect a tool
|
|
116
155
|
const connect = await ui.select({
|
|
117
156
|
message: 'Connect a coding tool now?',
|
|
118
157
|
options: [
|
|
@@ -126,11 +165,76 @@ class Setup extends base_1.BaseCommand {
|
|
|
126
165
|
],
|
|
127
166
|
});
|
|
128
167
|
if (connect !== 'skip') {
|
|
129
|
-
const argv = [connect, '--write', '--yes', ...(
|
|
168
|
+
const argv = [connect, '--write', '--yes', ...(provider.alias ? ['--model', provider.alias] : [])];
|
|
130
169
|
await this.config.runCommand('integrate', argv);
|
|
131
170
|
}
|
|
132
|
-
|
|
133
|
-
|
|
171
|
+
// Accurate close-out — never claim a provider is wired when it is not.
|
|
172
|
+
const tips = [`${ui.theme.accent('byte run "hello from byte"')}`, `${ui.theme.accent('byte usage')}`];
|
|
173
|
+
if (provider.status === 'saved') {
|
|
174
|
+
await ui.note(`Your provider is saved but no model loaded yet. Fix the key/URL, then run ${ui.theme.accent('byte providers test')}.`, 'One thing left');
|
|
175
|
+
}
|
|
176
|
+
else if (provider.status === 'skipped' && !flags['skip-provider']) {
|
|
177
|
+
tips.unshift(`${ui.theme.accent('byte providers add')}`);
|
|
178
|
+
}
|
|
179
|
+
await ui.outro(`${ui.theme.ok('Setup complete.')} Next: ${tips.join(' · ')}`);
|
|
180
|
+
return { status: 'setup_complete', profile, model: provider.alias, provider: provider.status, preset };
|
|
181
|
+
}
|
|
182
|
+
// Adapts the existing auth + API surface into the WizardPorts the Ink island calls. Every port
|
|
183
|
+
// wraps a call the clack flow already uses, so both paths hit identical backend behavior.
|
|
184
|
+
buildWizardPorts(flags, profile, base) {
|
|
185
|
+
const api = this.api(flags);
|
|
186
|
+
return {
|
|
187
|
+
signIn: async () => {
|
|
188
|
+
const r = await (0, auth_1.deviceLogin)({ baseUrl: base, profile });
|
|
189
|
+
return { email: r.email };
|
|
190
|
+
},
|
|
191
|
+
listProviders: async () => {
|
|
192
|
+
const existing = await api.providersList().catch(() => ({ connections: [] }));
|
|
193
|
+
return existing.connections ?? (Array.isArray(existing) ? existing : []);
|
|
194
|
+
},
|
|
195
|
+
connectProvider: async (a) => {
|
|
196
|
+
const res = await api.providersAdd({
|
|
197
|
+
api_key: a.apiKey,
|
|
198
|
+
gateway_mode: flags.mode || a.mode,
|
|
199
|
+
base_url: a.baseUrl,
|
|
200
|
+
auto_fetch: true,
|
|
201
|
+
});
|
|
202
|
+
const fetchResult = res.fetch_result ?? {};
|
|
203
|
+
const models = res.models ?? fetchResult.models ?? [];
|
|
204
|
+
const connId = res.id ?? res.connection?.id;
|
|
205
|
+
const failed = fetchResult.status === 'error' || Boolean(res.fetch_error) || Boolean(fetchResult.last_error);
|
|
206
|
+
const status = failed && !models.length ? 'saved' : 'connected';
|
|
207
|
+
return {
|
|
208
|
+
status,
|
|
209
|
+
models,
|
|
210
|
+
connId,
|
|
211
|
+
error: failed ? String(fetchResult.last_error || res.fetch_error || 'Provider models could not be loaded.') : undefined,
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
rotateAndTest: async (connId, apiKey) => {
|
|
215
|
+
try {
|
|
216
|
+
await api.providersRotate(connId, apiKey);
|
|
217
|
+
const test = await api.providersTest(connId);
|
|
218
|
+
const models = test.models ?? [];
|
|
219
|
+
return { models, error: models.length ? undefined : 'No models returned.' };
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
return { models: [], error: err?.message ?? String(err) };
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
enableModel: async (id) => {
|
|
226
|
+
await api.modelEnable(id).catch(() => undefined);
|
|
227
|
+
},
|
|
228
|
+
createByteKey: async (name, preset) => {
|
|
229
|
+
const res = await api.keysCreate({ name: name || 'cli', preset });
|
|
230
|
+
const key = String(res.key);
|
|
231
|
+
(0, credentials_1.setByteKey)(profile, key);
|
|
232
|
+
return { key };
|
|
233
|
+
},
|
|
234
|
+
applyPreset: async (preset) => {
|
|
235
|
+
await api.optPatch({ default_mode: preset }).catch(() => undefined);
|
|
236
|
+
},
|
|
237
|
+
};
|
|
134
238
|
}
|
|
135
239
|
async connectProvider(api, flags) {
|
|
136
240
|
const providerId = await ui.select({
|
|
@@ -151,27 +255,72 @@ class Setup extends base_1.BaseCommand {
|
|
|
151
255
|
const key = await ui.password({ message: `${preset.label} API key`, validate: (v) => (v.trim() ? undefined : 'Required') });
|
|
152
256
|
const spin = await ui.spinner();
|
|
153
257
|
spin.start(`Connecting ${preset.label}…`);
|
|
258
|
+
let res;
|
|
154
259
|
try {
|
|
155
|
-
|
|
156
|
-
const models = res.fetch_result?.models ?? res.models ?? [];
|
|
157
|
-
spin.stop(`Connected ${preset.label} — ${models.length} model(s) imported.`);
|
|
158
|
-
if (models.length) {
|
|
159
|
-
return ui.select({
|
|
160
|
-
message: 'Default model',
|
|
161
|
-
options: models.slice(0, 50).map((m) => ({ value: m.byte_alias, label: m.byte_alias, hint: m.model_id })),
|
|
162
|
-
});
|
|
163
|
-
}
|
|
260
|
+
res = await api.providersAdd({ api_key: key, gateway_mode: flags.mode || preset.gateway_mode, base_url: baseUrl, auto_fetch: true });
|
|
164
261
|
}
|
|
165
262
|
catch (err) {
|
|
166
|
-
spin.stop('
|
|
263
|
+
spin.stop('Could not save the provider connection.');
|
|
167
264
|
await ui.log.error(err?.message ?? String(err));
|
|
265
|
+
return { status: 'failed' };
|
|
266
|
+
}
|
|
267
|
+
const connId = res.id ?? res.connection?.id;
|
|
268
|
+
let fetchResult = res.fetch_result ?? {};
|
|
269
|
+
let models = res.models ?? fetchResult.models ?? [];
|
|
270
|
+
// Retry / keep / skip loop — a failed model import never silently passes as success.
|
|
271
|
+
for (;;) {
|
|
272
|
+
const failed = fetchResult.status === 'error' || Boolean(res.fetch_error) || Boolean(fetchResult.last_error);
|
|
273
|
+
if (!failed || models.length) {
|
|
274
|
+
spin.stop(`Connected ${preset.label} — ${models.length} model(s) imported.`);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
spin.stop(`Saved ${preset.label}, but its models could not be loaded.`);
|
|
278
|
+
await ui.log.warn(String(fetchResult.last_error || res.fetch_error || 'The provider URL or key looks unreachable right now.'));
|
|
279
|
+
const choice = await ui.select({
|
|
280
|
+
message: 'What would you like to do?',
|
|
281
|
+
options: [
|
|
282
|
+
{ value: 'retry', label: 'Re-enter the key and retry' },
|
|
283
|
+
{ value: 'keep', label: 'Keep it — I will pick a model later' },
|
|
284
|
+
{ value: 'skip', label: 'Skip the provider for now' },
|
|
285
|
+
],
|
|
286
|
+
});
|
|
287
|
+
if (choice !== 'retry' || !connId)
|
|
288
|
+
return { status: choice === 'skip' ? 'skipped' : 'saved' };
|
|
289
|
+
const retryKey = await ui.password({ message: `${preset.label} API key`, validate: (v) => (v.trim() ? undefined : 'Required') });
|
|
290
|
+
spin.start('Retrying…');
|
|
291
|
+
try {
|
|
292
|
+
await api.providersRotate(connId, retryKey);
|
|
293
|
+
const test = await api.providersTest(connId);
|
|
294
|
+
models = test.models ?? [];
|
|
295
|
+
fetchResult = { status: models.length ? 'ok' : 'error', last_error: models.length ? null : 'No models returned.' };
|
|
296
|
+
res = { ...res, fetch_error: undefined };
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
models = [];
|
|
300
|
+
fetchResult = { status: 'error', last_error: err?.message ?? String(err) };
|
|
301
|
+
res = { ...res, fetch_error: undefined };
|
|
302
|
+
}
|
|
168
303
|
}
|
|
169
|
-
|
|
304
|
+
if (models.length) {
|
|
305
|
+
const alias = await ui.select({
|
|
306
|
+
message: 'Default model',
|
|
307
|
+
options: models.slice(0, 50).map((m) => ({ value: m.byte_alias, label: m.byte_alias, hint: m.model_id })),
|
|
308
|
+
});
|
|
309
|
+
// Enable the chosen model so the gateway can route to it right away. Imported models
|
|
310
|
+
// start disabled — without this the first `byte run` would hit BYTE_ALIAS_NOT_CONFIGURED.
|
|
311
|
+
const picked = models.find((m) => m.byte_alias === alias);
|
|
312
|
+
if (picked?.id)
|
|
313
|
+
await api.modelEnable(picked.id).catch(() => undefined);
|
|
314
|
+
return { alias, status: 'connected' };
|
|
315
|
+
}
|
|
316
|
+
return { status: 'saved' };
|
|
170
317
|
}
|
|
171
318
|
async runNonInteractive(flags, profile, base) {
|
|
172
319
|
this.requireToken(flags);
|
|
173
320
|
const api = this.api(flags);
|
|
174
321
|
let imported = 0;
|
|
322
|
+
let providerStatus = flags['skip-provider'] ? 'skipped' : 'skipped';
|
|
323
|
+
let providerError;
|
|
175
324
|
let chosenAlias;
|
|
176
325
|
const key = flags['provider-key'];
|
|
177
326
|
if (key && !flags['skip-provider']) {
|
|
@@ -182,20 +331,42 @@ class Setup extends base_1.BaseCommand {
|
|
|
182
331
|
return this.error('Provide --provider <preset> or --provider-base-url for the upstream provider.', { exit: 2 });
|
|
183
332
|
}
|
|
184
333
|
const res = await api.providersAdd({ api_key: key, gateway_mode: mode, base_url: baseUrl, auto_fetch: true });
|
|
185
|
-
|
|
186
|
-
|
|
334
|
+
const fetchResult = res.fetch_result ?? {};
|
|
335
|
+
imported = fetchResult.imported ?? res.imported ?? 0;
|
|
336
|
+
const firstModel = res.models?.[0];
|
|
337
|
+
chosenAlias = firstModel?.byte_alias;
|
|
338
|
+
// Enable the first model so the gateway works immediately (imported models start disabled).
|
|
339
|
+
if (firstModel?.id)
|
|
340
|
+
await api.modelEnable(firstModel.id).catch(() => undefined);
|
|
341
|
+
if (fetchResult.status === 'error' || res.fetch_error) {
|
|
342
|
+
providerStatus = 'saved';
|
|
343
|
+
providerError = String(fetchResult.last_error || res.fetch_error);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
providerStatus = 'connected';
|
|
347
|
+
}
|
|
187
348
|
}
|
|
188
349
|
await api.optPatch({ default_mode: flags.preset }).catch(() => undefined);
|
|
189
350
|
const keyRes = await api.keysCreate({ name: flags['key-name'] || 'cli', preset: flags.preset });
|
|
190
351
|
(0, credentials_1.setByteKey)(profile, keyRes.key);
|
|
191
352
|
if (flags.connect) {
|
|
192
|
-
|
|
353
|
+
const argv = [flags.connect, '--write', '--yes', ...(chosenAlias ? ['--model', chosenAlias] : [])];
|
|
354
|
+
await this.config.runCommand('integrate', argv);
|
|
193
355
|
}
|
|
194
356
|
if (!this.jsonEnabled()) {
|
|
195
357
|
this.log(`Byte key: ${keyRes.key}`);
|
|
196
358
|
this.log(`Provider models imported: ${imported}`);
|
|
359
|
+
if (providerStatus === 'saved')
|
|
360
|
+
this.log(`Provider saved but models not loaded: ${providerError ?? 'unreachable'}. Run \`byte providers test\`.`);
|
|
197
361
|
}
|
|
198
|
-
return {
|
|
362
|
+
return {
|
|
363
|
+
byte_key: keyRes.key,
|
|
364
|
+
provider_models_imported: imported,
|
|
365
|
+
provider: providerStatus,
|
|
366
|
+
provider_error: providerError,
|
|
367
|
+
preset: flags.preset,
|
|
368
|
+
model: chosenAlias,
|
|
369
|
+
};
|
|
199
370
|
}
|
|
200
371
|
}
|
|
201
372
|
exports.default = Setup;
|
package/dist/commands/usage.js
CHANGED
|
@@ -16,16 +16,40 @@ class Usage extends base_1.BaseCommand {
|
|
|
16
16
|
return res;
|
|
17
17
|
const points = res.series ?? res.points ?? (Array.isArray(res) ? res : []);
|
|
18
18
|
if (!points.length) {
|
|
19
|
-
this.log('No usage recorded yet.');
|
|
19
|
+
this.log('No usage recorded yet. Send a request through your Byte key, then check back.');
|
|
20
20
|
return res;
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const num = (v) => {
|
|
23
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
24
|
+
return Number.isFinite(n) ? n : 0;
|
|
25
|
+
};
|
|
26
|
+
const tokensOf = (p) => num(p.tokens_in) + num(p.tokens_out);
|
|
27
|
+
const rows = points.map((p) => [
|
|
28
|
+
(0, output_1.fmtBucket)(p.timestamp ?? p.bucket ?? p.date),
|
|
29
|
+
(0, output_1.fmtNum)(num(p.requests)),
|
|
30
|
+
(0, output_1.fmtNum)(num(p.cached)),
|
|
31
|
+
(0, output_1.fmtNum)(tokensOf(p)),
|
|
32
|
+
(0, output_1.fmtUsd)(num(p.cost_byte_usd ?? p.cost_usd)),
|
|
33
|
+
(0, output_1.fmtUsd)(num(p.savings_usd)),
|
|
34
|
+
]);
|
|
35
|
+
const totals = {
|
|
36
|
+
requests: points.reduce((a, p) => a + num(p.requests), 0),
|
|
37
|
+
cached: points.reduce((a, p) => a + num(p.cached), 0),
|
|
38
|
+
tokens: points.reduce((a, p) => a + tokensOf(p), 0),
|
|
39
|
+
cost: points.reduce((a, p) => a + num(p.cost_byte_usd ?? p.cost_usd), 0),
|
|
40
|
+
savings: points.reduce((a, p) => a + num(p.savings_usd), 0),
|
|
41
|
+
raw: points.reduce((a, p) => a + num(p.cost_raw_usd), 0),
|
|
42
|
+
};
|
|
43
|
+
rows.push(['Total', (0, output_1.fmtNum)(totals.requests), (0, output_1.fmtNum)(totals.cached), (0, output_1.fmtNum)(totals.tokens), (0, output_1.fmtUsd)(totals.cost), (0, output_1.fmtUsd)(totals.savings)]);
|
|
44
|
+
this.log((0, output_1.renderTable)(['Bucket', 'Requests', 'Cached', 'Tokens', 'Cost', 'Savings'], rows));
|
|
45
|
+
const plain = Boolean(process.env.NO_COLOR) || process.env.BYTE_PLAIN === '1';
|
|
46
|
+
const spark = (0, output_1.sparkline)(points.map((p) => num(p.savings_usd)), plain);
|
|
47
|
+
if (spark)
|
|
48
|
+
this.log(`\nSavings trend ${spark}`);
|
|
49
|
+
const savedPct = totals.raw > 0 ? (0, output_1.pct)(totals.savings / totals.raw) : null;
|
|
50
|
+
const hitRate = totals.requests > 0 ? (0, output_1.pct)(totals.cached / totals.requests) : null;
|
|
51
|
+
this.log(`Saved ${(0, output_1.fmtUsd)(totals.savings)}${savedPct ? ` (${savedPct})` : ''} across ${(0, output_1.fmtNum)(totals.requests)} requests` +
|
|
52
|
+
`${hitRate ? `, cache hit rate ${hitRate}` : ''}.`);
|
|
29
53
|
return res;
|
|
30
54
|
}
|
|
31
55
|
}
|
package/dist/hooks/init/home.js
CHANGED
|
@@ -33,13 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const api_1 = require("../../lib/api");
|
|
36
37
|
const config_1 = require("../../lib/config");
|
|
37
38
|
const credentials_1 = require("../../lib/credentials");
|
|
39
|
+
const home_1 = require("../../lib/home");
|
|
40
|
+
const output_1 = require("../../lib/output");
|
|
38
41
|
const tty_1 = require("../../lib/tty");
|
|
39
42
|
const ui = __importStar(require("../../lib/ui"));
|
|
40
43
|
// oclif runs init hooks before it would print help. Bare `byte` (no command) in a real
|
|
41
|
-
// terminal opens an interactive
|
|
42
|
-
// returns and lets oclif behave normally.
|
|
44
|
+
// terminal opens the live home dashboard + an interactive menu; anything else (a command,
|
|
45
|
+
// a pipe, CI, --help) returns and lets oclif behave normally.
|
|
43
46
|
const hook = async function (opts) {
|
|
44
47
|
if (opts.id !== undefined)
|
|
45
48
|
return;
|
|
@@ -50,9 +53,42 @@ const hook = async function (opts) {
|
|
|
50
53
|
return;
|
|
51
54
|
const profile = (0, config_1.profileName)();
|
|
52
55
|
const prof = (0, config_1.getProfile)(profile);
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
+
const token = (0, credentials_1.getToken)(profile);
|
|
57
|
+
const signedIn = Boolean(token);
|
|
58
|
+
const byteKey = (0, credentials_1.getByteKey)(profile);
|
|
59
|
+
// Live, branded home dashboard. Every cell is best-effort: an unreachable service or a
|
|
60
|
+
// signed-out profile degrades to a dim placeholder rather than blocking the screen.
|
|
61
|
+
let data = { savingsSeries: [], savedTotal: 0, tokensTotal: 0, byteKey, gateway: 'unknown' };
|
|
62
|
+
if (signedIn) {
|
|
63
|
+
const api = new api_1.ByteApi(prof.base_url || config_1.DEFAULT_BASE_URL, token);
|
|
64
|
+
const loaded = await (0, home_1.loadHomeData)(api, byteKey, profile).catch(() => undefined);
|
|
65
|
+
if (loaded)
|
|
66
|
+
data = { ...data, ...loaded };
|
|
67
|
+
}
|
|
68
|
+
// Full-screen Ink home dashboard + quick-actions menu, when the terminal supports it. Any
|
|
69
|
+
// failure inside the island returns {status:'fallback'} and we drop straight through to the
|
|
70
|
+
// existing clack dashboard below — nothing regresses.
|
|
71
|
+
const tui = require('../../lib/tui');
|
|
72
|
+
if (tui.useInk()) {
|
|
73
|
+
const res = await tui.renderHomeIsland({
|
|
74
|
+
signedIn,
|
|
75
|
+
data: { ...data, gateway: data.gateway ?? 'unknown', byteKeyMasked: (0, output_1.maskKey)(byteKey) },
|
|
76
|
+
plainColor: Boolean(process.env.NO_COLOR),
|
|
77
|
+
ascii: tui.computeAscii(),
|
|
78
|
+
version: this.config.version,
|
|
79
|
+
});
|
|
80
|
+
if (res.status === 'exit') {
|
|
81
|
+
await this.exit(0);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (res.status === 'action') {
|
|
85
|
+
await this.config.runCommand(res.commandId, res.argv);
|
|
86
|
+
await this.exit(0);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// status === 'fallback' → continue to the clack dashboard.
|
|
90
|
+
}
|
|
91
|
+
this.log(`\n${(0, home_1.renderHome)(data, signedIn)}\n`);
|
|
56
92
|
const choice = await ui.select({
|
|
57
93
|
message: 'What would you like to do?',
|
|
58
94
|
initialValue: signedIn ? 'setup' : 'login',
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class ByteApi {
|
|
|
22
22
|
providersAdd(body: Record<string, unknown>): Promise<any>;
|
|
23
23
|
providersRotate(id: number, apiKey: string): Promise<any>;
|
|
24
24
|
providersTest(id: number): Promise<any>;
|
|
25
|
+
modelEnable(id: number): Promise<any>;
|
|
25
26
|
optShow(): Promise<any>;
|
|
26
27
|
optPatch(body: Record<string, unknown>): Promise<any>;
|
|
27
28
|
usage(granularity: string): Promise<any>;
|
package/dist/lib/api.js
CHANGED
|
@@ -94,6 +94,12 @@ class ByteApi {
|
|
|
94
94
|
providersTest(id) {
|
|
95
95
|
return this.request('POST', `/api/v1/model-connections/${id}/fetch-models`, { body: {} });
|
|
96
96
|
}
|
|
97
|
+
// Enable an imported provider model so the gateway can route to it. Imported models
|
|
98
|
+
// arrive disabled; without enabling at least one, the gateway returns
|
|
99
|
+
// BYTE_ALIAS_NOT_CONFIGURED. Setup/providers-add call this on the model the user picks.
|
|
100
|
+
modelEnable(id) {
|
|
101
|
+
return this.request('PATCH', `/api/v1/models/${id}`, { body: { enabled: true } });
|
|
102
|
+
}
|
|
97
103
|
// --- optimizations ---
|
|
98
104
|
optShow() {
|
|
99
105
|
return this.request('GET', '/api/v1/optimizations');
|