vipcode 0.2.0 → 0.2.2
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 +1 -1
- package/README.en.md +7 -1
- package/README.md +37 -9
- package/dist/agent.js +20 -1
- package/dist/agents.js +6 -3
- package/dist/app.js +27 -15
- package/dist/auth.js +2 -2
- package/dist/commands.js +4 -2
- package/dist/config.js +10 -5
- package/dist/debug.js +2 -2
- package/dist/discovery.js +2 -2
- package/dist/index.js +56 -3
- package/dist/labels.js +20 -0
- package/dist/markdown.js +5 -2
- package/dist/paths.js +26 -0
- package/dist/permissions.js +26 -2
- package/dist/ranking.js +2 -2
- package/dist/sanitize.js +38 -0
- package/dist/session.js +123 -53
- package/dist/skills.js +2 -1
- package/dist/tools.js +72 -8
- package/dist/trust.js +171 -0
- package/dist/tui/app.js +57 -36
- package/dist/tui/dialogs.js +3 -2
- package/dist/ui.js +44 -11
- package/dist/update.js +6 -4
- package/package.json +2 -2
package/LICENSE
CHANGED
package/README.en.md
CHANGED
|
@@ -41,8 +41,14 @@ vipcode skills add anthropics/skills # install skills from GitHub
|
|
|
41
41
|
vipcode update # update vipcode
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Every conversation is saved automatically in `~/.vipcode/sessions/` as `.json` (to resume it) and `.md` (the whole conversation, readable and never truncated by context compaction). When you resume one (`vipcode -c`, `/sessions`), the previous conversation is shown on screen.
|
|
45
|
+
|
|
44
46
|
Inside the UI: Enter sends, Tab switches between the `build` and `plan` agents, Ctrl+P opens the command palette, `@path` attaches files, `!command` runs a shell command, Esc interrupts and `/help` lists everything.
|
|
45
47
|
|
|
48
|
+
## Security
|
|
49
|
+
|
|
50
|
+
vipcode asks before editing files, running commands, going online or using MCP tools; only a few read-only commands (`git status`, `git diff`, `git log`, `git show`, `ls`, `pwd`) are auto-approved, and never when they chain another command, write to a file or use `$(…)`. A project's own vipcode configuration (MCP servers, providers, permissions, agents with permissions, commands that run shell) is ignored until you trust the folder: vipcode shows what it would change and asks (`vipcode trust` / `vipcode untrust`). Tools can't leave the project folder, not even through symlinks; vipcode can't read or change its own data folder (`~/.vipcode`: keys, config, trusted folders) nor modify its own installation, and commands that mention them always ask. Terminal escape sequences from the model or tools are stripped. To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
|
51
|
+
|
|
46
52
|
## Privacy
|
|
47
53
|
|
|
48
54
|
Free plans have fine print: anonymous providers and several free tiers may store what you send or use it for training. The first time vipcode is about to use a provider that needs no key, it tells you and lets you continue, add a key first or never use them (`"keyless": false`). Don't send secrets or confidential code; for that, use a local model with Ollama or LM Studio. vipcode itself sends no telemetry; it only checks npm and GitHub once a day for a new version (`"checkUpdates": false` turns it off).
|
|
@@ -53,4 +59,4 @@ vipcode does not ship LLM Stats data, because their terms don't allow redistribu
|
|
|
53
59
|
|
|
54
60
|
## License
|
|
55
61
|
|
|
56
|
-
[MIT](LICENSE). The full documentation (in Spanish) is in [README.md](README.md).
|
|
62
|
+
[MIT](LICENSE) © Pablo Vicente Juan. The full documentation (in Spanish) is in [README.md](README.md).
|
package/README.md
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
Agente de programación para la terminal (comando `vipcode`), al estilo de opencode, que solo usa **modelos gratuitos** y **rota automáticamente** entre proveedores cuando uno se agota.
|
|
6
6
|
|
|
7
7
|
- **Funciona nada más instalarlo, sin claves ni registro**: usa los proveedores que permiten el acceso anónimo (Kilo Gateway, OVHcloud, LLM7 y Pollinations).
|
|
8
|
-
- Si quieres más límites y mejores modelos, añade claves gratuitas de los más de 20 proveedores incluidos (Gemini, NVIDIA, Mistral, Cerebras, OpenRouter, Groq, Z.ai…). Los modelos gratis nuevos se descubren solos.
|
|
8
|
+
- Si quieres más límites y mejores modelos, añade claves gratuitas de los más de 20 proveedores incluidos (Gemini, NVIDIA, Mistral, Cerebras, OpenRouter, Groq, Z.ai…); con OpenRouter basta con iniciar sesión en el navegador. Los modelos gratis nuevos se descubren solos.
|
|
9
9
|
- Con una clave gratis de [LLM Stats](https://llm-stats.com), **usa primero los mejores modelos** según su ranking.
|
|
10
|
-
- Interfaz en la terminal con Markdown y código coloreado, diffs, diálogos de permiso, autocompletado de `@ficheros` y `/comandos`, historial y barra de estado.
|
|
11
|
-
- Herramientas: leer, escribir y editar ficheros, `list_dir`, `glob`, `grep`, `bash`, `webfetch`, `websearch`, lista de tareas, subagentes, diagnósticos LSP
|
|
12
|
-
- `/undo` y `/redo` con instantáneas de git (deshacen también lo que haya cambiado `bash`), sesiones guardadas, resumen automático del contexto, imágenes, agentes y comandos propios.
|
|
10
|
+
- Interfaz en la terminal con Markdown y código coloreado, diffs, diálogos de permiso, autocompletado de `@ficheros` y `/comandos`, paleta de comandos (Ctrl+P), `!comandos`, historial y barra de estado.
|
|
11
|
+
- Herramientas: leer, escribir y editar ficheros, `list_dir`, `glob`, `grep`, `bash`, `webfetch`, `websearch`, lista de tareas, subagentes, diagnósticos LSP, herramientas MCP y [skills](#skills).
|
|
12
|
+
- `/undo` y `/redo` con instantáneas de git (deshacen también lo que haya cambiado `bash`), sesiones guardadas, resumen automático del contexto, imágenes, agentes y comandos propios, y `/update` para actualizarlo.
|
|
13
|
+
- **Seguro por defecto**: pregunta antes de editar, ejecutar o salir a internet, no sale de la carpeta del proyecto y no usa la configuración que traiga un proyecto hasta que confías en él (ver [Seguridad](#seguridad)).
|
|
13
14
|
|
|
14
15
|
## Instalación
|
|
15
16
|
|
|
@@ -107,6 +108,7 @@ vipcode run "tarea" --yolo # hace la tarea sin interfaz y termina
|
|
|
107
108
|
vipcode --plan # empieza en modo solo lectura
|
|
108
109
|
vipcode run "/review" # también acepta comandos y skills: vipcode run "/mi-skill tarea"
|
|
109
110
|
vipcode update # actualiza vipcode
|
|
111
|
+
vipcode trust # confiar en la configuración que trae esta carpeta (vipcode untrust lo deshace)
|
|
110
112
|
```
|
|
111
113
|
|
|
112
114
|
### Actualizar
|
|
@@ -132,6 +134,15 @@ Mientras trabaja ves qué modelo está pensando, cada herramienta con su acción
|
|
|
132
134
|
|
|
133
135
|
Antes de editar un fichero, ejecutar un comando o salir a internet, vipcode pregunta enseñando el diff o el comando. Puedes contestar que sí una vez, que sí a todo lo de ese tipo durante la sesión, que sí siempre a un comando concreto (por ejemplo `npm test*`), que no, o que no y explicarle qué hacer.
|
|
134
136
|
|
|
137
|
+
### Sesiones
|
|
138
|
+
|
|
139
|
+
Cada conversación se guarda sola en `~/.vipcode/sessions/`, en dos ficheros:
|
|
140
|
+
|
|
141
|
+
- **`.json`**, para poder seguirla.
|
|
142
|
+
- **`.md`**, con la conversación entera para leerla o compartirla: lo que escribiste, cada respuesta con el modelo que contestó, las herramientas que usó y los cambios que hizo en cada fichero. Se actualiza solo mientras hablas y no pierde nada aunque la conversación se resuma para que quepa en el modelo.
|
|
143
|
+
|
|
144
|
+
Para volver a una conversación: `vipcode -c` (la última de esta carpeta), `/sessions` (eliges de la lista) o `vipcode -s <id>`. Al volver ves en pantalla toda la conversación anterior, y el modelo tiene su contexto para seguir donde lo dejasteis. `/export` guarda además una copia en `~/.vipcode/exports/`.
|
|
145
|
+
|
|
135
146
|
### Comandos
|
|
136
147
|
|
|
137
148
|
| Comando | Qué hace |
|
|
@@ -198,7 +209,7 @@ Una vez al día (o con `/models refresh`, o `vipcode models refresh` desde la te
|
|
|
198
209
|
## Configuración
|
|
199
210
|
|
|
200
211
|
- Global: `~/.vipcode/config.json` (se crea la primera vez).
|
|
201
|
-
- Por proyecto: `vipcode.json` o `.vipcode/config.json` en la carpeta del proyecto (se mezcla con la global).
|
|
212
|
+
- Por proyecto: `vipcode.json` o `.vipcode/config.json` en la carpeta del proyecto (se mezcla con la global). Solo se usa si confías en la carpeta: ver [Seguridad](#seguridad).
|
|
202
213
|
|
|
203
214
|
```json
|
|
204
215
|
{
|
|
@@ -250,7 +261,7 @@ Una vez al día (o con `/models refresh`, o `vipcode models refresh` desde la te
|
|
|
250
261
|
- **ranking**: `enabled: false` vuelve al orden del catálogo; `metric: "general"` ordena por el índice general de LLM Stats en vez de por programación; `anonymousLast: true` pone los anónimos (sin clave) detrás de los que tienen clave aunque tengan mejor nota.
|
|
251
262
|
- **providers**: si el nombre coincide con uno del catálogo, solo cambia lo que pongas (los modelos se añaden o se sobrescriben por id; con `"replaceModels": true` se usan solo los tuyos). Si es nuevo, es un proveedor propio compatible con la API de OpenAI y va el primero en la rotación. Admite `apiKeyEnv`, `apiKey`, `headers`, `noKey`, `discover: "openai"` para descubrir sus modelos, y en cada modelo `context`, `vision` y `disabled`.
|
|
252
263
|
- **order**: patrones `proveedor/modelo` que van primero. **model**: modelo fijado al arrancar.
|
|
253
|
-
- **permission**: `allow`, `ask` o `deny`. En `bash` y `mcp` gana el patrón más concreto, y los comandos encadenados (`&&`, `|`,
|
|
264
|
+
- **permission**: `allow`, `ask` o `deny`. En `bash` y `mcp` gana el patrón más concreto, y los comandos encadenados (`&&`, `&`, `|`, `;`, saltos de línea) se comprueban por separado. Aunque un comando esté permitido, se pregunta siempre si escribe en un fichero (`>`, `--output`) o lleva `$(…)` o comillas invertidas. Las reglas `deny` se respetan incluso con `--yolo`.
|
|
254
265
|
- **mcp**: servidores locales (`command`) o remotos (`url`, con HTTP o SSE). Sus herramientas aparecen como `servidor_herramienta`. En modo plan solo se usan las marcadas como de solo lectura.
|
|
255
266
|
- **lsp**: `false` desactiva los diagnósticos; `"id": false` desactiva un servidor concreto.
|
|
256
267
|
|
|
@@ -287,6 +298,8 @@ Arréglalos. Céntrate en $ARGUMENTS.
|
|
|
287
298
|
|
|
288
299
|
`$ARGUMENTS` es todo lo que escribes detrás del comando, `$1`…`$9` cada palabra (respetando comillas), y `` !`comando` `` se sustituye por su salida. Con `subtask: true` el comando se ejecuta como subagente y no llena la conversación.
|
|
289
300
|
|
|
301
|
+
Los agentes y comandos que traiga un proyecto se cargan siempre, pero hasta que confías en la carpeta un agente del proyecto no puede cambiar sus permisos (`permission`, `readOnly`) y los `` !`comando` `` de sus comandos no se ejecutan.
|
|
302
|
+
|
|
290
303
|
### Skills
|
|
291
304
|
|
|
292
305
|
Una skill es una carpeta con un `SKILL.md`: instrucciones (y, si hace falta, scripts y documentos) para un tipo de tarea concreto, como rellenar PDF, seguir la guía de estilo de la empresa o preparar un informe. Es el formato abierto [Agent Skills](https://agentskills.io), el mismo que usan Claude Code y opencode, así que las que encuentres para ellos también sirven aquí.
|
|
@@ -335,6 +348,19 @@ vipcode lee `AGENTS.md` (o `CLAUDE.md`, o `.vipcode.md`) de la raíz del proyect
|
|
|
335
348
|
|
|
336
349
|
Después de cada edición, vipcode pregunta al servidor de lenguaje si hay errores y se los pasa al modelo. Para TypeScript y JavaScript no hace falta instalar nada: usa el servidor que trae TypeScript 7. Para otros lenguajes usa el servidor si lo tienes instalado: pyright o basedpyright, pylsp, gopls, rust-analyzer, clangd, intelephense o phpactor, ruby-lsp, bash-language-server, yaml-language-server, los de VS Code para CSS, HTML y JSON, lua-language-server, zls, dart, csharp-ls, sourcekit-lsp, kotlin-language-server y svelte.
|
|
337
350
|
|
|
351
|
+
## Seguridad
|
|
352
|
+
|
|
353
|
+
vipcode ejecuta lo que le pide un modelo de IA, así que está hecho para que nada importante pase sin que lo veas:
|
|
354
|
+
|
|
355
|
+
- **Permisos.** Antes de editar un fichero, ejecutar un comando, salir a internet o usar una herramienta MCP, pregunta enseñando el diff o el comando. De serie solo se aprueban solos unos pocos comandos que no cambian nada (`git status`, `git diff`, `git log`, `git show`, `ls`, `pwd`), y ni siquiera esos si llevan otro comando detrás (`&`, `;`, `&&`, `|`), escriben en un fichero (`>`, `--output`) o llevan `$(…)`. Con `--yolo` o `/yolo` se aprueba todo menos lo que tengas en `deny`: úsalo solo en carpetas sin nada que perder.
|
|
356
|
+
- **Carpetas en las que confías.** Un proyecto puede traer su propia configuración de vipcode (`vipcode.json`, `.vipcode/config.json`, agentes con permisos propios, comandos con `` !`órdenes` ``). Como eso podría arrancar programas, mandar tus claves a otro servidor o quitar las preguntas de permiso, vipcode no lo usa hasta que confías en la carpeta. Al abrirlo te enseña qué cambiaría y te pregunta; con `vipcode run` solo avisa. Si la configuración cambia después (por ejemplo tras un `git pull`), vuelve a preguntar. `vipcode trust` y `vipcode untrust` lo cambian a mano.
|
|
357
|
+
- **Ficheros.** Las herramientas solo leen y escriben dentro de la carpeta del proyecto. Un enlace simbólico que lleve fuera (por ejemplo a `~/.ssh` o a tus claves) se rechaza.
|
|
358
|
+
- **vipcode no puede tocarse a sí mismo.** Aunque lo abras en una carpeta que las contenga (por ejemplo tu carpeta personal), el agente no puede leer ni cambiar `~/.vipcode` (tus claves, tu configuración y las carpetas en las que confías) ni modificar la carpeta donde está instalado vipcode, así que tampoco puede desactivar estas protecciones. Los comandos que nombran esas carpetas se preguntan siempre, aunque tengas una regla `allow` o hayas dicho «sí a todo», y con `--yolo` sin nadie delante se rechazan.
|
|
359
|
+
- **Terminal.** Lo que escriben el modelo o las herramientas no puede manejar tu terminal (borrar líneas, cambiar el portapapeles, falsear enlaces). En los diálogos de permiso esos caracteres se ven como `␛` en vez de actuar, para que nadie pueda disfrazar un comando.
|
|
360
|
+
- **Tus datos.** `~/.vipcode` solo lo puede abrir tu usuario y `auth.json` tiene permisos 600. vipcode no envía datos de uso a nadie.
|
|
361
|
+
|
|
362
|
+
Si encuentras un fallo de seguridad, avísalo en privado como explica [SECURITY.md](SECURITY.md).
|
|
363
|
+
|
|
338
364
|
## Privacidad
|
|
339
365
|
|
|
340
366
|
La primera vez que vipcode va a usar un proveedor que no pide clave, te lo avisa y te deja elegir entre seguir, añadir antes una clave o no usarlos nunca.
|
|
@@ -345,8 +371,8 @@ Los planes gratuitos tienen letra pequeña: los proveedores anónimos (Kilo, OVH
|
|
|
345
371
|
|
|
346
372
|
Todo está en `~/.vipcode` (o en `VIPCODE_HOME` si la defines):
|
|
347
373
|
|
|
348
|
-
- `config.json`: tu configuración. `auth.json`: claves (permisos 600). `state.json`: pausas de los proveedores. `notices.json`: avisos que ya has visto. `update.json`: última comprobación de versiones nuevas.
|
|
349
|
-
- `models.json`: modelos descubiertos. `ranking.json`: puntuaciones de LLM Stats descargadas con tu clave. `sessions/`: conversaciones. `history.jsonl`: lo que has escrito.
|
|
374
|
+
- `config.json`: tu configuración. `auth.json`: claves (permisos 600). `state.json`: pausas de los proveedores. `notices.json`: avisos que ya has visto. `update.json`: última comprobación de versiones nuevas. `trusted.json`: carpetas en las que confías.
|
|
375
|
+
- `models.json`: modelos descubiertos. `ranking.json`: puntuaciones de LLM Stats descargadas con tu clave. `sessions/`: conversaciones (un `.json` para seguirlas y un `.md` para leerlas). `history.jsonl`: lo que has escrito.
|
|
350
376
|
- `snapshots/`: instantáneas para `/undo` (un repositorio git aparte por proyecto; no toca tu `.git`).
|
|
351
377
|
- `exports/`: conversaciones exportadas. `agents/`, `commands/` y `skills/`: tus agentes, comandos y skills globales.
|
|
352
378
|
|
|
@@ -358,6 +384,8 @@ npm test # compila y pasa las pruebas (servidor de IA simulado, sin c
|
|
|
358
384
|
VIPCODE_DEBUG=1 vipcode # apunta los detalles internos en ~/.vipcode/debug.log
|
|
359
385
|
```
|
|
360
386
|
|
|
387
|
+
vipcode no se deja modificar a sí mismo. Si quieres trabajar en su código con un vipcode instalado desde esa misma carpeta (`npm link`), arráncalo con `VIPCODE_ALLOW_SELF_EDIT=1 vipcode`; o usa el instalado con npm para trabajar en tu copia del código.
|
|
388
|
+
|
|
361
389
|
### Publicar una versión
|
|
362
390
|
|
|
363
391
|
```bash
|
|
@@ -378,6 +406,6 @@ Sube el número en `package.json`, pasa las pruebas, crea el commit y la etiquet
|
|
|
378
406
|
|
|
379
407
|
## Licencia
|
|
380
408
|
|
|
381
|
-
[MIT](LICENSE).
|
|
409
|
+
[MIT](LICENSE) © Pablo Vicente Juan.
|
|
382
410
|
|
|
383
411
|
vipcode no envía datos de uso a nadie. Solo se conecta a los proveedores de IA que uses, a las webs que consulte el agente con `webfetch` y `websearch`, a tus servidores MCP, a npm y a GitHub una vez al día para ver si hay versión nueva (`"checkUpdates": false` lo desactiva) y, si le das tu clave, a LLM Stats para descargar el ranking (datos de LLM Stats, llm-stats.com).
|
package/dist/agent.js
CHANGED
|
@@ -94,6 +94,10 @@ export class Agent {
|
|
|
94
94
|
addNote(text) {
|
|
95
95
|
this.pendingNote += text;
|
|
96
96
|
}
|
|
97
|
+
// Apunta algo en el historial de la sesión (lo que se enseña al volver y se guarda en el .md)
|
|
98
|
+
logEntry(entry) {
|
|
99
|
+
this.session.log.push({ ...entry, time: Date.now() });
|
|
100
|
+
}
|
|
97
101
|
toolContext(agent, signal, depth) {
|
|
98
102
|
const lsp = this.deps.lsp;
|
|
99
103
|
const ctx = {
|
|
@@ -195,6 +199,7 @@ export class Agent {
|
|
|
195
199
|
{ role: "assistant", content: "Entendido, sigo desde aquí." },
|
|
196
200
|
];
|
|
197
201
|
this.session.epoch++;
|
|
202
|
+
this.logEntry({ role: "info", text: "Conversación resumida para ahorrar contexto: el modelo sigue con un resumen, pero aquí sigue todo lo anterior." });
|
|
198
203
|
saveSession(this.session);
|
|
199
204
|
this.events.onInfo?.(`Conversación resumida (~${estimateTokens(this.session.messages)} tokens).`);
|
|
200
205
|
}
|
|
@@ -274,6 +279,13 @@ export class Agent {
|
|
|
274
279
|
this.events.onToolStart?.(event);
|
|
275
280
|
const result = await runTool(tools, name, args, { ...ctx, onProgress: (text) => this.events.onToolProgress?.(event, text) }, agent.readOnly);
|
|
276
281
|
this.events.onToolEnd?.({ ...event, output: result.output, display: result.display, isError: Boolean(result.isError) });
|
|
282
|
+
if (depth === 0) {
|
|
283
|
+
this.logEntry({
|
|
284
|
+
role: "tool",
|
|
285
|
+
text: truncate(result.output, 4000),
|
|
286
|
+
tool: { name, summary: event.summary, isError: Boolean(result.isError), display: result.display ? truncate(result.display, 4000) : undefined },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
277
289
|
if (result.images) {
|
|
278
290
|
images.push(...result.images);
|
|
279
291
|
}
|
|
@@ -332,11 +344,12 @@ export class Agent {
|
|
|
332
344
|
await this.ensureRoom(tools, signal);
|
|
333
345
|
const before = this.deps.snapshots ? await this.deps.snapshots.track() : null;
|
|
334
346
|
beginTurn();
|
|
335
|
-
turn = { messageIndex: this.session.messages.length, before, after: null, epoch: this.session.epoch, userText: input.text };
|
|
347
|
+
turn = { messageIndex: this.session.messages.length, before, after: null, epoch: this.session.epoch, userText: input.display ?? input.text };
|
|
336
348
|
this.session.turns.push(turn);
|
|
337
349
|
this.session.redo = [];
|
|
338
350
|
this.session.agent = this.agent.name;
|
|
339
351
|
this.session.messages.push(this.buildUserMessage(input));
|
|
352
|
+
this.logEntry({ role: "user", text: input.display ?? input.text, images: input.images?.length || undefined });
|
|
340
353
|
saveSession(this.session);
|
|
341
354
|
await this.loop(tools, signal);
|
|
342
355
|
}
|
|
@@ -361,6 +374,9 @@ export class Agent {
|
|
|
361
374
|
const result = await this.step(this.fullMessages(tools), tools, agent, signal, 0);
|
|
362
375
|
const message = result.message;
|
|
363
376
|
this.session.messages.push(message);
|
|
377
|
+
if (typeof message.content === "string" && message.content.trim() !== "") {
|
|
378
|
+
this.logEntry({ role: "assistant", text: message.content, model: result.slot.key });
|
|
379
|
+
}
|
|
364
380
|
saveSession(this.session);
|
|
365
381
|
if (!message.tool_calls || message.tool_calls.length === 0) {
|
|
366
382
|
if (this.events.onAnswer) {
|
|
@@ -387,6 +403,7 @@ export class Agent {
|
|
|
387
403
|
if (this.queue.length > 0) {
|
|
388
404
|
for (const queued of this.queue.splice(0)) {
|
|
389
405
|
this.session.messages.push(this.buildUserMessage({ text: `[Mensaje nuevo del usuario mientras trabajabas] ${queued.text}`, images: queued.images }));
|
|
406
|
+
this.logEntry({ role: "user", text: queued.text, images: queued.images?.length || undefined });
|
|
390
407
|
}
|
|
391
408
|
this.events.onInfo?.("(mensaje entregado al agente)");
|
|
392
409
|
saveSession(this.session);
|
|
@@ -464,6 +481,7 @@ export class Agent {
|
|
|
464
481
|
}
|
|
465
482
|
this.session.redo.push({ turn, messages: removed });
|
|
466
483
|
this.readTimes.clear();
|
|
484
|
+
this.logEntry({ role: "info", text: `Deshecho: «${turn.userText}»${files.length > 0 ? ` (ficheros restaurados: ${files.join(", ")})` : ""}` });
|
|
467
485
|
saveSession(this.session);
|
|
468
486
|
return { files, text: turn.userText, restoredMessages };
|
|
469
487
|
}
|
|
@@ -481,6 +499,7 @@ export class Agent {
|
|
|
481
499
|
}
|
|
482
500
|
this.session.turns.push(entry.turn);
|
|
483
501
|
this.readTimes.clear();
|
|
502
|
+
this.logEntry({ role: "info", text: `Rehecho: «${entry.turn.userText}»` });
|
|
484
503
|
saveSession(this.session);
|
|
485
504
|
return { files };
|
|
486
505
|
}
|
package/dist/agents.js
CHANGED
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { readMarkdownDir } from "./frontmatter.js";
|
|
3
3
|
import { wildcardMatch } from "./match.js";
|
|
4
4
|
import { GLOBAL_AGENTS_DIR } from "./paths.js";
|
|
5
|
+
import { isProjectFile, projectTrust } from "./trust.js";
|
|
5
6
|
const PLAN_PROMPT = `You are in PLAN mode: you can only read and search (and browse the web). Do NOT try to modify anything.
|
|
6
7
|
Investigate the code and reply with a concrete, step-by-step plan: which files to change, what to change in each one and how to verify it.`;
|
|
7
8
|
const GENERAL_PROMPT = `You are a subagent launched by the main agent to do one task on your own.
|
|
@@ -107,7 +108,7 @@ function fromInput(name, input, base, source) {
|
|
|
107
108
|
};
|
|
108
109
|
}
|
|
109
110
|
// Agentes integrados + ~/.vipcode/agents + .vipcode/agents y .opencode/agent(s) del proyecto + "agents" del config
|
|
110
|
-
export function loadAgents(root, config) {
|
|
111
|
+
export function loadAgents(root, config, trusted = projectTrust(root).trusted) {
|
|
111
112
|
const agents = new Map();
|
|
112
113
|
for (const agent of BUILTIN) {
|
|
113
114
|
agents.set(agent.name, { ...agent, tools: { ...agent.tools } });
|
|
@@ -128,6 +129,8 @@ export function loadAgents(root, config) {
|
|
|
128
129
|
};
|
|
129
130
|
for (const entry of readMarkdownDir(dirs)) {
|
|
130
131
|
const data = entry.data;
|
|
132
|
+
// Un agente del proyecto en el que no confías no puede cambiar permisos ni dejar editar al modo plan
|
|
133
|
+
const own = trusted || !isProjectFile(root, entry.file);
|
|
131
134
|
apply(entry.name, {
|
|
132
135
|
description: data.description,
|
|
133
136
|
mode: data.mode,
|
|
@@ -135,10 +138,10 @@ export function loadAgents(root, config) {
|
|
|
135
138
|
model: data.model,
|
|
136
139
|
temperature: data.temperature,
|
|
137
140
|
tools: data.tools,
|
|
138
|
-
permission: data.permission,
|
|
141
|
+
permission: own ? data.permission : undefined,
|
|
139
142
|
maxSteps: data.maxSteps ?? data.steps,
|
|
140
143
|
color: data.color,
|
|
141
|
-
readOnly: data.readOnly,
|
|
144
|
+
readOnly: own ? data.readOnly : undefined,
|
|
142
145
|
disable: data.disable,
|
|
143
146
|
}, entry.file);
|
|
144
147
|
}
|
package/dist/app.js
CHANGED
|
@@ -6,20 +6,21 @@ import { loadAgents, primaryAgents, subagents } from "./agents.js";
|
|
|
6
6
|
import { envNames, maskKey, readAuth, removeAuth, resolveCredentials, saveAuth, urlVars } from "./auth.js";
|
|
7
7
|
import { CATALOG } from "./catalog.js";
|
|
8
8
|
import { expandCommand, loadCommands } from "./commands.js";
|
|
9
|
-
import { CONFIG_PATH, isAnonymous, loadConfig, setGlobalConfigValue } from "./config.js";
|
|
9
|
+
import { CONFIG_PATH, configNotices, isAnonymous, loadConfig, setGlobalConfigValue } from "./config.js";
|
|
10
10
|
import { buildProviders, detectLocal, refreshModels } from "./discovery.js";
|
|
11
11
|
import { isRemoteSession, loginOpenRouter, openBrowser } from "./login.js";
|
|
12
12
|
import { LspManager } from "./lsp.js";
|
|
13
13
|
import { McpManager } from "./mcp.js";
|
|
14
|
-
import { AUTH_PATH, HOME_DIR, NOTICES_PATH, STATE_PATH } from "./paths.js";
|
|
14
|
+
import { AUTH_PATH, HOME_DIR, NOTICES_PATH, STATE_PATH, ensureHomeDir } from "./paths.js";
|
|
15
15
|
import { SessionGrants, createAsker, permissionsFor } from "./permissions.js";
|
|
16
16
|
import { Ranking, loadRankingData, rankingKey, refreshRanking } from "./ranking.js";
|
|
17
17
|
import { Router } from "./router.js";
|
|
18
18
|
import { loadSkills, skillContent } from "./skills.js";
|
|
19
|
-
import { deleteSession, listSessions, newSession, saveExport, saveSession } from "./session.js";
|
|
19
|
+
import { deleteSession, listSessions, newSession, saveExport, saveSession, sessionMarkdownPath } from "./session.js";
|
|
20
20
|
import { Snapshots } from "./snapshot.js";
|
|
21
21
|
import { updateVipcode } from "./update.js";
|
|
22
22
|
import { formatTodos, truncate } from "./tools.js";
|
|
23
|
+
import { projectTrust } from "./trust.js";
|
|
23
24
|
export const BUILTIN_COMMANDS = [
|
|
24
25
|
{ name: "help", description: "esta ayuda" },
|
|
25
26
|
{ name: "models", args: "[refresh]", description: "estado de cada modelo; elige uno o busca modelos nuevos" },
|
|
@@ -115,6 +116,9 @@ export class App {
|
|
|
115
116
|
agents;
|
|
116
117
|
commands;
|
|
117
118
|
skills;
|
|
119
|
+
// Configuración que trae el proyecto y si la estamos usando (solo si confías en la carpeta)
|
|
120
|
+
trust;
|
|
121
|
+
trusted;
|
|
118
122
|
lsp;
|
|
119
123
|
mcp;
|
|
120
124
|
snapshots;
|
|
@@ -130,15 +134,23 @@ export class App {
|
|
|
130
134
|
});
|
|
131
135
|
constructor(root, options = {}) {
|
|
132
136
|
this.root = root;
|
|
133
|
-
this.
|
|
137
|
+
this.trust = projectTrust(root);
|
|
138
|
+
this.trusted = options.trusted ?? this.trust.trusted;
|
|
139
|
+
if (!this.trusted) {
|
|
140
|
+
const notice = `Esta carpeta trae configuración de vipcode que no estoy usando (${this.trust.files.join(", ")}): puede arrancar programas o cambiar a dónde van tus claves. Si confías en ella, ejecuta «vipcode trust».`;
|
|
141
|
+
if (!configNotices.includes(notice)) {
|
|
142
|
+
configNotices.push(notice);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
this.config = loadConfig(root, { trusted: this.trusted });
|
|
134
146
|
this.providers = buildProviders(this.config);
|
|
135
147
|
this.ranking = this.buildRanking();
|
|
136
148
|
this.router = new Router(this.config, this.providers, this.ranking);
|
|
137
149
|
if (this.config.model) {
|
|
138
150
|
this.router.pinned = this.config.model;
|
|
139
151
|
}
|
|
140
|
-
this.agents = loadAgents(root, this.config);
|
|
141
|
-
this.commands = loadCommands(root, this.config);
|
|
152
|
+
this.agents = loadAgents(root, this.config, this.trusted);
|
|
153
|
+
this.commands = loadCommands(root, this.config, this.trusted);
|
|
142
154
|
this.skills = loadSkills(root, this.config);
|
|
143
155
|
this.lsp = new LspManager(root, this.config);
|
|
144
156
|
if (!this.lsp.enabled()) {
|
|
@@ -205,7 +217,7 @@ export class App {
|
|
|
205
217
|
// primera vez
|
|
206
218
|
}
|
|
207
219
|
data.privacy = new Date().toISOString();
|
|
208
|
-
|
|
220
|
+
ensureHomeDir();
|
|
209
221
|
fs.writeFileSync(NOTICES_PATH, JSON.stringify(data, null, 2));
|
|
210
222
|
}
|
|
211
223
|
// «No usar nunca los que no piden clave»: se guarda en tu config global
|
|
@@ -807,7 +819,7 @@ export class App {
|
|
|
807
819
|
})));
|
|
808
820
|
if (choice) {
|
|
809
821
|
this.loadSession(choice);
|
|
810
|
-
|
|
822
|
+
return { loaded: true };
|
|
811
823
|
}
|
|
812
824
|
return {};
|
|
813
825
|
}
|
|
@@ -820,11 +832,9 @@ export class App {
|
|
|
820
832
|
const chosen = this.lastSessions[Number(arg) - 1];
|
|
821
833
|
if (chosen) {
|
|
822
834
|
this.loadSession(chosen);
|
|
823
|
-
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
ui.error("Número no válido. Usa /sessions primero.");
|
|
835
|
+
return { loaded: true };
|
|
827
836
|
}
|
|
837
|
+
ui.error("Número no válido. Usa /sessions primero.");
|
|
828
838
|
return {};
|
|
829
839
|
}
|
|
830
840
|
case "/delete": {
|
|
@@ -844,7 +854,7 @@ export class App {
|
|
|
844
854
|
ui.info("La conversación está vacía.");
|
|
845
855
|
}
|
|
846
856
|
else {
|
|
847
|
-
ui.info(`Conversación guardada en ${saveExport(this.agent.session)}`);
|
|
857
|
+
ui.info(`Conversación guardada en ${saveExport(this.agent.session)} (además se guarda sola en ${sessionMarkdownPath(this.agent.session)})`);
|
|
848
858
|
}
|
|
849
859
|
return {};
|
|
850
860
|
}
|
|
@@ -923,13 +933,13 @@ export class App {
|
|
|
923
933
|
const custom = this.commands.find((item) => item.name === name);
|
|
924
934
|
if (custom) {
|
|
925
935
|
const expanded = await expandCommand(custom, arg, this.root);
|
|
926
|
-
return { run: { input: { text: expanded }, options: { agent: custom.agent, model: custom.model, subtask: custom.subtask } } };
|
|
936
|
+
return { run: { input: { text: expanded, display: text.trim() }, options: { agent: custom.agent, model: custom.model, subtask: custom.subtask } } };
|
|
927
937
|
}
|
|
928
938
|
// /nombre-de-skill tarea: se le da la skill ya cargada junto con la tarea
|
|
929
939
|
const skill = this.skills.find((item) => item.name === name && item.userInvocable);
|
|
930
940
|
if (skill) {
|
|
931
941
|
const task = arg ? `\n\nTarea: ${arg}` : "";
|
|
932
|
-
return { run: { input: { text: `Usa la skill «${skill.name}» para esto.${task}\n\n${skillContent(skill)}
|
|
942
|
+
return { run: { input: { text: `Usa la skill «${skill.name}» para esto.${task}\n\n${skillContent(skill)}`, display: text.trim() }, options: {} } };
|
|
933
943
|
}
|
|
934
944
|
const agent = this.primaryAgents().find((item) => item.name === name);
|
|
935
945
|
if (agent) {
|
|
@@ -998,6 +1008,8 @@ export class App {
|
|
|
998
1008
|
signal?.removeEventListener("abort", stop);
|
|
999
1009
|
const clean = output.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "").trimEnd();
|
|
1000
1010
|
this.agent.addNote(`(El usuario ha ejecutado en la terminal \`${command}\`, código de salida ${code ?? "desconocido"}:\n\`\`\`\n${truncate(clean, 8000)}\n\`\`\`)\n\n`);
|
|
1011
|
+
this.agent.logEntry({ role: "tool", text: truncate(clean, 4000), tool: { name: "bash", summary: `(tú) ${command}`, isError: code !== 0 } });
|
|
1012
|
+
saveSession(this.agent.session);
|
|
1001
1013
|
resolve({ output: clean, code });
|
|
1002
1014
|
};
|
|
1003
1015
|
child.on("error", (err) => {
|
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { AUTH_PATH,
|
|
2
|
+
import { AUTH_PATH, ensureHomeDir } from "./paths.js";
|
|
3
3
|
export function readAuth() {
|
|
4
4
|
try {
|
|
5
5
|
const data = JSON.parse(fs.readFileSync(AUTH_PATH, "utf8"));
|
|
@@ -13,7 +13,7 @@ export function readAuth() {
|
|
|
13
13
|
return {};
|
|
14
14
|
}
|
|
15
15
|
function writeAuth(data) {
|
|
16
|
-
|
|
16
|
+
ensureHomeDir();
|
|
17
17
|
// 0600: solo tu usuario puede leer las claves
|
|
18
18
|
fs.writeFileSync(AUTH_PATH, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
19
19
|
fs.chmodSync(AUTH_PATH, 0o600);
|
package/dist/commands.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { readMarkdownDir } from "./frontmatter.js";
|
|
4
4
|
import { GLOBAL_COMMANDS_DIR } from "./paths.js";
|
|
5
5
|
import { truncate } from "./tools.js";
|
|
6
|
+
import { isProjectFile, projectTrust } from "./trust.js";
|
|
6
7
|
const INIT_TEMPLATE = `Analiza este proyecto y crea (o mejora, si ya existe) el fichero AGENTS.md en la raíz, con las instrucciones que necesitaría otro agente de programación para trabajar aquí sin perder tiempo.
|
|
7
8
|
|
|
8
9
|
Incluye solo lo que sea útil y concreto:
|
|
@@ -25,7 +26,7 @@ const BUILTIN = [
|
|
|
25
26
|
{ name: "review", description: "Revisa los cambios sin confirmar (no modifica nada)", template: REVIEW_TEMPLATE, agent: "plan", subtask: false, builtIn: true },
|
|
26
27
|
];
|
|
27
28
|
// Comandos integrados + ~/.vipcode/commands + .vipcode/commands y .opencode/command(s) del proyecto + "commands" del config
|
|
28
|
-
export function loadCommands(root, config) {
|
|
29
|
+
export function loadCommands(root, config, trusted = projectTrust(root).trusted) {
|
|
29
30
|
const commands = new Map();
|
|
30
31
|
for (const command of BUILTIN) {
|
|
31
32
|
commands.set(command.name, { ...command });
|
|
@@ -47,6 +48,7 @@ export function loadCommands(root, config) {
|
|
|
47
48
|
subtask: Boolean(data.subtask),
|
|
48
49
|
builtIn: false,
|
|
49
50
|
source: entry.file,
|
|
51
|
+
allowShell: trusted || !isProjectFile(root, entry.file),
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
for (const [name, input] of Object.entries(config.commands || {})) {
|
|
@@ -112,7 +114,7 @@ export async function expandCommand(command, args, root) {
|
|
|
112
114
|
const shellPattern = /!`([^`]+)`/g;
|
|
113
115
|
const commands = [...text.matchAll(shellPattern)];
|
|
114
116
|
for (const match of commands) {
|
|
115
|
-
const output = await runShell(match[1], root);
|
|
117
|
+
const output = command.allowShell === false ? "(no ejecutado: esta carpeta no es de confianza; usa «vipcode trust»)" : await runShell(match[1], root);
|
|
116
118
|
text = text.replace(match[0], () => output);
|
|
117
119
|
}
|
|
118
120
|
return text.trim();
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CONFIG_PATH,
|
|
3
|
+
import { CONFIG_PATH, ensureHomeDir } from "./paths.js";
|
|
4
|
+
import { projectTrust } from "./trust.js";
|
|
4
5
|
export { CONFIG_PATH, HOME_DIR, SESSIONS_DIR, STATE_PATH } from "./paths.js";
|
|
5
6
|
export const DEFAULT_PERMISSION = {
|
|
6
7
|
edit: "ask",
|
|
@@ -201,8 +202,8 @@ export function setGlobalConfigValue(key, value) {
|
|
|
201
202
|
raw[key] = value;
|
|
202
203
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(raw, null, 2));
|
|
203
204
|
}
|
|
204
|
-
export function loadConfig(root = process.cwd()) {
|
|
205
|
-
|
|
205
|
+
export function loadConfig(root = process.cwd(), options = {}) {
|
|
206
|
+
ensureHomeDir();
|
|
206
207
|
// La primera vez creamos el config con los valores por defecto para que puedas editarlo
|
|
207
208
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
208
209
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(TEMPLATE, null, 2));
|
|
@@ -215,8 +216,12 @@ export function loadConfig(root = process.cwd()) {
|
|
|
215
216
|
configNotices.push(`He actualizado ${CONFIG_PATH} al formato nuevo (copia del antiguo en config.json.bak).`);
|
|
216
217
|
}
|
|
217
218
|
let config = mergeConfig(DEFAULT_CONFIG, raw);
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
// La del proyecto puede arrancar programas o mandar tus claves a otro servidor: solo si confías en la carpeta
|
|
220
|
+
const trusted = options.trusted ?? projectTrust(root).trusted;
|
|
221
|
+
if (trusted) {
|
|
222
|
+
for (const file of projectConfigFiles(root)) {
|
|
223
|
+
config = mergeConfig(config, readJson(file));
|
|
224
|
+
}
|
|
220
225
|
}
|
|
221
226
|
if (!Array.isArray(config.providers)) {
|
|
222
227
|
config.providers = [];
|
package/dist/debug.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import { DEBUG_LOG_PATH,
|
|
2
|
+
import { DEBUG_LOG_PATH, ensureHomeDir } from "./paths.js";
|
|
3
3
|
// Con VIPCODE_DEBUG=1 se apunta en ~/.vipcode/debug.log lo que pasa por dentro (peticiones, errores…)
|
|
4
4
|
const enabled = process.env.VIPCODE_DEBUG === "1" || process.env.VIPCODE_DEBUG === "true";
|
|
5
5
|
export function debugLog(...parts) {
|
|
@@ -23,7 +23,7 @@ export function debugLog(...parts) {
|
|
|
23
23
|
})
|
|
24
24
|
.join(" ");
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
ensureHomeDir();
|
|
27
27
|
fs.appendFileSync(DEBUG_LOG_PATH, `[${new Date().toISOString()}] ${text}\n`);
|
|
28
28
|
}
|
|
29
29
|
catch {
|
package/dist/discovery.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readAuth, resolveCredentials } from "./auth.js";
|
|
|
3
3
|
import { CATALOG, RETIRED_PROVIDERS } from "./catalog.js";
|
|
4
4
|
import { configNotices, isAnonymous, mergeProvider } from "./config.js";
|
|
5
5
|
import { debugLog } from "./debug.js";
|
|
6
|
-
import {
|
|
6
|
+
import { MODELS_CACHE_PATH, ensureHomeDir } from "./paths.js";
|
|
7
7
|
// Modelos que no sirven para chatear (embeddings, voz, imágenes, vídeo…)
|
|
8
8
|
const NON_CHAT = /embed|bge-|minilm|rerank|guard|safety|moderation|whisper|tts|transcri|speech|audio-|native-audio|image|imagen|flux|diffusion|sdxl|video|veo|lyria|ocr|retriever|reward|nvclip|clip-|cosmos|bevformer|sparsedrive|streampetr|detector|studiovoice|riva|canary|parakeet|paligemma|grounding|aqa|deplot|kosmos|fuyu|neva|translate|computer-use|robotics|-live|usdcode|usdvalidate|usdsearch/i;
|
|
9
9
|
export function readDiscovery() {
|
|
@@ -20,7 +20,7 @@ export function readDiscovery() {
|
|
|
20
20
|
}
|
|
21
21
|
function writeDiscovery(cache) {
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
ensureHomeDir();
|
|
24
24
|
fs.writeFileSync(MODELS_CACHE_PATH, JSON.stringify(cache, null, 2));
|
|
25
25
|
}
|
|
26
26
|
catch {
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,12 @@ import { isImagePath, toDataUrl } from "./images.js";
|
|
|
10
10
|
import { REPO_URL, VERSION } from "./meta.js";
|
|
11
11
|
import { NoSlotError } from "./router.js";
|
|
12
12
|
import { appendHistory, exportMarkdown, listSessions, loadSession, saveExport } from "./session.js";
|
|
13
|
+
import { stripControl } from "./sanitize.js";
|
|
13
14
|
import { createSkill, installSkills, loadSkills, removeSkill } from "./skills.js";
|
|
15
|
+
import { projectTrust, trustProject, untrustProject } from "./trust.js";
|
|
14
16
|
import { updateVipcode } from "./update.js";
|
|
15
17
|
import { completeMention } from "./tools.js";
|
|
16
|
-
import { plainAdapter, plainEvents, plainPermissionPrompt, ui } from "./ui.js";
|
|
18
|
+
import { plainAdapter, plainEvents, plainPermissionPrompt, printLog, ui } from "./ui.js";
|
|
17
19
|
const USAGE = `vipcode ${VERSION} · agente de programación para la terminal con IAs gratuitas
|
|
18
20
|
|
|
19
21
|
Uso:
|
|
@@ -28,6 +30,7 @@ Uso:
|
|
|
28
30
|
vipcode skills new <nombre> crea una skill vacía para rellenar
|
|
29
31
|
vipcode skills remove <nombre> borra una skill instalada con vipcode
|
|
30
32
|
vipcode update actualiza vipcode (del repositorio o a la última versión publicada)
|
|
33
|
+
vipcode trust confiar en la configuración que trae esta carpeta (vipcode untrust lo deshace)
|
|
31
34
|
vipcode sessions sesiones guardadas de esta carpeta
|
|
32
35
|
vipcode export [id] guarda una sesión en Markdown (por defecto la última)
|
|
33
36
|
|
|
@@ -70,7 +73,7 @@ function parseArgs(argv) {
|
|
|
70
73
|
project: false,
|
|
71
74
|
force: false,
|
|
72
75
|
};
|
|
73
|
-
const commands = ["run", "auth", "models", "skills", "sessions", "export", "update", "help"];
|
|
76
|
+
const commands = ["run", "auth", "models", "skills", "sessions", "export", "update", "trust", "untrust", "help"];
|
|
74
77
|
for (let i = 0; i < argv.length; i++) {
|
|
75
78
|
const arg = argv[i];
|
|
76
79
|
if (arg === "-c" || arg === "--continue") {
|
|
@@ -288,6 +291,28 @@ function runSkills(options, root) {
|
|
|
288
291
|
ui.error(`Subcomando desconocido: ${sub}. Usa list, add, new o remove.`);
|
|
289
292
|
process.exitCode = 1;
|
|
290
293
|
}
|
|
294
|
+
function showTrust(trust) {
|
|
295
|
+
console.log(pc.yellow("⚠ Esta carpeta trae configuración propia de vipcode:"));
|
|
296
|
+
for (const change of trust.changes) {
|
|
297
|
+
console.log(` · ${stripControl(change)}`);
|
|
298
|
+
}
|
|
299
|
+
console.log(pc.dim(" Puede hacer que vipcode arranque programas, mande tus claves a otro servidor o deje de pedirte permiso."));
|
|
300
|
+
}
|
|
301
|
+
// Antes de abrir la interfaz: ¿usamos la configuración del proyecto?
|
|
302
|
+
async function askTrust(trust) {
|
|
303
|
+
showTrust(trust);
|
|
304
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
305
|
+
try {
|
|
306
|
+
const answer = (await rl.question("¿Confías en esta carpeta y quieres usar esa configuración? (s/N) ")).trim().toLowerCase();
|
|
307
|
+
return ["s", "si", "sí", "y", "yes"].includes(answer);
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
rl.close();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
291
316
|
function noModelsHelp() {
|
|
292
317
|
ui.error('No hay ningún modelo disponible (los proveedores sin clave se desactivan con "keyless": false o al limitar enabledProviders).');
|
|
293
318
|
console.log(`Añade una clave gratuita con: vipcode auth login
|
|
@@ -395,6 +420,7 @@ async function plainLoop(app, options, root) {
|
|
|
395
420
|
};
|
|
396
421
|
const adapter = { ...plainAdapter, setInput: (text) => ui.info(`Tu mensaje era: ${text}`) };
|
|
397
422
|
ui.banner(app.agent.agent.name, app.router.slots.length);
|
|
423
|
+
printLog(app.agent.session);
|
|
398
424
|
if (options.args.length > 0) {
|
|
399
425
|
await runTask(options.args.join(" "), {}, loadImages(options.images, root));
|
|
400
426
|
}
|
|
@@ -428,6 +454,9 @@ async function plainLoop(app, options, root) {
|
|
|
428
454
|
if (result.exit) {
|
|
429
455
|
break;
|
|
430
456
|
}
|
|
457
|
+
if (result.loaded) {
|
|
458
|
+
printLog(app.agent.session);
|
|
459
|
+
}
|
|
431
460
|
if (result.run) {
|
|
432
461
|
await runTask(result.run.input.text, result.run.options);
|
|
433
462
|
}
|
|
@@ -452,6 +481,21 @@ async function main() {
|
|
|
452
481
|
runSkills(options, root);
|
|
453
482
|
return;
|
|
454
483
|
}
|
|
484
|
+
if (options.command === "trust") {
|
|
485
|
+
const trust = projectTrust(root);
|
|
486
|
+
if (!trust.fingerprint) {
|
|
487
|
+
ui.info("Esta carpeta no trae configuración de vipcode: no hace falta confiar en ella.");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
showTrust(trust);
|
|
491
|
+
trustProject(root);
|
|
492
|
+
ui.info("Hecho: vipcode usará esa configuración en esta carpeta. Si cambia, te volverá a preguntar.");
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (options.command === "untrust") {
|
|
496
|
+
ui.info(untrustProject(root) ? "Hecho: ya no uso la configuración de esta carpeta." : "No confiabas en esta carpeta.");
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
455
499
|
if (options.command === "update") {
|
|
456
500
|
const result = await updateVipcode((text) => ui.info(text));
|
|
457
501
|
if (result.status === "error") {
|
|
@@ -503,7 +547,16 @@ async function main() {
|
|
|
503
547
|
else if (options.continueLast) {
|
|
504
548
|
session = listSessions(root)[0];
|
|
505
549
|
}
|
|
506
|
-
|
|
550
|
+
// En la interfaz se pregunta si usar la configuración del proyecto; con «run» se avisa y no se usa
|
|
551
|
+
let trusted;
|
|
552
|
+
const trust = projectTrust(root);
|
|
553
|
+
if (!trust.trusted && options.command === "" && process.stdin.isTTY) {
|
|
554
|
+
trusted = await askTrust(trust);
|
|
555
|
+
if (trusted) {
|
|
556
|
+
trustProject(root);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const app = new App(root, { session, agentName: options.agent || undefined, trusted });
|
|
507
560
|
if (options.agent && !app.agents.some((agent) => agent.name === options.agent)) {
|
|
508
561
|
ui.error(`No existe el agente «${options.agent}».`);
|
|
509
562
|
process.exitCode = 1;
|