@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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import { initialState, reducer, selectSummary } from '../state.js';
|
|
4
|
+
const base = (over = {}) => ({ ...initialState({ signedIn: false }), ...over });
|
|
5
|
+
test('initialState seeds maximum preset and pending provider', () => {
|
|
6
|
+
const s = initialState({ signedIn: true, email: 'a@b.co', byteKey: 'byte_sk_live_x' });
|
|
7
|
+
assert.equal(s.step, 'welcome');
|
|
8
|
+
assert.equal(s.signedIn, true);
|
|
9
|
+
assert.equal(s.email, 'a@b.co');
|
|
10
|
+
assert.equal(s.preset, 'maximum');
|
|
11
|
+
assert.equal(s.providerStatus, 'pending');
|
|
12
|
+
assert.equal(s.byteKey, 'byte_sk_live_x');
|
|
13
|
+
assert.equal(s.byteKeyRevealed, false);
|
|
14
|
+
});
|
|
15
|
+
test('happy path advances welcome→signin→provider→model→key→preset→integrate→done', () => {
|
|
16
|
+
let s = base();
|
|
17
|
+
s = reducer(s, { type: 'GOTO', step: 'signin' });
|
|
18
|
+
assert.equal(s.step, 'signin');
|
|
19
|
+
s = reducer(s, { type: 'SIGNED_IN', email: 'me@byte.co' });
|
|
20
|
+
assert.equal(s.step, 'provider');
|
|
21
|
+
assert.equal(s.signedIn, true);
|
|
22
|
+
assert.equal(s.email, 'me@byte.co');
|
|
23
|
+
s = reducer(s, { type: 'PICK_PROVIDER', id: 'openai', baseUrl: undefined });
|
|
24
|
+
assert.equal(s.providerChoice, 'openai');
|
|
25
|
+
s = reducer(s, { type: 'PROVIDER_RESULT', status: 'connected', models: [{ id: 1, byte_alias: 'byte-gpt' }], connId: 7 });
|
|
26
|
+
assert.equal(s.step, 'model');
|
|
27
|
+
assert.equal(s.providerStatus, 'connected');
|
|
28
|
+
assert.equal(s.connId, 7);
|
|
29
|
+
assert.equal(s.models.length, 1);
|
|
30
|
+
s = reducer(s, { type: 'PICK_MODEL', model: 'byte-gpt' });
|
|
31
|
+
assert.equal(s.step, 'key');
|
|
32
|
+
assert.equal(s.model, 'byte-gpt');
|
|
33
|
+
s = reducer(s, { type: 'KEY_CREATED', key: 'byte_sk_live_abc' });
|
|
34
|
+
assert.equal(s.byteKey, 'byte_sk_live_abc');
|
|
35
|
+
assert.equal(s.byteKeyRevealed, true);
|
|
36
|
+
// KEY_CREATED reveals but does not auto-advance; NEXT moves on.
|
|
37
|
+
s = reducer(s, { type: 'NEXT' });
|
|
38
|
+
assert.equal(s.step, 'preset');
|
|
39
|
+
s = reducer(s, { type: 'PICK_PRESET', preset: 'balanced' });
|
|
40
|
+
assert.equal(s.step, 'integrate');
|
|
41
|
+
assert.equal(s.preset, 'balanced');
|
|
42
|
+
s = reducer(s, { type: 'PICK_CONNECT', connect: 'opencode' });
|
|
43
|
+
assert.equal(s.step, 'done');
|
|
44
|
+
const summary = selectSummary(s);
|
|
45
|
+
assert.equal(summary.provider, 'connected');
|
|
46
|
+
assert.equal(summary.model, 'byte-gpt');
|
|
47
|
+
assert.equal(summary.preset, 'balanced');
|
|
48
|
+
assert.equal(summary.byteKeyCreated, true);
|
|
49
|
+
assert.equal(summary.connect, 'opencode');
|
|
50
|
+
});
|
|
51
|
+
test('PROVIDER_RESULT connected without models skips the model step', () => {
|
|
52
|
+
const s = reducer(base({ step: 'provider' }), { type: 'PROVIDER_RESULT', status: 'connected', models: [], connId: 3 });
|
|
53
|
+
assert.equal(s.step, 'key');
|
|
54
|
+
});
|
|
55
|
+
test('PROVIDER_RESULT saved routes to key and summary reports saved', () => {
|
|
56
|
+
const s = reducer(base({ step: 'provider' }), { type: 'PROVIDER_RESULT', status: 'saved', models: [], connId: 9 });
|
|
57
|
+
assert.equal(s.step, 'key');
|
|
58
|
+
assert.equal(s.providerStatus, 'saved');
|
|
59
|
+
assert.equal(selectSummary(s).provider, 'saved');
|
|
60
|
+
});
|
|
61
|
+
test('ERROR is non-fatal: keeps the step, drops busy, records message', () => {
|
|
62
|
+
const s = reducer(base({ step: 'provider', busy: true }), { type: 'ERROR', from: 'provider', message: 'bad key' });
|
|
63
|
+
assert.equal(s.step, 'provider');
|
|
64
|
+
assert.equal(s.busy, false);
|
|
65
|
+
assert.deepEqual(s.error, { from: 'provider', message: 'bad key' });
|
|
66
|
+
// a following NEXT clears the error
|
|
67
|
+
const cleared = reducer(s, { type: 'CLEAR_ERROR' });
|
|
68
|
+
assert.equal(cleared.error, undefined);
|
|
69
|
+
});
|
|
70
|
+
test('PROVIDER_RETRY returns to the provider step with a pending status', () => {
|
|
71
|
+
const errored = reducer(base({ step: 'provider' }), { type: 'ERROR', from: 'provider', message: 'unreachable' });
|
|
72
|
+
const retry = reducer(errored, { type: 'PROVIDER_RETRY' });
|
|
73
|
+
assert.equal(retry.step, 'provider');
|
|
74
|
+
assert.equal(retry.providerStatus, 'pending');
|
|
75
|
+
assert.equal(retry.error, undefined);
|
|
76
|
+
});
|
|
77
|
+
test('PROVIDER_SKIP marks skipped and summary downgrades pending→skipped', () => {
|
|
78
|
+
const s = reducer(base({ step: 'provider' }), { type: 'PROVIDER_SKIP' });
|
|
79
|
+
assert.equal(s.providerStatus, 'skipped');
|
|
80
|
+
assert.equal(s.step, 'key');
|
|
81
|
+
assert.equal(selectSummary(s).provider, 'skipped');
|
|
82
|
+
// a never-touched provider (still pending) also reports as skipped in the summary
|
|
83
|
+
assert.equal(selectSummary(base()).provider, 'skipped');
|
|
84
|
+
});
|
|
85
|
+
test('selectSummary drops a "skip" connect choice', () => {
|
|
86
|
+
const s = reducer(base({ step: 'integrate' }), { type: 'PICK_CONNECT', connect: 'skip' });
|
|
87
|
+
assert.equal(selectSummary(s).connect, undefined);
|
|
88
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { WizardIslandProps, WizardResult } from '../contract.js';
|
|
3
|
+
interface AppProps extends WizardIslandProps {
|
|
4
|
+
onDone: (result: WizardResult) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function App({ plainColor, ascii, version, initial, ports, providerPresets, presetCards, onDone, }: AppProps): React.ReactElement;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { initialState, reducer, selectSummary } from '../state.js';
|
|
6
|
+
import { glyphs, tones } from '../theme.js';
|
|
7
|
+
import { Brand } from './Brand.js';
|
|
8
|
+
import { DoneScreen } from './DoneScreen.js';
|
|
9
|
+
import { KeyStep } from './KeyStep.js';
|
|
10
|
+
import { Picker } from './Picker.js';
|
|
11
|
+
import { PresetStep } from './PresetStep.js';
|
|
12
|
+
import { ProviderStep } from './ProviderStep.js';
|
|
13
|
+
import { Stepper } from './Stepper.js';
|
|
14
|
+
// Tools offered at the integrate step — mirrors the clack setup list so both paths wire the
|
|
15
|
+
// same harnesses.
|
|
16
|
+
const TOOLS = [
|
|
17
|
+
{ value: 'skip', label: 'Skip for now' },
|
|
18
|
+
{ value: 'opencode', label: 'opencode' },
|
|
19
|
+
{ value: 'claude-code', label: 'Claude Code' },
|
|
20
|
+
{ value: 'codex', label: 'Codex CLI' },
|
|
21
|
+
{ value: 'cursor', label: 'Cursor' },
|
|
22
|
+
{ value: 'cline', label: 'Cline' },
|
|
23
|
+
{ value: 'aider', label: 'Aider' },
|
|
24
|
+
];
|
|
25
|
+
// The wizard root. Owns the reducer + all side effects (it calls the ports and dispatches the
|
|
26
|
+
// results), draws the brand + 8-step stepper + the active step, and resolves the island Promise
|
|
27
|
+
// via onDone. Ctrl-C / Esc at any step → {status:'cancelled'} so nothing is half-applied
|
|
28
|
+
// without the user knowing.
|
|
29
|
+
export function App({ plainColor, ascii, version, initial, ports, providerPresets, presetCards, onDone, }) {
|
|
30
|
+
const g = glyphs(ascii);
|
|
31
|
+
const tone = tones(plainColor);
|
|
32
|
+
const { exit } = useApp();
|
|
33
|
+
const [state, dispatch] = React.useReducer(reducer, initial, initialState);
|
|
34
|
+
const finish = React.useCallback((result) => {
|
|
35
|
+
exit();
|
|
36
|
+
onDone(result);
|
|
37
|
+
}, [exit, onDone]);
|
|
38
|
+
useInput((input, key) => {
|
|
39
|
+
if (key.escape || (key.ctrl && input === 'c'))
|
|
40
|
+
finish({ status: 'cancelled' });
|
|
41
|
+
});
|
|
42
|
+
// ---- welcome: Enter to begin (skip sign-in if already authed) ----
|
|
43
|
+
const beginWelcome = React.useCallback(() => {
|
|
44
|
+
dispatch({ type: 'GOTO', step: initial.signedIn ? 'provider' : 'signin' });
|
|
45
|
+
}, [initial.signedIn]);
|
|
46
|
+
// ---- sign-in: run the device-login port ----
|
|
47
|
+
const runSignIn = React.useCallback(async () => {
|
|
48
|
+
dispatch({ type: 'BUSY', busy: true });
|
|
49
|
+
try {
|
|
50
|
+
const r = await ports.signIn();
|
|
51
|
+
dispatch({ type: 'SIGNED_IN', email: r.email });
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
dispatch({ type: 'ERROR', from: 'signin', message: err?.message ?? String(err) });
|
|
55
|
+
}
|
|
56
|
+
}, [ports]);
|
|
57
|
+
// ---- key: create via the port, reveal once ----
|
|
58
|
+
const createKey = React.useCallback(async (name) => {
|
|
59
|
+
dispatch({ type: 'BUSY', busy: true });
|
|
60
|
+
try {
|
|
61
|
+
const r = await ports.createByteKey(name, state.preset);
|
|
62
|
+
dispatch({ type: 'KEY_CREATED', key: r.key });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
dispatch({ type: 'ERROR', from: 'key', message: err?.message ?? String(err) });
|
|
66
|
+
}
|
|
67
|
+
}, [ports, state.preset]);
|
|
68
|
+
// ---- preset: apply to the org, then advance ----
|
|
69
|
+
const applyPreset = React.useCallback(async (preset) => {
|
|
70
|
+
dispatch({ type: 'BUSY', busy: true });
|
|
71
|
+
try {
|
|
72
|
+
await ports.applyPreset(preset);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// The clack path also treats this as best-effort; never block the wizard on it.
|
|
76
|
+
}
|
|
77
|
+
dispatch({ type: 'PICK_PRESET', preset });
|
|
78
|
+
}, [ports]);
|
|
79
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Brand, { ascii: ascii, plainColor: plainColor, version: version }), _jsx(Box, { marginTop: 1, children: _jsx(Stepper, { current: state.step, ascii: ascii, plainColor: plainColor }) }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(StepBody, { state: state, ascii: ascii, plainColor: plainColor, version: version, ports: ports, providerPresets: providerPresets, presetCards: presetCards, tools: TOOLS, dispatch: dispatch, onBeginWelcome: beginWelcome, onSignIn: runSignIn, onCreateKey: createKey, onApplyPreset: applyPreset, onFinish: finish, glyphArrow: g.arrow, glyphBullet: g.bullet, muted: tone.muted, accent: tone.accent, bad: tone.bad }) }), state.error ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tone.bad, children: `${g.bullet} ${state.error.message}` }) })) : null, _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tone.muted, children: `esc cancel ${g.divider} Byte never claims success it can't prove` }) })] }));
|
|
80
|
+
}
|
|
81
|
+
// Step router. Kept as a plain function (not a component) so the parent owns all hooks/state and
|
|
82
|
+
// the per-step UI stays declarative.
|
|
83
|
+
function StepBody(props) {
|
|
84
|
+
const { state, ascii, plainColor, version, ports, providerPresets, presetCards, tools, dispatch, onBeginWelcome, onSignIn, onCreateKey, onApplyPreset, onFinish, muted, accent, } = props;
|
|
85
|
+
switch (state.step) {
|
|
86
|
+
case 'welcome':
|
|
87
|
+
return _jsx(WelcomeStep, { signedIn: state.signedIn, email: state.email, accent: accent, muted: muted, onBegin: onBeginWelcome });
|
|
88
|
+
case 'signin':
|
|
89
|
+
return _jsx(SignInStep, { busy: state.busy, ascii: ascii, accent: accent, muted: muted, onSignIn: onSignIn });
|
|
90
|
+
case 'provider':
|
|
91
|
+
return (_jsx(ProviderStep, { presets: providerPresets, ports: ports, ascii: ascii, plainColor: plainColor, busy: state.busy, error: state.error?.from === 'provider' ? state.error.message : undefined, onConnected: (r) => dispatch({ type: 'PROVIDER_RESULT', status: r.status, models: r.models, connId: r.connId }), onSkip: () => dispatch({ type: 'PROVIDER_SKIP' }), onBusy: (busy) => dispatch({ type: 'BUSY', busy }), onError: (message) => dispatch({ type: 'ERROR', from: 'provider', message }) }));
|
|
92
|
+
case 'model':
|
|
93
|
+
return (_jsx(ModelStep, { models: state.models, ascii: ascii, plainColor: plainColor, accent: accent, muted: muted, onPick: (model) => dispatch({ type: 'PICK_MODEL', model }) }));
|
|
94
|
+
case 'key':
|
|
95
|
+
return (_jsx(KeyStep, { ascii: ascii, plainColor: plainColor, busy: state.busy, preset: state.preset, byteKey: state.byteKeyRevealed ? state.byteKey : undefined, existingKey: !state.byteKeyRevealed ? state.byteKey : undefined, onCreate: onCreateKey, onContinue: () => dispatch({ type: 'NEXT' }) }));
|
|
96
|
+
case 'preset':
|
|
97
|
+
return (_jsx(PresetStep, { cards: presetCards, ascii: ascii, plainColor: plainColor, busy: state.busy, initial: state.preset, onSelect: onApplyPreset }));
|
|
98
|
+
case 'integrate':
|
|
99
|
+
return (_jsx(IntegrateStep, { tools: tools, ascii: ascii, plainColor: plainColor, accent: accent, muted: muted, onPick: (connect) => dispatch({ type: 'PICK_CONNECT', connect }) }));
|
|
100
|
+
case 'done':
|
|
101
|
+
return (_jsx(DoneScreen, { summary: selectSummary(state), ascii: ascii, plainColor: plainColor, version: version, onClose: () => onFinish({ status: 'done', summary: selectSummary(state) }) }));
|
|
102
|
+
default:
|
|
103
|
+
return _jsx(Text, { children: " " });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function WelcomeStep({ signedIn, email, accent, muted, onBegin, }) {
|
|
107
|
+
useInput((_input, key) => {
|
|
108
|
+
if (key.return)
|
|
109
|
+
onBegin();
|
|
110
|
+
});
|
|
111
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: accent, bold: true, children: "Welcome to Byte" }), _jsx(Text, { color: muted, children: "Sign in, connect a provider, create a key, pick a mode, and wire a tool \u2014 a few keystrokes." }), signedIn ? _jsx(Text, { color: muted, children: `Already signed in${email ? ` as ${email}` : ''}.` }) : null, _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: muted, children: "Press enter to begin" }) })] }));
|
|
112
|
+
}
|
|
113
|
+
function SignInStep({ busy, ascii, accent, muted, onSignIn, }) {
|
|
114
|
+
useInput((_input, key) => {
|
|
115
|
+
if (key.return)
|
|
116
|
+
onSignIn();
|
|
117
|
+
}, { isActive: !busy });
|
|
118
|
+
if (busy) {
|
|
119
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: accent, children: _jsx(Spinner, { type: ascii ? 'line' : 'dots' }) }), _jsx(Text, { children: " Opening your browser \u2014 approve the device there\u2026" })] }));
|
|
120
|
+
}
|
|
121
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: accent, bold: true, children: "Sign in" }), _jsx(Text, { color: muted, children: "We'll open your browser for a one-time device approval." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: muted, children: "Press enter to sign in" }) })] }));
|
|
122
|
+
}
|
|
123
|
+
function ModelStep({ models, ascii, plainColor, accent, muted, onPick, }) {
|
|
124
|
+
const items = React.useMemo(() => models.slice(0, 50).map((m) => ({ value: m.byte_alias, label: m.byte_alias, hint: m.model_id })), [models]);
|
|
125
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: accent, bold: true, children: "Default model" }), _jsx(Text, { color: muted, children: "Pick the model your tools route to by default. Type to filter." }), _jsx(Box, { marginTop: 1, children: _jsx(Picker, { items: items, onSelect: onPick, ascii: ascii, plainColor: plainColor, search: true, searchLabel: "Model" }) })] }));
|
|
126
|
+
}
|
|
127
|
+
function IntegrateStep({ tools, ascii, plainColor, accent, muted, onPick, }) {
|
|
128
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: accent, bold: true, children: "Connect a coding tool" }), _jsx(Text, { color: muted, children: "Wire a harness now, or skip and do it later with `byte integrate`." }), _jsx(Box, { marginTop: 1, children: _jsx(Picker, { items: tools, onSelect: onPick, ascii: ascii, plainColor: plainColor, initialValue: "skip" }) })] }));
|
|
129
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { glyphs, gradient, tones } from '../theme.js';
|
|
4
|
+
// The Byte signature: a dim `›b_` prompt glyph followed by BYTE rendered one character at a
|
|
5
|
+
// time across the cyan→blue gradient, with a muted tagline + version to the right. Under
|
|
6
|
+
// plainColor the gradient collapses to bold plain text; under ascii the glyph swaps to `>b_`.
|
|
7
|
+
export function Brand({ ascii, plainColor, version, tagline = 'optimization gateway' }) {
|
|
8
|
+
const g = glyphs(ascii);
|
|
9
|
+
const tone = tones(plainColor);
|
|
10
|
+
const letters = 'BYTE'.split('');
|
|
11
|
+
const ramp = gradient(letters.length);
|
|
12
|
+
return (_jsxs(Box, { flexDirection: "row", alignItems: "center", children: [_jsxs(Text, { children: [_jsx(Text, { color: tone.muted, children: g.brand[0] }), _jsx(Text, { color: tone.accent, bold: true, children: g.brand[1] }), _jsx(Text, { color: tone.muted, children: g.brand[2] })] }), _jsx(Text, { children: " " }), _jsx(Text, { children: letters.map((ch, i) => (_jsx(Text, { bold: true, color: plainColor ? undefined : ramp[i], children: ch }, i))) }), _jsx(Text, { children: " " }), _jsx(Text, { color: tone.muted, children: tagline }), _jsx(Text, { color: tone.muted, children: ` · v${version}` })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface CardProps {
|
|
3
|
+
label: string;
|
|
4
|
+
hint?: string;
|
|
5
|
+
blurb?: string;
|
|
6
|
+
focused: boolean;
|
|
7
|
+
ascii: boolean;
|
|
8
|
+
plainColor: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function Card({ label, hint, blurb, focused, ascii, plainColor }: CardProps): React.ReactElement;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { tones } from '../theme.js';
|
|
4
|
+
// A selectable card: when focused it grows a left accent bar and brightens the label; the hint
|
|
5
|
+
// sits inline (dim) and an optional blurb wraps underneath. Unfocused cards stay quiet so the
|
|
6
|
+
// selection reads at a glance. The bar is a solid block (unicode) or '|' (ascii).
|
|
7
|
+
export function Card({ label, hint, blurb, focused, ascii, plainColor }) {
|
|
8
|
+
const tone = tones(plainColor);
|
|
9
|
+
const bar = ascii ? '|' : '▌';
|
|
10
|
+
const accent = focused ? tone.accent : undefined;
|
|
11
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: focused ? tone.accent : tone.muted, children: focused ? bar : ' ' }), _jsx(Text, { children: " " }), _jsx(Text, { bold: focused, color: accent, children: label }), hint ? _jsx(Text, { color: tone.muted, children: ` ${hint}` }) : null] }), blurb ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { children: ' ' }), _jsx(Text, { color: tone.muted, dimColor: true, wrap: "wrap", children: blurb })] })) : null] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { WizardSummary } from '../contract.js';
|
|
3
|
+
interface DoneScreenProps {
|
|
4
|
+
summary: WizardSummary;
|
|
5
|
+
ascii: boolean;
|
|
6
|
+
plainColor: boolean;
|
|
7
|
+
version: string;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function DoneScreen({ summary, ascii, plainColor, version, onClose }: DoneScreenProps): React.ReactElement;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { glyphs, gradient, tones } from '../theme.js';
|
|
4
|
+
import { Brand } from './Brand.js';
|
|
5
|
+
// The celebratory close-out. A big gradient check, a tidy summary of what got wired, and the
|
|
6
|
+
// exact next commands to run. The summary never over-claims: a provider that only saved (no
|
|
7
|
+
// models) says so, and tips adapt accordingly. Enter closes the wizard.
|
|
8
|
+
export function DoneScreen({ summary, ascii, plainColor, version, onClose }) {
|
|
9
|
+
const g = glyphs(ascii);
|
|
10
|
+
const tone = tones(plainColor);
|
|
11
|
+
const ramp = gradient(3);
|
|
12
|
+
useInput((_input, key) => {
|
|
13
|
+
if (key.return || key.escape)
|
|
14
|
+
onClose();
|
|
15
|
+
});
|
|
16
|
+
const providerLine = summary.provider === 'connected'
|
|
17
|
+
? { color: tone.ok, text: summary.model ? `connected · ${summary.model}` : 'connected' }
|
|
18
|
+
: summary.provider === 'saved'
|
|
19
|
+
? { color: tone.warn, text: 'saved (finish the key/URL, then `byte providers test`)' }
|
|
20
|
+
: summary.provider === 'failed'
|
|
21
|
+
? { color: tone.bad, text: 'failed' }
|
|
22
|
+
: { color: tone.muted, text: 'skipped' };
|
|
23
|
+
const tips = ['byte run "hello from byte"', 'byte usage'];
|
|
24
|
+
if (summary.provider === 'skipped')
|
|
25
|
+
tips.unshift('byte providers add');
|
|
26
|
+
if (summary.provider === 'saved')
|
|
27
|
+
tips.unshift('byte providers test');
|
|
28
|
+
const Line = ({ label, children }) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 12, children: _jsx(Text, { color: tone.muted, children: label }) }), _jsx(Text, { children: children })] }));
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", children: [_jsx(Text, { bold: true, color: plainColor ? undefined : ramp[0], children: g.check }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, color: tone.ok, children: "Setup complete." })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", borderStyle: g.border, borderColor: tone.ok, paddingX: 2, paddingY: 1, children: [_jsx(Brand, { ascii: ascii, plainColor: plainColor, version: version, tagline: "you are wired up" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Line, { label: "Provider", children: _jsx(Text, { color: providerLine.color, children: providerLine.text }) }), summary.model ? (_jsx(Line, { label: "Model", children: _jsx(Text, { children: summary.model }) })) : null, _jsxs(Line, { label: "Mode", children: [_jsx(Text, { children: summary.preset }), _jsx(Text, { color: tone.muted, children: " applied" })] }), _jsx(Line, { label: "Byte key", children: _jsx(Text, { color: summary.byteKeyCreated ? tone.ok : tone.muted, children: summary.byteKeyCreated ? 'created' : '—' }) }), summary.connect ? (_jsxs(Line, { label: "Tool", children: [_jsx(Text, { color: tone.accent, children: summary.connect }), _jsx(Text, { color: tone.muted, children: " queued" })] })) : null] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: tone.muted, children: "Next" }), tips.map((t) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: tone.accent, children: ` ${g.arrow} ` }), _jsx(Text, { color: tone.accent, children: t })] }, t)))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tone.muted, children: "Press enter to finish" }) })] }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { HomeData, HomeResult } from '../contract.js';
|
|
3
|
+
interface HomeProps {
|
|
4
|
+
signedIn: boolean;
|
|
5
|
+
data: HomeData;
|
|
6
|
+
ascii: boolean;
|
|
7
|
+
plainColor: boolean;
|
|
8
|
+
version: string;
|
|
9
|
+
onDone: (result: HomeResult) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function Home({ signedIn, data, ascii, plainColor, version, onDone }: HomeProps): React.ReactElement;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { glyphs, gradient, tones } from '../theme.js';
|
|
6
|
+
import { Brand } from './Brand.js';
|
|
7
|
+
import { Picker } from './Picker.js';
|
|
8
|
+
// Actions that need a single text argument before they can run, with the prompt label and the
|
|
9
|
+
// same defaults the clack menu used — so the Ink path collects them inline rather than firing a
|
|
10
|
+
// command that would fail oclif's required-arg check.
|
|
11
|
+
const NEEDS_INPUT = {
|
|
12
|
+
run: { label: 'Prompt', placeholder: 'hello from byte', fallback: 'hello from byte' },
|
|
13
|
+
compare: { label: 'Prompt to compare', placeholder: 'Summarize the latest changes', fallback: 'Summarize the latest changes' },
|
|
14
|
+
'keys:create': { label: 'Key name', placeholder: 'cli', fallback: 'cli' },
|
|
15
|
+
};
|
|
16
|
+
const fmtUsd = (n) => `$${(Number.isFinite(n) ? n : 0).toFixed(n >= 1 ? 2 : 4)}`;
|
|
17
|
+
const fmtNum = (v) => {
|
|
18
|
+
const n = Number.isFinite(v) ? v : 0;
|
|
19
|
+
const abs = Math.abs(n);
|
|
20
|
+
if (abs >= 1_000_000)
|
|
21
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
22
|
+
if (abs >= 1_000)
|
|
23
|
+
return `${(n / 1_000).toFixed(1)}K`;
|
|
24
|
+
return String(Math.round(n));
|
|
25
|
+
};
|
|
26
|
+
const fmtPct = (v) => {
|
|
27
|
+
if (v === undefined || !Number.isFinite(v))
|
|
28
|
+
return undefined;
|
|
29
|
+
const scaled = v <= 1 ? v * 100 : v;
|
|
30
|
+
return `${scaled.toFixed(1)}%`;
|
|
31
|
+
};
|
|
32
|
+
// Block sparkline driven by the theme ramp. Each value maps to a glyph and gets a per-column
|
|
33
|
+
// color from the cyan→blue gradient so the series reads as a little branded bar chart.
|
|
34
|
+
function Sparkline({ values, ascii, plainColor }) {
|
|
35
|
+
const g = glyphs(ascii);
|
|
36
|
+
const nums = values.map((v) => (Number.isFinite(v) ? v : 0));
|
|
37
|
+
if (!nums.length)
|
|
38
|
+
return _jsx(Text, { color: tones(plainColor).muted, children: "no activity yet" });
|
|
39
|
+
const max = Math.max(...nums);
|
|
40
|
+
const min = Math.min(...nums);
|
|
41
|
+
const span = max - min || 1;
|
|
42
|
+
const ramp = gradient(nums.length);
|
|
43
|
+
return (_jsx(Text, { children: nums.map((v, i) => {
|
|
44
|
+
const idx = Math.round(((v - min) / span) * (g.spark.length - 1));
|
|
45
|
+
const ch = g.spark[Math.max(0, Math.min(g.spark.length - 1, idx))];
|
|
46
|
+
return (_jsx(Text, { color: plainColor ? undefined : ramp[i], children: ch }, i));
|
|
47
|
+
}) }));
|
|
48
|
+
}
|
|
49
|
+
// Eases the saved-$ figure from 0 → target over ~700ms on mount, a small touch of life when the
|
|
50
|
+
// screen opens. Disabled under ascii/plainColor (and when nothing was saved) so it never
|
|
51
|
+
// animates a static or non-color terminal — those just show the final number immediately.
|
|
52
|
+
function useSavingsTicker(target, animate) {
|
|
53
|
+
const [value, setValue] = React.useState(animate ? 0 : target);
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
if (!animate || target <= 0) {
|
|
56
|
+
setValue(target);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const duration = 700;
|
|
60
|
+
const stepMs = 40;
|
|
61
|
+
const steps = Math.max(1, Math.round(duration / stepMs));
|
|
62
|
+
let frame = 0;
|
|
63
|
+
const id = setInterval(() => {
|
|
64
|
+
frame += 1;
|
|
65
|
+
const t = frame / steps;
|
|
66
|
+
// ease-out cubic
|
|
67
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
68
|
+
setValue(t >= 1 ? target : target * eased);
|
|
69
|
+
if (frame >= steps)
|
|
70
|
+
clearInterval(id);
|
|
71
|
+
}, stepMs);
|
|
72
|
+
return () => clearInterval(id);
|
|
73
|
+
}, [target, animate]);
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
const Row = ({ label, children, plainColor }) => {
|
|
77
|
+
const tone = tones(plainColor);
|
|
78
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 11, children: _jsx(Text, { color: tone.muted, children: label }) }), _jsx(Text, { children: children })] }));
|
|
79
|
+
};
|
|
80
|
+
// The live home dashboard. A bordered card carries identity, providers, the masked key, a
|
|
81
|
+
// gateway health dot, and a 7-day savings sparkline with an animated total; below it a
|
|
82
|
+
// quick-actions Picker resolves to {status:'action'} (run a command) or {status:'exit'}.
|
|
83
|
+
export function Home({ signedIn, data, ascii, plainColor, version, onDone }) {
|
|
84
|
+
const g = glyphs(ascii);
|
|
85
|
+
const tone = tones(plainColor);
|
|
86
|
+
const { exit } = useApp();
|
|
87
|
+
const hasSavings = data.savingsSeries.some((v) => v > 0) || data.savedTotal > 0;
|
|
88
|
+
const animate = !ascii && !plainColor && hasSavings;
|
|
89
|
+
const saved = useSavingsTicker(data.savedTotal, animate);
|
|
90
|
+
// When a chosen action needs a text argument, we switch into an inline input phase rather than
|
|
91
|
+
// resolving immediately. null = the quick-actions menu is showing.
|
|
92
|
+
const [pending, setPending] = React.useState(null);
|
|
93
|
+
const [inputValue, setInputValue] = React.useState('');
|
|
94
|
+
// Ctrl-C / Esc / q → exit. Disabled while typing so those chars reach the TextInput. Picker
|
|
95
|
+
// owns Up/Down/Enter on the menu; this only adds the escapes.
|
|
96
|
+
useInput((input, key) => {
|
|
97
|
+
if (key.escape || input === 'q' || (key.ctrl && input === 'c')) {
|
|
98
|
+
exit();
|
|
99
|
+
onDone({ status: 'exit' });
|
|
100
|
+
}
|
|
101
|
+
}, { isActive: pending === null });
|
|
102
|
+
const gateway = data.gateway === 'healthy'
|
|
103
|
+
? { color: tone.ok, text: 'healthy' }
|
|
104
|
+
: data.gateway === 'down'
|
|
105
|
+
? { color: tone.bad, text: 'unreachable' }
|
|
106
|
+
: { color: tone.muted, text: 'unknown' };
|
|
107
|
+
const actions = [
|
|
108
|
+
{ value: 'setup', label: 'Set up Byte', hint: 'guided' },
|
|
109
|
+
{ value: 'providers:add', label: 'Connect a provider' },
|
|
110
|
+
{ value: 'keys:create', label: 'Create a Byte key' },
|
|
111
|
+
{ value: 'integrate', label: 'Connect a tool' },
|
|
112
|
+
{ value: 'run', label: 'Run a test prompt' },
|
|
113
|
+
{ value: 'compare', label: 'Compare direct vs Byte', hint: 'prove the win' },
|
|
114
|
+
{ value: 'usage', label: 'View usage' },
|
|
115
|
+
{ value: signedIn ? 'logout' : 'login', label: signedIn ? 'Sign out' : 'Sign in' },
|
|
116
|
+
{ value: 'exit', label: 'Exit' },
|
|
117
|
+
];
|
|
118
|
+
const onAction = (value) => {
|
|
119
|
+
if (value === 'exit') {
|
|
120
|
+
exit();
|
|
121
|
+
onDone({ status: 'exit' });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (NEEDS_INPUT[value]) {
|
|
125
|
+
setInputValue('');
|
|
126
|
+
setPending(value);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
exit();
|
|
130
|
+
onDone({ status: 'action', commandId: value, argv: [] });
|
|
131
|
+
};
|
|
132
|
+
const submitInput = (commandId, raw) => {
|
|
133
|
+
const spec = NEEDS_INPUT[commandId];
|
|
134
|
+
const argv = [raw.trim() || spec.fallback];
|
|
135
|
+
exit();
|
|
136
|
+
onDone({ status: 'action', commandId, argv });
|
|
137
|
+
};
|
|
138
|
+
// Inline input phase: a single labelled TextInput for run/compare/keys:create.
|
|
139
|
+
if (pending) {
|
|
140
|
+
const spec = NEEDS_INPUT[pending];
|
|
141
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tone.accent, bold: true, children: spec.label }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsxs(Text, { color: tone.accent, children: [g.arrow, " "] }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, placeholder: spec.placeholder, onSubmit: (v) => submitInput(pending, v) })] }), _jsx(Text, { color: tone.muted, children: "Enter to run" })] }));
|
|
142
|
+
}
|
|
143
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: g.border, borderColor: tone.accent, paddingX: 2, paddingY: 1, children: [_jsx(Brand, { ascii: ascii, plainColor: plainColor, version: version }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Row, { label: "Account", plainColor: plainColor, children: signedIn ? (_jsxs(Text, { children: [data.email ?? 'you', data.org ? _jsx(Text, { color: tone.muted, children: ` · ${data.org}` }) : null] })) : (_jsx(Text, { color: tone.warn, children: "not signed in" })) }), _jsx(Row, { label: "Providers", plainColor: plainColor, children: data.providers ? (_jsxs(Text, { children: [data.providers, " connected", data.models ? _jsx(Text, { color: tone.muted, children: ` · ${data.models} models` }) : null] })) : (_jsx(Text, { color: tone.muted, children: "\u2014" })) }), _jsx(Row, { label: "Byte key", plainColor: plainColor, children: data.byteKeyMasked ? _jsx(Text, { color: tone.accent, children: data.byteKeyMasked }) : _jsx(Text, { color: tone.muted, children: "\u2014" }) }), _jsxs(Row, { label: "Gateway", plainColor: plainColor, children: [_jsx(Text, { color: gateway.color, children: g.filled }), _jsx(Text, { children: ` ${gateway.text}` })] }), hasSavings ? (_jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Box, { width: 11, children: _jsx(Text, { color: tone.muted, children: "7-day" }) }), _jsx(Sparkline, { values: data.savingsSeries, ascii: ascii, plainColor: plainColor }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: tone.ok, children: fmtUsd(saved) }), _jsx(Text, { color: tone.muted, children: ' saved · ' }), _jsx(Text, { children: fmtNum(data.tokensTotal) }), _jsx(Text, { color: tone.muted, children: " tokens" }), fmtPct(data.hitRate) ? _jsx(Text, { color: tone.muted, children: ` · ${fmtPct(data.hitRate)} cache hits` }) : null] })) : null] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: tone.muted, children: `What would you like to do? ${g.divider} ↑↓ move · enter select · q quit` }), _jsx(Box, { marginTop: 1, children: _jsx(Picker, { items: actions, onSelect: onAction, ascii: ascii, plainColor: plainColor, initialValue: signedIn ? 'setup' : 'login', pageSize: actions.length }) })] })] }));
|
|
144
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface KeyStepProps {
|
|
3
|
+
ascii: boolean;
|
|
4
|
+
plainColor: boolean;
|
|
5
|
+
busy: boolean;
|
|
6
|
+
preset: string;
|
|
7
|
+
existingKey?: string;
|
|
8
|
+
byteKey?: string;
|
|
9
|
+
onCreate: (name: string) => void;
|
|
10
|
+
onContinue: () => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function KeyStep({ ascii, plainColor, busy, byteKey, existingKey, onCreate, onContinue }: KeyStepProps): React.ReactElement;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import TextInput from 'ink-text-input';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { glyphs, tones } from '../theme.js';
|
|
7
|
+
import { Panel } from './Panel.js';
|
|
8
|
+
// Byte key step. If a key already exists we skip straight to a continue prompt; otherwise the
|
|
9
|
+
// user names the key, it's created via the port, then revealed ONCE in a panel with the
|
|
10
|
+
// "copy it now" warning. Enter past the reveal advances the wizard.
|
|
11
|
+
export function KeyStep({ ascii, plainColor, busy, byteKey, existingKey, onCreate, onContinue }) {
|
|
12
|
+
const g = glyphs(ascii);
|
|
13
|
+
const tone = tones(plainColor);
|
|
14
|
+
const [name, setName] = React.useState('cli');
|
|
15
|
+
const revealed = Boolean(byteKey);
|
|
16
|
+
// Once a key is on screen, Enter continues. (TextInput is unmounted by then, so this hook
|
|
17
|
+
// owns the keystroke.)
|
|
18
|
+
useInput((_input, key) => {
|
|
19
|
+
if (key.return)
|
|
20
|
+
onContinue();
|
|
21
|
+
}, { isActive: revealed });
|
|
22
|
+
if (busy) {
|
|
23
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: tone.accent, children: _jsx(Spinner, { type: ascii ? 'line' : 'dots' }) }), _jsx(Text, { children: " Creating your Byte key\u2026" })] }));
|
|
24
|
+
}
|
|
25
|
+
if (revealed && byteKey) {
|
|
26
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Panel, { title: "Your Byte API key", tone: "accent", ascii: ascii, plainColor: plainColor, children: [_jsx(Text, { color: tone.accent, children: byteKey }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tone.warn, children: `${g.bullet} Copy it now — this is shown only once.` }) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tone.muted, children: "Press enter to continue" }) })] }));
|
|
27
|
+
}
|
|
28
|
+
if (existingKey) {
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tone.accent, bold: true, children: "Byte key" }), _jsx(Text, { color: tone.muted, children: "This profile already has a Byte key \u2014 reusing it." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: tone.muted, children: "Press enter to continue" }) }), _jsx(EnterToContinue, { onContinue: onContinue })] }));
|
|
30
|
+
}
|
|
31
|
+
const valid = name.trim().length > 0;
|
|
32
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: tone.accent, bold: true, children: "Create a Byte key" }), _jsx(Text, { color: tone.muted, children: "This is the key your tools and SDKs will use." }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsx(Text, { color: tone.muted, children: "Name " }), _jsxs(Text, { color: tone.accent, children: [g.arrow, " "] }), _jsx(TextInput, { value: name, onChange: setName, placeholder: "cli", onSubmit: () => {
|
|
33
|
+
if (valid)
|
|
34
|
+
onCreate(name.trim() || 'cli');
|
|
35
|
+
} })] }), _jsx(Text, { color: tone.muted, children: "Enter to create" })] }));
|
|
36
|
+
}
|
|
37
|
+
// Tiny helper: a hook-only Enter handler used when there is no TextInput to own the key.
|
|
38
|
+
function EnterToContinue({ onContinue }) {
|
|
39
|
+
useInput((_input, key) => {
|
|
40
|
+
if (key.return)
|
|
41
|
+
onContinue();
|
|
42
|
+
});
|
|
43
|
+
return _jsx(Text, { children: " " });
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type ToneName = 'accent' | 'ok' | 'warn' | 'bad' | 'muted';
|
|
3
|
+
interface PanelProps {
|
|
4
|
+
title?: string;
|
|
5
|
+
tone?: ToneName;
|
|
6
|
+
ascii: boolean;
|
|
7
|
+
plainColor: boolean;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function Panel({ title, tone, ascii, plainColor, children }: PanelProps): React.ReactElement;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { glyphs, tones } from '../theme.js';
|
|
4
|
+
// A titled, bordered region. The border style follows the glyph set (round vs classic) and the
|
|
5
|
+
// border + title color follow the requested tone, degrading to the terminal default under
|
|
6
|
+
// plainColor. Used for the reveal-once key, errors, and grouped wizard content.
|
|
7
|
+
export function Panel({ title, tone = 'accent', ascii, plainColor, children }) {
|
|
8
|
+
const g = glyphs(ascii);
|
|
9
|
+
const t = tones(plainColor);
|
|
10
|
+
const color = t[tone];
|
|
11
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: g.border, borderColor: color, paddingX: 1, children: [title ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: color, bold: true, children: title }) })) : null, children] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PickItem {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
hint?: string;
|
|
6
|
+
}
|
|
7
|
+
interface PickerProps {
|
|
8
|
+
items: PickItem[];
|
|
9
|
+
onSelect: (value: string) => void;
|
|
10
|
+
ascii: boolean;
|
|
11
|
+
plainColor: boolean;
|
|
12
|
+
initialValue?: string;
|
|
13
|
+
search?: boolean;
|
|
14
|
+
searchLabel?: string;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
isActive?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function Picker({ items, onSelect, ascii, plainColor, initialValue, search, searchLabel, pageSize, isActive, }: PickerProps): React.ReactElement;
|
|
19
|
+
export {};
|