@bytevion/cli 0.4.1 → 0.5.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/commands/connect.d.ts +12 -0
- package/dist/commands/connect.js +64 -0
- package/dist/commands/dash.d.ts +9 -0
- package/dist/commands/dash.js +120 -0
- package/dist/commands/integrate.d.ts +3 -0
- package/dist/commands/integrate.js +58 -6
- package/dist/commands/keys/rotate.d.ts +3 -0
- package/dist/commands/keys/rotate.js +9 -1
- package/dist/commands/mcp/add.d.ts +13 -0
- package/dist/commands/mcp/add.js +31 -0
- package/dist/commands/mcp/list.d.ts +5 -0
- package/dist/commands/mcp/list.js +22 -0
- package/dist/commands/mcp/remove.d.ts +11 -0
- package/dist/commands/mcp/remove.js +19 -0
- package/dist/commands/mcp/test.d.ts +11 -0
- package/dist/commands/mcp/test.js +20 -0
- package/dist/commands/providers/compare.d.ts +13 -0
- package/dist/commands/providers/compare.js +52 -0
- package/dist/commands/sync.d.ts +10 -0
- package/dist/commands/sync.js +39 -0
- package/dist/lib/api.d.ts +7 -0
- package/dist/lib/api.js +24 -0
- package/dist/lib/config-formats.d.ts +3 -0
- package/dist/lib/config-formats.js +88 -0
- package/dist/lib/detect.d.ts +9 -0
- package/dist/lib/detect.js +83 -0
- package/dist/lib/friendly.js +10 -0
- package/dist/lib/integrations.d.ts +1 -0
- package/dist/lib/integrations.js +33 -4
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.js +13 -0
- package/dist/lib/tui.d.ts +42 -0
- package/dist/lib/tui.js +10 -0
- package/dist/lib/wiring.d.ts +10 -0
- package/dist/lib/wiring.js +53 -0
- package/dist/tui/components/Dash.d.ts +16 -0
- package/dist/tui/components/Dash.js +53 -0
- package/dist/tui/contract.d.ts +42 -0
- package/dist/tui/index.d.ts +2 -1
- package/dist/tui/index.js +34 -0
- package/oclif.manifest.json +585 -96
- package/package.json +4 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_1 = require("../base");
|
|
5
|
+
const wiring_1 = require("../lib/wiring");
|
|
6
|
+
class Sync extends base_1.BaseCommand {
|
|
7
|
+
static description = 'Re-apply your current Byte key/model to every wired coding agent (run after rotating a key or switching model).';
|
|
8
|
+
static examples = ['<%= config.bin %> sync', '<%= config.bin %> sync --model byte-2-deepseek-v4-flash'];
|
|
9
|
+
static flags = {
|
|
10
|
+
model: core_1.Flags.string({ description: 'Model id (byte alias) to wire (defaults to the last wired model)' }),
|
|
11
|
+
yes: core_1.Flags.boolean({ char: 'y', description: 'Skip confirmation prompts' }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(Sync);
|
|
15
|
+
this.requireByteKey(flags);
|
|
16
|
+
const profile = this.profileFrom(flags);
|
|
17
|
+
const wiring = (0, wiring_1.getWiring)(profile);
|
|
18
|
+
const tools = wiring.tools || [];
|
|
19
|
+
if (!tools.length) {
|
|
20
|
+
if (!this.jsonEnabled())
|
|
21
|
+
this.log('No wired tools yet. Run `byte connect` first.');
|
|
22
|
+
return { synced: [] };
|
|
23
|
+
}
|
|
24
|
+
const model = flags.model || wiring.model;
|
|
25
|
+
if (flags.model)
|
|
26
|
+
(0, wiring_1.setSelectedModel)(profile, flags.model);
|
|
27
|
+
const synced = [];
|
|
28
|
+
for (const key of tools) {
|
|
29
|
+
const argv = [key, '--write', '--yes', ...(model ? ['--model', model] : [])];
|
|
30
|
+
// eslint-disable-next-line no-await-in-loop
|
|
31
|
+
await this.config.runCommand('integrate', argv);
|
|
32
|
+
synced.push(key);
|
|
33
|
+
}
|
|
34
|
+
if (!this.jsonEnabled())
|
|
35
|
+
this.log(`Synced ${synced.length} tool(s): ${synced.join(', ')}.`);
|
|
36
|
+
return { synced, model };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.default = Sync;
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -23,10 +23,17 @@ export declare class ByteApi {
|
|
|
23
23
|
providersRotate(id: number, apiKey: string): Promise<any>;
|
|
24
24
|
providersTest(id: number): Promise<any>;
|
|
25
25
|
modelEnable(id: number): Promise<any>;
|
|
26
|
+
modelsList(): Promise<any>;
|
|
27
|
+
modelCompare(id: number, on: boolean): Promise<any>;
|
|
28
|
+
mcpServers(): Promise<any>;
|
|
29
|
+
mcpAdd(body: Record<string, unknown>): Promise<any>;
|
|
30
|
+
mcpRemove(id: number): Promise<any>;
|
|
31
|
+
mcpTest(id: number): Promise<any>;
|
|
26
32
|
optShow(): Promise<any>;
|
|
27
33
|
optPatch(body: Record<string, unknown>): Promise<any>;
|
|
28
34
|
usage(granularity: string): Promise<any>;
|
|
29
35
|
dashboard(): Promise<any>;
|
|
36
|
+
requests(): Promise<any>;
|
|
30
37
|
cacheStats(): Promise<any>;
|
|
31
38
|
costs(): Promise<any>;
|
|
32
39
|
health(): Promise<any>;
|
package/dist/lib/api.js
CHANGED
|
@@ -100,6 +100,27 @@ class ByteApi {
|
|
|
100
100
|
modelEnable(id) {
|
|
101
101
|
return this.request('PATCH', `/api/v1/models/${id}`, { body: { enabled: true } });
|
|
102
102
|
}
|
|
103
|
+
modelsList() {
|
|
104
|
+
return this.request('GET', '/api/v1/models');
|
|
105
|
+
}
|
|
106
|
+
// Toggle direct-vs-Byte comparison eligibility. Two priced models with compare on
|
|
107
|
+
// form the auto-pair `byte compare` needs; one is enough for `byte compare --model <alias>`.
|
|
108
|
+
modelCompare(id, on) {
|
|
109
|
+
return this.request('PATCH', `/api/v1/models/${id}`, { body: { compare_enabled: on } });
|
|
110
|
+
}
|
|
111
|
+
// --- MCP servers (Byte-hosted) ---
|
|
112
|
+
mcpServers() {
|
|
113
|
+
return this.request('GET', '/api/v1/mcp/servers');
|
|
114
|
+
}
|
|
115
|
+
mcpAdd(body) {
|
|
116
|
+
return this.request('POST', '/api/v1/mcp/servers', { body });
|
|
117
|
+
}
|
|
118
|
+
mcpRemove(id) {
|
|
119
|
+
return this.request('DELETE', `/api/v1/mcp/servers/${id}`);
|
|
120
|
+
}
|
|
121
|
+
mcpTest(id) {
|
|
122
|
+
return this.request('POST', `/api/v1/mcp/servers/${id}/test`, { body: {} });
|
|
123
|
+
}
|
|
103
124
|
// --- optimizations ---
|
|
104
125
|
optShow() {
|
|
105
126
|
return this.request('GET', '/api/v1/optimizations');
|
|
@@ -114,6 +135,9 @@ class ByteApi {
|
|
|
114
135
|
dashboard() {
|
|
115
136
|
return this.request('GET', '/api/v1/dashboard/summary');
|
|
116
137
|
}
|
|
138
|
+
requests() {
|
|
139
|
+
return this.request('GET', '/api/v1/requests');
|
|
140
|
+
}
|
|
117
141
|
cacheStats() {
|
|
118
142
|
return this.request('GET', '/api/v1/cache/stats');
|
|
119
143
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseConfig = parseConfig;
|
|
4
|
+
exports.stringifyConfig = stringifyConfig;
|
|
5
|
+
function parseConfig(format, text) {
|
|
6
|
+
if (format === 'toml')
|
|
7
|
+
return parseToml(text);
|
|
8
|
+
return text.trim() ? JSON.parse(text) : {};
|
|
9
|
+
}
|
|
10
|
+
function stringifyConfig(format, value) {
|
|
11
|
+
if (format === 'toml')
|
|
12
|
+
return stringifyToml(value);
|
|
13
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
14
|
+
}
|
|
15
|
+
function parseTomlValue(raw) {
|
|
16
|
+
const v = raw.trim();
|
|
17
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))
|
|
18
|
+
return v.slice(1, -1);
|
|
19
|
+
if (v === 'true')
|
|
20
|
+
return true;
|
|
21
|
+
if (v === 'false')
|
|
22
|
+
return false;
|
|
23
|
+
if (/^-?\d+$/.test(v))
|
|
24
|
+
return Number(v);
|
|
25
|
+
if (/^-?\d*\.\d+$/.test(v))
|
|
26
|
+
return Number(v);
|
|
27
|
+
if (v.startsWith('[') || v.startsWith('{'))
|
|
28
|
+
throw new Error('BYTE_TOML_UNSUPPORTED');
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
function parseToml(text) {
|
|
32
|
+
const root = {};
|
|
33
|
+
let current = root;
|
|
34
|
+
for (const line of text.split(/\r?\n/)) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
37
|
+
continue;
|
|
38
|
+
const section = trimmed.match(/^\[([^\]]+)\]$/);
|
|
39
|
+
if (section) {
|
|
40
|
+
current = root;
|
|
41
|
+
for (const part of section[1].split('.').map((p) => p.trim())) {
|
|
42
|
+
current[part] = current[part] && typeof current[part] === 'object' ? current[part] : {};
|
|
43
|
+
current = current[part];
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const eq = trimmed.indexOf('=');
|
|
48
|
+
if (eq === -1)
|
|
49
|
+
throw new Error('BYTE_TOML_UNSUPPORTED');
|
|
50
|
+
current[trimmed.slice(0, eq).trim()] = parseTomlValue(trimmed.slice(eq + 1));
|
|
51
|
+
}
|
|
52
|
+
return root;
|
|
53
|
+
}
|
|
54
|
+
function formatTomlValue(value) {
|
|
55
|
+
if (typeof value === 'boolean')
|
|
56
|
+
return value ? 'true' : 'false';
|
|
57
|
+
if (typeof value === 'number')
|
|
58
|
+
return String(value);
|
|
59
|
+
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
60
|
+
}
|
|
61
|
+
function stringifyToml(obj) {
|
|
62
|
+
const scalars = [];
|
|
63
|
+
const tables = [];
|
|
64
|
+
const emitTable = (path, table) => {
|
|
65
|
+
const lines = [`[${path}]`];
|
|
66
|
+
const nested = [];
|
|
67
|
+
for (const [key, value] of Object.entries(table)) {
|
|
68
|
+
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
69
|
+
nested.push([key, value]);
|
|
70
|
+
else
|
|
71
|
+
lines.push(`${key} = ${formatTomlValue(value)}`);
|
|
72
|
+
}
|
|
73
|
+
tables.push(lines.join('\n'));
|
|
74
|
+
for (const [key, value] of nested)
|
|
75
|
+
emitTable(`${path}.${key}`, value);
|
|
76
|
+
};
|
|
77
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
78
|
+
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
79
|
+
emitTable(key, value);
|
|
80
|
+
else
|
|
81
|
+
scalars.push(`${key} = ${formatTomlValue(value)}`);
|
|
82
|
+
}
|
|
83
|
+
const parts = [...scalars];
|
|
84
|
+
if (scalars.length && tables.length)
|
|
85
|
+
parts.push('');
|
|
86
|
+
parts.push(...tables);
|
|
87
|
+
return `${parts.join('\n')}\n`;
|
|
88
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectTools = detectTools;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const integrations_1 = require("./integrations");
|
|
8
|
+
// Per-tool detection hints: binaries we look for on PATH, and config dirs/files whose
|
|
9
|
+
// presence also implies the tool is installed. Keys must match integrations.ts entries.
|
|
10
|
+
const PROBES = {
|
|
11
|
+
opencode: { bins: ['opencode'], configs: [(0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'opencode')] },
|
|
12
|
+
'claude-code': { bins: ['claude'], configs: [(0, node_path_1.join)((0, node_os_1.homedir)(), '.claude')] },
|
|
13
|
+
codex: { bins: ['codex'], configs: [process.env.CODEX_HOME || (0, node_path_1.join)((0, node_os_1.homedir)(), '.codex')] },
|
|
14
|
+
cursor: { bins: ['cursor'], configs: [] },
|
|
15
|
+
cline: { bins: [], configs: [] },
|
|
16
|
+
aider: { bins: ['aider'], configs: [(0, node_path_1.join)((0, node_os_1.homedir)(), '.aider.conf.yml')] },
|
|
17
|
+
continue: { bins: ['cn'], configs: [(0, node_path_1.join)((0, node_os_1.homedir)(), '.continue')] },
|
|
18
|
+
zed: { bins: ['zed', 'zeditor'], configs: [(0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'zed')] },
|
|
19
|
+
goose: { bins: ['goose'], configs: [] },
|
|
20
|
+
'gemini-cli': { bins: ['gemini'], configs: [] },
|
|
21
|
+
'qwen-code': { bins: ['qwen'], configs: [] },
|
|
22
|
+
};
|
|
23
|
+
function exists(path) {
|
|
24
|
+
try {
|
|
25
|
+
return (0, node_fs_1.existsSync)(path);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function binaryOnPath(name) {
|
|
32
|
+
const dirs = (process.env.PATH || '').split(node_path_1.delimiter).filter(Boolean);
|
|
33
|
+
const exts = process.platform === 'win32' ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.PS1').split(';').filter(Boolean) : [''];
|
|
34
|
+
for (const dir of dirs) {
|
|
35
|
+
if (process.platform !== 'win32' && exists((0, node_path_1.join)(dir, name)))
|
|
36
|
+
return true;
|
|
37
|
+
for (const ext of exts) {
|
|
38
|
+
if (exists((0, node_path_1.join)(dir, name + ext)))
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
function hasWriter(key) {
|
|
45
|
+
const integ = (0, integrations_1.findIntegration)(key);
|
|
46
|
+
if (!integ)
|
|
47
|
+
return false;
|
|
48
|
+
try {
|
|
49
|
+
return Boolean(integ.build({ baseUrl: '', byteKey: '', modelAlias: '' }).settingsFile);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Inspect the local machine for installed coding agents (PATH binaries + known config
|
|
56
|
+
// dirs). Pure filesystem + env reads — no process spawning, sandbox-safe and fast.
|
|
57
|
+
function detectTools() {
|
|
58
|
+
const known = new Set((0, integrations_1.listIntegrations)().map((i) => i.key));
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const [key, probe] of Object.entries(PROBES)) {
|
|
61
|
+
if (!known.has(key))
|
|
62
|
+
continue;
|
|
63
|
+
const integ = (0, integrations_1.findIntegration)(key);
|
|
64
|
+
if (!integ)
|
|
65
|
+
continue;
|
|
66
|
+
const hasBin = probe.bins.some(binaryOnPath);
|
|
67
|
+
const cfgPath = probe.configs.find(exists);
|
|
68
|
+
const hasCfg = Boolean(cfgPath);
|
|
69
|
+
if (!hasBin && !hasCfg) {
|
|
70
|
+
out.push({ key, title: integ.title, installed: false, reason: 'none', canAutoWrite: hasWriter(key) });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
out.push({
|
|
74
|
+
key,
|
|
75
|
+
title: integ.title,
|
|
76
|
+
installed: true,
|
|
77
|
+
reason: hasBin && hasCfg ? 'both' : hasBin ? 'binary' : 'config',
|
|
78
|
+
canAutoWrite: hasWriter(key),
|
|
79
|
+
configPath: cfgPath,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
package/dist/lib/friendly.js
CHANGED
|
@@ -33,6 +33,16 @@ function friendlyError(err) {
|
|
|
33
33
|
why: 'The provider URL or key looks unreachable right now — your connection is still saved.',
|
|
34
34
|
next: 'byte providers test',
|
|
35
35
|
},
|
|
36
|
+
BYTE_AUTO_PAIR_INCOMPLETE: {
|
|
37
|
+
issue: 'Compare needs a baseline↔Byte model pair.',
|
|
38
|
+
why: 'No two priced models have comparison enabled yet, so there is nothing to compare against.',
|
|
39
|
+
next: 'byte providers compare --all',
|
|
40
|
+
},
|
|
41
|
+
BYTE_MODEL_COMPARE_DISABLED: {
|
|
42
|
+
issue: 'Comparison is turned off for this model.',
|
|
43
|
+
why: 'A model must have comparison enabled to run the direct-vs-Byte lanes.',
|
|
44
|
+
next: 'byte providers compare <alias>',
|
|
45
|
+
},
|
|
36
46
|
BYTE_DEVICE_DENIED: {
|
|
37
47
|
issue: 'Sign-in was declined in the browser.',
|
|
38
48
|
why: 'The device approval was rejected or closed.',
|
package/dist/lib/integrations.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.listIntegrations = listIntegrations;
|
|
|
4
4
|
exports.findIntegration = findIntegration;
|
|
5
5
|
const node_os_1 = require("node:os");
|
|
6
6
|
const node_path_1 = require("node:path");
|
|
7
|
+
const paths_1 = require("./paths");
|
|
7
8
|
const openaiEnv = (ctx) => [
|
|
8
9
|
['OPENAI_BASE_URL', `${ctx.baseUrl}/v1`],
|
|
9
10
|
['OPENAI_API_KEY', ctx.byteKey],
|
|
@@ -39,8 +40,21 @@ const INTEGRATIONS = [
|
|
|
39
40
|
title: 'Codex CLI',
|
|
40
41
|
compat: 'openai',
|
|
41
42
|
build: (ctx) => ({
|
|
42
|
-
env:
|
|
43
|
-
instructions: [
|
|
43
|
+
env: [['BYTE_API_KEY', ctx.byteKey]],
|
|
44
|
+
instructions: [
|
|
45
|
+
`Wires the "byte" provider in ${(0, paths_1.codexConfigPath)()} (model: ${ctx.modelAlias}).`,
|
|
46
|
+
'Export BYTE_API_KEY (above) so Codex reads it via env_key.',
|
|
47
|
+
],
|
|
48
|
+
settingsFile: {
|
|
49
|
+
path: (0, paths_1.codexConfigPath)(),
|
|
50
|
+
format: 'toml',
|
|
51
|
+
apply: (existing) => {
|
|
52
|
+
const cfg = existing && typeof existing === 'object' ? existing : {};
|
|
53
|
+
const providers = { ...(cfg.model_providers ?? {}) };
|
|
54
|
+
providers.byte = { ...(providers.byte ?? {}), name: 'Byte', base_url: `${ctx.baseUrl}/v1`, wire_api: 'chat', env_key: 'BYTE_API_KEY' };
|
|
55
|
+
return { ...cfg, model_provider: 'byte', model: ctx.modelAlias, model_providers: providers };
|
|
56
|
+
},
|
|
57
|
+
},
|
|
44
58
|
}),
|
|
45
59
|
},
|
|
46
60
|
{
|
|
@@ -138,8 +152,23 @@ const INTEGRATIONS = [
|
|
|
138
152
|
title: 'Zed',
|
|
139
153
|
compat: 'openai',
|
|
140
154
|
build: (ctx) => ({
|
|
141
|
-
env: [],
|
|
142
|
-
instructions: [
|
|
155
|
+
env: [['BYTE_API_KEY', ctx.byteKey]],
|
|
156
|
+
instructions: [
|
|
157
|
+
`Wires an OpenAI-compatible "byte" provider in ${(0, paths_1.zedSettingsPath)()} (model ${ctx.modelAlias}).`,
|
|
158
|
+
'Enter the BYTE_API_KEY (above) when Zed prompts for the provider key.',
|
|
159
|
+
],
|
|
160
|
+
settingsFile: {
|
|
161
|
+
path: (0, paths_1.zedSettingsPath)(),
|
|
162
|
+
format: 'json',
|
|
163
|
+
apply: (existing) => {
|
|
164
|
+
const cfg = existing && typeof existing === 'object' ? existing : {};
|
|
165
|
+
const languageModels = { ...(cfg.language_models ?? {}) };
|
|
166
|
+
const openaiCompatible = { ...(languageModels.openai_compatible ?? {}) };
|
|
167
|
+
openaiCompatible.byte = { ...(openaiCompatible.byte ?? {}), api_url: `${ctx.baseUrl}/v1`, available_models: [{ name: ctx.modelAlias }] };
|
|
168
|
+
languageModels.openai_compatible = openaiCompatible;
|
|
169
|
+
return { ...cfg, language_models: languageModels };
|
|
170
|
+
},
|
|
171
|
+
},
|
|
143
172
|
}),
|
|
144
173
|
},
|
|
145
174
|
{
|
package/dist/lib/paths.d.ts
CHANGED
package/dist/lib/paths.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.byteDir = byteDir;
|
|
4
4
|
exports.configPath = configPath;
|
|
5
5
|
exports.credentialsPath = credentialsPath;
|
|
6
|
+
exports.codexConfigPath = codexConfigPath;
|
|
7
|
+
exports.zedSettingsPath = zedSettingsPath;
|
|
6
8
|
const node_fs_1 = require("node:fs");
|
|
7
9
|
const node_os_1 = require("node:os");
|
|
8
10
|
const node_path_1 = require("node:path");
|
|
@@ -17,3 +19,14 @@ function configPath() {
|
|
|
17
19
|
function credentialsPath() {
|
|
18
20
|
return (0, node_path_1.join)(byteDir(), 'credentials.json');
|
|
19
21
|
}
|
|
22
|
+
function xdgConfigHome() {
|
|
23
|
+
return process.env.XDG_CONFIG_HOME || (0, node_path_1.join)((0, node_os_1.homedir)(), '.config');
|
|
24
|
+
}
|
|
25
|
+
// Canonical config locations for the coding agents Byte auto-wires. Centralised here so
|
|
26
|
+
// detection (detect.ts) and the writers (integrations.ts) never disagree on a path.
|
|
27
|
+
function codexConfigPath() {
|
|
28
|
+
return (0, node_path_1.join)(process.env.CODEX_HOME || (0, node_path_1.join)((0, node_os_1.homedir)(), '.codex'), 'config.toml');
|
|
29
|
+
}
|
|
30
|
+
function zedSettingsPath() {
|
|
31
|
+
return (0, node_path_1.join)(xdgConfigHome(), 'zed', 'settings.json');
|
|
32
|
+
}
|
package/dist/lib/tui.d.ts
CHANGED
|
@@ -103,3 +103,45 @@ export declare function useInk(opts?: {
|
|
|
103
103
|
export declare function computeAscii(): boolean;
|
|
104
104
|
export declare function renderHomeIsland(props: HomeIslandProps): Promise<HomeResult>;
|
|
105
105
|
export declare function renderWizardIsland(props: WizardIslandProps): Promise<WizardResult>;
|
|
106
|
+
export interface DashRequest {
|
|
107
|
+
model: string;
|
|
108
|
+
status: string;
|
|
109
|
+
latencyMs: number;
|
|
110
|
+
tokens: number;
|
|
111
|
+
cache?: string;
|
|
112
|
+
}
|
|
113
|
+
export interface DashLayer {
|
|
114
|
+
label: string;
|
|
115
|
+
active: boolean;
|
|
116
|
+
}
|
|
117
|
+
export interface DashData {
|
|
118
|
+
email?: string;
|
|
119
|
+
org?: string;
|
|
120
|
+
gateway: 'healthy' | 'down' | 'unknown';
|
|
121
|
+
mode?: string;
|
|
122
|
+
requests: number;
|
|
123
|
+
cacheHitRate: number;
|
|
124
|
+
savedTotal: number;
|
|
125
|
+
tokensTotal: number;
|
|
126
|
+
costTotal: number;
|
|
127
|
+
savingsSeries: number[];
|
|
128
|
+
providers: number;
|
|
129
|
+
models: number;
|
|
130
|
+
recent: DashRequest[];
|
|
131
|
+
layers: DashLayer[];
|
|
132
|
+
}
|
|
133
|
+
export interface DashIslandProps {
|
|
134
|
+
data: DashData;
|
|
135
|
+
ports: {
|
|
136
|
+
refresh(): Promise<DashData>;
|
|
137
|
+
};
|
|
138
|
+
plainColor: boolean;
|
|
139
|
+
ascii: boolean;
|
|
140
|
+
version: string;
|
|
141
|
+
}
|
|
142
|
+
export type DashResult = {
|
|
143
|
+
status: 'fallback';
|
|
144
|
+
} | {
|
|
145
|
+
status: 'exit';
|
|
146
|
+
};
|
|
147
|
+
export declare function renderDashIsland(props: DashIslandProps): Promise<DashResult>;
|
package/dist/lib/tui.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.useInk = useInk;
|
|
|
7
7
|
exports.computeAscii = computeAscii;
|
|
8
8
|
exports.renderHomeIsland = renderHomeIsland;
|
|
9
9
|
exports.renderWizardIsland = renderWizardIsland;
|
|
10
|
+
exports.renderDashIsland = renderDashIsland;
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const node_url_1 = require("node:url");
|
|
12
13
|
// CJS→ESM bridge for the Ink island. This file stays under src/lib (CommonJS) and is the ONLY
|
|
@@ -60,3 +61,12 @@ async function renderWizardIsland(props) {
|
|
|
60
61
|
return { status: 'fallback' };
|
|
61
62
|
}
|
|
62
63
|
}
|
|
64
|
+
async function renderDashIsland(props) {
|
|
65
|
+
try {
|
|
66
|
+
const mod = await importESM(islandUrl());
|
|
67
|
+
return (await mod.renderDash(props));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { status: 'fallback' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface Wiring {
|
|
2
|
+
model?: string;
|
|
3
|
+
preset?: string;
|
|
4
|
+
tools: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function getWiring(profile: string): Wiring;
|
|
7
|
+
export declare function setWiring(profile: string, patch: Partial<Wiring>): void;
|
|
8
|
+
export declare function addWiredTool(profile: string, key: string): void;
|
|
9
|
+
export declare function setSelectedModel(profile: string, model: string): void;
|
|
10
|
+
export declare function setSelectedPreset(profile: string, preset: string): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getWiring = getWiring;
|
|
4
|
+
exports.setWiring = setWiring;
|
|
5
|
+
exports.addWiredTool = addWiredTool;
|
|
6
|
+
exports.setSelectedModel = setSelectedModel;
|
|
7
|
+
exports.setSelectedPreset = setSelectedPreset;
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const paths_1 = require("./paths");
|
|
11
|
+
function wiringPath() {
|
|
12
|
+
return (0, node_path_1.join)((0, paths_1.byteDir)(), 'wiring.json');
|
|
13
|
+
}
|
|
14
|
+
function load() {
|
|
15
|
+
try {
|
|
16
|
+
const data = JSON.parse((0, node_fs_1.readFileSync)(wiringPath(), 'utf8'));
|
|
17
|
+
return data && typeof data === 'object' ? data : {};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function persist(store) {
|
|
24
|
+
const path = wiringPath();
|
|
25
|
+
const tmp = `${path}.tmp`;
|
|
26
|
+
(0, node_fs_1.writeFileSync)(tmp, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
|
|
27
|
+
(0, node_fs_1.renameSync)(tmp, path);
|
|
28
|
+
}
|
|
29
|
+
function getWiring(profile) {
|
|
30
|
+
return load()[profile] || { tools: [] };
|
|
31
|
+
}
|
|
32
|
+
function setWiring(profile, patch) {
|
|
33
|
+
const store = load();
|
|
34
|
+
const current = store[profile] || { tools: [] };
|
|
35
|
+
store[profile] = { ...current, ...patch };
|
|
36
|
+
persist(store);
|
|
37
|
+
}
|
|
38
|
+
function addWiredTool(profile, key) {
|
|
39
|
+
const store = load();
|
|
40
|
+
const current = store[profile] || { tools: [] };
|
|
41
|
+
if (!current.tools.includes(key))
|
|
42
|
+
current.tools = [...current.tools, key];
|
|
43
|
+
store[profile] = current;
|
|
44
|
+
persist(store);
|
|
45
|
+
}
|
|
46
|
+
function setSelectedModel(profile, model) {
|
|
47
|
+
if (model)
|
|
48
|
+
setWiring(profile, { model });
|
|
49
|
+
}
|
|
50
|
+
function setSelectedPreset(profile, preset) {
|
|
51
|
+
if (preset)
|
|
52
|
+
setWiring(profile, { preset });
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { DashData } from '../contract.js';
|
|
3
|
+
interface DashProps {
|
|
4
|
+
data: DashData;
|
|
5
|
+
ports: {
|
|
6
|
+
refresh(): Promise<DashData>;
|
|
7
|
+
};
|
|
8
|
+
ascii: boolean;
|
|
9
|
+
plainColor: boolean;
|
|
10
|
+
version: string;
|
|
11
|
+
onDone: (result: {
|
|
12
|
+
status: 'fallback' | 'exit';
|
|
13
|
+
}) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function Dash({ data: initial, ports, ascii, plainColor, version, onDone }: DashProps): React.ReactElement;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { glyphs, gradient, tones } from '../theme.js';
|
|
5
|
+
import { Panel } from './Panel.js';
|
|
6
|
+
function Spark({ series, ascii, plainColor }) {
|
|
7
|
+
const ramp = glyphs(ascii).spark;
|
|
8
|
+
const values = series.length ? series : [0];
|
|
9
|
+
const max = Math.max(...values, 1);
|
|
10
|
+
const cols = gradient(values.length);
|
|
11
|
+
return (_jsx(Box, { children: values.map((value, i) => {
|
|
12
|
+
const idx = Math.min(ramp.length - 1, Math.max(0, Math.round((value / max) * (ramp.length - 1))));
|
|
13
|
+
return (_jsx(Text, { color: plainColor ? undefined : cols[i], children: ramp[idx] }, i));
|
|
14
|
+
}) }));
|
|
15
|
+
}
|
|
16
|
+
// Dense live monitor: a brand/health header, a one-line metric strip with a savings sparkline,
|
|
17
|
+
// a Recent-requests panel beside an Optimizations panel, and a key-hint footer. Polls the
|
|
18
|
+
// injected refresh port every few seconds; q / Esc / Ctrl-C exits cleanly.
|
|
19
|
+
export function Dash({ data: initial, ports, ascii, plainColor, version, onDone }) {
|
|
20
|
+
const { exit } = useApp();
|
|
21
|
+
const [data, setData] = React.useState(initial);
|
|
22
|
+
const [tick, setTick] = React.useState(0);
|
|
23
|
+
const t = tones(plainColor);
|
|
24
|
+
const g = glyphs(ascii);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
let alive = true;
|
|
27
|
+
const timer = setInterval(() => {
|
|
28
|
+
ports
|
|
29
|
+
.refresh()
|
|
30
|
+
.then((next) => {
|
|
31
|
+
if (alive) {
|
|
32
|
+
setData(next);
|
|
33
|
+
setTick((n) => n + 1);
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.catch(() => undefined);
|
|
37
|
+
}, 2500);
|
|
38
|
+
return () => {
|
|
39
|
+
alive = false;
|
|
40
|
+
clearInterval(timer);
|
|
41
|
+
};
|
|
42
|
+
}, [ports]);
|
|
43
|
+
useInput((input, key) => {
|
|
44
|
+
if (input === 'q' || key.escape || (key.ctrl && input === 'c')) {
|
|
45
|
+
onDone({ status: 'exit' });
|
|
46
|
+
exit();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const dot = data.gateway === 'healthy' ? t.ok : data.gateway === 'down' ? t.bad : t.muted;
|
|
50
|
+
const recent = data.recent.slice(0, 8);
|
|
51
|
+
const activeCount = data.layers.filter((l) => l.active).length;
|
|
52
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: t.brandMid, bold: true, children: [g.brand.join(''), " byte dash "] }), _jsxs(Text, { color: t.muted, children: ["v", version, " "] }), _jsxs(Text, { color: dot, children: [g.filled, " ", data.gateway] }), _jsxs(Text, { color: t.muted, children: [" ", data.email ?? '', data.org ? ` (${data.org})` : ''] })] }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: t.muted, children: "mode " }), _jsx(Text, { color: t.accent, children: data.mode ?? 'maximum' }), _jsx(Text, { color: t.muted, children: " saved " }), _jsxs(Text, { color: t.ok, children: ["$", data.savedTotal.toFixed(4)] }), _jsx(Text, { color: t.muted, children: " req " }), _jsx(Text, { children: data.requests }), _jsx(Text, { color: t.muted, children: " cache " }), _jsxs(Text, { color: t.accent, children: [Math.round(data.cacheHitRate * 100), "%"] }), _jsx(Text, { color: t.muted, children: " tok " }), _jsx(Text, { children: data.tokensTotal }), _jsx(Text, { color: t.muted, children: " " }), _jsx(Spark, { series: data.savingsSeries, ascii: ascii, plainColor: plainColor })] }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: "56%", marginRight: 1, children: _jsx(Panel, { title: "Recent requests", ascii: ascii, plainColor: plainColor, children: recent.length === 0 ? (_jsx(Text, { color: t.muted, children: "no requests yet" })) : (recent.map((r, i) => (_jsxs(Box, { children: [_jsxs(Text, { color: r.status === 'ok' ? t.ok : t.bad, children: [g.bullet, " "] }), _jsx(Text, { children: r.model.padEnd(22).slice(0, 22) }), _jsxs(Text, { color: t.muted, children: [" ", String(r.latencyMs).padStart(5), "ms "] }), _jsxs(Text, { color: t.muted, children: [String(r.tokens).padStart(6), "tok "] }), _jsx(Text, { color: t.accent, children: r.cache ?? '' })] }, i)))) }) }), _jsx(Box, { width: "42%", children: _jsx(Panel, { title: `Optimizations ${activeCount}/${data.layers.length}`, tone: "ok", ascii: ascii, plainColor: plainColor, children: data.layers.slice(0, 12).map((l, i) => (_jsxs(Box, { children: [_jsxs(Text, { color: l.active ? t.ok : t.muted, children: [l.active ? g.filled : g.empty, " "] }), _jsx(Text, { color: l.active ? undefined : t.muted, children: l.label })] }, i))) }) })] }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: t.muted, children: ["providers ", data.providers, " \u00B7 models ", data.models, " \u00B7 refresh #", tick, " "] }), _jsx(Text, { color: t.accent, children: "q" }), _jsx(Text, { color: t.muted, children: " quit" })] })] }));
|
|
53
|
+
}
|
package/dist/tui/contract.d.ts
CHANGED
|
@@ -97,3 +97,45 @@ export type WizardResult = {
|
|
|
97
97
|
status: 'done';
|
|
98
98
|
summary: WizardSummary;
|
|
99
99
|
};
|
|
100
|
+
export interface DashRequest {
|
|
101
|
+
model: string;
|
|
102
|
+
status: string;
|
|
103
|
+
latencyMs: number;
|
|
104
|
+
tokens: number;
|
|
105
|
+
cache?: string;
|
|
106
|
+
}
|
|
107
|
+
export interface DashLayer {
|
|
108
|
+
label: string;
|
|
109
|
+
active: boolean;
|
|
110
|
+
}
|
|
111
|
+
export interface DashData {
|
|
112
|
+
email?: string;
|
|
113
|
+
org?: string;
|
|
114
|
+
gateway: 'healthy' | 'down' | 'unknown';
|
|
115
|
+
mode?: string;
|
|
116
|
+
requests: number;
|
|
117
|
+
cacheHitRate: number;
|
|
118
|
+
savedTotal: number;
|
|
119
|
+
tokensTotal: number;
|
|
120
|
+
costTotal: number;
|
|
121
|
+
savingsSeries: number[];
|
|
122
|
+
providers: number;
|
|
123
|
+
models: number;
|
|
124
|
+
recent: DashRequest[];
|
|
125
|
+
layers: DashLayer[];
|
|
126
|
+
}
|
|
127
|
+
export interface DashPorts {
|
|
128
|
+
refresh(): Promise<DashData>;
|
|
129
|
+
}
|
|
130
|
+
export interface DashIslandProps {
|
|
131
|
+
data: DashData;
|
|
132
|
+
ports: DashPorts;
|
|
133
|
+
plainColor: boolean;
|
|
134
|
+
ascii: boolean;
|
|
135
|
+
version: string;
|
|
136
|
+
}
|
|
137
|
+
export type DashResult = {
|
|
138
|
+
status: 'fallback';
|
|
139
|
+
} | {
|
|
140
|
+
status: 'exit';
|
|
141
|
+
};
|
package/dist/tui/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type { HomeIslandProps, HomeResult, WizardIslandProps, WizardResult } from './contract.js';
|
|
1
|
+
import type { DashIslandProps, DashResult, HomeIslandProps, HomeResult, WizardIslandProps, WizardResult } from './contract.js';
|
|
2
2
|
export declare function renderHome(props: HomeIslandProps): Promise<HomeResult>;
|
|
3
3
|
export declare function renderWizard(props: WizardIslandProps): Promise<WizardResult>;
|
|
4
|
+
export declare function renderDash(props: DashIslandProps): Promise<DashResult>;
|