npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kikedealba/kit

v0.7.1

Published

Shared contract for the @kikedealba tools: installed-tool registry, JSON envelope, event bus, agent integrations and credential store.

Readme

kit

El contrato común de las herramientas @kikedealba: bita (tiempo), atl (Atlassian), inkwell (documentación), recap (reuniones) y Den (la app de escritorio).

Ninguna herramienta depende de otra. Todas dependen solo de kit, y por medio de él se encuentran cuando están instaladas juntas: una herramienta que no está simplemente no aparece, y lo que dependía de ella falla con un error claro y una pista de cómo instalarla.

kit es una librería sin dependencias de ejecución. Trae además una CLI pequeña para ver qué hay instalado.

Requisitos

Node 24 o superior. Funciona en macOS, Windows y Linux.

Las cinco piezas

1. Registro de herramientas instaladas

Cada herramienta, al correr su setup, deja un manifiesto en el registro:

| Sistema | Ruta | |---|---| | macOS / Linux | ${XDG_CONFIG_HOME:-~/.config}/kikedealba/tools.d/<herramienta>.json | | Windows | %APPDATA%\kikedealba\tools.d\<herramienta>.json |

KIT_REGISTRY_DIR cambia la ruta (útil en pruebas).

{
  "manifestVersion": 1,
  "name": "atl",
  "version": "1.0.0",
  "bin": ["/usr/local/bin/node", "/usr/local/lib/node_modules/@kikedealba/atl/dist/bin/atl.js"],
  "envelope": 1,
  "capabilities": ["jira.issue.write", "confluence.page.write"],
  "emits": [],
  "subscribes": [],
  "mcp": { "stdio": ["atl", "mcp"], "http": "http://127.0.0.1:7781/mcp" },
  "install": "npm install -g @kikedealba/atl"
}
  • bin es la línea de comando completa para invocarla, con rutas absolutas. Quien la llama no depende del PATH.
  • capabilities son cadenas dominio.objeto.acción. Se pregunta por capacidad, no por versión, siempre que se pueda.
import { findTool, invokeTool, requireTool } from '@kikedealba/kit'

const atl = await findTool('atl', { capability: 'confluence.page.write' })
if (atl) {
  const { envelope } = await invokeTool(atl, ['confluence', 'page', 'update', '123', '--md', 'page.md'])
}

const recap = await requireTool('recap', 'npm install -g @kikedealba/recap', { minVersion: '1.0.0' })

requireTool lanza ToolMissingError con code = <HERRAMIENTA>_MISSING y la pista de instalación.

2. Sobre JSON

Todo comando con --json imprime una sola línea con este sobre:

{ "schemaVersion": 3, "ok": true, "command": "ls", "generatedAt": "2026-10-10T12:00:00.000Z", "data": {} }
{ "schemaVersion": 3, "ok": false, "command": "ls", "generatedAt": "…", "error": { "code": "NOT_FOUND", "message": "…", "hint": "…" } }

schemaVersion es la versión de los datos de cada herramienta; la forma del sobre es la misma para todas.

Toda herramienta responde capabilities --json:

{ "schemaVersion": 1, "ok": true, "command": "capabilities", "generatedAt": "…",
  "data": { "name": "atl", "version": "1.0.0", "envelope": 1, "capabilities": ["…"], "emits": [] } }

3. Eventos entre herramientas

Una herramienta declara en emits los eventos que dispara. Otra se suscribe desde su propio manifiesto, sin tocar la configuración de la primera:

"subscribes": [
  {
    "tool": "bita",
    "events": ["start", "stop", "cancel", "amend"],
    "filter": { "kind": ["remote-meeting", "in-person-meeting"] },
    "command": ["/abs/node", "/abs/recap.js", "bita-hook"]
  }
]

El emisor llama a fireEvents, que lee los suscriptores del registro, aplica el filtro y lanza cada comando desacoplado, con el documento del evento por stdin y KIT_EVENT / KIT_EVENT_SOURCE en el entorno. KIT_NO_EVENTS=1 los apaga; así una herramienta que responde a un evento no dispara otro en cadena.

4. Integración con agentes de código

agents.installIntegration instala skills, comandos, subagentes, memoria global, hooks y servidores MCP en los agentes que encuentre: Claude Code, opencode, Codex, Gemini CLI y Antigravity CLI (agy), más los servidores MCP de Claude Desktop.

| | Claude Code | opencode | Codex | Gemini CLI | Antigravity CLI | |---|---|---|---|---|---| | Skill | ~/.claude/skills/<n> | ~/.config/opencode/skills/<n> | ~/.agents/skills/<n> | extensión skills/<n> | plugin skills/<n> | | Comandos | ~/.claude/commands/*.md | ~/.config/opencode/commands/*.md | ~/.codex/prompts/*.md | extensión commands/*.toml | no soportado (skipped) | | Hooks | settings.json | plugin | ~/.codex/hooks.json | extensión hooks/hooks.json | no soportado | | MCP | claude mcp add --scope user | opencode.json | config.toml | gemini-extension.json | ~/.gemini/config/mcp_config.json | | Subagentes | ~/.claude/agents/<n>.md | ~/.config/opencode/agents/<n>.md | ~/.codex/agents/<n>.toml | extensión agents/<n>.md | plugin agents/<n>.md | | Memoria | ~/.claude/CLAUDE.md | ~/.config/opencode/AGENTS.md | ~/.codex/AGENTS.md | ~/.gemini/GEMINI.md | ~/.gemini/GEMINI.md (mismo bloque que Gemini) |

Para Gemini se escribe una extensión completa en ~/.gemini/extensions/<herramienta>/. Gemini se detecta por el binario gemini o por la carpeta ~/.gemini/extensions; ~/.gemini sola ya no basta porque Antigravity también la crea.

Para Antigravity kit genera un plugin en <XDG_DATA_HOME>/kikedealba/agents/antigravity/<herramienta>/ (se borra y se regenera en cada instalación) con plugin.json, agents/, skills/ (de skills y userSkills, con el frontmatter reducido a name y description) y rules/<herramienta>.md si la integración trae antigravity.rules (se escribe con trigger: always_on). Luego lo instala con agy:

  1. agy plugin list --json; si ya está y su carpeta es idéntica, sale present.
  2. agy plugin validate <raíz> --strict --json; si falla, sale failed y no se instala.
  3. agy plugin install <raíz>.

Sin agy en el PATH el plugin se genera igual y el paso sale unavailable con el comando a correr. Antigravity se detecta por el binario agy o por ~/.gemini/config (se cambia con homes.antigravity). Desinstalar corre agy plugin uninstall <herramienta>.

Los plugins de agy no admiten hooks ni servidores MCP, así que los servidores MCP se escriben directo en <homes.antigravity>/mcp_config.json, sin pasar por agy mcp. Cada servidor queda bajo mcpServers.<nombre> como { command, args, env?, disabled: false, _kit: "<herramienta>" } o, si es remoto, { serverUrl, headers?, disabled: false, _kit: "<herramienta>" }. _kit marca la entrada como propia: agy lo conserva al reescribir el archivo. Si ya hay una entrada con el mismo nombre sin esa marca, no se pisa y el paso sale skipped con el conflicto en detail. La excepción es una entrada sin marca idéntica a la que kit escribiría (de una instalación anterior): se adopta y se le pone la marca. Las demás claves del archivo se conservan, la escritura es atómica, conserva el modo del archivo (o 0600 si es nuevo) y deja mcp_config.json.backup. Un archivo vacío cuenta como {}; uno con JSON inválido no se toca y el paso sale failed. uninstallIntegration quita solo las entradas con la marca de la herramienta.

Binarios de los agentes

detectAgents y la instalación buscan cada binario con findAgentBinary (findAgentBinarySync en la versión síncrona), que no se queda en el PATH. Esto importa cuando la herramienta corre desde un hook o una app de escritorio y no hereda el PATH de la terminal. El orden de búsqueda es este:

  1. override, si se pasa. Si no es ejecutable devuelve null y no sigue buscando.
  2. El PATH y luego defaultSearchDirs.
  3. Los gestores de Node, con toolchainSearchDirs: NVM_BIN, ~/.nvm/versions/node/*/bin de la versión más nueva a la más vieja, el alias default de fnm, Volta, NPM_CONFIG_PREFIX, ~/.npm-global/bin, pnpm, Bun y ~/.local/bin. En Windows también %APPDATA%\npm y %LOCALAPPDATA%\Volta\bin.
  4. Las carpetas propias de cada agente, con agentSearchDirs: ~/.gemini/antigravity-cli/bin (agy), ~/.opencode/bin y ~/.claude/local.

Agentes sin interfaz (headless)

runHeadless corre un prompt en cualquiera de los agentes de HEADLESS_AGENTS y devuelve el texto de la respuesta, más el id de sesión si lo hay:

import { agents } from '@kikedealba/kit'

const picked = await agents.pickHeadlessAgent(config.agent ?? 'claude')
if (!picked) throw new Error('no hay ningún agente instalado')
if (picked.fallback) console.warn(`usando ${picked.agent}`)
const { text, sessionId } = await agents.runHeadless({ agent: picked.agent, bin: picked.bin, prompt, cwd, access: 'read' })

El prompt va por stdin en todos los agentes, así que no cuenta para el límite de la línea de comandos, un prompt que empieza con - no se toma como flag y no queda visible en la lista de procesos (ps). A agy le llega como una línea NDJSON {"event":"user","message":{"content":"<prompt>"}} con --input-format stream-json y -p= vacío; la respuesta sale del evento result. Ojo: agy recorta por su cuenta los mensajes de más de unos 190 KB (avisa con <truncated N bytes>), venga el prompt por donde venga.

| Agente | Invocación | access: 'read' (por omisión) | access: 'write' | Respuesta | |---|---|---|---|---| | claude | claude -p --output-format json [--model m] | --strict-mcp-config --tools Read,Grep,Glob --permission-mode plan --setting-sources user | --permission-mode acceptEdits | result, session_id | | codex | codex exec --json -C <cwd> --skip-git-repo-check [-m m] - | --sandbox read-only | --sandbox workspace-write | último item.completed con agent_message; id en thread.started | | gemini | gemini -o json [-m m] | --approval-mode plan, sin GEMINI_CLI_TRUST_WORKSPACE | --approval-mode auto_edit y GEMINI_CLI_TRUST_WORKSPACE=true | response, session_id | | antigravity | agy --input-format stream-json --output-format stream-json [--model m] … -p= | --mode plan | --dangerously-skip-permissions | result.response, result.conversation_id del evento result | | opencode | opencode run --format json [-m proveedor/modelo] | --agent plan | agente por omisión | part.text de los eventos text, unidos; sessionID |

extraArgs va después de los flags de acceso (en agy, antes de -p=). En agy cuenta la respuesta: si response trae texto y status no es ERROR, la corrida es buena aunque traiga error o denied_actions. Sin texto útil, falla con FAILED y el mensaje sale de error, de status o de las acciones denegadas. Cuenta el último evento result; si su response viene vacío, se usan los text_delta de los pasos agent_response de ese turno. Si el stream acaba sin result pero con deltas y agy sale con 0, se devuelve ese texto parcial. Un evento error hace fallar con FAILED y su mensaje, salvo que el result traiga una respuesta útil.

tools fija la lista exacta de herramientas de claude y reemplaza el --tools Read,Grep,Glob de lectura (claude suma los --tools repetidos, así que kit no manda el suyo). Va como --strict-mcp-config --tools <a,b>, de modo que tampoco se cargan los servidores MCP del usuario. Con tools: [] no queda ninguna: kit manda --strict-mcp-config --tools Read,Grep,Glob --disallowedTools Read,Grep,Glob, que el evento init de claude reporta como tools: []. Los demás agentes ignoran tools. Sin tools, la lectura deja solo Read,Grep,Glob y nunca carga los servidores MCP del usuario (--strict-mcp-config), porque esos servidores pueden escribir. La escritura sin tools usa las herramientas y los MCP que tenga el usuario.

maxOutputBytes (8 MiB por omisión, HEADLESS_DEFAULT_MAX_OUTPUT_BYTES) limita el stdout del hijo: si lo pasa, kit mata el árbol de procesos y lanza FAILED. También acota stderr, pero ahí no falla: guarda solo los últimos maxOutputBytes bytes.

El hijo no hereda todo el entorno. headlessEnv(agent, env) filtra con una lista blanca común, y bita, recap e inkwell pueden usarla para lo mismo:

  • PATH, HOME, USER, LOGNAME, SHELL, TMPDIR/TMP/TEMP, LANG, LC_*, TERM, XDG_*, NVM_* y KIT_HEADLESS.
  • Proxy y certificados: HTTP_PROXY, HTTPS_PROXY, NO_PROXY, ALL_PROXY (también en minúsculas), NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, SSL_CERT_DIR y REQUESTS_CA_BUNDLE.
  • Windows: USERPROFILE, USERNAME, HOMEDRIVE, HOMEPATH, APPDATA, LOCALAPPDATA, PROGRAMDATA, ALLUSERSPROFILE, ProgramFiles, ProgramFiles(x86), ProgramW6432, CommonProgramFiles, SystemRoot, SystemDrive, WINDIR, ComSpec, PATHEXT, PROCESSOR_ARCHITECTURE y NUMBER_OF_PROCESSORS. En Windows los nombres se comparan sin distinguir mayúsculas, también al quitar variables como GEMINI_CLI_TRUST_WORKSPACE.

A eso se suman solo los prefijos del agente que corre:

| Agente | Prefijos | Condicionales | |---|---|---| | claude | ANTHROPIC_, CLAUDE_ | AWS_* si CLAUDE_CODE_USE_BEDROCK; GOOGLE_* y CLOUD_ML_REGION si CLAUDE_CODE_USE_VERTEX | | codex | OPENAI_, CODEX_ | | | gemini | GEMINI_, GOOGLE_ | GOOGLE_APPLICATION_CREDENTIALS solo si GOOGLE_GENAI_USE_VERTEXAI | | antigravity | ANTIGRAVITY_, GEMINI_ | | | opencode | OPENCODE_ | |

Así claude no ve OPENAI_API_KEY ni codex ANTHROPIC_API_KEY. opencode solo recibe OPENCODE_*: sus credenciales salen de opencode auth login o se pasan en env. Lo que el llamador pase en env se agrega encima del filtro y gana, así que puede reintroducir cualquier variable, incluso una que el filtro quitó; esa decisión es de quien llama. Al final se fija KIT_HEADLESS=1, que los hooks de bita y gaze respetan para no registrar esa sesión, y se pone la carpeta del binario al principio del PATH.

El tiempo límite por omisión es de 10 minutos. Al vencer, o al abortar con signal, se mata el árbol de procesos: en unix el hijo corre en su propio grupo y se manda SIGTERM y luego SIGKILL al grupo; en Windows se usa %SystemRoot%\System32\taskkill.exe /T /F. En unix, mientras haya hijos vivos (liveHeadlessGroups los lista), kit escucha exit, SIGINT, SIGTERM y SIGHUP del proceso padre y mata sus grupos. Si nadie más escucha esa señal, se quita su manejador y la vuelve a mandar al padre, que termina como lo haría sin kit. Al terminar el último hijo quita todos los manejadores. Un SIGKILL al padre no se puede atrapar.

Los fallos lanzan HeadlessError con un code, además de exitCode, stdout y stderr:

| code | Cuándo | |---|---| | UNAVAILABLE | No se encontró el binario o no se pudo lanzar. | | FAILED | Salió con código distinto de cero, el agente reportó un error en su JSON, se abortó, su stdout pasó de maxOutputBytes, cwd no existe o el prompt trae saltos de línea y debe ir como argumento a un .cmd. | | TIMEOUT | Se venció timeoutMs. | | PARSE | Salió bien pero su salida no trae la respuesta. | | PROMPT_TOO_LONG | El sistema rechazó la línea de comandos (E2BIG), o en Windows la línea ya escapada para cmd.exe pasa de 8191 caracteres. |

parseHeadlessOutput(agent, stdout) y headlessInvocation(agent, opciones) están expuestos para probar sin lanzar procesos. isHeadlessAgent valida un id; uno desconocido hace fallar runHeadless con UNAVAILABLE. El mensaje de error toma la primera línea de stderr que contenga «Error», o la última si no hay. pickHeadlessAgent(preferido) devuelve el preferido si está instalado. Si no, devuelve el primero disponible de order (claude va primero) con fallback: true para que quien llama avise.

Subagentes

subagents recibe archivos en el formato de Claude Code: frontmatter con name, description, tools, model, effort y maxTurns, y el cuerpo es el prompt. En Claude se enlazan tal cual (o renderizados si tienen bloques ::: agent). En los demás agentes kit los convierte:

| Campo | opencode | Codex | Gemini CLI | Antigravity CLI | |---|---|---|---|---| | description | description, con mode: subagent | description | description, con kind: local | description, con model: inherit | | tools | permission con deny para edit, bash, webfetch y websearch que no estén | [features] shell_tool = false si no hay Bash | tools con los nombres de Gemini | tools: [...] con los nombres de Antigravity | | effort | — | model_reasoning_effort (max pasa a xhigh) | — | — | | maxTurns | steps | — | max_turns | — | | cuerpo | cuerpo | developer_instructions | cuerpo | cuerpo |

En Antigravity Read pasa a view_file y list_dir, Grep a grep_search, Glob a find_by_name, LS a list_dir, Bash a run_command, Edit a replace_file_content, MultiEdit a multi_replace_file_content, Write a write_to_file, WebFetch a read_url_content y WebSearch a search_web. Lo demás (Skill, Agent, Task, TodoWrite...) se descarta. Si tools no viene, viene vacío o incluye *, se escribe la lista completa (view_file, list_dir, grep_search, find_by_name, run_command, replace_file_content, multi_replace_file_content, write_to_file, read_url_content, search_web), porque sin la clave agy deja al subagente sin escribir ni ejecutar. Si tools trae valores y ninguno existe en Antigravity, la conversión falla y el plugin no se instala.

model solo se respeta en Claude. En los demás se hereda el modelo de la sesión, porque los alias de Claude no existen ahí.

Memoria global

memory es un markdown que se escribe como bloque administrado en la memoria global de cada agente, entre <!-- kit:<herramienta>:begin --> y <!-- kit:<herramienta>:end -->. Lo demás del archivo no se toca: instalar de nuevo reemplaza solo el bloque, y desinstalar lo quita. Antes de cambiar un archivo existente se deja una copia en <archivo>.backup. Admite bloques ::: agent.

Gemini y Antigravity comparten ~/.gemini/GEMINI.md: los dos escriben el mismo bloque, renderizado como gemini (renderMemory), así que la segunda instalación sale present. Al desinstalar uno, el bloque se queda (skipped, "still used by") mientras el otro siga instalado; lo quita el último. memoryFile(agent, homes) da la ruta de la memoria de cada agente.

Hay una sola skill por herramienta. Lo que aplica solo a algunos agentes va en bloques:

::: agent codex opencode
Esto solo lo leen Codex y opencode.
:::

::: agent !gemini
Esto lo leen todos menos Gemini.
:::

Si la skill no tiene bloques se enlaza tal cual; si los tiene, se genera una copia por agente.

userSkills son skills que solo dispara el usuario (disable-model-invocation: true). En Claude Code viajan dentro del plugin; en opencode, Codex y Gemini kit las convierte en comando, igual que commands. Como el comando es una copia del SKILL.md, la skill no debe depender de otros archivos de su directorio.

En opencode, si hay un plugin que reejecuta los hooks y comandos de Claude Code (por ejemplo plugins/claude-compat), kit no instala lo que se duplicaría:

  • el plugin de opencode, si los hooks de la herramienta siguen en ~/.claude/settings.json;
  • cada comando que siga en ~/.claude/commands.

Lo marca como skipped y borra la copia que había dejado antes. Las skills se instalan siempre, y también los comandos que salen de userSkills, porque el relay no los ve. claudeCompat: false desactiva esta detección y true omite el plugin y los comandos sin revisar. El valor por defecto es 'auto'.

Claude Desktop

El agente claude-desktop solo recibe servidores MCP, y solo los de claudeDesktop.mcp. El mcp genérico nunca llega ahí, así que una herramienta puede tener MCP en Claude Desktop y quedarse en skill y CLI en los demás agentes. Si la herramienta no define claudeDesktop, el paso sale como skipped.

| Sistema | Archivo | |---|---| | macOS | ~/Library/Application Support/Claude/claude_desktop_config.json | | Windows | %APPDATA%\Claude\claude_desktop_config.json | | Linux | no existe; el paso sale como unavailable |

Se detecta si existe la carpeta Claude. kit fusiona mcpServers.<nombre> y respeta el resto del archivo (por ejemplo preferences); crea el archivo solo si la carpeta ya existe. Un servidor stdio queda como { command, args, env: { PATH } }, con el directorio del node que corre la instalación al frente del PATH, porque las apps gráficas no heredan el PATH de la terminal. Un servidor http queda como { type: 'http', url, headers }. Desinstalar quita solo las entradas de la herramienta. Hay que reiniciar Claude Desktop para que los cargue.

EDIT_TOOL_MATCHERS da el matcher de las herramientas de edición de cada agente. Sirve para que un hook PostToolUse no corra tras cada herramienta.

Plugins de Claude Code

En Claude Code cada herramienta se instala como plugin. El marketplace es su propio repo de GitHub, con .claude-plugin/plugin.json y marketplace.json.

import { agents } from '@kikedealba/kit'

const spec = { marketplace: 'KikeDeAlba/bita-cli', marketplaceName: 'bita', plugin: 'bita' }
await agents.installClaudePlugin(spec)
await agents.removeLegacyClaude(integration)
await agents.installIntegration(integration, { agents: ['opencode', 'codex', 'gemini'] })

installClaudePlugin corre lo siguiente:

  1. claude plugin marketplace add <marketplace>, si el marketplace no estaba.
  2. claude plugin marketplace update <marketplaceName>.
  3. claude plugin install <plugin>@<marketplaceName> --scope user si el plugin es nuevo, o claude plugin update si ya estaba.

Para saber qué hay instalado usa claude plugin list --json. Si no puede leerlo, intenta instalar y después actualiza. Si claude no está en el PATH, devuelve un paso unavailable con los comandos para correrlos a mano. uninstallClaudePlugin quita el plugin y deja el marketplace, porque otro plugin puede compartirlo.

removeLegacyClaude quita lo que kit 0.2 dejaba suelto en ~/.claude:

  • los enlaces de skills y comandos, solo si apuntan al paquete de la herramienta o a la copia que kit generó;
  • los hooks y permisos de la integración en settings.json;
  • sus servidores MCP (claude mcp remove --scope user), salvo con skipMcp.

Hay que pasarle la integración tal como la instaló la versión anterior. Si los hooks cambiaron, los viejos no se reconocen.

Una herramienta que ya trae plugin debe llamar a installIntegration sin claude en agents. Si no, vuelve a escribir en ~/.claude lo que el plugin ya trae y Claude Code lo carga dos veces.

Procesos, notificaciones y Windows

spawnSpec(comando, args, ctx) devuelve { command, args, windowsVerbatimArguments } listo para spawn. En Windows un .cmd o .bat se envuelve en %SystemRoot%\System32\cmd.exe /d /s /c con cada argumento entre comillas y los metacaracteres escapados, así un argumento con &, | o comillas no se interpreta como comando. Un argumento con saltos de línea lanza un error, porque cmd.exe corta ahí la línea. En los demás casos devuelve el comando tal cual.

run usa spawnSpec. Esto cambia su comportamiento en Windows respecto a 0.6: antes corría los .cmd/.bat con shell: true y sin escapar, y ahora pasan por %SystemRoot%\System32\cmd.exe (no por ComSpec) con los argumentos escapados. Un argumento con saltos de línea ahora lanza un error en vez de cortarse. whichSync es la versión síncrona de which, y las dos aceptan un cuarto argumento con carpetas que se buscan después de defaultSearchDirs.

notify(título, cuerpo) (nuevo en 0.7) muestra una notificación del sistema y devuelve true o false, nunca lanza. El título se recorta a 256 caracteres y el cuerpo a 2000. En macOS usa osascript con el texto por argv después de --, en Linux notify-send con <, > y & escapados, y en Windows un toast de PowerShell con -EncodedCommand. El texto va en base64 dentro del script y no se salta la política de ejecución (-ExecutionPolicy no se toca). notifyCommand da el comando sin ejecutarlo.

5. Credenciales

credentialStore() elige el almacén del sistema:

| Sistema | Almacén | |---|---| | macOS | Llavero (/usr/bin/security) | | Windows | Administrador de credenciales (advapi32 vía PowerShell) | | Linux | Secret Service (secret-tool); si no está, archivo 0600 |

KIT_CREDENTIALS=file fuerza el archivo. resolveSecret revisa primero una variable de entorno, para CI. El secreto nunca viaja como argumento de línea de comandos, salvo en macOS, donde security no admite otra vía.

CLI

kit ls            # herramientas registradas
kit doctor        # verifica que cada una responda capabilities --json
kit events bita   # quién escucha los eventos de bita
kit agents        # agentes de código encontrados

Pruebas de conformidad

Cada herramienta corre esto en su CI:

import { assertConformance } from '@kikedealba/kit/conformance'

await assertConformance([process.execPath, 'src/bin/atl.ts'], manifest)

Revisa que el manifiesto sea válido, que capabilities --json imprima un sobre correcto que coincida con el manifiesto, y que un comando desconocido falle con un sobre de error y código distinto de cero.