@jobshimo/browser-link 0.1.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/README.md +58 -21
- package/dist/bridge/client.d.ts +101 -0
- package/dist/bridge/client.js +435 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/bridge/dispatch.d.ts +29 -0
- package/dist/bridge/dispatch.js +39 -0
- package/dist/bridge/dispatch.js.map +1 -0
- package/dist/bridge/events.d.ts +39 -0
- package/dist/bridge/events.js +47 -0
- package/dist/bridge/events.js.map +1 -0
- package/dist/bridge/protocol.d.ts +80 -0
- package/dist/bridge/protocol.js +79 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/server.d.ts +42 -0
- package/dist/bridge/server.js +336 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/bridge/token.d.ts +17 -0
- package/dist/bridge/token.js +79 -0
- package/dist/bridge/token.js.map +1 -0
- package/dist/cli.js +132 -39
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts +3 -6
- package/dist/commands/about.js +2 -18
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/doctor.d.ts +12 -1
- package/dist/commands/doctor.js +90 -20
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/extension.d.ts +3 -2
- package/dist/commands/extension.js +53 -28
- package/dist/commands/extension.js.map +1 -1
- package/dist/commands/multi-agent.d.ts +7 -0
- package/dist/commands/multi-agent.js +109 -0
- package/dist/commands/multi-agent.js.map +1 -0
- package/dist/commands/tools.d.ts +11 -0
- package/dist/commands/tools.js +168 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/updates.d.ts +20 -0
- package/dist/commands/updates.js +100 -0
- package/dist/commands/updates.js.map +1 -0
- package/dist/commands/welcome.d.ts +2 -1
- package/dist/commands/welcome.js +11 -47
- package/dist/commands/welcome.js.map +1 -1
- package/dist/config.d.ts +25 -3
- package/dist/config.js +35 -2
- package/dist/config.js.map +1 -1
- package/dist/installers/copilot.d.ts +2 -0
- package/dist/installers/copilot.js +72 -0
- package/dist/installers/copilot.js.map +1 -0
- package/dist/installers/index.js +2 -1
- package/dist/installers/index.js.map +1 -1
- package/dist/installers/types.d.ts +1 -1
- package/dist/messages.d.ts +7 -0
- package/dist/permissions.d.ts +37 -0
- package/dist/permissions.js +156 -0
- package/dist/permissions.js.map +1 -0
- package/dist/server.d.ts +7 -3
- package/dist/server.js +160 -32
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +18 -0
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +7 -0
- package/dist/tools/browser-dispatch.js +11 -0
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.d.ts +1 -1
- package/dist/tools/server-instructions.js +4 -0
- package/dist/tools/server-instructions.js.map +1 -1
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +77 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/components.d.ts +18 -0
- package/dist/ui/components.js +27 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/screens.d.ts +61 -0
- package/dist/ui/screens.js +603 -0
- package/dist/ui/screens.js.map +1 -0
- package/dist/ui/start.d.ts +6 -0
- package/dist/ui/start.js +19 -0
- package/dist/ui/start.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +10 -4
- package/dist/commands/menu.d.ts +0 -25
- package/dist/commands/menu.js +0 -167
- package/dist/commands/menu.js.map +0 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Language } from '../commands/welcome.js';
|
|
2
|
+
import { type ClientId } from '../installers/index.js';
|
|
3
|
+
import { type InstallReport } from '../commands/install.js';
|
|
4
|
+
interface CommonProps {
|
|
5
|
+
language: Language;
|
|
6
|
+
}
|
|
7
|
+
interface WelcomeProps extends CommonProps {
|
|
8
|
+
hideDismiss: boolean;
|
|
9
|
+
onAccept: () => void;
|
|
10
|
+
onDismiss: () => void;
|
|
11
|
+
onSwapLang: () => void;
|
|
12
|
+
onQuit: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function WelcomeScreen({ language, hideDismiss, onAccept, onDismiss, onSwapLang, onQuit, }: WelcomeProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export type MenuAction = 'register' | 'permissions' | 'multiAgent' | 'extension' | 'doctor' | 'updates' | 'language' | 'welcome' | 'about' | 'repo' | 'quit';
|
|
16
|
+
interface MainMenuProps extends CommonProps {
|
|
17
|
+
onSelect: (action: MenuAction) => void;
|
|
18
|
+
onSwapLang: () => void;
|
|
19
|
+
onQuit: () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function MainMenu({ language, onSelect, onSwapLang, onQuit }: MainMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
interface ClientPickerProps extends CommonProps {
|
|
23
|
+
onPick: (id: ClientId) => void;
|
|
24
|
+
onBack: () => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function ClientPicker({ language, onPick, onBack }: ClientPickerProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
interface InstallResultProps extends CommonProps {
|
|
28
|
+
report: InstallReport;
|
|
29
|
+
onBack: () => void;
|
|
30
|
+
}
|
|
31
|
+
export declare function InstallResultView({ language, report, onBack }: InstallResultProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
interface ExtensionViewProps extends CommonProps {
|
|
33
|
+
onBack: () => void;
|
|
34
|
+
}
|
|
35
|
+
export declare function ExtensionView({ language, onBack }: ExtensionViewProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
interface DoctorViewProps extends CommonProps {
|
|
37
|
+
onBack: () => void;
|
|
38
|
+
}
|
|
39
|
+
export declare function DoctorView({ language, onBack }: DoctorViewProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
interface AboutViewProps extends CommonProps {
|
|
41
|
+
onBack: () => void;
|
|
42
|
+
}
|
|
43
|
+
export declare function AboutView({ language, onBack }: AboutViewProps): import("react/jsx-runtime").JSX.Element;
|
|
44
|
+
interface PermissionsViewProps extends CommonProps {
|
|
45
|
+
onBack: () => void;
|
|
46
|
+
}
|
|
47
|
+
export declare function PermissionsView({ language, onBack }: PermissionsViewProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
interface UpdatesViewProps extends CommonProps {
|
|
49
|
+
onBack: () => void;
|
|
50
|
+
}
|
|
51
|
+
export declare function UpdatesView({ language, onBack }: UpdatesViewProps): import("react/jsx-runtime").JSX.Element;
|
|
52
|
+
interface LanguageViewProps extends CommonProps {
|
|
53
|
+
onPick: (next: Language) => void;
|
|
54
|
+
onBack: () => void;
|
|
55
|
+
}
|
|
56
|
+
export declare function LanguageView({ language, onPick, onBack }: LanguageViewProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
interface MultiAgentViewProps extends CommonProps {
|
|
58
|
+
onBack: () => void;
|
|
59
|
+
}
|
|
60
|
+
export declare function MultiAgentView({ language, onBack }: MultiAgentViewProps): import("react/jsx-runtime").JSX.Element;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { Frame, Menu } from './components.js';
|
|
5
|
+
import { I18N_WELCOME } from '../commands/welcome.js';
|
|
6
|
+
import { I18N_ABOUT } from '../commands/about.js';
|
|
7
|
+
import { INSTALLERS } from '../installers/index.js';
|
|
8
|
+
import { runDoctor, formatDoctor } from '../commands/doctor.js';
|
|
9
|
+
import { getExtensionInfo } from '../commands/extension.js';
|
|
10
|
+
import { checkUpdates } from '../commands/updates.js';
|
|
11
|
+
import { PACKAGE_NAME } from '../version.js';
|
|
12
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
13
|
+
import { PRESETS, TOOL_CATALOGUE, sanitizeDisabledTools, } from '../permissions.js';
|
|
14
|
+
export function WelcomeScreen({ language, hideDismiss, onAccept, onDismiss, onSwapLang, onQuit, }) {
|
|
15
|
+
const t = I18N_WELCOME[language];
|
|
16
|
+
const items = [
|
|
17
|
+
{ value: 'accept', label: t.options.accept },
|
|
18
|
+
];
|
|
19
|
+
if (!hideDismiss)
|
|
20
|
+
items.push({ value: 'dismiss', label: t.options.dismiss });
|
|
21
|
+
items.push({ value: 'swap', label: t.options.swap }, { value: 'quit', label: t.options.quit });
|
|
22
|
+
const [idx, setIdx] = useState(0);
|
|
23
|
+
useInput((input, key) => {
|
|
24
|
+
if (key.upArrow)
|
|
25
|
+
setIdx((i) => (i - 1 + items.length) % items.length);
|
|
26
|
+
else if (key.downArrow)
|
|
27
|
+
setIdx((i) => (i + 1) % items.length);
|
|
28
|
+
else if (key.return) {
|
|
29
|
+
const v = items[idx].value;
|
|
30
|
+
if (v === 'accept')
|
|
31
|
+
onAccept();
|
|
32
|
+
else if (v === 'dismiss')
|
|
33
|
+
onDismiss();
|
|
34
|
+
else if (v === 'swap')
|
|
35
|
+
onSwapLang();
|
|
36
|
+
else
|
|
37
|
+
onQuit();
|
|
38
|
+
}
|
|
39
|
+
else if (input === 'q' || key.escape)
|
|
40
|
+
onQuit();
|
|
41
|
+
else if (input === 'l')
|
|
42
|
+
onSwapLang();
|
|
43
|
+
});
|
|
44
|
+
const footer = language === 'es'
|
|
45
|
+
? '↑↓ moverse · ↵ elegir · l idioma · q salir'
|
|
46
|
+
: '↑↓ navigate · ↵ select · l language · q quit';
|
|
47
|
+
return (_jsxs(Frame, { title: t.title, footer: footer, children: [_jsx(Section, { title: t.aboutTitle, body: t.about }), _jsx(Section, { title: t.capabilitiesTitle, body: t.capabilities }), _jsx(Section, { title: t.warningTitle, body: t.warning, warn: true }), _jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", italic: true, children: t.responsibility }), _jsx(Text, { color: "gray", italic: true, children: t.extensionNote })] }), _jsx(Text, { color: "white", bold: true, children: t.prompt }), _jsx(Box, { marginTop: 1, children: _jsx(Menu, { items: items, selectedIndex: idx }) })] }));
|
|
48
|
+
}
|
|
49
|
+
function Section({ title, body, warn }) {
|
|
50
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: warn ? 'yellow' : 'cyan', bold: true, children: [warn ? '⚠ ' : '', title] }), _jsx(Text, { children: body })] }));
|
|
51
|
+
}
|
|
52
|
+
const MENU_I18N = {
|
|
53
|
+
en: {
|
|
54
|
+
title: 'browser-link — setup',
|
|
55
|
+
prompt: 'Pick an action',
|
|
56
|
+
footer: '↑↓ navigate · ↵ select · l language · q quit',
|
|
57
|
+
options: {
|
|
58
|
+
register: 'Register browser-link with an MCP client',
|
|
59
|
+
permissions: 'Permissions — pick which MCP tools to expose',
|
|
60
|
+
multiAgent: 'Multi-agent — let multiple MCP clients share one bridge',
|
|
61
|
+
extension: 'Show Chrome extension install steps',
|
|
62
|
+
doctor: 'Run doctor (diagnose current setup)',
|
|
63
|
+
updates: 'Check for updates on npm',
|
|
64
|
+
language: 'Language — switch between English and Español',
|
|
65
|
+
welcome: 'Show the welcome screen',
|
|
66
|
+
about: 'About / Help — what is this and how it works',
|
|
67
|
+
repo: 'Open the GitHub repository',
|
|
68
|
+
quit: 'Quit',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
es: {
|
|
72
|
+
title: 'browser-link — configuración',
|
|
73
|
+
prompt: 'Elegí una acción',
|
|
74
|
+
footer: '↑↓ moverse · ↵ elegir · l idioma · q salir',
|
|
75
|
+
options: {
|
|
76
|
+
register: 'Registrar browser-link en un cliente MCP',
|
|
77
|
+
permissions: 'Permisos — elegí qué tools del MCP se exponen',
|
|
78
|
+
multiAgent: 'Multi-agente — varios clientes MCP comparten el mismo puente',
|
|
79
|
+
extension: 'Ver pasos para instalar la extensión de Chrome',
|
|
80
|
+
doctor: 'Diagnóstico (estado actual de la instalación)',
|
|
81
|
+
updates: 'Buscar actualizaciones en npm',
|
|
82
|
+
language: 'Idioma — cambiar entre English y Español',
|
|
83
|
+
welcome: 'Mostrar la pantalla de bienvenida',
|
|
84
|
+
about: 'Información / ayuda — qué es esto y cómo funciona',
|
|
85
|
+
repo: 'Abrir el repositorio en GitHub',
|
|
86
|
+
quit: 'Salir',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
export function MainMenu({ language, onSelect, onSwapLang, onQuit }) {
|
|
91
|
+
const t = MENU_I18N[language];
|
|
92
|
+
const items = [
|
|
93
|
+
'register',
|
|
94
|
+
'permissions',
|
|
95
|
+
'multiAgent',
|
|
96
|
+
'extension',
|
|
97
|
+
'doctor',
|
|
98
|
+
'updates',
|
|
99
|
+
'language',
|
|
100
|
+
'welcome',
|
|
101
|
+
'about',
|
|
102
|
+
'repo',
|
|
103
|
+
'quit',
|
|
104
|
+
].map((a) => ({ value: a, label: t.options[a] }));
|
|
105
|
+
const [idx, setIdx] = useState(0);
|
|
106
|
+
useInput((input, key) => {
|
|
107
|
+
if (key.upArrow)
|
|
108
|
+
setIdx((i) => (i - 1 + items.length) % items.length);
|
|
109
|
+
else if (key.downArrow)
|
|
110
|
+
setIdx((i) => (i + 1) % items.length);
|
|
111
|
+
else if (key.return) {
|
|
112
|
+
const v = items[idx].value;
|
|
113
|
+
if (v === 'quit')
|
|
114
|
+
onQuit();
|
|
115
|
+
else
|
|
116
|
+
onSelect(v);
|
|
117
|
+
}
|
|
118
|
+
else if (input === 'q' || key.escape)
|
|
119
|
+
onQuit();
|
|
120
|
+
else if (input === 'l')
|
|
121
|
+
onSwapLang();
|
|
122
|
+
});
|
|
123
|
+
return (_jsxs(Frame, { title: t.title, footer: t.footer, children: [_jsx(Text, { color: "white", bold: true, children: t.prompt }), _jsx(Box, { marginTop: 1, children: _jsx(Menu, { items: items, selectedIndex: idx }) })] }));
|
|
124
|
+
}
|
|
125
|
+
const PICKER_I18N = {
|
|
126
|
+
en: {
|
|
127
|
+
title: 'Register browser-link in…',
|
|
128
|
+
prompt: 'Which MCP client?',
|
|
129
|
+
footer: '↑↓ navigate · ↵ register · Esc back',
|
|
130
|
+
},
|
|
131
|
+
es: {
|
|
132
|
+
title: 'Registrar browser-link en…',
|
|
133
|
+
prompt: '¿Qué cliente MCP?',
|
|
134
|
+
footer: '↑↓ moverse · ↵ registrar · Esc volver',
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
export function ClientPicker({ language, onPick, onBack }) {
|
|
138
|
+
const t = PICKER_I18N[language];
|
|
139
|
+
const STATUS = {
|
|
140
|
+
en: { registered: 'registered', notRegistered: 'not registered', notDetected: 'not detected' },
|
|
141
|
+
es: { registered: 'registrado', notRegistered: 'no registrado', notDetected: 'no detectado' },
|
|
142
|
+
};
|
|
143
|
+
const s = STATUS[language];
|
|
144
|
+
const items = INSTALLERS.map((inst) => {
|
|
145
|
+
const d = inst.detect();
|
|
146
|
+
const hint = !d.installed ? s.notDetected : d.registered ? s.registered : s.notRegistered;
|
|
147
|
+
return { value: inst.id, label: inst.displayName, hint };
|
|
148
|
+
});
|
|
149
|
+
const [idx, setIdx] = useState(0);
|
|
150
|
+
useInput((input, key) => {
|
|
151
|
+
if (key.upArrow)
|
|
152
|
+
setIdx((i) => (i - 1 + items.length) % items.length);
|
|
153
|
+
else if (key.downArrow)
|
|
154
|
+
setIdx((i) => (i + 1) % items.length);
|
|
155
|
+
else if (key.return)
|
|
156
|
+
onPick(items[idx].value);
|
|
157
|
+
else if (input === 'q' || key.escape)
|
|
158
|
+
onBack();
|
|
159
|
+
});
|
|
160
|
+
return (_jsxs(Frame, { title: t.title, footer: t.footer, children: [_jsx(Text, { color: "white", bold: true, children: t.prompt }), _jsx(Box, { marginTop: 1, children: _jsx(Menu, { items: items, selectedIndex: idx }) })] }));
|
|
161
|
+
}
|
|
162
|
+
const RESULT_I18N = {
|
|
163
|
+
en: {
|
|
164
|
+
ok: 'Registered',
|
|
165
|
+
warn: 'Not registered',
|
|
166
|
+
hint: 'Restart the MCP client so it picks up the new entry.',
|
|
167
|
+
footer: '↵ / Esc back to menu',
|
|
168
|
+
},
|
|
169
|
+
es: {
|
|
170
|
+
ok: 'Registrado',
|
|
171
|
+
warn: 'No registrado',
|
|
172
|
+
hint: 'Reiniciá el cliente MCP para que tome la nueva entrada.',
|
|
173
|
+
footer: '↵ / Esc volver al menú',
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
export function InstallResultView({ language, report, onBack }) {
|
|
177
|
+
const t = RESULT_I18N[language];
|
|
178
|
+
useInput((_input, key) => {
|
|
179
|
+
if (key.return || key.escape)
|
|
180
|
+
onBack();
|
|
181
|
+
});
|
|
182
|
+
const ok = report.installedClient;
|
|
183
|
+
return (_jsxs(Frame, { title: `${report.displayName} — ${ok ? t.ok : t.warn}`, footer: t.footer, children: [_jsxs(Text, { color: ok ? 'green' : 'yellow', children: [ok ? '✓ ' : '⚠ ', report.message] }), ok && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "cyan", children: ["\u2192 ", t.hint] }) }))] }));
|
|
184
|
+
}
|
|
185
|
+
/* ── Extension info ────────────────────────────────────────────────────── */
|
|
186
|
+
const EXT_I18N = {
|
|
187
|
+
en: {
|
|
188
|
+
title: 'Chrome extension — install steps',
|
|
189
|
+
pathLabel: 'Extension assets are at:',
|
|
190
|
+
stepsLabel: 'Install steps:',
|
|
191
|
+
afterLoading: 'After loading, open the extension popup on any tab and click "Conectar" to bridge it.',
|
|
192
|
+
notFound: 'Extension assets not found. Run `npm run build:extension` (dev) or reinstall the package.',
|
|
193
|
+
footer: '↵ / Esc back to menu',
|
|
194
|
+
},
|
|
195
|
+
es: {
|
|
196
|
+
title: 'Extensión de Chrome — pasos de instalación',
|
|
197
|
+
pathLabel: 'Los assets de la extensión están en:',
|
|
198
|
+
stepsLabel: 'Pasos:',
|
|
199
|
+
afterLoading: 'Después de cargarla, abrí el popup de la extensión en cualquier pestaña y hacé click en "Conectar" para puentearla.',
|
|
200
|
+
notFound: 'No se encontraron los assets. Corré `npm run build:extension` (dev) o reinstalá el paquete.',
|
|
201
|
+
footer: '↵ / Esc volver al menú',
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
export function ExtensionView({ language, onBack }) {
|
|
205
|
+
const t = EXT_I18N[language];
|
|
206
|
+
const info = getExtensionInfo();
|
|
207
|
+
useInput((_input, key) => {
|
|
208
|
+
if (key.return || key.escape)
|
|
209
|
+
onBack();
|
|
210
|
+
});
|
|
211
|
+
return (_jsx(Frame, { title: t.title, footer: t.footer, children: info.path ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: t.pathLabel }), _jsx(Text, { color: "cyan", children: info.path }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: t.stepsLabel }), _jsx(Text, { children: info.hints })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", italic: true, children: t.afterLoading }) })] })) : (_jsx(Text, { color: "yellow", children: t.notFound })) }));
|
|
212
|
+
}
|
|
213
|
+
/* ── Doctor ────────────────────────────────────────────────────────────── */
|
|
214
|
+
const DOCTOR_I18N = {
|
|
215
|
+
en: {
|
|
216
|
+
title: 'Doctor — diagnose current setup',
|
|
217
|
+
loading: 'Diagnosing…',
|
|
218
|
+
refresh: 'r refresh · ↵ / Esc back to menu',
|
|
219
|
+
footer: '↵ / Esc back to menu',
|
|
220
|
+
},
|
|
221
|
+
es: {
|
|
222
|
+
title: 'Diagnóstico — estado actual de la instalación',
|
|
223
|
+
loading: 'Diagnosticando…',
|
|
224
|
+
refresh: 'r refrescar · ↵ / Esc volver al menú',
|
|
225
|
+
footer: '↵ / Esc volver al menú',
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
export function DoctorView({ language, onBack }) {
|
|
229
|
+
const t = DOCTOR_I18N[language];
|
|
230
|
+
const [output, setOutput] = useState(null);
|
|
231
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
let cancelled = false;
|
|
234
|
+
setOutput(null);
|
|
235
|
+
runDoctor().then((r) => {
|
|
236
|
+
if (!cancelled)
|
|
237
|
+
setOutput(formatDoctor(r));
|
|
238
|
+
});
|
|
239
|
+
return () => {
|
|
240
|
+
cancelled = true;
|
|
241
|
+
};
|
|
242
|
+
}, [refreshKey]);
|
|
243
|
+
useInput((input, key) => {
|
|
244
|
+
if (key.return || key.escape)
|
|
245
|
+
onBack();
|
|
246
|
+
else if (input === 'r')
|
|
247
|
+
setRefreshKey((k) => k + 1);
|
|
248
|
+
});
|
|
249
|
+
return (_jsx(Frame, { title: t.title, footer: t.refresh, children: output === null ? _jsx(Text, { color: "gray", children: t.loading }) : _jsx(Text, { children: output }) }));
|
|
250
|
+
}
|
|
251
|
+
export function AboutView({ language, onBack }) {
|
|
252
|
+
const t = I18N_ABOUT[language];
|
|
253
|
+
useInput((_input, key) => {
|
|
254
|
+
if (key.return || key.escape)
|
|
255
|
+
onBack();
|
|
256
|
+
});
|
|
257
|
+
const footer = language === 'es' ? '↵ / Esc volver al menú' : '↵ / Esc back to menu';
|
|
258
|
+
return (_jsxs(Frame, { title: t.title, footer: footer, children: [_jsx(AboutSection, { title: t.whatTitle, body: t.what }), _jsx(AboutSection, { title: t.howTitle, body: t.how }), _jsx(AboutSection, { title: t.bindingTitle, body: t.binding }), _jsx(AboutSection, { title: t.bridgeToolsTitle, body: t.bridgeTools }), _jsx(AboutSection, { title: t.mapToolsTitle, body: t.mapTools }), _jsx(AboutSection, { title: t.privacyTitle, body: t.privacy }), _jsx(AboutSection, { title: t.helpTitle, body: t.help })] }));
|
|
259
|
+
}
|
|
260
|
+
function AboutSection({ title, body }) {
|
|
261
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: title }), _jsx(Text, { children: body })] }));
|
|
262
|
+
}
|
|
263
|
+
/* ── Permissions ───────────────────────────────────────────────────────── */
|
|
264
|
+
const PERM_I18N = {
|
|
265
|
+
en: {
|
|
266
|
+
title: 'Permissions — pick which MCP tools to expose',
|
|
267
|
+
intro: 'Apply a preset (↵) or toggle individual tools (Space). Press s to save.',
|
|
268
|
+
presetsHeader: 'Presets',
|
|
269
|
+
bridgeHeader: 'Browser bridge',
|
|
270
|
+
mapHeader: 'Persistent UI map',
|
|
271
|
+
applyPrefix: 'Apply: ',
|
|
272
|
+
unsaved: '* Unsaved changes — press s to save',
|
|
273
|
+
saved: '✓ Saved.',
|
|
274
|
+
restart: 'Changes take effect the next time your MCP client starts the server.',
|
|
275
|
+
footer: '↑↓ navigate · Space toggle · ↵ apply preset · s save · Esc back',
|
|
276
|
+
},
|
|
277
|
+
es: {
|
|
278
|
+
title: 'Permisos — elegí qué tools del MCP se exponen',
|
|
279
|
+
intro: 'Aplicá un preset (↵) o cambiá tools individuales (Espacio). Apretá s para guardar.',
|
|
280
|
+
presetsHeader: 'Presets',
|
|
281
|
+
bridgeHeader: 'Bridge del browser',
|
|
282
|
+
mapHeader: 'Mapa persistente',
|
|
283
|
+
applyPrefix: 'Aplicar: ',
|
|
284
|
+
unsaved: '* Cambios sin guardar — apretá s para guardar',
|
|
285
|
+
saved: '✓ Guardado.',
|
|
286
|
+
restart: 'Los cambios tienen efecto la próxima vez que el cliente MCP arranque el servidor.',
|
|
287
|
+
footer: '↑↓ moverse · Espacio cambiar · ↵ aplicar preset · s guardar · Esc volver',
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
function setsDiffer(a, b) {
|
|
291
|
+
if (a.size !== b.size)
|
|
292
|
+
return true;
|
|
293
|
+
for (const x of a)
|
|
294
|
+
if (!b.has(x))
|
|
295
|
+
return true;
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
export function PermissionsView({ language, onBack }) {
|
|
299
|
+
const t = PERM_I18N[language];
|
|
300
|
+
const rows = useMemo(() => {
|
|
301
|
+
const r = [];
|
|
302
|
+
for (const preset of PRESETS)
|
|
303
|
+
r.push({ kind: 'preset', preset });
|
|
304
|
+
r.push({ kind: 'header', family: 'bridge' });
|
|
305
|
+
for (const tool of TOOL_CATALOGUE) {
|
|
306
|
+
if (tool.family === 'bridge')
|
|
307
|
+
r.push({ kind: 'tool', tool });
|
|
308
|
+
}
|
|
309
|
+
r.push({ kind: 'header', family: 'map' });
|
|
310
|
+
for (const tool of TOOL_CATALOGUE) {
|
|
311
|
+
if (tool.family === 'map')
|
|
312
|
+
r.push({ kind: 'tool', tool });
|
|
313
|
+
}
|
|
314
|
+
return r;
|
|
315
|
+
}, []);
|
|
316
|
+
const initial = useMemo(() => new Set(loadConfig().disabledTools ?? []), []);
|
|
317
|
+
const [savedDisabled, setSavedDisabled] = useState(initial);
|
|
318
|
+
const [disabled, setDisabled] = useState(initial);
|
|
319
|
+
const firstNonHeader = rows.findIndex((r) => r.kind !== 'header');
|
|
320
|
+
const [cursor, setCursor] = useState(firstNonHeader);
|
|
321
|
+
const [justSaved, setJustSaved] = useState(false);
|
|
322
|
+
const move = (dir) => {
|
|
323
|
+
setCursor((idx) => {
|
|
324
|
+
let i = idx;
|
|
325
|
+
for (let n = 0; n < rows.length; n++) {
|
|
326
|
+
i = (i + dir + rows.length) % rows.length;
|
|
327
|
+
if (rows[i].kind !== 'header')
|
|
328
|
+
return i;
|
|
329
|
+
}
|
|
330
|
+
return idx;
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
const toggleTool = (name) => {
|
|
334
|
+
setDisabled((prev) => {
|
|
335
|
+
const next = new Set(prev);
|
|
336
|
+
if (next.has(name))
|
|
337
|
+
next.delete(name);
|
|
338
|
+
else
|
|
339
|
+
next.add(name);
|
|
340
|
+
return next;
|
|
341
|
+
});
|
|
342
|
+
setJustSaved(false);
|
|
343
|
+
};
|
|
344
|
+
const applyPreset = (preset) => {
|
|
345
|
+
setDisabled(new Set(preset.disabled));
|
|
346
|
+
setJustSaved(false);
|
|
347
|
+
};
|
|
348
|
+
const save = () => {
|
|
349
|
+
const list = sanitizeDisabledTools([...disabled]);
|
|
350
|
+
saveConfig({ disabledTools: list });
|
|
351
|
+
setSavedDisabled(new Set(list));
|
|
352
|
+
setJustSaved(true);
|
|
353
|
+
};
|
|
354
|
+
useInput((input, key) => {
|
|
355
|
+
if (key.upArrow)
|
|
356
|
+
move(-1);
|
|
357
|
+
else if (key.downArrow)
|
|
358
|
+
move(1);
|
|
359
|
+
else if (input === ' ') {
|
|
360
|
+
const row = rows[cursor];
|
|
361
|
+
if (row?.kind === 'tool')
|
|
362
|
+
toggleTool(row.tool.name);
|
|
363
|
+
}
|
|
364
|
+
else if (key.return) {
|
|
365
|
+
const row = rows[cursor];
|
|
366
|
+
if (row?.kind === 'preset')
|
|
367
|
+
applyPreset(row.preset);
|
|
368
|
+
}
|
|
369
|
+
else if (input === 's' || input === 'S') {
|
|
370
|
+
save();
|
|
371
|
+
}
|
|
372
|
+
else if (input === 'q' || key.escape)
|
|
373
|
+
onBack();
|
|
374
|
+
});
|
|
375
|
+
const unsaved = setsDiffer(disabled, savedDisabled);
|
|
376
|
+
return (_jsxs(Frame, { title: t.title, footer: t.footer, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "white", children: t.intro }) }), _jsx(Box, { flexDirection: "column", children: rows.map((row, i) => {
|
|
377
|
+
if (row.kind === 'header') {
|
|
378
|
+
const label = row.family === 'bridge' ? t.bridgeHeader : t.mapHeader;
|
|
379
|
+
return (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "cyan", bold: true, children: [label, ":"] }) }, `h-${i}`));
|
|
380
|
+
}
|
|
381
|
+
const isCursor = i === cursor;
|
|
382
|
+
const cursorMark = isCursor ? '❯' : ' ';
|
|
383
|
+
if (row.kind === 'preset') {
|
|
384
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isCursor ? 'cyan' : 'gray', children: [cursorMark, " "] }), _jsxs(Text, { color: isCursor ? 'white' : 'gray', bold: isCursor, children: [t.applyPrefix, row.preset.label] })] }, `p-${row.preset.id}`));
|
|
385
|
+
}
|
|
386
|
+
const isDisabled = disabled.has(row.tool.name);
|
|
387
|
+
const checkbox = isDisabled ? '[ ]' : '[x]';
|
|
388
|
+
const checkboxColor = isDisabled ? 'red' : 'green';
|
|
389
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isCursor ? 'cyan' : 'gray', children: [cursorMark, " "] }), _jsxs(Text, { color: checkboxColor, children: [checkbox, " "] }), _jsx(Text, { color: isCursor ? 'white' : isDisabled ? 'gray' : 'white', bold: isCursor, children: row.tool.name.padEnd(28) }), _jsxs(Text, { color: "gray", dimColor: true, children: [' ', row.tool.summary] })] }, `t-${row.tool.name}`));
|
|
390
|
+
}) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [unsaved ? (_jsx(Text, { color: "yellow", children: t.unsaved })) : justSaved ? (_jsx(Text, { color: "green", children: t.saved })) : null, _jsx(Text, { color: "gray", italic: true, children: t.restart })] })] }));
|
|
391
|
+
}
|
|
392
|
+
/* ── Updates ───────────────────────────────────────────────────────────── */
|
|
393
|
+
const UPDATES_I18N = {
|
|
394
|
+
en: {
|
|
395
|
+
title: 'Check for updates',
|
|
396
|
+
checking: 'Checking the npm registry…',
|
|
397
|
+
current: 'Current version',
|
|
398
|
+
latest: 'Latest on npm ',
|
|
399
|
+
upToDate: '✓ You are on the latest published version.',
|
|
400
|
+
available: '⚠ A newer version is available.',
|
|
401
|
+
upgradeCmd: 'To upgrade, run:',
|
|
402
|
+
error: 'Could not check the registry',
|
|
403
|
+
footer: 'r retry · ↵ / Esc back to menu',
|
|
404
|
+
},
|
|
405
|
+
es: {
|
|
406
|
+
title: 'Buscar actualizaciones',
|
|
407
|
+
checking: 'Consultando el registry de npm…',
|
|
408
|
+
current: 'Versión instalada',
|
|
409
|
+
latest: 'Última en npm ',
|
|
410
|
+
upToDate: '✓ Estás en la última versión publicada.',
|
|
411
|
+
available: '⚠ Hay una versión más nueva disponible.',
|
|
412
|
+
upgradeCmd: 'Para actualizar, corré:',
|
|
413
|
+
error: 'No se pudo consultar el registry',
|
|
414
|
+
footer: 'r reintentar · ↵ / Esc volver al menú',
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
export function UpdatesView({ language, onBack }) {
|
|
418
|
+
const t = UPDATES_I18N[language];
|
|
419
|
+
const [info, setInfo] = useState(null);
|
|
420
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
let cancelled = false;
|
|
423
|
+
setInfo(null);
|
|
424
|
+
checkUpdates().then((r) => {
|
|
425
|
+
if (!cancelled)
|
|
426
|
+
setInfo(r);
|
|
427
|
+
});
|
|
428
|
+
return () => {
|
|
429
|
+
cancelled = true;
|
|
430
|
+
};
|
|
431
|
+
}, [refreshKey]);
|
|
432
|
+
useInput((input, key) => {
|
|
433
|
+
if (key.return || key.escape)
|
|
434
|
+
onBack();
|
|
435
|
+
else if (input === 'r')
|
|
436
|
+
setRefreshKey((k) => k + 1);
|
|
437
|
+
});
|
|
438
|
+
return (_jsx(Frame, { title: t.title, footer: t.footer, children: info === null ? (_jsx(Text, { color: "gray", children: t.checking })) : info.error || info.latest === null ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: "white", children: [t.current, ": "] }), _jsx(Text, { bold: true, children: info.current })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: [t.error, ": ", info.error ?? 'unknown error'] }) })] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsxs(Text, { color: "white", children: [t.current, ": "] }), _jsx(Text, { bold: true, children: info.current })] }), _jsxs(Text, { children: [_jsxs(Text, { color: "white", children: [t.latest, ": "] }), _jsx(Text, { bold: true, color: info.newer ? 'yellow' : 'green', children: info.latest })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: info.newer ? 'yellow' : 'green', children: info.newer ? t.available : t.upToDate }) }), info.newer && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "gray", children: t.upgradeCmd }), _jsx(Text, { color: "cyan", children: `npm install -g ${PACKAGE_NAME}@latest` })] }))] })) }));
|
|
439
|
+
}
|
|
440
|
+
/* ── Language ──────────────────────────────────────────────────────────── */
|
|
441
|
+
const LANG_I18N = {
|
|
442
|
+
en: {
|
|
443
|
+
title: 'Language — switch between English and Español',
|
|
444
|
+
prompt: 'Pick a language',
|
|
445
|
+
saved: '✓ Saved. All output will now use the selected language.',
|
|
446
|
+
footer: '↑↓ navigate · ↵ pick · Esc back',
|
|
447
|
+
},
|
|
448
|
+
es: {
|
|
449
|
+
title: 'Idioma — cambiar entre English y Español',
|
|
450
|
+
prompt: 'Elegí un idioma',
|
|
451
|
+
saved: '✓ Guardado. Todo el output va a usar el idioma elegido.',
|
|
452
|
+
footer: '↑↓ moverse · ↵ elegir · Esc volver',
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
export function LanguageView({ language, onPick, onBack }) {
|
|
456
|
+
const t = LANG_I18N[language];
|
|
457
|
+
const items = [
|
|
458
|
+
{ value: 'en', label: 'English' },
|
|
459
|
+
{ value: 'es', label: 'Español' },
|
|
460
|
+
];
|
|
461
|
+
const [idx, setIdx] = useState(language === 'es' ? 1 : 0);
|
|
462
|
+
const [saved, setSaved] = useState(false);
|
|
463
|
+
useInput((_input, key) => {
|
|
464
|
+
if (key.upArrow)
|
|
465
|
+
setIdx((i) => (i - 1 + items.length) % items.length);
|
|
466
|
+
else if (key.downArrow)
|
|
467
|
+
setIdx((i) => (i + 1) % items.length);
|
|
468
|
+
else if (key.return) {
|
|
469
|
+
const next = items[idx].value;
|
|
470
|
+
onPick(next);
|
|
471
|
+
setSaved(true);
|
|
472
|
+
}
|
|
473
|
+
else if (key.escape)
|
|
474
|
+
onBack();
|
|
475
|
+
});
|
|
476
|
+
return (_jsxs(Frame, { title: t.title, footer: t.footer, children: [_jsx(Text, { color: "white", bold: true, children: t.prompt }), _jsx(Box, { marginTop: 1, children: _jsx(Menu, { items: items, selectedIndex: idx }) }), saved && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "green", children: t.saved }) }))] }));
|
|
477
|
+
}
|
|
478
|
+
const MULTI_AGENT_I18N = {
|
|
479
|
+
en: {
|
|
480
|
+
title: 'Multi-agent — let multiple MCP clients share one browser-link',
|
|
481
|
+
multiHeader: 'Multi-agent mode',
|
|
482
|
+
multiBody: [
|
|
483
|
+
'browser-link normally binds 127.0.0.1:17529 from the first MCP',
|
|
484
|
+
'client that starts it. Other clients (Claude + Copilot + OpenCode at',
|
|
485
|
+
'the same time) crash with "port in use".',
|
|
486
|
+
'',
|
|
487
|
+
'When multi-agent is ON, the second instance becomes a proxy that',
|
|
488
|
+
'forwards MCP requests to the first one over an internal port',
|
|
489
|
+
'(127.0.0.1:17530, kernel-validated like the main one). All agents',
|
|
490
|
+
'end up sharing the same connected Chrome tabs and the same',
|
|
491
|
+
'persistent UI map.',
|
|
492
|
+
].join('\n'),
|
|
493
|
+
multiToggle: 'Enable multi-agent mode',
|
|
494
|
+
reelectHeader: 'Auto-reelect on primary close (advanced)',
|
|
495
|
+
reelectBody: [
|
|
496
|
+
'When the primary client closes, secondary clients lose the bridge.',
|
|
497
|
+
'With auto-reelect ON, one of them takes over the primary role',
|
|
498
|
+
'automatically (race on bind). With it OFF, you relaunch your MCP',
|
|
499
|
+
'client manually to reconnect.',
|
|
500
|
+
].join('\n'),
|
|
501
|
+
reelectToggle: 'Auto-reelect when primary closes',
|
|
502
|
+
reelectDisabled: '(enable multi-agent first)',
|
|
503
|
+
unsaved: '* Unsaved changes — press s to save',
|
|
504
|
+
saved: '✓ Saved.',
|
|
505
|
+
restart: 'Restart every MCP client for these changes to take effect.',
|
|
506
|
+
footer: '↑↓ navigate · Space toggle · s save · Esc back',
|
|
507
|
+
},
|
|
508
|
+
es: {
|
|
509
|
+
title: 'Multi-agente — varios clientes MCP comparten el mismo puente',
|
|
510
|
+
multiHeader: 'Modo multi-agente',
|
|
511
|
+
multiBody: [
|
|
512
|
+
'browser-link normalmente bindea 127.0.0.1:17529 desde el primer',
|
|
513
|
+
'cliente MCP que lo arranca. Otros clientes (Claude + Copilot +',
|
|
514
|
+
'OpenCode al mismo tiempo) caen con "port in use".',
|
|
515
|
+
'',
|
|
516
|
+
'Con multi-agente activado, el segundo proceso se vuelve un proxy',
|
|
517
|
+
'que reenvía los pedidos MCP al primero por un puerto interno',
|
|
518
|
+
'(127.0.0.1:17530, validado por kernel igual que el principal).',
|
|
519
|
+
'Todos los agentes terminan viendo las mismas pestañas conectadas',
|
|
520
|
+
'y el mismo mapa persistente.',
|
|
521
|
+
].join('\n'),
|
|
522
|
+
multiToggle: 'Activar modo multi-agente',
|
|
523
|
+
reelectHeader: 'Re-elección automática al cerrar el primary (avanzado)',
|
|
524
|
+
reelectBody: [
|
|
525
|
+
'Cuando el cliente primary cierra, los secundarios pierden el',
|
|
526
|
+
'puente. Con re-elección automática activada, uno de ellos toma el',
|
|
527
|
+
'rol de primary automáticamente (race en el bind). Con ella',
|
|
528
|
+
'desactivada, tenés que relanzar el cliente MCP a mano para',
|
|
529
|
+
'reconectar.',
|
|
530
|
+
].join('\n'),
|
|
531
|
+
reelectToggle: 'Re-elegir automáticamente al cerrar el primary',
|
|
532
|
+
reelectDisabled: '(activá primero multi-agente)',
|
|
533
|
+
unsaved: '* Cambios sin guardar — apretá s para guardar',
|
|
534
|
+
saved: '✓ Guardado.',
|
|
535
|
+
restart: 'Reiniciá cada cliente MCP para que los cambios tengan efecto.',
|
|
536
|
+
footer: '↑↓ moverse · Espacio cambiar · s guardar · Esc volver',
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
export function MultiAgentView({ language, onBack }) {
|
|
540
|
+
const t = MULTI_AGENT_I18N[language];
|
|
541
|
+
const initialCfg = useMemo(() => loadConfig(), []);
|
|
542
|
+
const [savedMulti, setSavedMulti] = useState(initialCfg.multiAgent === true);
|
|
543
|
+
const [savedReelect, setSavedReelect] = useState(initialCfg.autoReelect === true);
|
|
544
|
+
const [multi, setMulti] = useState(savedMulti);
|
|
545
|
+
const [reelect, setReelect] = useState(savedReelect);
|
|
546
|
+
const [justSaved, setJustSaved] = useState(false);
|
|
547
|
+
// Only the two toggles are navigable.
|
|
548
|
+
const [cursor, setCursor] = useState('multi');
|
|
549
|
+
const move = (dir) => {
|
|
550
|
+
setCursor((c) => {
|
|
551
|
+
if (dir === 1)
|
|
552
|
+
return c === 'multi' ? 'reelect' : 'multi';
|
|
553
|
+
return c === 'reelect' ? 'multi' : 'reelect';
|
|
554
|
+
});
|
|
555
|
+
};
|
|
556
|
+
const toggle = () => {
|
|
557
|
+
if (cursor === 'multi') {
|
|
558
|
+
setMulti((m) => {
|
|
559
|
+
const next = !m;
|
|
560
|
+
// Turning multi off also clears the working reelect state, matching
|
|
561
|
+
// the config normalisation rule (autoReelect implies multiAgent).
|
|
562
|
+
if (!next)
|
|
563
|
+
setReelect(false);
|
|
564
|
+
return next;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
// Auto-reelect can only be turned on when multi is on.
|
|
569
|
+
if (!multi)
|
|
570
|
+
return;
|
|
571
|
+
setReelect((r) => !r);
|
|
572
|
+
}
|
|
573
|
+
setJustSaved(false);
|
|
574
|
+
};
|
|
575
|
+
const save = () => {
|
|
576
|
+
saveConfig({ multiAgent: multi, autoReelect: multi ? reelect : false });
|
|
577
|
+
setSavedMulti(multi);
|
|
578
|
+
setSavedReelect(multi ? reelect : false);
|
|
579
|
+
setJustSaved(true);
|
|
580
|
+
};
|
|
581
|
+
useInput((input, key) => {
|
|
582
|
+
if (key.upArrow)
|
|
583
|
+
move(-1);
|
|
584
|
+
else if (key.downArrow)
|
|
585
|
+
move(1);
|
|
586
|
+
else if (input === ' ')
|
|
587
|
+
toggle();
|
|
588
|
+
else if (input === 's' || input === 'S')
|
|
589
|
+
save();
|
|
590
|
+
else if (input === 'q' || key.escape)
|
|
591
|
+
onBack();
|
|
592
|
+
});
|
|
593
|
+
const unsaved = multi !== savedMulti || reelect !== savedReelect;
|
|
594
|
+
const checkbox = (on, dim) => {
|
|
595
|
+
if (dim)
|
|
596
|
+
return { mark: '[ ]', color: 'gray' };
|
|
597
|
+
return on ? { mark: '[x]', color: 'green' } : { mark: '[ ]', color: 'red' };
|
|
598
|
+
};
|
|
599
|
+
const multiBox = checkbox(multi, false);
|
|
600
|
+
const reelectBox = checkbox(multi ? reelect : false, !multi);
|
|
601
|
+
return (_jsxs(Frame, { title: t.title, footer: t.footer, children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: t.multiHeader }), _jsx(Text, { children: t.multiBody })] }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: cursor === 'multi' ? 'cyan' : 'gray', children: cursor === 'multi' ? '❯ ' : ' ' }), _jsxs(Text, { color: multiBox.color, children: [multiBox.mark, " "] }), _jsx(Text, { color: cursor === 'multi' ? 'white' : 'gray', bold: cursor === 'multi', children: t.multiToggle })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: t.reelectHeader }), _jsx(Text, { children: t.reelectBody })] }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: cursor === 'reelect' ? 'cyan' : 'gray', children: cursor === 'reelect' ? '❯ ' : ' ' }), _jsxs(Text, { color: reelectBox.color, children: [reelectBox.mark, " "] }), _jsxs(Text, { color: !multi ? 'gray' : cursor === 'reelect' ? 'white' : 'gray', bold: cursor === 'reelect' && multi, children: [t.reelectToggle, !multi && (_jsxs(Text, { color: "gray", dimColor: true, children: [' ', t.reelectDisabled] }))] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [unsaved ? (_jsx(Text, { color: "yellow", children: t.unsaved })) : justSaved ? (_jsx(Text, { color: "green", children: t.saved })) : null, _jsx(Text, { color: "gray", italic: true, children: t.restart })] })] }));
|
|
602
|
+
}
|
|
603
|
+
//# sourceMappingURL=screens.js.map
|