@bytevion/cli 0.2.0 → 0.3.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 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
- return this.error(err.message, { code: err.code, exit: err.exit });
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 preset for the whole org.';
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: 'Preset to apply',
10
+ description: 'Mode to apply',
10
11
  required: true,
11
- options: ['balanced', 'max_savings', 'lowest_latency', 'reliability_first'],
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
- this.log(`Optimization preset set to "${args.preset}".`);
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 preset and layer states for the org.';
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
- this.log(`Preset (default_mode): ${settings.default_mode ?? '-'}`);
14
- this.log(`Cache mode: ${settings.cache_mode ?? '-'}`);
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 enabled = layers.filter((l) => l.enabled).length;
18
- this.log(`Layers enabled: ${enabled}/${layers.length}`);
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('Run with --json for the full settings object.');
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
  }
@@ -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 preset',
54
- options: ['balanced', 'max_savings', 'lowest_latency', 'reliability_first'],
55
- default: 'balanced',
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' }),
@@ -82,37 +83,46 @@ class Setup extends base_1.BaseCommand {
82
83
  await (0, auth_1.deviceLogin)({ baseUrl: base, profile });
83
84
  }
84
85
  // 2) Connect a provider
85
- let chosenAlias;
86
+ let provider = { status: 'skipped' };
86
87
  if (!flags['skip-provider']) {
87
88
  const existing = await api.providersList().catch(() => ({ connections: [] }));
88
89
  const conns = existing.connections ?? (Array.isArray(existing) ? existing : []);
89
90
  let addNew = true;
90
91
  if (conns.length) {
91
- addNew = (await ui.select({
92
- message: 'Provider',
93
- options: [
94
- { value: 'existing', label: `Use existing connection (${conns.length})` },
95
- { value: 'new', label: 'Connect a new provider' },
96
- ],
97
- })) === 'new';
92
+ addNew =
93
+ (await ui.select({
94
+ message: 'Provider',
95
+ options: [
96
+ { value: 'existing', label: `Use existing connection (${conns.length})` },
97
+ { value: 'new', label: 'Connect a new provider' },
98
+ ],
99
+ })) === 'new';
100
+ if (!addNew)
101
+ provider = { status: 'connected' };
98
102
  }
99
103
  if (addNew) {
100
- chosenAlias = await this.connectProvider(api, flags);
104
+ provider = await this.connectProvider(api, flags);
101
105
  }
102
106
  }
103
- // 3) Create a Byte key
107
+ // 3) Choose the optimization mode
108
+ const preset = await ui.select({
109
+ message: 'Optimization mode',
110
+ options: presets_1.PRESET_CARDS.map((c) => ({ value: c.value, label: c.label, hint: c.hint })),
111
+ initialValue: presets_1.PRESET_CARDS.some((c) => c.value === flags.preset) ? flags.preset : 'maximum',
112
+ });
113
+ // 4) Create a Byte key
104
114
  let byteKey = (0, credentials_1.getByteKey)(profile);
105
115
  if (!byteKey) {
106
116
  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: flags.preset });
108
- const created = String(res.key);
109
- (0, credentials_1.setByteKey)(profile, created);
110
- await ui.revealSecret('Your Byte API key', created);
117
+ const res = await api.keysCreate({ name: name || 'cli', preset });
118
+ byteKey = String(res.key);
119
+ (0, credentials_1.setByteKey)(profile, byteKey);
120
+ await ui.revealSecret('Your Byte API key', byteKey);
111
121
  }
112
- // 4) Optimization preset
113
- await api.optPatch({ default_mode: flags.preset }).catch(() => undefined);
114
- await ui.log.success(`Optimizations: ${flags.preset} preset applied`);
115
- // 5) Connect a tool
122
+ // 5) Apply the optimization mode to the org
123
+ await api.optPatch({ default_mode: preset }).catch(() => undefined);
124
+ await ui.log.success(`Optimizations: ${preset} mode applied — every layer this mode enables now runs on each request.`);
125
+ // 6) Connect a tool
116
126
  const connect = await ui.select({
117
127
  message: 'Connect a coding tool now?',
118
128
  options: [
@@ -126,11 +136,19 @@ class Setup extends base_1.BaseCommand {
126
136
  ],
127
137
  });
128
138
  if (connect !== 'skip') {
129
- const argv = [connect, '--write', '--yes', ...(chosenAlias ? ['--model', chosenAlias] : [])];
139
+ const argv = [connect, '--write', '--yes', ...(provider.alias ? ['--model', provider.alias] : [])];
130
140
  await this.config.runCommand('integrate', argv);
131
141
  }
132
- await ui.outro(`${ui.theme.ok('Setup complete.')} Next: ${ui.theme.accent('byte run "hello from byte"')} · ${ui.theme.accent('byte usage')}`);
133
- return { status: 'setup_complete', profile, model: chosenAlias };
142
+ // Accurate close-out never claim a provider is wired when it is not.
143
+ const tips = [`${ui.theme.accent('byte run "hello from byte"')}`, `${ui.theme.accent('byte usage')}`];
144
+ if (provider.status === 'saved') {
145
+ 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');
146
+ }
147
+ else if (provider.status === 'skipped' && !flags['skip-provider']) {
148
+ tips.unshift(`${ui.theme.accent('byte providers add')}`);
149
+ }
150
+ await ui.outro(`${ui.theme.ok('Setup complete.')} Next: ${tips.join(' · ')}`);
151
+ return { status: 'setup_complete', profile, model: provider.alias, provider: provider.status, preset };
134
152
  }
135
153
  async connectProvider(api, flags) {
136
154
  const providerId = await ui.select({
@@ -151,27 +169,67 @@ class Setup extends base_1.BaseCommand {
151
169
  const key = await ui.password({ message: `${preset.label} API key`, validate: (v) => (v.trim() ? undefined : 'Required') });
152
170
  const spin = await ui.spinner();
153
171
  spin.start(`Connecting ${preset.label}…`);
172
+ let res;
154
173
  try {
155
- const res = await api.providersAdd({ api_key: key, gateway_mode: flags.mode || preset.gateway_mode, base_url: baseUrl, auto_fetch: true });
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
- }
174
+ res = await api.providersAdd({ api_key: key, gateway_mode: flags.mode || preset.gateway_mode, base_url: baseUrl, auto_fetch: true });
164
175
  }
165
176
  catch (err) {
166
- spin.stop('Provider connection failed.');
177
+ spin.stop('Could not save the provider connection.');
167
178
  await ui.log.error(err?.message ?? String(err));
179
+ return { status: 'failed' };
180
+ }
181
+ const connId = res.id ?? res.connection?.id;
182
+ let fetchResult = res.fetch_result ?? {};
183
+ let models = res.models ?? fetchResult.models ?? [];
184
+ // Retry / keep / skip loop — a failed model import never silently passes as success.
185
+ for (;;) {
186
+ const failed = fetchResult.status === 'error' || Boolean(res.fetch_error) || Boolean(fetchResult.last_error);
187
+ if (!failed || models.length) {
188
+ spin.stop(`Connected ${preset.label} — ${models.length} model(s) imported.`);
189
+ break;
190
+ }
191
+ spin.stop(`Saved ${preset.label}, but its models could not be loaded.`);
192
+ await ui.log.warn(String(fetchResult.last_error || res.fetch_error || 'The provider URL or key looks unreachable right now.'));
193
+ const choice = await ui.select({
194
+ message: 'What would you like to do?',
195
+ options: [
196
+ { value: 'retry', label: 'Re-enter the key and retry' },
197
+ { value: 'keep', label: 'Keep it — I will pick a model later' },
198
+ { value: 'skip', label: 'Skip the provider for now' },
199
+ ],
200
+ });
201
+ if (choice !== 'retry' || !connId)
202
+ return { status: choice === 'skip' ? 'skipped' : 'saved' };
203
+ const retryKey = await ui.password({ message: `${preset.label} API key`, validate: (v) => (v.trim() ? undefined : 'Required') });
204
+ spin.start('Retrying…');
205
+ try {
206
+ await api.providersRotate(connId, retryKey);
207
+ const test = await api.providersTest(connId);
208
+ models = test.models ?? [];
209
+ fetchResult = { status: models.length ? 'ok' : 'error', last_error: models.length ? null : 'No models returned.' };
210
+ res = { ...res, fetch_error: undefined };
211
+ }
212
+ catch (err) {
213
+ models = [];
214
+ fetchResult = { status: 'error', last_error: err?.message ?? String(err) };
215
+ res = { ...res, fetch_error: undefined };
216
+ }
217
+ }
218
+ if (models.length) {
219
+ const alias = await ui.select({
220
+ message: 'Default model',
221
+ options: models.slice(0, 50).map((m) => ({ value: m.byte_alias, label: m.byte_alias, hint: m.model_id })),
222
+ });
223
+ return { alias, status: 'connected' };
168
224
  }
169
- return undefined;
225
+ return { status: 'saved' };
170
226
  }
171
227
  async runNonInteractive(flags, profile, base) {
172
228
  this.requireToken(flags);
173
229
  const api = this.api(flags);
174
230
  let imported = 0;
231
+ let providerStatus = flags['skip-provider'] ? 'skipped' : 'skipped';
232
+ let providerError;
175
233
  let chosenAlias;
176
234
  const key = flags['provider-key'];
177
235
  if (key && !flags['skip-provider']) {
@@ -182,20 +240,38 @@ class Setup extends base_1.BaseCommand {
182
240
  return this.error('Provide --provider <preset> or --provider-base-url for the upstream provider.', { exit: 2 });
183
241
  }
184
242
  const res = await api.providersAdd({ api_key: key, gateway_mode: mode, base_url: baseUrl, auto_fetch: true });
185
- imported = res.fetch_result?.imported ?? res.imported ?? 0;
186
- chosenAlias = res.fetch_result?.models?.[0]?.byte_alias;
243
+ const fetchResult = res.fetch_result ?? {};
244
+ imported = fetchResult.imported ?? res.imported ?? 0;
245
+ chosenAlias = res.models?.[0]?.byte_alias;
246
+ if (fetchResult.status === 'error' || res.fetch_error) {
247
+ providerStatus = 'saved';
248
+ providerError = String(fetchResult.last_error || res.fetch_error);
249
+ }
250
+ else {
251
+ providerStatus = 'connected';
252
+ }
187
253
  }
188
254
  await api.optPatch({ default_mode: flags.preset }).catch(() => undefined);
189
255
  const keyRes = await api.keysCreate({ name: flags['key-name'] || 'cli', preset: flags.preset });
190
256
  (0, credentials_1.setByteKey)(profile, keyRes.key);
191
257
  if (flags.connect) {
192
- await this.config.runCommand('integrate', [flags.connect, '--write', '--yes', ...(chosenAlias ? ['--model', chosenAlias] : [])]);
258
+ const argv = [flags.connect, '--write', '--yes', ...(chosenAlias ? ['--model', chosenAlias] : [])];
259
+ await this.config.runCommand('integrate', argv);
193
260
  }
194
261
  if (!this.jsonEnabled()) {
195
262
  this.log(`Byte key: ${keyRes.key}`);
196
263
  this.log(`Provider models imported: ${imported}`);
264
+ if (providerStatus === 'saved')
265
+ this.log(`Provider saved but models not loaded: ${providerError ?? 'unreachable'}. Run \`byte providers test\`.`);
197
266
  }
198
- return { byte_key: keyRes.key, provider_models_imported: imported, preset: flags.preset, model: chosenAlias };
267
+ return {
268
+ byte_key: keyRes.key,
269
+ provider_models_imported: imported,
270
+ provider: providerStatus,
271
+ provider_error: providerError,
272
+ preset: flags.preset,
273
+ model: chosenAlias,
274
+ };
199
275
  }
200
276
  }
201
277
  exports.default = Setup;
@@ -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
- this.log((0, output_1.renderTable)(['Bucket', 'Requests', 'Tokens', 'Cost', 'Savings'], points.map((p) => [
23
- p.bucket ?? p.date ?? p.t ?? '-',
24
- p.requests ?? p.total_requests ?? '-',
25
- p.tokens ?? p.total_tokens ?? '-',
26
- (0, output_1.fmtUsd)(p.cost_usd ?? p.cost_byte_usd),
27
- (0, output_1.fmtUsd)(p.savings_usd),
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
  }
@@ -33,13 +33,15 @@ 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");
38
40
  const tty_1 = require("../../lib/tty");
39
41
  const ui = __importStar(require("../../lib/ui"));
40
42
  // oclif runs init hooks before it would print help. Bare `byte` (no command) in a real
41
- // terminal opens an interactive home menu; anything else (a command, a pipe, CI, --help)
42
- // returns and lets oclif behave normally.
43
+ // terminal opens the live home dashboard + an interactive menu; anything else (a command,
44
+ // a pipe, CI, --help) returns and lets oclif behave normally.
43
45
  const hook = async function (opts) {
44
46
  if (opts.id !== undefined)
45
47
  return;
@@ -50,9 +52,19 @@ const hook = async function (opts) {
50
52
  return;
51
53
  const profile = (0, config_1.profileName)();
52
54
  const prof = (0, config_1.getProfile)(profile);
53
- const signedIn = Boolean((0, credentials_1.getToken)(profile));
54
- const status = signedIn ? ui.theme.muted(`signed in · ${prof.email ?? prof.org_name ?? profile}`) : ui.theme.muted('not signed in');
55
- await ui.intro(`${ui.banner()} ${status}`);
55
+ const token = (0, credentials_1.getToken)(profile);
56
+ const signedIn = Boolean(token);
57
+ const byteKey = (0, credentials_1.getByteKey)(profile);
58
+ // Live, branded home dashboard. Every cell is best-effort: an unreachable service or a
59
+ // signed-out profile degrades to a dim placeholder rather than blocking the screen.
60
+ let data = { savingsSeries: [], savedTotal: 0, tokensTotal: 0, byteKey, gateway: 'unknown' };
61
+ if (signedIn) {
62
+ const api = new api_1.ByteApi(prof.base_url || config_1.DEFAULT_BASE_URL, token);
63
+ const loaded = await (0, home_1.loadHomeData)(api, byteKey, profile).catch(() => undefined);
64
+ if (loaded)
65
+ data = { ...data, ...loaded };
66
+ }
67
+ this.log(`\n${(0, home_1.renderHome)(data, signedIn)}\n`);
56
68
  const choice = await ui.select({
57
69
  message: 'What would you like to do?',
58
70
  initialValue: signedIn ? 'setup' : 'login',
@@ -0,0 +1,8 @@
1
+ import { ByteError } from './errors';
2
+ export interface Friendly {
3
+ issue: string;
4
+ why: string;
5
+ next: string;
6
+ }
7
+ export declare function friendlyError(err: ByteError): Friendly;
8
+ export declare function renderErrorPanel(f: Friendly): string;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.friendlyError = friendlyError;
7
+ exports.renderErrorPanel = renderErrorPanel;
8
+ const picocolors_1 = __importDefault(require("picocolors"));
9
+ // Translates a ByteError (HTTP status + BYTE_* / provider detail code) into plain
10
+ // language a first-time user can act on: what happened, why, and the exact next
11
+ // command. The goal is that no error ever leaves someone stuck.
12
+ function friendlyError(err) {
13
+ const code = (err.code || '').toUpperCase();
14
+ const status = err.status;
15
+ const byCode = {
16
+ BYTE_AUTH_REQUIRED: {
17
+ issue: 'You are not signed in yet.',
18
+ why: 'This command needs your Byte account.',
19
+ next: 'byte login',
20
+ },
21
+ BYTE_KEY_REQUIRED: {
22
+ issue: 'This profile has no Byte API key.',
23
+ why: 'Requests run through a Byte key so every optimization applies automatically.',
24
+ next: 'byte keys create',
25
+ },
26
+ BYTE_API_OFFLINE: {
27
+ issue: 'Could not reach the Byte service.',
28
+ why: 'You may be offline, or the base URL is pointed somewhere unexpected.',
29
+ next: 'byte doctor',
30
+ },
31
+ BYTE_MODEL_FETCH_FAILED: {
32
+ issue: 'Your provider was saved, but its model list could not be loaded.',
33
+ why: 'The provider URL or key looks unreachable right now — your connection is still saved.',
34
+ next: 'byte providers test',
35
+ },
36
+ BYTE_DEVICE_DENIED: {
37
+ issue: 'Sign-in was declined in the browser.',
38
+ why: 'The device approval was rejected or closed.',
39
+ next: 'byte login',
40
+ },
41
+ BYTE_DEVICE_EXPIRED: {
42
+ issue: 'The sign-in code expired.',
43
+ why: 'It was not approved in time.',
44
+ next: 'byte login',
45
+ },
46
+ };
47
+ if (byCode[code])
48
+ return byCode[code];
49
+ if (status === 0) {
50
+ return { issue: 'Could not reach the Byte service.', why: 'You may be offline.', next: 'byte doctor' };
51
+ }
52
+ if (status === 401 || status === 403) {
53
+ return { issue: 'Your session or key was rejected.', why: 'It may have expired, been revoked, or be missing a scope.', next: 'byte login' };
54
+ }
55
+ if (status === 404) {
56
+ return {
57
+ issue: 'That route or model was not found.',
58
+ why: 'The model may not be wired to your key, or the surface (e.g. /v1/messages) is not enabled for it.',
59
+ next: 'byte doctor',
60
+ };
61
+ }
62
+ if (status === 429) {
63
+ return { issue: 'Rate limit reached.', why: 'Too many requests landed in a short window.', next: 'wait a few seconds, then retry' };
64
+ }
65
+ if (status && status >= 500) {
66
+ return {
67
+ issue: 'The service hit an error finishing your request.',
68
+ why: 'This is on the Byte side, not your input — your key and provider are fine.',
69
+ next: 'byte doctor (then retry shortly)',
70
+ };
71
+ }
72
+ return { issue: err.message || 'Something went wrong.', why: `Error code: ${err.code}`, next: 'byte doctor' };
73
+ }
74
+ // A compact, left-accent error panel. picocolors auto-disables under NO_COLOR, so this
75
+ // degrades to clean monochrome text in plain terminals.
76
+ function renderErrorPanel(f) {
77
+ const bar = picocolors_1.default.red('▌');
78
+ const lines = [
79
+ `${bar} ${picocolors_1.default.bold(picocolors_1.default.red('Byte hit a problem'))}`,
80
+ `${bar} ${f.issue}`,
81
+ `${bar}`,
82
+ `${bar} ${picocolors_1.default.dim('Why ')} ${f.why}`,
83
+ `${bar} ${picocolors_1.default.dim('Next')} ${picocolors_1.default.cyan(f.next)}`,
84
+ ];
85
+ return `\n${lines.join('\n')}\n`;
86
+ }
@@ -0,0 +1,16 @@
1
+ import { ByteApi } from './api';
2
+ export declare function wordmark(): string;
3
+ export interface HomeData {
4
+ email?: string;
5
+ org?: string;
6
+ providers?: number;
7
+ models?: number;
8
+ byteKey?: string;
9
+ gateway?: 'healthy' | 'down' | 'unknown';
10
+ savingsSeries: number[];
11
+ savedTotal: number;
12
+ tokensTotal: number;
13
+ hitRate?: number;
14
+ }
15
+ export declare function loadHomeData(api: ByteApi, byteKey: string | undefined, fallbackId: string): Promise<HomeData>;
16
+ export declare function renderHome(data: HomeData, signedIn: boolean): string;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.wordmark = wordmark;
7
+ exports.loadHomeData = loadHomeData;
8
+ exports.renderHome = renderHome;
9
+ const picocolors_1 = __importDefault(require("picocolors"));
10
+ const output_1 = require("./output");
11
+ const ANSI = /\x1b\[[0-9;]*m/g;
12
+ const visLen = (s) => s.replace(ANSI, '').length;
13
+ const plainMode = () => Boolean(process.env.NO_COLOR) || process.env.BYTE_PLAIN === '1';
14
+ // Cyan→blue gradient wordmark. picocolors has no true gradient, so we step the letters
15
+ // across a cyan→blue ramp — a recognizable Byte signature that degrades to plain text.
16
+ function wordmark() {
17
+ const mark = `${picocolors_1.default.dim('›')}${picocolors_1.default.bold(picocolors_1.default.cyanBright('b'))}${picocolors_1.default.dim('_')}`;
18
+ if (plainMode())
19
+ return `${mark} BYTE`;
20
+ const ramp = [picocolors_1.default.cyanBright, picocolors_1.default.cyan, picocolors_1.default.blue, picocolors_1.default.blueBright];
21
+ const name = 'BYTE'
22
+ .split('')
23
+ .map((ch, i) => picocolors_1.default.bold(ramp[i % ramp.length](ch)))
24
+ .join('');
25
+ return `${mark} ${name}`;
26
+ }
27
+ function box(lines, pad = 2) {
28
+ const inner = Math.max(...lines.map(visLen));
29
+ const horiz = '─'.repeat(inner + pad * 2);
30
+ const body = lines.map((l) => `${picocolors_1.default.dim('│')}${' '.repeat(pad)}${l}${' '.repeat(inner - visLen(l) + pad)}${picocolors_1.default.dim('│')}`);
31
+ return [picocolors_1.default.dim(`╭${horiz}╮`), ...body, picocolors_1.default.dim(`╰${horiz}╯`)].join('\n');
32
+ }
33
+ function withTimeout(p, ms) {
34
+ return new Promise((resolve) => {
35
+ const timer = setTimeout(() => resolve(undefined), ms);
36
+ if (typeof timer.unref === 'function')
37
+ timer.unref();
38
+ p.then((v) => {
39
+ clearTimeout(timer);
40
+ resolve(v);
41
+ }, () => {
42
+ clearTimeout(timer);
43
+ resolve(undefined);
44
+ });
45
+ });
46
+ }
47
+ // Fetches everything bare `byte` shows, in parallel, each guarded by a short timeout so a
48
+ // slow or unreachable service never blocks the home screen — missing cells just render dim.
49
+ async function loadHomeData(api, byteKey, fallbackId) {
50
+ const [me, provs, usage, health] = await Promise.all([
51
+ withTimeout(api.me(), 4000),
52
+ withTimeout(api.providersList(), 4000),
53
+ withTimeout(api.usage('day'), 4000),
54
+ withTimeout(api.health(), 3000),
55
+ ]);
56
+ const conns = provs?.connections ?? (Array.isArray(provs) ? provs : []);
57
+ const models = conns.reduce((a, c) => a + (Number(c.models ?? c.model_count ?? 0) || 0), 0);
58
+ const series = usage?.series ?? [];
59
+ const num = (v) => {
60
+ const n = typeof v === 'number' ? v : Number(v);
61
+ return Number.isFinite(n) ? n : 0;
62
+ };
63
+ const requests = series.reduce((a, p) => a + num(p.requests), 0);
64
+ const cached = series.reduce((a, p) => a + num(p.cached), 0);
65
+ return {
66
+ email: me?.user?.email,
67
+ org: me?.org?.name ?? me?.user?.org_name,
68
+ providers: conns.length || undefined,
69
+ models: models || undefined,
70
+ byteKey,
71
+ gateway: health ? (health.status === 'ok' || health.ok ? 'healthy' : 'down') : 'unknown',
72
+ savingsSeries: series.map((p) => num(p.savings_usd)),
73
+ savedTotal: series.reduce((a, p) => a + num(p.savings_usd), 0),
74
+ tokensTotal: series.reduce((a, p) => a + num(p.tokens_in) + num(p.tokens_out), 0),
75
+ hitRate: requests > 0 ? cached / requests : undefined,
76
+ };
77
+ }
78
+ function renderHome(data, signedIn) {
79
+ const dim = picocolors_1.default.dim;
80
+ const label = (s) => dim(s.padEnd(10));
81
+ const dash = dim('—');
82
+ const gatewayDot = data.gateway === 'healthy' ? `${picocolors_1.default.green('●')} healthy` : data.gateway === 'down' ? `${picocolors_1.default.red('●')} unreachable` : dim('● unknown');
83
+ const lines = [
84
+ `${wordmark()}${' '.repeat(6)}${dim('optimization gateway')}`,
85
+ '',
86
+ `${label('Account')}${signedIn ? `${data.email ?? 'you'}${data.org ? ` ${dim('·')} ${data.org}` : ''}` : picocolors_1.default.yellow('not signed in')}`,
87
+ `${label('Providers')}${data.providers ? `${data.providers} connected${data.models ? ` ${dim('·')} ${data.models} models` : ''}` : dash}`,
88
+ `${label('Byte key')}${data.byteKey ? picocolors_1.default.cyan((0, output_1.maskKey)(data.byteKey)) : dash}`,
89
+ `${label('Gateway')}${gatewayDot}`,
90
+ ];
91
+ if (data.savingsSeries.some((v) => v > 0) || data.savedTotal > 0) {
92
+ const spark = (0, output_1.sparkline)(data.savingsSeries, plainMode());
93
+ const summary = `${picocolors_1.default.green((0, output_1.fmtUsd)(data.savedTotal))} saved ${dim('·')} ${(0, output_1.fmtNum)(data.tokensTotal)} tokens${data.hitRate !== undefined ? ` ${dim('·')} ${(0, output_1.pct)(data.hitRate)} cache hits` : ''}`;
94
+ lines.push('', `${label('7-day')}${picocolors_1.default.cyan(spark)} ${summary}`);
95
+ }
96
+ return box(lines);
97
+ }
@@ -2,3 +2,6 @@ export declare function renderTable(head: string[], rows: Array<Array<string | n
2
2
  export declare function maskKey(value: string | undefined): string;
3
3
  export declare function fmtUsd(value: unknown): string;
4
4
  export declare function pct(value: unknown): string;
5
+ export declare function fmtNum(value: unknown): string;
6
+ export declare function fmtBucket(value: unknown): string;
7
+ export declare function sparkline(values: number[], plain?: boolean): string;
@@ -7,6 +7,9 @@ exports.renderTable = renderTable;
7
7
  exports.maskKey = maskKey;
8
8
  exports.fmtUsd = fmtUsd;
9
9
  exports.pct = pct;
10
+ exports.fmtNum = fmtNum;
11
+ exports.fmtBucket = fmtBucket;
12
+ exports.sparkline = sparkline;
10
13
  const cli_table3_1 = __importDefault(require("cli-table3"));
11
14
  function renderTable(head, rows) {
12
15
  const table = new cli_table3_1.default({ head });
@@ -35,3 +38,43 @@ function pct(value) {
35
38
  const scaled = n <= 1 ? n * 100 : n;
36
39
  return `${scaled.toFixed(1)}%`;
37
40
  }
41
+ // 1234 -> "1.2K", 1_500_000 -> "1.5M". Keeps small counts exact.
42
+ function fmtNum(value) {
43
+ const n = typeof value === 'number' ? value : Number(value);
44
+ if (!Number.isFinite(n))
45
+ return '-';
46
+ const abs = Math.abs(n);
47
+ if (abs >= 1_000_000)
48
+ return `${(n / 1_000_000).toFixed(1)}M`;
49
+ if (abs >= 1_000)
50
+ return `${(n / 1_000).toFixed(1)}K`;
51
+ return String(Math.round(n));
52
+ }
53
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
54
+ // "2026-05-30" -> "May 30"; "2026-05-30T14:00:00Z" -> "May 30 14:00".
55
+ function fmtBucket(value) {
56
+ if (typeof value !== 'string' || !value)
57
+ return '-';
58
+ const m = value.match(/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):)?/);
59
+ if (!m)
60
+ return value;
61
+ const mon = MONTHS[Number(m[2]) - 1] ?? m[2];
62
+ const day = String(Number(m[3]));
63
+ return m[4] ? `${mon} ${day} ${m[4]}:00` : `${mon} ${day}`;
64
+ }
65
+ // Unicode block sparkline with an ASCII fallback for legacy terminals (NO_COLOR/conhost).
66
+ function sparkline(values, plain = false) {
67
+ const nums = values.map((v) => (Number.isFinite(v) ? v : 0));
68
+ if (!nums.length)
69
+ return '';
70
+ const ramp = plain ? '.:-=+*#'.split('') : '▁▂▃▄▅▆▇█'.split('');
71
+ const max = Math.max(...nums);
72
+ const min = Math.min(...nums);
73
+ const span = max - min || 1;
74
+ return nums
75
+ .map((v) => {
76
+ const idx = Math.round(((v - min) / span) * (ramp.length - 1));
77
+ return ramp[Math.max(0, Math.min(ramp.length - 1, idx))];
78
+ })
79
+ .join('');
80
+ }
@@ -0,0 +1,9 @@
1
+ export interface PresetCard {
2
+ value: string;
3
+ label: string;
4
+ hint: string;
5
+ blurb: string;
6
+ }
7
+ export declare const PRESET_CARDS: PresetCard[];
8
+ export declare const PRESET_VALUES: string[];
9
+ export declare function presetCard(value: string): PresetCard | undefined;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRESET_VALUES = exports.PRESET_CARDS = void 0;
4
+ exports.presetCard = presetCard;
5
+ exports.PRESET_CARDS = [
6
+ {
7
+ value: 'maximum',
8
+ label: 'Maximum — everything on (recommended)',
9
+ hint: 'best savings + quality',
10
+ blurb: 'Every production-safe optimization runs on every request: full caching, compression, routing, and quality guards.',
11
+ },
12
+ {
13
+ value: 'max_savings',
14
+ label: 'Maximum savings',
15
+ hint: 'cheapest',
16
+ blurb: 'Leans hardest on caching, compression, and cheaper routing to cut spend the most.',
17
+ },
18
+ {
19
+ value: 'balanced',
20
+ label: 'Balanced',
21
+ hint: 'savings + speed',
22
+ blurb: 'A middle ground: strong savings without the deepest latency-adding passes.',
23
+ },
24
+ {
25
+ value: 'lowest_latency',
26
+ label: 'Lowest latency',
27
+ hint: 'fastest',
28
+ blurb: 'Skips the heavier passes so responses come back as fast as possible.',
29
+ },
30
+ {
31
+ value: 'reliability_first',
32
+ label: 'Reliability first',
33
+ hint: 'most robust',
34
+ blurb: 'Prioritizes verification, repair, and fallbacks over maximum savings.',
35
+ },
36
+ ];
37
+ exports.PRESET_VALUES = exports.PRESET_CARDS.map((c) => c.value);
38
+ function presetCard(value) {
39
+ return exports.PRESET_CARDS.find((c) => c.value === value);
40
+ }
package/dist/lib/ui.js CHANGED
@@ -32,8 +32,20 @@ exports.theme = {
32
32
  muted: (s) => picocolors_1.default.dim(s),
33
33
  bold: (s) => picocolors_1.default.bold(s),
34
34
  };
35
+ // The Byte signature: a `›b_` prompt glyph and a cyan→blue gradient wordmark. picocolors
36
+ // has no true gradient, so the letters step across a cyan→blue ramp; degrades to plain
37
+ // text under NO_COLOR/BYTE_PLAIN. Kept in sync with the home dashboard wordmark.
35
38
  function banner() {
36
- return `${picocolors_1.default.dim('>')}${picocolors_1.default.bold(picocolors_1.default.cyan('b'))}${picocolors_1.default.dim('_')} ${picocolors_1.default.bold('Byte')}`;
39
+ const plain = Boolean(process.env.NO_COLOR) || process.env.BYTE_PLAIN === '1';
40
+ const mark = `${picocolors_1.default.dim('›')}${picocolors_1.default.bold(picocolors_1.default.cyanBright('b'))}${picocolors_1.default.dim('_')}`;
41
+ if (plain)
42
+ return `${mark} BYTE`;
43
+ const ramp = [picocolors_1.default.cyanBright, picocolors_1.default.cyan, picocolors_1.default.blue, picocolors_1.default.blueBright];
44
+ const name = 'BYTE'
45
+ .split('')
46
+ .map((ch, i) => picocolors_1.default.bold(ramp[i % ramp.length](ch)))
47
+ .join('');
48
+ return `${mark} ${name}`;
37
49
  }
38
50
  function guard(value, c) {
39
51
  if (c.isCancel(value)) {
@@ -609,12 +609,13 @@
609
609
  "type": "option"
610
610
  },
611
611
  "preset": {
612
- "description": "Optimization preset",
612
+ "description": "Optimization mode",
613
613
  "name": "preset",
614
- "default": "balanced",
614
+ "default": "maximum",
615
615
  "hasDynamicHelp": false,
616
616
  "multiple": false,
617
617
  "options": [
618
+ "maximum",
618
619
  "balanced",
619
620
  "max_savings",
620
621
  "lowest_latency",
@@ -1198,118 +1199,23 @@
1198
1199
  "rotate.js"
1199
1200
  ]
1200
1201
  },
1201
- "sessions": {
1202
- "aliases": [],
1203
- "args": {},
1204
- "description": "List active terminal sessions (CLI tokens) for the org.",
1205
- "flags": {
1206
- "json": {
1207
- "description": "Format output as json.",
1208
- "helpGroup": "GLOBAL",
1209
- "name": "json",
1210
- "allowNo": false,
1211
- "type": "boolean"
1212
- },
1213
- "profile": {
1214
- "description": "Configuration profile to use",
1215
- "env": "BYTE_PROFILE",
1216
- "name": "profile",
1217
- "hasDynamicHelp": false,
1218
- "multiple": false,
1219
- "type": "option"
1220
- },
1221
- "base-url": {
1222
- "description": "Override the API base URL",
1223
- "env": "BYTE_BASE_URL",
1224
- "name": "base-url",
1225
- "hasDynamicHelp": false,
1226
- "multiple": false,
1227
- "type": "option"
1228
- }
1229
- },
1230
- "hasDynamicHelp": false,
1231
- "hiddenAliases": [],
1232
- "id": "sessions",
1233
- "pluginAlias": "@bytevion/cli",
1234
- "pluginName": "@bytevion/cli",
1235
- "pluginType": "core",
1236
- "strict": true,
1237
- "enableJsonFlag": true,
1238
- "isESM": false,
1239
- "relativePath": [
1240
- "dist",
1241
- "commands",
1242
- "sessions",
1243
- "index.js"
1244
- ]
1245
- },
1246
- "sessions:revoke": {
1247
- "aliases": [],
1248
- "args": {
1249
- "id": {
1250
- "description": "Session id (see `byte sessions`)",
1251
- "name": "id",
1252
- "required": true
1253
- }
1254
- },
1255
- "description": "Revoke a terminal session (CLI token) by id.",
1256
- "flags": {
1257
- "json": {
1258
- "description": "Format output as json.",
1259
- "helpGroup": "GLOBAL",
1260
- "name": "json",
1261
- "allowNo": false,
1262
- "type": "boolean"
1263
- },
1264
- "profile": {
1265
- "description": "Configuration profile to use",
1266
- "env": "BYTE_PROFILE",
1267
- "name": "profile",
1268
- "hasDynamicHelp": false,
1269
- "multiple": false,
1270
- "type": "option"
1271
- },
1272
- "base-url": {
1273
- "description": "Override the API base URL",
1274
- "env": "BYTE_BASE_URL",
1275
- "name": "base-url",
1276
- "hasDynamicHelp": false,
1277
- "multiple": false,
1278
- "type": "option"
1279
- }
1280
- },
1281
- "hasDynamicHelp": false,
1282
- "hiddenAliases": [],
1283
- "id": "sessions:revoke",
1284
- "pluginAlias": "@bytevion/cli",
1285
- "pluginName": "@bytevion/cli",
1286
- "pluginType": "core",
1287
- "strict": true,
1288
- "enableJsonFlag": true,
1289
- "isESM": false,
1290
- "relativePath": [
1291
- "dist",
1292
- "commands",
1293
- "sessions",
1294
- "revoke.js"
1295
- ]
1296
- },
1297
1202
  "opt:preset": {
1298
1203
  "aliases": [],
1299
1204
  "args": {
1300
1205
  "preset": {
1301
- "description": "Preset to apply",
1206
+ "description": "Mode to apply",
1302
1207
  "name": "preset",
1303
1208
  "options": [
1304
- "balanced",
1209
+ "maximum",
1305
1210
  "max_savings",
1211
+ "balanced",
1306
1212
  "lowest_latency",
1307
1213
  "reliability_first"
1308
1214
  ],
1309
1215
  "required": true
1310
1216
  }
1311
1217
  },
1312
- "description": "Apply an optimization preset for the whole org.",
1218
+ "description": "Apply an optimization mode for the whole org (maximum, balanced, max_savings, lowest_latency, reliability_first).",
1313
1219
  "flags": {
1314
1220
  "json": {
1315
1221
  "description": "Format output as json.",
@@ -1420,7 +1326,7 @@
1420
1326
  "opt:show": {
1421
1327
  "aliases": [],
1422
1328
  "args": {},
1423
- "description": "Show the current optimization preset and layer states for the org.",
1329
+ "description": "Show the current optimization mode and every layer running on your requests.",
1424
1330
  "flags": {
1425
1331
  "json": {
1426
1332
  "description": "Format output as json.",
@@ -1707,7 +1613,103 @@
1707
1613
  "providers",
1708
1614
  "test.js"
1709
1615
  ]
1616
+ },
1617
+ "sessions": {
1618
+ "aliases": [],
1619
+ "args": {},
1620
+ "description": "List active terminal sessions (CLI tokens) for the org.",
1621
+ "flags": {
1622
+ "json": {
1623
+ "description": "Format output as json.",
1624
+ "helpGroup": "GLOBAL",
1625
+ "name": "json",
1626
+ "allowNo": false,
1627
+ "type": "boolean"
1628
+ },
1629
+ "profile": {
1630
+ "description": "Configuration profile to use",
1631
+ "env": "BYTE_PROFILE",
1632
+ "name": "profile",
1633
+ "hasDynamicHelp": false,
1634
+ "multiple": false,
1635
+ "type": "option"
1636
+ },
1637
+ "base-url": {
1638
+ "description": "Override the API base URL",
1639
+ "env": "BYTE_BASE_URL",
1640
+ "name": "base-url",
1641
+ "hasDynamicHelp": false,
1642
+ "multiple": false,
1643
+ "type": "option"
1644
+ }
1645
+ },
1646
+ "hasDynamicHelp": false,
1647
+ "hiddenAliases": [],
1648
+ "id": "sessions",
1649
+ "pluginAlias": "@bytevion/cli",
1650
+ "pluginName": "@bytevion/cli",
1651
+ "pluginType": "core",
1652
+ "strict": true,
1653
+ "enableJsonFlag": true,
1654
+ "isESM": false,
1655
+ "relativePath": [
1656
+ "dist",
1657
+ "commands",
1658
+ "sessions",
1659
+ "index.js"
1660
+ ]
1661
+ },
1662
+ "sessions:revoke": {
1663
+ "aliases": [],
1664
+ "args": {
1665
+ "id": {
1666
+ "description": "Session id (see `byte sessions`)",
1667
+ "name": "id",
1668
+ "required": true
1669
+ }
1670
+ },
1671
+ "description": "Revoke a terminal session (CLI token) by id.",
1672
+ "flags": {
1673
+ "json": {
1674
+ "description": "Format output as json.",
1675
+ "helpGroup": "GLOBAL",
1676
+ "name": "json",
1677
+ "allowNo": false,
1678
+ "type": "boolean"
1679
+ },
1680
+ "profile": {
1681
+ "description": "Configuration profile to use",
1682
+ "env": "BYTE_PROFILE",
1683
+ "name": "profile",
1684
+ "hasDynamicHelp": false,
1685
+ "multiple": false,
1686
+ "type": "option"
1687
+ },
1688
+ "base-url": {
1689
+ "description": "Override the API base URL",
1690
+ "env": "BYTE_BASE_URL",
1691
+ "name": "base-url",
1692
+ "hasDynamicHelp": false,
1693
+ "multiple": false,
1694
+ "type": "option"
1695
+ }
1696
+ },
1697
+ "hasDynamicHelp": false,
1698
+ "hiddenAliases": [],
1699
+ "id": "sessions:revoke",
1700
+ "pluginAlias": "@bytevion/cli",
1701
+ "pluginName": "@bytevion/cli",
1702
+ "pluginType": "core",
1703
+ "strict": true,
1704
+ "enableJsonFlag": true,
1705
+ "isESM": false,
1706
+ "relativePath": [
1707
+ "dist",
1708
+ "commands",
1709
+ "sessions",
1710
+ "revoke.js"
1711
+ ]
1710
1712
  }
1711
1713
  },
1712
- "version": "0.2.0"
1714
+ "version": "0.3.0"
1713
1715
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytevion/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Byte — control your LLM optimization gateway from the terminal.",
5
5
  "author": "Byte",
6
6
  "license": "UNLICENSED",