claude-connect 0.1.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/LICENSE +21 -0
- package/README.md +175 -0
- package/bin/claude-connect.js +40 -0
- package/package.json +43 -0
- package/src/data/catalog-store.js +482 -0
- package/src/gateway/constants.js +4 -0
- package/src/gateway/messages.js +362 -0
- package/src/gateway/server.js +481 -0
- package/src/gateway/state.js +104 -0
- package/src/index.js +61 -0
- package/src/lib/app-paths.js +269 -0
- package/src/lib/claude-settings.js +364 -0
- package/src/lib/oauth.js +485 -0
- package/src/lib/profile.js +104 -0
- package/src/lib/secrets.js +45 -0
- package/src/lib/terminal.js +350 -0
- package/src/lib/theme.js +44 -0
- package/src/wizard.js +887 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
import { colorize, colors, padRight, truncate, visibleWidth } from './theme.js';
|
|
4
|
+
|
|
5
|
+
const KEY_NAMES = new Set(['up', 'down', 'return', 'escape', 'backspace', 'tab']);
|
|
6
|
+
export const navigation = {
|
|
7
|
+
BACK: '__CLAUDE_CONNECT_BACK__',
|
|
8
|
+
EXIT: '__CLAUDE_CONNECT_EXIT__'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function assertInteractiveTerminal() {
|
|
12
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
13
|
+
throw new Error('Este CLI requiere una terminal interactiva.');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function openAppScreen() {
|
|
18
|
+
process.stdout.write('\x1b[?1049h\x1b[?25l');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function closeAppScreen() {
|
|
22
|
+
process.stdout.write('\x1b[?25h\x1b[?1049l');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function clearScreen() {
|
|
26
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function renderScreen(lines) {
|
|
30
|
+
clearScreen();
|
|
31
|
+
process.stdout.write(`${lines.join('\n')}\n`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function frameLine(content, width) {
|
|
35
|
+
return `│ ${padRight(truncate(content, width - 4), width - 4)} │`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildFrame({ eyebrow, title, subtitle, body = [], footer = [] }) {
|
|
39
|
+
const width = Math.min(88, Math.max(72, process.stdout.columns || 80));
|
|
40
|
+
const innerWidth = width - 4;
|
|
41
|
+
const lines = [];
|
|
42
|
+
|
|
43
|
+
lines.push(colorize(`╭${'─'.repeat(width - 2)}╮`, colors.accentSoft));
|
|
44
|
+
lines.push(frameLine(colorize(eyebrow, colors.bold, colors.accent), width));
|
|
45
|
+
lines.push(frameLine(colorize(title, colors.bold, colors.text), width));
|
|
46
|
+
lines.push(frameLine(colorize(subtitle, colors.soft), width));
|
|
47
|
+
lines.push(colorize(`├${'─'.repeat(width - 2)}┤`, colors.accentSoft));
|
|
48
|
+
|
|
49
|
+
for (const line of body) {
|
|
50
|
+
lines.push(frameLine(line, width));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (footer.length > 0) {
|
|
54
|
+
lines.push(colorize(`├${'─'.repeat(width - 2)}┤`, colors.accentSoft));
|
|
55
|
+
for (const line of footer) {
|
|
56
|
+
lines.push(frameLine(line, width));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
lines.push(colorize(`╰${'─'.repeat(width - 2)}╯`, colors.accentSoft));
|
|
61
|
+
|
|
62
|
+
return lines.map((line) => truncate(line, visibleWidth(line) > innerWidth + 4 ? width : width));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function waitForAnyKey(message = 'Presiona una tecla para continuar.') {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
readline.emitKeypressEvents(process.stdin);
|
|
68
|
+
process.stdin.setRawMode(true);
|
|
69
|
+
let escapePending = false;
|
|
70
|
+
|
|
71
|
+
const cleanup = () => {
|
|
72
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
73
|
+
process.stdin.setRawMode(false);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const onKeypress = (_input, key = {}) => {
|
|
77
|
+
if (key.ctrl && key.name === 'c') {
|
|
78
|
+
cleanup();
|
|
79
|
+
resolve(navigation.EXIT);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (key.name === 'escape') {
|
|
84
|
+
if (escapePending) {
|
|
85
|
+
cleanup();
|
|
86
|
+
resolve(navigation.EXIT);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
escapePending = true;
|
|
91
|
+
process.stdout.write(`\n${colorize('Presiona Esc otra vez para salir.', colors.warning)}\n`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
cleanup();
|
|
96
|
+
resolve(message);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
process.stdin.on('keypress', onKeypress);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function selectFromList({
|
|
104
|
+
step,
|
|
105
|
+
totalSteps,
|
|
106
|
+
title,
|
|
107
|
+
subtitle,
|
|
108
|
+
items,
|
|
109
|
+
detailBuilder,
|
|
110
|
+
footerHint,
|
|
111
|
+
allowBack = false
|
|
112
|
+
}) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const options = allowBack
|
|
115
|
+
? [
|
|
116
|
+
...items,
|
|
117
|
+
{
|
|
118
|
+
label: 'Volver',
|
|
119
|
+
description: 'Regresa a la pantalla anterior.',
|
|
120
|
+
value: navigation.BACK
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
: items;
|
|
124
|
+
let selectedIndex = 0;
|
|
125
|
+
let escapePending = false;
|
|
126
|
+
|
|
127
|
+
readline.emitKeypressEvents(process.stdin);
|
|
128
|
+
process.stdin.setRawMode(true);
|
|
129
|
+
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
132
|
+
process.stdin.setRawMode(false);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const render = () => {
|
|
136
|
+
const selected = options[selectedIndex];
|
|
137
|
+
const detailLines = selected.value === navigation.BACK
|
|
138
|
+
? ['Regresa a la pantalla anterior.']
|
|
139
|
+
: detailBuilder(selected);
|
|
140
|
+
const body = [
|
|
141
|
+
colorize(`Paso ${step}/${totalSteps}`, colors.warning),
|
|
142
|
+
'',
|
|
143
|
+
...options.flatMap((item, index) => {
|
|
144
|
+
const active = index === selectedIndex;
|
|
145
|
+
const prefix = active
|
|
146
|
+
? colorize('›', colors.bold, colors.accent)
|
|
147
|
+
: colorize(' ', colors.muted);
|
|
148
|
+
const label = active
|
|
149
|
+
? colorize(item.label, colors.bold, colors.text)
|
|
150
|
+
: colorize(item.label, colors.text);
|
|
151
|
+
const description = active
|
|
152
|
+
? colorize(item.description, colors.soft)
|
|
153
|
+
: colorize(item.description, colors.muted);
|
|
154
|
+
return [`${prefix} ${label}`, ` ${description}`];
|
|
155
|
+
}),
|
|
156
|
+
'',
|
|
157
|
+
colorize('Detalle', colors.bold, colors.accentSoft),
|
|
158
|
+
...detailLines.map((line) => colorize(line, colors.soft))
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const footer = [
|
|
162
|
+
colorize(
|
|
163
|
+
escapePending
|
|
164
|
+
? `Esc otra vez salir · Enter seleccionar${allowBack ? ' · Tab volver' : ''}`
|
|
165
|
+
: footerHint ?? `↑/↓ mover · Enter seleccionar${allowBack ? ' · Tab volver' : ''} · Esc salir`,
|
|
166
|
+
colors.dim,
|
|
167
|
+
escapePending ? colors.warning : colors.muted
|
|
168
|
+
)
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
renderScreen(
|
|
172
|
+
buildFrame({
|
|
173
|
+
eyebrow: 'CLAUDE CONNECT',
|
|
174
|
+
title,
|
|
175
|
+
subtitle,
|
|
176
|
+
body,
|
|
177
|
+
footer
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const onKeypress = (_input, key = {}) => {
|
|
183
|
+
if (key.ctrl && key.name === 'c') {
|
|
184
|
+
cleanup();
|
|
185
|
+
resolve(navigation.EXIT);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (escapePending && key.name !== 'escape') {
|
|
190
|
+
escapePending = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (key.name === 'up') {
|
|
194
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
195
|
+
render();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (key.name === 'down') {
|
|
200
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
201
|
+
render();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (key.name === 'escape') {
|
|
206
|
+
if (escapePending) {
|
|
207
|
+
cleanup();
|
|
208
|
+
resolve(navigation.EXIT);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
escapePending = true;
|
|
213
|
+
render();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (key.name === 'tab' && allowBack) {
|
|
218
|
+
cleanup();
|
|
219
|
+
resolve(navigation.BACK);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (key.name === 'return') {
|
|
224
|
+
const selected = options[selectedIndex];
|
|
225
|
+
cleanup();
|
|
226
|
+
resolve(selected.value);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
process.stdin.on('keypress', onKeypress);
|
|
231
|
+
render();
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function promptText({
|
|
236
|
+
step,
|
|
237
|
+
totalSteps,
|
|
238
|
+
title,
|
|
239
|
+
subtitle,
|
|
240
|
+
label,
|
|
241
|
+
defaultValue = '',
|
|
242
|
+
placeholder = '',
|
|
243
|
+
secret = false,
|
|
244
|
+
allowBack = false
|
|
245
|
+
}) {
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
let value = '';
|
|
248
|
+
let escapePending = false;
|
|
249
|
+
|
|
250
|
+
readline.emitKeypressEvents(process.stdin);
|
|
251
|
+
process.stdin.setRawMode(true);
|
|
252
|
+
|
|
253
|
+
const cleanup = () => {
|
|
254
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
255
|
+
process.stdin.setRawMode(false);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const render = () => {
|
|
259
|
+
const displayValue = value.length === 0
|
|
260
|
+
? colorize(placeholder || 'Escribe aqui...', colors.muted)
|
|
261
|
+
: secret
|
|
262
|
+
? colorize('•'.repeat(value.length), colors.text)
|
|
263
|
+
: colorize(value, colors.text);
|
|
264
|
+
|
|
265
|
+
const body = [
|
|
266
|
+
colorize(`Paso ${step}/${totalSteps}`, colors.warning),
|
|
267
|
+
'',
|
|
268
|
+
colorize(label, colors.bold, colors.text),
|
|
269
|
+
displayValue,
|
|
270
|
+
'',
|
|
271
|
+
colorize('Sugerencia', colors.bold, colors.accentSoft),
|
|
272
|
+
colorize(defaultValue ? `Enter usa el valor por defecto: ${defaultValue}` : 'Enter confirma el valor actual.', colors.soft)
|
|
273
|
+
];
|
|
274
|
+
|
|
275
|
+
const footer = [
|
|
276
|
+
colorize(
|
|
277
|
+
escapePending
|
|
278
|
+
? `Esc otra vez salir · Enter confirmar${allowBack ? ' · Tab volver' : ''}`
|
|
279
|
+
: `Escribe para editar · Backspace borrar · Enter confirmar${allowBack ? ' · Tab volver' : ''} · Esc salir`,
|
|
280
|
+
colors.dim,
|
|
281
|
+
escapePending ? colors.warning : colors.muted
|
|
282
|
+
)
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
renderScreen(
|
|
286
|
+
buildFrame({
|
|
287
|
+
eyebrow: 'CLAUDE CONNECT',
|
|
288
|
+
title,
|
|
289
|
+
subtitle,
|
|
290
|
+
body,
|
|
291
|
+
footer
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const onKeypress = (input = '', key = {}) => {
|
|
297
|
+
if (key.ctrl && key.name === 'c') {
|
|
298
|
+
cleanup();
|
|
299
|
+
resolve(navigation.EXIT);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (key.name === 'escape') {
|
|
304
|
+
if (escapePending) {
|
|
305
|
+
cleanup();
|
|
306
|
+
resolve(navigation.EXIT);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
escapePending = true;
|
|
311
|
+
render();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (escapePending) {
|
|
316
|
+
escapePending = false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (key.name === 'tab' && allowBack) {
|
|
320
|
+
cleanup();
|
|
321
|
+
resolve(navigation.BACK);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (key.name === 'return') {
|
|
326
|
+
cleanup();
|
|
327
|
+
resolve(value.trim() || defaultValue.trim());
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (key.name === 'backspace') {
|
|
332
|
+
value = value.slice(0, -1);
|
|
333
|
+
render();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (KEY_NAMES.has(key.name)) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!key.ctrl && !key.meta && input) {
|
|
342
|
+
value += input;
|
|
343
|
+
render();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
process.stdin.on('keypress', onKeypress);
|
|
348
|
+
render();
|
|
349
|
+
});
|
|
350
|
+
}
|
package/src/lib/theme.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const RESET = '\x1b[0m';
|
|
2
|
+
|
|
3
|
+
export const colors = {
|
|
4
|
+
reset: RESET,
|
|
5
|
+
panel: '\x1b[48;2;12;18;30m',
|
|
6
|
+
surface: '\x1b[48;2;18;27;44m',
|
|
7
|
+
soft: '\x1b[38;2;148;163;184m',
|
|
8
|
+
text: '\x1b[38;2;226;232;240m',
|
|
9
|
+
muted: '\x1b[38;2;100;116;139m',
|
|
10
|
+
accent: '\x1b[38;2;56;189;248m',
|
|
11
|
+
accentSoft: '\x1b[38;2;125;211;252m',
|
|
12
|
+
accentBg: '\x1b[48;2;10;71;104m',
|
|
13
|
+
success: '\x1b[38;2;74;222;128m',
|
|
14
|
+
warning: '\x1b[38;2;251;191;36m',
|
|
15
|
+
danger: '\x1b[38;2;248;113;113m',
|
|
16
|
+
dim: '\x1b[2m',
|
|
17
|
+
bold: '\x1b[1m'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function colorize(text, ...tokens) {
|
|
21
|
+
return `${tokens.join('')}${text}${RESET}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function stripAnsi(value) {
|
|
25
|
+
return value.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function visibleWidth(value) {
|
|
29
|
+
return stripAnsi(value).length;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function padRight(value, width) {
|
|
33
|
+
const padding = Math.max(0, width - visibleWidth(value));
|
|
34
|
+
return `${value}${' '.repeat(padding)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function truncate(value, width) {
|
|
38
|
+
if (visibleWidth(value) <= width) {
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const plain = stripAnsi(value);
|
|
43
|
+
return `${plain.slice(0, Math.max(0, width - 1))}…`;
|
|
44
|
+
}
|