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

@tofe98/dsplug

v0.0.1

Published

dsplug v0 — gestisci plugin host DSH (add / remove / list / find / check / update)

Downloads

276

Readme

dsplug v0

CLI minima per gestire i plugin host di DeepSeek Harness, stile npx skills: invoca dsh plugin --profile <name> <add|remove> <pkg> e tiene sincronizzato $DSH_HOME/profiles/<name>/cordis.patch.yml.

Solo 6 comandi, solo plugin HOST.

Comandi

dsplug                      mostra header + lista comandi (fra due righe blu)
dsplug help                 header + Commands/Options (o -h / --help)
dsplug add <pkg|path> [--profile <name>] [--no-dsh]
dsplug remove <pkg> [--profile <name>] [--no-dsh]
dsplug list         [--profile <name>]
dsplug find [query] [--local] [--category <name>] [--index <path>]
dsplug check        [--profile <name>] [--index <path>]
dsplug update       [--profile <name>] [--index <path>] [--no-dsh]
  • --profile <name> — profilo DSH da toccare (default: web).
  • --no-dsh — non invocare il binario dsh: solo edit del patch file (usato dai test; nessuna dipendenza da rete o dal binario dsh).
  • --index <path> — indice plugin custom per find, add, check e update (default: index.json bundled nel package; pensato per test e per un futuro indice remoto). In find, passare --index implica --local (un indice custom è per definizione locale).
  • --local (solo find) — usa l'indice statico index.json invece del catalogo remoto (comportamento storico di find, invariato).
  • --category <name> (solo find) — filtra il catalogo remoto per categoria: substring case-insensitive sul nome della categoria (es. --category ui matcha UI Enhancements).
  • --no-color — disattiva i colori ANSI (oppure env NO_COLOR; vedi Colori).
  • Il binario invocato si può sovrascrivere con l'env DSPLUG_DSH (default dsh).

Contratto

  1. add — PRIMA di installare stampa l'avviso di sicurezza su stderr (vedi Sicurezza), poi esegue dsh plugin --profile <name> add <pkg>, poi appende la riga al patch file se non già presente e registra la entry nel lock (id -> { pkg, version }, con la version letta dall'indice se il plugin vi compare, altrimenti 'unknown'). Idempotente (seconda esecuzione = no-op). Guardia: il package viene normalizzato prima del check (via stripVersion, vedi sotto) e se il nome normalizzato compare nell'indice con "bundled": true (plugin già incluso nell'installazione DSH), add rifiuta con messaggio su stderr + exit 1, senza invocare dsh e senza toccare patch o lock. dsh riceve il package come digitato (con eventuale version spec, es. [email protected]); patch e lock usano il nome normalizzato. Path locale: se <pkg> è un path (inizia con ., /, ~ o file:), add non lo tratta come un nome npm: risolve il nome reale dal package.json del path (campo name) e usa quel nome per la guardia bundled, per l'id e per la riga del patch; dsh riceve il path originale (dsh plugin ... add <path>). stripVersion non si applica al path. Se il package.json locale non esiste o non ha name → errore chiaro su stderr + exit 1, senza toccare nulla (vedi Installare da un path locale).
  2. remove — elimina le due righe del patch per id (trova - id: <id> e rimuove quella riga + la successiva name:), pulisce la entry dal lock, poi esegue dsh plugin --profile <name> remove <pkg>. Idempotente (id assente = no-op). Il match è sul solo id, quindi funziona anche con spelling del package diverso da quello usato in add (es. add @scope/pkgremove pkg: stesso id pkg).
  3. list — elenca gli id dei plugin del profilo leggendo cordis.patch.yml (un id per riga, nell'ordine del file).
  4. finddefault: inventario REMOTO CURATO del repo awesome-dsh-plugin/awesome-dsh-plugin (vedi Inventario remoto): stampa PRIMA l'header (fra le due righe blu), poi legge il README.md del repo (solo la sezione tra i marker <!-- BEGIN PLUGINS -->/<!-- END PLUGINS -->), nell'ordine del README (deterministico), filtra per query (substring case-insensitive su name O description O category) e per --category (substring case-insensitive sulla categoria), e stampa la riga Install with dsplug add <owner/repo> seguita, per ogni plugin, da <name> <description> (name in blu/bold, description in grigio dim su TTY) e sotto └ <url>, con una riga vuota fra le entry. Query assente (o solo spazi) = tutti i plugin. Nessun match = nessuna riga, exit 0. Con --local (o --index <path>) legge l'indice statico index.json (o l'indice custom), filtra per query e stampa <name> - <description> una riga per plugin, nell'ordine dell'indice (niente istruzione, niente URL). Errore di rete/API: messaggio chiaro su stderr + exit != 0, con il suggerimento --local per usare l'indice bundled.
  5. check — legge lock + indice e, per ogni plugin del lock (ordine del lock), confronta version (lock) vs version (indice). Una riga deterministica per plugin, usando l'id (come list):
    • <id> <lock-version> -> <index-version> (outdated) — l'indice offre una versione più nuova;
    • <id> <version> (up to date) — versioni uguali, o lock più nuovo dell'indice;
    • <id> <version> (not in index) — il plugin non compare nell'indice. Nessun plugin nel lock = stdout vuoto. Exit 0 (il check non modifica nulla).
  6. update — per ogni plugin del lock outdated ri-esegue add con il pkg registrato nel lock (dsh plugin ... add + riga nel patch + lock aggiornato alla version dell'indice), stampando <id> (<lock-version> -> <index-version>). Idempotente: se nulla è outdated = no-op (nessuna riga, exit 0). I plugin not in index non sono outdated e non vengono toccati. Supporta --no-dsh (solo aggiornamento di patch/lock, senza re-installare via dsh).

Colori

Colori ANSI a zero dipendenze (src/ansi.js), stile npx skills: attivi solo quando stdout è un TTY — in pipe (script, test, CI) l'output è sempre nudo e deterministico. Tre modi di controllarli, in ordine di priorità:

| controllo | effetto | |---|---| | --no-color (flag CLI) | colori SPENTI (vince su tutto) | | FORCE_COLOR=1 (env) | colori ACCESI anche in pipe (FORCE_COLOR=0 = spenti) | | NO_COLOR (env, anche vuoto) | colori SPENTI (convenzione no-color.org) |

  • Banner (dsplug, dsplug help, dsplug find): ASCII art dsPLUG renderizzata da figlet (font ANSI Shadow, gli stessi caratteri box-drawing del banner SKILLS di npx skills), gradiente di blu DeepSeek — true-color #4D6BFE con interpolo #2F51E0 → #9CACFF se il terminale lo supporta (COLORTERM=truecolor|24bit), altrimenti fallback 256-color (38;5;69#4D6BFE). Le due righe blu (in cima e in fondo) sono larghe quanto l'art (83 colonne).
  • find: name in blu/bold, description in grigio dim (\x1b[90m); nel find remoto la riga è <name> <description> (stessa formattazione di formatEntry) e l'url sotto └ <url> è nudo.

Inventario remoto (awesome-dsh-plugin)

dsplug find (default) legge il README.md del repo curato awesome-dsh-plugin/awesome-dsh-plugin.

Via di fetch (scelta documentata): si usa la GitHub Contents API, NON raw.githubusercontent.com:

https://api.github.com/repos/awesome-dsh-plugin/awesome-dsh-plugin/contents/README.md
  • l'API risponde con un JSON stabile { content: <base64>, ... } in una sola richiesta, senza redirect e senza token per repo pubblici (rate limit 60 req/h per IP);
  • raw.githubusercontent.com è una CDN che rate-limita in modo aggressivo per IP (misurato: HTTP 429 Too Many Requests su IP condiviso) e richiede di seguire una catena di redirect.

Il campo content (base64) si decodifica e il testo si parsa come README.

Parsing: solo la sezione tra i marker <!-- BEGIN PLUGINS --> e <!-- END PLUGINS --> (match per substringa: nel README reale il marker di apertura è spezzato su due righe, <!-- + newline + BEGIN PLUGINS -->, ed entrambe le forme funzionano). Formato della sezione:

### <Categoria>

- [<name>](<url>) - <description>

ogni heading ### <Categoria> apre un blocco di entry - [<name>](<url>) - <description>, da cui si estrae { name, url, category, description }. Le righe che non sono né heading né entry (blank, commenti HTML, testo libero) vengono ignorate. L'ordine è quello del README (curato, deterministico — nessun sort per stelle).

Pipeline di find:

  1. fetchsrc/awesome.js (fetchAwesomeReadme): usa la fetch globale di Node (node >= 18). Unico punto di I/O: iniettabile ({ fetchImpl }) per i test, più un hook env per i test del binario (DSPLUG_AWESOME_FIXTURE=<path> = testo del README letto da un file, nessuna rete).
  2. filtro categoria (--category <name>) — substring case-insensitive sulla categoria (es. --category ui matcha UI Enhancements).
  3. filtro query — substring case-insensitive su name O description O category (query vuota o solo spazi = nessun filtro).
  4. output — la riga Install with dsplug add <owner/repo>, poi per ogni plugin due righe: <name> <description> (name blu/bold, description grigio dim su TTY) e sotto └ <url> (nuda), con una riga vuota fra le entry.

Errori: rete irraggiungibile, HTTP != 2xx o risposta malformata → messaggio chiaro su stderr + exit != 0, con il suggerimento --local per usare l'indice bundled senza rete. find --local è completamente offline.

Sicurezza

Installing a plugin runs third-party code on your machine with your own permissions — it can read your files, use your credentials, and reach the network. Being on a list is not a security review: check the source before you install.

La costante SECURITY_WARNING (src/security.js, un'unica fonte di verità) è mostrata in due punti:

  1. dsplug add (CLI) — su stderr, prima di installare;
  2. welcome/help (dsplug / dsplug --help) e questo README — blocco avviso nell'header / sezione Sicurezza.

Indice plugin (index.json)

Indice curato bundled con dsplug: array JSON di { "name", "description", "type", "bundled", "version" }. type è "host" (v0 supporta solo plugin host; l'id non è memorizzato — si deriva dal name con sanitizeId, vedi sotto). bundled è opzionale (default false): true = plugin già incluso nell'installazione DSH, che add rifiuta di installare una seconda volta. version è opzionale ma, se presente, deve essere una stringa semver (major.minor.patch, con pre-release/build opzionali): version malformata = errore chiaro (stderr + exit 1). È la fonte di verità per check e update. Seed reale: i tool host @deepseek-ai/dsh-tool-* attuali (tutti bundled: true, version 0.1.0-rc.6 letta dai loro package.json reali), più la fixture dsplug-fixture (bundled: false, version 0.0.0, plugin host minimale in ../fixture-plugin/, per l'e2e reale).

Regola dell'id (documentata)

id = nome del package dopo l'ultimo /, senza i caratteri @ (lo scope @owner/ viene eliminato: l'id di DSH è il nome nudo del plugin).

| package | id | |---|---| | @deepseek-ai/dsh-tool-todo | dsh-tool-todo | | dsh-tool-todo | dsh-tool-todo | | @scope/pkg | pkg |

Normalizzazione della version spec (stripVersion)

Prima di ogni uso (guardia bundled, derivazione id, riga del patch, lock) il package viene normalizzato togliendo l'eventuale version spec. Regola: il separatore di versione è la prima @ a posizione > 0 (la @ a posizione 0 è lo scope npm e va conservata).

| package | normalizzato | |---|---| | [email protected] | pkg | | @scope/[email protected] | @scope/pkg | | @scope/pkg | @scope/pkg (invariato) | | pkg | pkg (invariato) |

La riga scritta/rimossa è esattamente due righe:

- id: dsh-tool-todo
  name: '@deepseek-ai/dsh-tool-todo'

Installare da un path locale

add accetta anche un path locale (una directory con package.json, o il file package.json stesso) al posto di un nome npm. Il path si riconosce dal prefisso: . (./x, ../x, .), / (path assoluto), ~ (~/x, ~) o file: (file:///...). Il nome reale si risolve dal campo name del package.json; quel nome guida la guardia bundled, l'id e la riga del patch, mentre dsh riceve il path come digitato:

dsplug add /path/to/my-plugin --profile web
# 1. legge /path/to/my-plugin/package.json -> name: 'my-plugin'
# 2. `dsh plugin --profile web add /path/to/my-plugin`  (path originale)
# 3. patch: - id: my-plugin
#             name: 'my-plugin'
# 4. lock:   my-plugin -> { pkg: 'my-plugin', version: <indice|unknown> }

Errori chiari (stderr + exit 1, nulla scritto): path inesistente, file che non è una directory né un package.json, package.json JSON invalido, o package.json senza campo name. La guardia bundled si applica al nome risolto (un plugin locale il cui package.json dichiara il nome di un plugin bundled viene rifiutato); stripVersion non viene applicato al path.

Lock file (dsplug-lock.json)

dsplug tiene un lock JSON accanto al patch ($DSH_HOME/profiles/<name>/dsplug-lock.json) con le righe che ha scritto lui stesso:

{
  "plugins": {
    "dsh-tool-todo": { "pkg": "@deepseek-ai/dsh-tool-todo", "version": "0.1.0-rc.6" }
  }
}
  • add registra/aggiorna la entry (id -> { pkg, version }), con la version letta dall'indice se il plugin vi compare, altrimenti 'unknown'; remove la elimina per id.
  • Migrazione: se il lock non esiste ma il patch contiene blocchi nel formato scritto da dsplug, il lock viene ricostruito leggendo il patch al primo add/remove (le entry esistenti sopravvivono; il patch non ha versioni, quindi le entry migrate partono da version: 'unknown'). Formato non-dsplug (commenti, config: manuali, name: senza virgolette) non viene registrato.
  • Lock assente + patch vuoto = lock { "plugins": {} }; JSON invalido o forma errata → errore chiaro su stderr + exit 1 (mai corretto da soli).
  • check/update leggono il lock così com'è (mai scritto da loro).

Confronto versioni (compareSemver)

src/semver.js implementa un confronto semver minimale a zero dipendenze: split su . e confronto numerico parte per parte (major/minor/patch; parti mancanti = 0). Limite documentato: i suffissi pre-release/build (-rc.6, +build) non sono interpretati semanticamente — si confronta solo la cifra iniziale di ogni parte, quindi due pre-release diverse vengono ordinate solo approssimativamente e 0.1.0-rc.6 risulta diverso da 0.1.0. Versioni identiche (stessa stringa) confrontano sempre uguali, quindi il caso reale di check (lock == indice) è corretto. 'unknown' si comporta come 0.0.0. L'indice valida version con isSemver (src/index.js): forma major.minor.patch con pre-release/build opzionali.

Edit del patch file

  • Solo edit testuale chirurgico (appendi/rimuovi righe): mai riscrivere il file con un parser YAML, per preservare commenti e tag !!js.
  • Se il file non esiste, viene creato con contenuto [] (lista YAML vuota); il blocco viene poi appeso in coda.
  • $DSH_HOME è rispettato (default ~/.dsh); il file gestito è $DSH_HOME/profiles/<name>/cordis.patch.yml.
  • remove toglie per id le due righe scritte da dsplug; eventuali righe aggiuntive oltre quelle due (es. un config: scritto a mano) non vengono toccate (limite documentato).

Wart noti (v1)

  1. list mostra tutte le righe - id: del patch, incluse quelle NON scritte da dsplug (aggiunte a mano o da dsh plugin add diretto): list legge il file così com'è, senza distinguere la provenienza. Il lock file registra solo le righe dsplug: un futuro list può filtrare su di esso.

Stack

  • Zero framework CLI: parsing manuale di argv.
  • Dipendenze runtime: solo figlet (banner); il resto è stdlib — node:fs, node:os, node:path, node:child_process, node:url, e la fetch globale (node >= 18) per l'inventario remoto (src/awesome.js). Colori e banner sono ANSI puro (src/ansi.js, src/banner.js).
  • Test con il runner built-in node --test (niente vitest, niente install); i test dell'inventario remoto non toccano mai la rete (fetch fake iniettato in unit, fixture del README via env DSPLUG_AWESOME_FIXTURE nei test del bin).

Test

node --test            # oppure: npm test / pnpm test

Suite: unit (id, stripVersion, validazione input, compareSemver, parsing/ validazione index — incluso bundled e version — matching query, logica pura di check/update, isLocalPath + resolveLocalPkgName per i path locali, isGitSpec + gitSpecId per gli spec git-hosted, colori ANSI e banner: gray/blue/boldBlue/gradientBlue, priorità NO_COLOR/FORCE_COLOR/--no-color, bannerLines/renderBanner, formatEntry colorata) · inventario remoto curato (src/awesome.js: estrazione della sezione tra i marker BEGIN PLUGINS/END PLUGINS — inclusa la forma spezzata del README reale — con errore chiaro se un marker manca; parsing di categorie/entry { name, url, category, description }; filtro query substring case-insensitive su name/description/category; filtro --category; formatFindEntry <name> <description> + └ <url>; fetchAwesomeReadme con fetch iniettabile — mai rete nei test — e hook env DSPLUG_AWESOME_FIXTURE; CLI del bin: header + Install with dsplug add <owner/repo> + entry, ordine del README, filtro query, --category, --local invariato, errori remoto con exit != 0) · integrazione su fixture con DSH_HOME temporaneo e --no-dsh (contenuto corretto + idempotenza) · lock file (path, read/write, migrazione dal patch, ciclo add/remove con version) · CLI (spawn del bin, output deterministico, exit code, guardia anti bundled in add, add con path locale e con spec git (github:/owner/repo/&path:) che passa lo spec verbatim a dsh e deriva il nome reale, remove per id con spelling diverso, check deterministico, update idempotente, dsplug senza argomenti = header, help/-h/--help, --no-color senza escape code, security warning integrale su stderr in add) · errori (input invalido, path locale senza package.json o senza name, version malformata nell'indice, lock malformato → messaggio + exit ≠ 0) · fixture (fixture-plugin/ package valido

  • modulo ESM).

Come eseguire l'e2e reale

tests/e2e.test.js gira SOLO se DSPLUG_E2E=1 E DSPLUG_DSH punta al binario dsh reale. Il bersaglio è la fixture locale (../fixture-plugin/, con name: 'dsplug-fixture'): dsplug add riceve il path della fixture, risolve il nome dal suo package.json e invoca il dsh reale. Flusso verificato dal test: DSH_HOME temporaneo → dsplug add <path> --profile web → il patch file contiene la riga dsplug-fixture<dsh> --dump-config mostra la riga → pulizia del temporaneo.

DSPLUG_E2E=1 DSPLUG_DSH=/path/to/dsh node --test tests/e2e.test.js

Senza questi env il test risulta skipped (mai fallito); se dsh non è disponibile a runtime, il test si auto-skippa con messaggio chiaro.