vipcode 0.0.0-stage → 0.2.1
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.en.md +60 -0
- package/README.md +401 -2
- package/dist/agent.js +502 -0
- package/dist/agents.js +174 -0
- package/dist/app.js +1025 -0
- package/dist/auth.js +108 -0
- package/dist/catalog.js +487 -0
- package/dist/commands.js +121 -0
- package/dist/config.js +230 -0
- package/dist/debug.js +32 -0
- package/dist/discovery.js +277 -0
- package/dist/frontmatter.js +45 -0
- package/dist/images.js +81 -0
- package/dist/index.js +623 -0
- package/dist/login.js +157 -0
- package/dist/lsp.js +480 -0
- package/dist/markdown.js +262 -0
- package/dist/match.js +8 -0
- package/dist/mcp.js +225 -0
- package/dist/meta.js +7 -0
- package/dist/paths.js +53 -0
- package/dist/permissions.js +144 -0
- package/dist/prompt.js +123 -0
- package/dist/ranking.js +230 -0
- package/dist/router.js +726 -0
- package/dist/sanitize.js +38 -0
- package/dist/session.js +219 -0
- package/dist/skills.js +374 -0
- package/dist/snapshot.js +199 -0
- package/dist/tools.js +1311 -0
- package/dist/trust.js +171 -0
- package/dist/tui/app.js +1019 -0
- package/dist/tui/dialogs.js +197 -0
- package/dist/tui/editor.js +120 -0
- package/dist/ui.js +184 -0
- package/dist/update.js +237 -0
- package/package.json +64 -4
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput, usePaste } from "ink";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { suggestPattern } from "../permissions.js";
|
|
5
|
+
import { showControl, stripControl } from "../sanitize.js";
|
|
6
|
+
import { colorPreview } from "../ui.js";
|
|
7
|
+
import { EMPTY, backspace, deleteWordBack, insert, moveLeft, moveRight, splitEnter } from "./editor.js";
|
|
8
|
+
const KIND_LABEL = {
|
|
9
|
+
write: "ediciones de ficheros",
|
|
10
|
+
bash: "comandos",
|
|
11
|
+
web: "accesos a internet",
|
|
12
|
+
mcp: "herramientas MCP",
|
|
13
|
+
};
|
|
14
|
+
// Pregunta antes de editar, ejecutar o salir a internet, enseñando el diff o el comando
|
|
15
|
+
export function PermissionDialog(props) {
|
|
16
|
+
const { request } = props;
|
|
17
|
+
const pattern = request.kind === "bash" ? suggestPattern(request.subject) : "";
|
|
18
|
+
const options = [{ key: "s", label: "Sí" }];
|
|
19
|
+
options.push({ key: "t", label: `Sí, y no preguntar más por ${KIND_LABEL[request.kind] || "esto"} en esta sesión` });
|
|
20
|
+
if (pattern) {
|
|
21
|
+
options.push({ key: "p", label: `Sí, y permitir siempre «${pattern}» en esta sesión` });
|
|
22
|
+
}
|
|
23
|
+
options.push({ key: "n", label: "No" });
|
|
24
|
+
options.push({ key: "e", label: "No, y decirle qué hacer en su lugar" });
|
|
25
|
+
const [selected, setSelected] = useState(0);
|
|
26
|
+
const [explaining, setExplaining] = useState(false);
|
|
27
|
+
const [feedback, setFeedback] = useState(EMPTY);
|
|
28
|
+
const choose = (key) => {
|
|
29
|
+
if (key === "s") {
|
|
30
|
+
props.onDone({ reply: { allow: true } });
|
|
31
|
+
}
|
|
32
|
+
else if (key === "t") {
|
|
33
|
+
props.onDone({ reply: { allow: true }, grant: "kind" });
|
|
34
|
+
}
|
|
35
|
+
else if (key === "p") {
|
|
36
|
+
props.onDone({ reply: { allow: true }, grant: "pattern", pattern });
|
|
37
|
+
}
|
|
38
|
+
else if (key === "n") {
|
|
39
|
+
props.onDone({ reply: { allow: false } });
|
|
40
|
+
}
|
|
41
|
+
else if (key === "e") {
|
|
42
|
+
setExplaining(true);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
useInput((input, key) => {
|
|
46
|
+
const { text, enter } = splitEnter(input, key.return);
|
|
47
|
+
if (explaining) {
|
|
48
|
+
if (enter) {
|
|
49
|
+
const value = insert(feedback, text).value;
|
|
50
|
+
props.onDone({ reply: { allow: false, feedback: value.trim() || undefined } });
|
|
51
|
+
}
|
|
52
|
+
else if (key.escape) {
|
|
53
|
+
setExplaining(false);
|
|
54
|
+
}
|
|
55
|
+
else if (key.backspace) {
|
|
56
|
+
setFeedback(backspace(feedback));
|
|
57
|
+
}
|
|
58
|
+
else if (key.leftArrow) {
|
|
59
|
+
setFeedback(moveLeft(feedback));
|
|
60
|
+
}
|
|
61
|
+
else if (key.rightArrow) {
|
|
62
|
+
setFeedback(moveRight(feedback));
|
|
63
|
+
}
|
|
64
|
+
else if (key.ctrl && input === "w") {
|
|
65
|
+
setFeedback(deleteWordBack(feedback));
|
|
66
|
+
}
|
|
67
|
+
else if (input && !key.ctrl && !key.meta) {
|
|
68
|
+
setFeedback(insert(feedback, input));
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const letter = text.trim().toLowerCase();
|
|
73
|
+
if (key.upArrow) {
|
|
74
|
+
setSelected((selected - 1 + options.length) % options.length);
|
|
75
|
+
}
|
|
76
|
+
else if (key.downArrow || key.tab) {
|
|
77
|
+
setSelected((selected + 1) % options.length);
|
|
78
|
+
}
|
|
79
|
+
else if (key.escape) {
|
|
80
|
+
choose("n");
|
|
81
|
+
}
|
|
82
|
+
else if (letter && options.some((option) => option.key === letter)) {
|
|
83
|
+
choose(letter);
|
|
84
|
+
}
|
|
85
|
+
else if (letter === "y") {
|
|
86
|
+
choose("s");
|
|
87
|
+
}
|
|
88
|
+
else if (enter) {
|
|
89
|
+
choose(options[selected].key);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
usePaste((text) => {
|
|
93
|
+
if (explaining) {
|
|
94
|
+
setFeedback(insert(feedback, text.replace(/\r?\n/g, " ")));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const lines = request.preview.split("\n");
|
|
98
|
+
const shown = lines.slice(0, 30).join("\n");
|
|
99
|
+
const hidden = lines.length - 30;
|
|
100
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["? ", showControl(request.title), props.agent !== "build" ? _jsxs(Text, { dimColor: true, children: [" (", props.agent, ")"] }) : null] }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { children: colorPreview(shown) }), hidden > 0 ? _jsxs(Text, { dimColor: true, children: ["\u2026 ", hidden, " l\u00EDneas m\u00E1s"] }) : null] }), explaining ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "\u00BFQu\u00E9 deber\u00EDa hacer en su lugar? (Enter env\u00EDa, Esc vuelve)" }), _jsxs(Text, { children: [_jsx(Text, { color: "yellow", children: "\u276F " }), feedback.value.slice(0, feedback.cursor), _jsx(Text, { inverse: true, children: feedback.value[feedback.cursor] || " " }), feedback.value.slice(feedback.cursor + 1)] })] })) : (_jsxs(_Fragment, { children: [options.map((option, index) => (_jsxs(Text, { color: index === selected ? "cyan" : undefined, children: [index === selected ? "❯ " : " ", _jsxs(Text, { dimColor: true, children: ["[", option.key, "]"] }), " ", option.label] }, option.key))), _jsx(Text, { dimColor: true, children: "\u2191\u2193 elige \u00B7 Enter acepta \u00B7 la letra es un atajo \u00B7 Esc = no" })] }))] }));
|
|
101
|
+
}
|
|
102
|
+
function matches(item, query) {
|
|
103
|
+
return `${item.label} ${item.description || ""}`.toLowerCase().includes(query);
|
|
104
|
+
}
|
|
105
|
+
// Lista para elegir (modelos, sesiones, agentes, proveedores); escribir filtra
|
|
106
|
+
export function SelectDialog(props) {
|
|
107
|
+
const [filter, setFilter] = useState("");
|
|
108
|
+
const [selected, setSelected] = useState(0);
|
|
109
|
+
const filtered = useMemo(() => {
|
|
110
|
+
const query = filter.toLowerCase();
|
|
111
|
+
if (!query) {
|
|
112
|
+
return props.items;
|
|
113
|
+
}
|
|
114
|
+
return props.items.filter((item) => matches(item, query));
|
|
115
|
+
}, [filter, props.items]);
|
|
116
|
+
useInput((input, key) => {
|
|
117
|
+
const { text, enter } = splitEnter(input, key.return);
|
|
118
|
+
if (key.escape) {
|
|
119
|
+
props.onDone(null);
|
|
120
|
+
}
|
|
121
|
+
else if (enter) {
|
|
122
|
+
// Si el Enter vino pegado a texto, ese texto también filtra
|
|
123
|
+
let list = filtered;
|
|
124
|
+
let index = selected;
|
|
125
|
+
if (text) {
|
|
126
|
+
const query = (filter + text).toLowerCase();
|
|
127
|
+
list = props.items.filter((item) => matches(item, query));
|
|
128
|
+
index = 0;
|
|
129
|
+
}
|
|
130
|
+
const item = list[index];
|
|
131
|
+
props.onDone(item ? item.value : null);
|
|
132
|
+
}
|
|
133
|
+
else if (key.upArrow) {
|
|
134
|
+
setSelected(Math.max(0, selected - 1));
|
|
135
|
+
}
|
|
136
|
+
else if (key.downArrow || key.tab) {
|
|
137
|
+
setSelected(Math.min(filtered.length - 1, selected + 1));
|
|
138
|
+
}
|
|
139
|
+
else if (key.pageUp) {
|
|
140
|
+
setSelected(Math.max(0, selected - 10));
|
|
141
|
+
}
|
|
142
|
+
else if (key.pageDown) {
|
|
143
|
+
setSelected(Math.min(filtered.length - 1, selected + 10));
|
|
144
|
+
}
|
|
145
|
+
else if (key.backspace) {
|
|
146
|
+
setFilter(filter.slice(0, -1));
|
|
147
|
+
setSelected(0);
|
|
148
|
+
}
|
|
149
|
+
else if (input && !key.ctrl && !key.meta) {
|
|
150
|
+
setFilter(filter + input);
|
|
151
|
+
setSelected(0);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const windowSize = 12;
|
|
155
|
+
const start = Math.max(0, Math.min(selected - Math.floor(windowSize / 2), filtered.length - windowSize));
|
|
156
|
+
const visible = filtered.slice(start, start + windowSize);
|
|
157
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: props.title }), _jsx(Text, { dimColor: true, children: filter ? `Filtro: ${filter}` : "Escribe para filtrar · ↑↓ elegir · Enter aceptar · Esc salir" }), visible.length === 0 ? _jsx(Text, { dimColor: true, children: "(nada coincide)" }) : null, visible.map((item, index) => {
|
|
158
|
+
const active = start + index === selected;
|
|
159
|
+
return (_jsxs(Text, { color: active ? "cyan" : undefined, wrap: "truncate-end", children: [active ? "❯ " : " ", stripControl(item.label), item.description ? _jsxs(Text, { dimColor: true, children: [" ", stripControl(item.description)] }) : null] }, `${start + index}-${item.label}`));
|
|
160
|
+
}), filtered.length > windowSize ? (_jsxs(Text, { dimColor: true, children: [selected + 1, "/", filtered.length] })) : null] }));
|
|
161
|
+
}
|
|
162
|
+
// Pide un texto (las claves se ven como •••)
|
|
163
|
+
export function PromptDialog(props) {
|
|
164
|
+
const [state, setState] = useState(EMPTY);
|
|
165
|
+
useInput((input, key) => {
|
|
166
|
+
const { text, enter } = splitEnter(input, key.return);
|
|
167
|
+
if (key.escape) {
|
|
168
|
+
props.onDone(null);
|
|
169
|
+
}
|
|
170
|
+
else if (enter) {
|
|
171
|
+
props.onDone(insert(state, text).value);
|
|
172
|
+
}
|
|
173
|
+
else if (key.backspace) {
|
|
174
|
+
setState(backspace(state));
|
|
175
|
+
}
|
|
176
|
+
else if (key.leftArrow) {
|
|
177
|
+
setState(moveLeft(state));
|
|
178
|
+
}
|
|
179
|
+
else if (key.rightArrow) {
|
|
180
|
+
setState(moveRight(state));
|
|
181
|
+
}
|
|
182
|
+
else if (key.ctrl && input === "u") {
|
|
183
|
+
setState(EMPTY);
|
|
184
|
+
}
|
|
185
|
+
else if (input && !key.ctrl && !key.meta) {
|
|
186
|
+
setState(insert(state, input));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
usePaste((text) => {
|
|
190
|
+
setState(insert(state, text.replace(/\r?\n/g, "").trim()));
|
|
191
|
+
});
|
|
192
|
+
let shown = state.value;
|
|
193
|
+
if (props.mask) {
|
|
194
|
+
shown = "•".repeat(state.value.length);
|
|
195
|
+
}
|
|
196
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: props.title }), _jsxs(Text, { children: [_jsx(Text, { color: "magenta", children: "\u276F " }), shown.length === 0 && props.placeholder ? _jsx(Text, { dimColor: true, children: props.placeholder }) : null, shown.slice(0, state.cursor), _jsx(Text, { inverse: true, children: shown[state.cursor] || " " }), shown.slice(state.cursor + 1)] }), _jsxs(Text, { dimColor: true, children: ["Enter acepta \u00B7 Esc cancela", props.mask ? " · pega la clave con Cmd+V" : ""] })] }));
|
|
197
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Edición del cuadro de texto: funciones puras sobre el texto y la posición del cursor
|
|
2
|
+
export const EMPTY = { value: "", cursor: 0 };
|
|
3
|
+
export function insert(state, text) {
|
|
4
|
+
const value = state.value.slice(0, state.cursor) + text + state.value.slice(state.cursor);
|
|
5
|
+
return { value, cursor: state.cursor + text.length };
|
|
6
|
+
}
|
|
7
|
+
export function backspace(state) {
|
|
8
|
+
if (state.cursor === 0) {
|
|
9
|
+
return state;
|
|
10
|
+
}
|
|
11
|
+
return { value: state.value.slice(0, state.cursor - 1) + state.value.slice(state.cursor), cursor: state.cursor - 1 };
|
|
12
|
+
}
|
|
13
|
+
export function deleteForward(state) {
|
|
14
|
+
if (state.cursor >= state.value.length) {
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
return { value: state.value.slice(0, state.cursor) + state.value.slice(state.cursor + 1), cursor: state.cursor };
|
|
18
|
+
}
|
|
19
|
+
function isWordChar(char) {
|
|
20
|
+
return /[\p{L}\p{N}_]/u.test(char);
|
|
21
|
+
}
|
|
22
|
+
function wordStart(value, cursor) {
|
|
23
|
+
let i = cursor;
|
|
24
|
+
while (i > 0 && !isWordChar(value[i - 1])) {
|
|
25
|
+
i--;
|
|
26
|
+
}
|
|
27
|
+
while (i > 0 && isWordChar(value[i - 1])) {
|
|
28
|
+
i--;
|
|
29
|
+
}
|
|
30
|
+
return i;
|
|
31
|
+
}
|
|
32
|
+
function wordEnd(value, cursor) {
|
|
33
|
+
let i = cursor;
|
|
34
|
+
while (i < value.length && !isWordChar(value[i])) {
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
while (i < value.length && isWordChar(value[i])) {
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
return i;
|
|
41
|
+
}
|
|
42
|
+
export function moveLeft(state, word = false) {
|
|
43
|
+
if (word) {
|
|
44
|
+
return { ...state, cursor: wordStart(state.value, state.cursor) };
|
|
45
|
+
}
|
|
46
|
+
return { ...state, cursor: Math.max(0, state.cursor - 1) };
|
|
47
|
+
}
|
|
48
|
+
export function moveRight(state, word = false) {
|
|
49
|
+
if (word) {
|
|
50
|
+
return { ...state, cursor: wordEnd(state.value, state.cursor) };
|
|
51
|
+
}
|
|
52
|
+
return { ...state, cursor: Math.min(state.value.length, state.cursor + 1) };
|
|
53
|
+
}
|
|
54
|
+
export function deleteWordBack(state) {
|
|
55
|
+
const start = wordStart(state.value, state.cursor);
|
|
56
|
+
return { value: state.value.slice(0, start) + state.value.slice(state.cursor), cursor: start };
|
|
57
|
+
}
|
|
58
|
+
function lineBounds(value, cursor) {
|
|
59
|
+
const start = value.lastIndexOf("\n", cursor - 1) + 1;
|
|
60
|
+
let end = value.indexOf("\n", cursor);
|
|
61
|
+
if (end < 0) {
|
|
62
|
+
end = value.length;
|
|
63
|
+
}
|
|
64
|
+
return { start, end };
|
|
65
|
+
}
|
|
66
|
+
export function lineStart(state) {
|
|
67
|
+
return { ...state, cursor: lineBounds(state.value, state.cursor).start };
|
|
68
|
+
}
|
|
69
|
+
export function lineEnd(state) {
|
|
70
|
+
return { ...state, cursor: lineBounds(state.value, state.cursor).end };
|
|
71
|
+
}
|
|
72
|
+
export function deleteToLineStart(state) {
|
|
73
|
+
const { start } = lineBounds(state.value, state.cursor);
|
|
74
|
+
return { value: state.value.slice(0, start) + state.value.slice(state.cursor), cursor: start };
|
|
75
|
+
}
|
|
76
|
+
export function deleteToLineEnd(state) {
|
|
77
|
+
const { end } = lineBounds(state.value, state.cursor);
|
|
78
|
+
return { value: state.value.slice(0, state.cursor) + state.value.slice(end), cursor: state.cursor };
|
|
79
|
+
}
|
|
80
|
+
export function cursorPosition(state) {
|
|
81
|
+
const before = state.value.slice(0, state.cursor);
|
|
82
|
+
const line = before.split("\n").length - 1;
|
|
83
|
+
const column = before.length - (before.lastIndexOf("\n") + 1);
|
|
84
|
+
return { line, column, lines: state.value.split("\n") };
|
|
85
|
+
}
|
|
86
|
+
// Sube o baja una línea; devuelve null si ya está en la primera/última (entonces se usa el historial)
|
|
87
|
+
export function moveVertical(state, direction) {
|
|
88
|
+
const { line, column, lines } = cursorPosition(state);
|
|
89
|
+
const target = line + direction;
|
|
90
|
+
if (target < 0 || target >= lines.length) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
let offset = 0;
|
|
94
|
+
for (let i = 0; i < target; i++) {
|
|
95
|
+
offset += lines[i].length + 1;
|
|
96
|
+
}
|
|
97
|
+
return { ...state, cursor: offset + Math.min(column, lines[target].length) };
|
|
98
|
+
}
|
|
99
|
+
// La palabra @… que se está escribiendo justo antes del cursor
|
|
100
|
+
export function mentionAtCursor(state) {
|
|
101
|
+
const before = state.value.slice(0, state.cursor);
|
|
102
|
+
const match = /(^|\s)@([^\s@]*)$/.exec(before);
|
|
103
|
+
if (!match) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return { start: before.length - match[2].length - 1, text: match[2] };
|
|
107
|
+
}
|
|
108
|
+
// Algunas terminales mandan "texto + Enter" en un solo bloque (o Enter como \n): separa el texto del Enter final
|
|
109
|
+
export function splitEnter(input, isReturn) {
|
|
110
|
+
if (isReturn) {
|
|
111
|
+
return { text: "", enter: true };
|
|
112
|
+
}
|
|
113
|
+
if (/[\r\n]$/.test(input) && !/[\r\n]/.test(input.slice(0, -1))) {
|
|
114
|
+
return { text: input.slice(0, -1), enter: true };
|
|
115
|
+
}
|
|
116
|
+
return { text: input, enter: false };
|
|
117
|
+
}
|
|
118
|
+
export function replaceRange(state, start, end, text) {
|
|
119
|
+
return { value: state.value.slice(0, start) + text + state.value.slice(end), cursor: start + text.length };
|
|
120
|
+
}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { suggestPattern } from "./permissions.js";
|
|
3
|
+
import { showControl, stripControl, stripUnsafe } from "./sanitize.js";
|
|
4
|
+
import { formatTodos } from "./tools.js";
|
|
5
|
+
export const ui = {
|
|
6
|
+
text(chunk) {
|
|
7
|
+
process.stdout.write(chunk);
|
|
8
|
+
},
|
|
9
|
+
// Los mensajes pueden llevar texto de los proveedores o del modelo: sin secuencias de la terminal
|
|
10
|
+
info(message) {
|
|
11
|
+
console.log(pc.dim(stripUnsafe(message)));
|
|
12
|
+
},
|
|
13
|
+
notice(message) {
|
|
14
|
+
console.log(pc.yellow(`⚠ ${stripUnsafe(message)}`));
|
|
15
|
+
},
|
|
16
|
+
error(message) {
|
|
17
|
+
console.log(pc.red(`✖ ${stripUnsafe(message)}`));
|
|
18
|
+
},
|
|
19
|
+
tool(name, summary, depth = 0) {
|
|
20
|
+
const indent = depth > 0 ? " " : " ";
|
|
21
|
+
console.log(pc.cyan(`${indent}● ${showControl(name)}`) + pc.dim(` ${showControl(summary)}`));
|
|
22
|
+
},
|
|
23
|
+
banner(agent, slots) {
|
|
24
|
+
console.log(pc.bold(pc.magenta("vipcode")) + pc.dim(` · agente ${agent} · ${slots} modelos · /help para ayuda`));
|
|
25
|
+
},
|
|
26
|
+
prompt(agent) {
|
|
27
|
+
if (agent === "plan") {
|
|
28
|
+
return pc.blue("plan ❯ ");
|
|
29
|
+
}
|
|
30
|
+
if (agent !== "build") {
|
|
31
|
+
return pc.yellow(`${agent} ❯ `);
|
|
32
|
+
}
|
|
33
|
+
return pc.magenta("❯ ");
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
// Líneas quitadas en rojo, añadidas en verde, cabeceras @@ en cian y el contexto atenuado.
|
|
37
|
+
// Es lo que apruebas: los caracteres de control se ven (␛) en vez de actuar
|
|
38
|
+
export function colorPreview(preview) {
|
|
39
|
+
return showControl(preview)
|
|
40
|
+
.split("\n")
|
|
41
|
+
.map((line) => {
|
|
42
|
+
if (line.startsWith("+ ")) {
|
|
43
|
+
return pc.green(line);
|
|
44
|
+
}
|
|
45
|
+
if (line.startsWith("- ")) {
|
|
46
|
+
return pc.red(line);
|
|
47
|
+
}
|
|
48
|
+
if (line.startsWith("@@")) {
|
|
49
|
+
return pc.cyan(line);
|
|
50
|
+
}
|
|
51
|
+
if (line.startsWith(" ")) {
|
|
52
|
+
return pc.dim(line);
|
|
53
|
+
}
|
|
54
|
+
return line;
|
|
55
|
+
})
|
|
56
|
+
.join("\n");
|
|
57
|
+
}
|
|
58
|
+
function indent(text, prefix) {
|
|
59
|
+
return text
|
|
60
|
+
.split("\n")
|
|
61
|
+
.map((line) => prefix + line)
|
|
62
|
+
.join("\n");
|
|
63
|
+
}
|
|
64
|
+
// Eventos del agente pintados como texto normal
|
|
65
|
+
export function plainEvents(options) {
|
|
66
|
+
let reasoning = "";
|
|
67
|
+
let reasoningShown = false;
|
|
68
|
+
const flushReasoning = () => {
|
|
69
|
+
if (reasoningShown) {
|
|
70
|
+
process.stdout.write("\n");
|
|
71
|
+
}
|
|
72
|
+
reasoning = "";
|
|
73
|
+
reasoningShown = false;
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
onText: (chunk, depth) => {
|
|
77
|
+
if (depth > 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
flushReasoning();
|
|
81
|
+
process.stdout.write(stripControl(chunk));
|
|
82
|
+
},
|
|
83
|
+
onTextEnd: (depth) => {
|
|
84
|
+
if (depth === 0) {
|
|
85
|
+
process.stdout.write("\n");
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
onReasoning: (chunk, depth) => {
|
|
89
|
+
if (!options.showReasoning || depth > 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Solo el principio del razonamiento, en una línea atenuada
|
|
93
|
+
if (reasoning.length < 240) {
|
|
94
|
+
if (!reasoningShown) {
|
|
95
|
+
process.stdout.write(pc.dim(" 💭 "));
|
|
96
|
+
reasoningShown = true;
|
|
97
|
+
}
|
|
98
|
+
const piece = stripControl(chunk).replace(/\s+/g, " ").slice(0, 240 - reasoning.length);
|
|
99
|
+
reasoning += piece;
|
|
100
|
+
process.stdout.write(pc.dim(piece));
|
|
101
|
+
if (reasoning.length >= 240) {
|
|
102
|
+
process.stdout.write(pc.dim("…"));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
onToolStart: (event) => {
|
|
107
|
+
flushReasoning();
|
|
108
|
+
ui.tool(event.name, event.summary, event.depth);
|
|
109
|
+
},
|
|
110
|
+
onToolEnd: (event) => {
|
|
111
|
+
const pad = event.depth > 0 ? " " : " ";
|
|
112
|
+
if (event.isError) {
|
|
113
|
+
console.log(pc.red(`${pad}${stripControl(event.output.split("\n")[0]).slice(0, 200)}`));
|
|
114
|
+
}
|
|
115
|
+
else if (event.display && event.depth === 0) {
|
|
116
|
+
console.log(indent(colorPreview(event.display), pad));
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
onNotice: (message) => {
|
|
120
|
+
flushReasoning();
|
|
121
|
+
ui.notice(message);
|
|
122
|
+
},
|
|
123
|
+
onInfo: (message) => ui.info(message),
|
|
124
|
+
onTodos: (todos) => console.log(pc.dim(indent(formatTodos(todos), " "))),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Pregunta s/n/t (y p para comandos) antes de escribir ficheros o ejecutar comandos
|
|
128
|
+
export function plainPermissionPrompt(getReadline, grants) {
|
|
129
|
+
return async (request) => {
|
|
130
|
+
const rl = getReadline();
|
|
131
|
+
if (!rl || !process.stdin.isTTY) {
|
|
132
|
+
return { allow: false, feedback: "no hay nadie para aprobarlo (usa --yolo o ajusta los permisos)" };
|
|
133
|
+
}
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(pc.bold(pc.yellow(`? ${showControl(request.title)}`)));
|
|
136
|
+
console.log(colorPreview(request.preview));
|
|
137
|
+
let options = "[s]í / [n]o / [t]odo en esta sesión / [e] no y explicar";
|
|
138
|
+
let pattern = "";
|
|
139
|
+
if (request.kind === "bash") {
|
|
140
|
+
pattern = suggestPattern(request.subject);
|
|
141
|
+
options = `[s]í / [n]o / [t]odo / [p] siempre «${pattern}» / [e] no y explicar`;
|
|
142
|
+
}
|
|
143
|
+
while (true) {
|
|
144
|
+
let answer = "";
|
|
145
|
+
try {
|
|
146
|
+
answer = (await rl.question(pc.yellow(` ¿Permitir? ${options}: `))).trim().toLowerCase();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return { allow: false, feedback: "sin respuesta" };
|
|
150
|
+
}
|
|
151
|
+
if (["s", "si", "sí", "y", "yes"].includes(answer)) {
|
|
152
|
+
return { allow: true };
|
|
153
|
+
}
|
|
154
|
+
if (answer === "n" || answer === "no") {
|
|
155
|
+
return { allow: false };
|
|
156
|
+
}
|
|
157
|
+
if (answer === "t") {
|
|
158
|
+
grants().allowKind(request.kind);
|
|
159
|
+
return { allow: true };
|
|
160
|
+
}
|
|
161
|
+
if (answer === "p" && pattern) {
|
|
162
|
+
grants().allowPattern("bash", pattern);
|
|
163
|
+
return { allow: true };
|
|
164
|
+
}
|
|
165
|
+
if (answer === "e") {
|
|
166
|
+
let why = "";
|
|
167
|
+
try {
|
|
168
|
+
why = (await rl.question(pc.yellow(" ¿Qué debería hacer en su lugar?: "))).trim();
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
why = "";
|
|
172
|
+
}
|
|
173
|
+
return { allow: false, feedback: why || undefined };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// Interfaz mínima para los comandos / en el modo de texto
|
|
179
|
+
export const plainAdapter = {
|
|
180
|
+
info: (text) => ui.info(text),
|
|
181
|
+
notice: (text) => ui.notice(text),
|
|
182
|
+
error: (text) => ui.error(text),
|
|
183
|
+
block: (text) => console.log(stripUnsafe(text)),
|
|
184
|
+
};
|