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/inkwell

v0.7.0

Published

Living technical documentation from the terminal: page trees and per-entry notes in markdown with git history, a backlog, diagrams, PDF export and Confluence sync.

Readme

inkwell

Documentación técnica viva desde la terminal: árboles de páginas en markdown con historia en git, la nota de cada entrada de tiempo de bita, un backlog de pendientes y hallazgos, diagramas (mermaid y draw.io), exportación a PDF y sincronización con Confluence.

Es una de las herramientas @kikedealba. No depende de ninguna otra: se encuentra con ellas en tiempo de ejecución por medio de @kikedealba/kit cuando están instaladas juntas.

| Si también está… | inkwell… | |---|---| | bita | lleva una nota en markdown por entrada de tiempo (note …), ata las páginas a las entradas (page link --entry), toma el cronómetro que corre para ubicar un ítem del backlog y escucha los eventos start, stop, amend, merge, cancel y delete para crear, renombrar, sellar, juntar y borrar las notas, y para que los enlaces apunten siempre a entradas que existen. | | atl | publica y sincroniza páginas con Confluence (confluence sync, publish-diagrams). | | recap | exporta a PDF la minuta de una reunión (export meeting). |

Sin ellas, todo lo demás funciona igual; lo que depende de una herramienta ausente falla con un error claro (BITA_MISSING, ATL_MISSING, RECAP_MISSING) y la pista para instalarla.

Requisitos

  • Node 24 o superior. macOS, Windows y Linux.
  • git, para la historia de las páginas (sin git se escribe igual, sin commits).
  • Opcional: Chrome o cualquier Chromium para el PDF (INKWELL_CHROME apunta a otro binario) y draw.io Desktop para los diagramas .drawio (INKWELL_DRAWIO).

Instalación

npm install -g @kikedealba/inkwell
inkwell setup                          # registro de kit + plugin de Claude Code + skill para los demás agentes
inkwell setup --agents codex,gemini    # o solo algunos: claude, opencode, codex, gemini, antigravity, claude-desktop

setup deja el manifiesto en el registro de kit (con la suscripción a los eventos de bita) y prepara a cada agente:

| Agente | Qué recibe | |---|---| | Claude Code | El plugin inkwell de este mismo repositorio, que es su propio marketplace (claude plugin marketplace add KikeDeAlba/inkwell + claude plugin install inkwell@inkwell); si ya existía, se actualiza. Trae la skill. Con el plugin instalado se quita la skill suelta que versiones anteriores enlazaron en ~/.claude/skills, y se mantienen en ~/.claude/settings.json los permisos de solo lectura de inkwell y el MCP de draw.io a nivel de usuario (--no-drawio lo omite; si otra herramienta, como bita, ya lo registró, no se quita). | | opencode | La skill con sus archivos de apoyo y el MCP de draw.io (--no-drawio lo omite). | | Codex y Gemini CLI | Lo mismo, más los hooks del autodoc: PreCompact y SessionEnd en Codex (~/.codex/hooks.json), PreCompress y SessionEnd en la extensión de Gemini; ambos corren inkwell hook session-end --harness <agente>. La primera vez que abras Codex después de setup, te pide confiar en los hooks nuevos; mientras no los apruebes, no corren. | | Antigravity CLI (agy) | Un plugin con la skill y una regla always_on de dos líneas que manda a la skill; el MCP de draw.io va en ~/.gemini/config/mcp_config.json. agy no tiene hooks, así que no hay autodoc. | | Claude Desktop | Nada: no carga skills ni corre la CLI, así que el paso sale skipped. |

Si claude no está en el PATH, setup lo avisa con los comandos para instalar el plugin a mano y deja la instalación anterior como estaba. inkwell uninstall desinstala el plugin y revierte lo demás sin tocar las páginas (el marketplace inkwell queda agregado; se quita con claude plugin marketplace remove inkwell).

La skill

La skill (skills/inkwell/SKILL.md) se mantiene corta: solo se carga al invocarla, y los detalles viven en archivos de apoyo que se leen cuando la tarea los pide:

| Archivo | Contenido | |---|---| | writing.md | Las reglas de redacción (frases prohibidas, «así no / así sí», tono). Es la fuente única: tally y las demás herramientas la enlazan. | | entry-notes.md | Cómo escribir y leer la nota de una entrada de bita. | | diagrams.md | mermaid contra draw.io y el flujo del MCP de draw.io. | | confluence.md | Cómo se publica y sincroniza con Confluence (siempre por atl). |

inkwell guide <writing|entry-notes|diagrams|confluence> imprime cualquiera de ellos, para quien no tenga la skill a mano.

Datos

| Sistema | Base de datos y repositorio | |---|---| | macOS / Linux | ${XDG_DATA_HOME:-~/.local/share}/inkwell/inkwell.db y …/inkwell/docs | | Windows | %LOCALAPPDATA%\inkwell\inkwell.db y %LOCALAPPDATA%\inkwell\docs |

INKWELL_DB_PATH e INKWELL_DOCS_DIR cambian las rutas (si solo se cambia la base, el repositorio va junto a ella).

Uso

inkwell space add "Apartados" --key APT --confluence https://acme.atlassian.net/wiki/spaces/VB --pull on
inkwell page new "Arquitectura" --project APT
inkwell page write 1 --md arquitectura.md
inkwell page show 1
inkwell page show 1 --brief --json            # solo el markdown y los identificadores
inkwell page show 1 --section "Red" --json    # una sola sección
inkwell tree
inkwell search "waf" --project APT

inkwell backlog add --kind pending --title "Rotar las llaves del bucket" --page 1
inkwell backlog resolve APT-1 --resolution "Rotadas con KMS"

inkwell note save 42 --section "Qué se hizo" --md -   # el texto llega por stdin
inkwell note show 42
inkwell note ls today
inkwell note search "waf" --project APT

inkwell page history 1
inkwell git propose 1 --branch reunion/7 --reason "Acuerdos de la reunión" --md cambios.md
inkwell branch apply reunion/7 --commit <sha>

inkwell diagrams render 1
inkwell export page 1 --out arquitectura.pdf
inkwell confluence sync APT --dry-run

page show y note show aceptan --brief (el markdown sin front matter, la lista de secciones y los identificadores mínimos, sin entradas, issues ni metadatos) y --section <H2> (una sola sección, con la misma forma corta). Son las formas que usan los agentes para no gastar contexto; una sección que no existe falla con SECTION_NOT_FOUND y la pista trae las que hay.

inkwell help lista todos los comandos. Todos aceptan --json y responden con el sobre de kit ({ schemaVersion, ok, command, generatedAt, data | error }). --project (alias --space) acepta el nombre, la clave, el id del espacio o el id del proyecto de bita del que se migró.

Borrar páginas

inkwell page rm <id> borra el registro, el .md y la carpeta .assets de la página, y deja el borrado en un commit. Con --recursive hace lo mismo con cada página hija que la base tenga registrada; sin él, una página con hijas falla con un error de uso. Solo se borran archivos de las páginas condenadas: después se quitan las carpetas de las hijas que quedaron vacías, de la más honda a la más alta, y cualquier otro archivo que viva ahí (por ejemplo, las notas de entrada de proj/2026/ cuando la página se llama «2026») se queda.

Si alguno de esos archivos tiene cambios sin commitear, el comando falla con PAGE_FILES_DIRTY (código de salida 9) y no toca nada; --force los borra de todos modos. Si no hay un historial de git utilizable (git no está instalado o el repositorio no se pudo iniciar), falla con DOCS_REPO_UNAVAILABLE y no toca nada; con --force borra los archivos sin commit. Si el borrado no se pudo commitear, avisa por stderr y lo deja pendiente para inkwell git commit.

Un archivo registrado como nota de entrada nunca se borra, aunque la ruta de una página coincida con la suya: se omite y se avisa por stderr.

Con --json, data trae removed (pageId, title, relPath de cada página), sha (el commit, o null si no hubo nada que commitear, como cuando el archivo ya no existía) y skipped (las rutas omitidas por ser notas de entrada). meta ya no trae keptDocuments.

Notas por entrada

Cada entrada de tiempo de bita tiene una nota en <espacio>/<aaaa>/<mm>/<dd>-<id>-<título>.md, con front matter (entryId, title, project, startedAt, endedAt, duration, kind…) y las secciones Contexto, Qué se hizo y Pendiente (más Decisiones, Hallazgos, Verificación, Tocado o las que agregue otra herramienta, como la Reunión de recap). La tabla entry_notes las indexa con la referencia bita:entry:<id>.

| Comando | Qué hace | |---|---| | note save <id> [--section H2] [--md FILE\|-] | Crea la nota si falta y reemplaza la sección (por omisión, Qué se hizo). --note-md es sinónimo de --md; --file, --command y --resource llenan Tocado. | | note show <id> \| --path <rel> [--brief] [--section H2] | La entrada y la nota con sus secciones, su front matter y su markdown; --brief y --section, la forma corta. | | note get <id> [--raw] o note <id> | La fila de la nota con su markdown. | | note path <id> [--create] | Dónde vive o viviría la nota. | | note ls <id> | Los archivos de esa entrada. | | note ls [preset] [--from --to] [--project X] | Las notas de un periodo (today, yesterday, week, last-week, month, last-month). | | note search "<texto>" [--project X] [--section H2] | Busca en las notas. |

--entry <id> sirve en lugar del id posicional, y --draft toma el único cronómetro sin título. Los datos de la entrada salen de bita entries get (por kit) y, si bita no está, de la última copia guardada con la nota.

Los eventos de bita las mantienen al día: start crea el esqueleto, amend la renombra o la mueve cuando cambia el título o el proyecto, stop sella el fin y la duración, merge pasa las secciones de las entradas absorbidas a la que sobrevive y borra las suyas, y cancel y delete la borran. Cada cambio es un commit.

Eventos

Cada commit que inkwell hace en la rama principal del repositorio de documentos emite docs.changed por kit, para que otra herramienta (un índice, un buscador) se entere sin vigilar el repositorio. Lo emiten page new, write, rename, rm y restore, las notas por entrada (también las que escriben los eventos de bita), inkwell git commit, normalize, branch apply, confluence sync y la migración desde bita. Una propuesta (git propose) vive en su rama y no emite nada hasta que se aplica. Un commit sin cambios en markdown (solo assets o .gitignore) tampoco emite.

El documento del evento:

{
  "docsRoot": "/home/ana/.local/share/inkwell/docs",
  "commit": "9f1c…",
  "changes": [
    { "action": "written", "relPath": "apartados/arquitectura.md" },
    { "action": "moved", "relPath": "apartados/red.md", "from": "apartados/infra.md" },
    { "action": "deleted", "relPath": "apartados/2026/10/09-41-revisar-el-waf.md" }
  ]
}
  • changes lista solo archivos .md fuera de las carpetas .assets, con rutas relativas a docsRoot, tal como las guarda git (espacios y acentos incluidos).
  • action es written (creado o modificado), deleted o moved; moved trae from y sale de la detección de renombres de git (similitud de 30 %).
  • commit es el último commit que cubre el evento.
  • El atributo kind vale page, entry (notas por entrada) o mixed, y sirve para filtrar la suscripción (filter: { kind: ["entry"] }).

Un comando que hace varios commits (confluence sync, la resolución de un conflicto, normalize, la migración desde bita) emite un solo evento al terminar, aunque falle a medias, con los cambios fusionados por ruta: lo último que le pasó a cada archivo, un moved encadenado se reduce a su origen, un archivo creado y borrado dentro del mismo comando no aparece, y uno creado y luego movido aparece como written en su destino. Si el evento no se puede emitir, el commit se queda y inkwell avisa por stderr; KIT_NO_EVENTS=1 los apaga.

Autodoc

Al cerrar (o compactar) una sesión de Claude Code, Codex o Gemini CLI, su hook corre inkwell hook session-end, que lanza en segundo plano una revisión de la conversación con un agente sin interfaz (Claude con haiku por omisión) y sale de inmediato. Dentro de un agente lanzado sin interfaz (KIT_HEADLESS=1, que kit pone a todo agente headless) el hook no hace nada. Esa revisión:

  • Ubica el espacio de la sesión por el repositorio donde se trabajó: primero bita repo which (el proyecto de bita enlazado al espacio) y, si no, el repositorio de las páginas que ya existen. Si no hay espacio, no hace nada: nunca crea espacios.
  • Toma solo lo nuevo desde la última revisión de esa sesión (los turnos de texto del usuario y del asistente) y se queda con el conocimiento durable del sistema; lo efímero se descarta.
  • Un cambio a una página existente nunca toca la página: queda como propuesta en una rama autodoc/<sesión>-<página>.
  • Una página nueva se crea directamente, colgada del padre que le corresponde en el árbol del espacio (en la raíz solo si el espacio está vacío), sin títulos repetidos ni secciones de backlog. Queda pendiente de aprobar: no sube a Confluence hasta inkwell autodoc approve <pageId> o hasta que una persona la reescriba completa con page write (una escritura por --section no basta).
  • Antes de llamar al modelo reemplaza por [REDACTED] lo que parezca un secreto (llaves, tokens, contraseñas, credenciales en URLs, tarjetas), y descarta toda acción que todavía traiga uno, o que traiga imágenes, HTML o enlaces que no sean http(s), relativos o #ancla.
  • El agente corre con runHeadless de kit en modo de solo lectura (no necesita escribir: inkwell aplica las acciones que devuelve), en una carpeta temporal vacía y con un entorno mínimo. Claude, además, corre sin herramientas (--disallowedTools Read,Grep,Glob), sin MCP, sin los ajustes del usuario y con CLAUDE_CODE_SAFE_MODE=1. Recibe el árbol del espacio y el contenido (recortado) de las páginas que tienen que ver con la sesión. Un cambio a una página puede reescribir una sección, agregar una nueva después de otra, o reescribir la página completa.

No escribe nada en bita ni en las notas por entrada. Cada revisión deja una línea, solo con metadatos y códigos, en ${XDG_DATA_HOME:-~/.local/share}/inkwell/autodoc.log (%LOCALAPPDATA%\inkwell\autodoc.log en Windows).

Las propuestas se revisan y se aceptan así:

inkwell autodoc ls                                  # ramas autodoc/* pendientes y páginas nuevas por aprobar
inkwell autodoc approve 12                          # la página nueva 12 ya puede subir a Confluence
inkwell branch diff autodoc/abcdef12-4              # qué cambiaría
inkwell branch apply autodoc/abcdef12-4 --commit <sha>
inkwell branch drop autodoc/abcdef12-4              # descartarla

Se configura en ~/.config/inkwell/config.json (%APPDATA%\inkwell\config.json en Windows); estos son los valores por omisión:

{
  "autodoc": {
    "enabled": true,
    "agent": "claude",
    "model": null,
    "models": {},
    "maxChars": 40000,
    "minChars": 600,
    "maxActions": 5,
    "timeoutSeconds": 240
  }
}

agent es uno de claude, codex, gemini, antigravity u opencode. Si no está instalado se usa el primero que sí (Claude primero) y el log deja una línea warn:agent-fallback-<pedido>-to-<usado>. INKWELL_<AGENTE>_BIN vale también para el agente elegido por fallback. model es solo de Claude (haiku si queda vacío). Para los demás se usa models.<agente> (por ejemplo "models": { "codex": "gpt-5" }) y, si no está, el modelo por omisión de ese agente.

Cuando el agente falla, el log dice skip:model-<código>. Los códigos son los mismos que antes: exit-<n> (o exit-signal), is-error, not-json, timeout, spawn (no hay agente o no arrancó) y empty. Hay uno nuevo, prompt-too-long, para cuando el sistema rechaza la línea de comandos. Ya no existen too-large ni unsafe-argument: kit arma los argumentos en Windows.

Para apagarlo, "enabled": false, o INKWELL_NO_AUTODOC=1 en el entorno del agente. INKWELL_<AGENTE>_BIN (INKWELL_CLAUDE_BIN, INKWELL_CODEX_BIN…) cambia el binario de ese agente.

Venir desde bita

Hasta la versión 0.18, bita guardaba la documentación junto con el tiempo. inkwell se queda con las páginas, el backlog, los enlaces, la sincronización con Confluence y, desde la 0.2.0, las notas por entrada.

inkwell migrate --from-bita --dry-run   # cuenta lo que traería, sin escribir
inkwell migrate --from-bita             # lo trae
inkwell migrate status --json           # { migrated, bitaDatabase, migratedAt }
inkwell migrate notes --from-bita [--dry-run]   # solo las notas por entrada

La migración:

  • convierte los proyectos de bita en espacios (external_ref = bita:project:<id>), con su clave y su configuración de Confluence;
  • copia las páginas con sus ids, los enlaces a entradas e issues como referencias opacas (bita:entry:<id>, jira:<KEY>) con la duración ya medida, los enlaces externos, el backlog con sus claves y el mapa de Confluence;
  • clona el repositorio de documentación de bita para conservar toda la historia y las ramas de propuestas, trae las ediciones sin commitear de las páginas y de las notas, e indexa las notas por entrada;
  • es idempotente: una segunda corrida no hace nada. Se niega a mezclar si inkwell ya tiene páginas o backlog.

Quien migró con inkwell 0.1.x trajo las páginas pero no las notas por entrada. inkwell migrate notes --from-bita las copia ahora: lee la tabla entry_docs y los archivos del repositorio de bita, los escribe con la misma ruta en el de inkwell, los indexa y hace un solo commit. Se puede repetir (cuenta las que ya están como already), no pisa un archivo distinto que ya esté en la misma ruta (lo reporta como conflict) y no borra nada de bita.

Lee la base de bita de BITA_DB_PATH (o ~/.local/share/bita/bita.db) y el repositorio de BITA_DOCS_DIR (o docs junto a la base); --bita-db y --bita-docs las cambian.

Desarrollo

pnpm install
pnpm typecheck
pnpm test
node src/bin/inkwell.ts help

Licencia

MIT