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

bd-manager

v0.6.23

Published

Multi-site Brilliant Directories content manager — Node.js CLI + local web dashboard for git-versioned pull/push of widgets, pages, post types, menus, forms and more across multiple BD sites.

Readme

bd-manager

Multi-site Brilliant Directories content manager — Node.js CLI + local web dashboard for git-versioned pull / push of widgets, pages, post types, menus, forms and more across multiple BD sites.

Each BD site lives as its own independent git repo (with main + bd-live branches). bd-manager orchestrates the round-trip between local git working tree and the live BD database: pull live state → edit locally → commit → push to BD live → resync generated fields back to disk → push to GitHub. All via a single auto-installed PHP bridge widget on each BD site.

Auth + per-site secrets

bd-manager authenticates users through a separate Cloudflare Workers backend — source lives in the private Devorahub-Metaviz/bd-manager-worker repo. The worker:

  • Runs GitHub Device Flow for bd login
  • Stores each BD site's site_url + api_key encrypted (AES-GCM) at rest in D1
  • Returns decrypted credentials to CLI users who have at least read access to the matching GitHub repo
  • Keeps a webhook-driven mirror of org membership and repo access so permission changes propagate in seconds

End users don't need to clone or deploy the worker — it runs at https://bd.devorahub.com (or whichever domain your org has deployed it to). Just bd login <worker-url> once and the CLI handles the rest.

Only admins who operate the backend clone the worker repo to deploy / update it.


Table of contents


Why

Brilliant Directories stores all customizable content — widgets, page templates, post types, menu structures, form schemas, email templates, SEO templates, settings, labels, redirects, webhooks, plugins, subscriptions — inside its MySQL database. Edits are made through the admin UI, which means:

  • No version control
  • No local IDE editing with syntax highlighting
  • No code review
  • No diff / blame / rollback
  • No way to replicate a change across multiple sites
  • Accidental deletions are permanent

bd-manager treats each BD site's content as source code. You snapshot the live database into a clean directory structure on disk, edit with your normal tools, commit to git, and push changes back to BD with a full audit trail.

Features

Multi-site from day one

  • bd sites, bd push --all, per-site dashboards in the web UI
  • Each site is a separate git repo with its own remote
  • Sequential multi-site push that halts safely on merge conflict and resumes from where it left off

Round-trip fidelity

  • Pull: 20 BD tables → 20 section folders, code fields split into individual files (.php, .html, .css) with js-beautify formatting
  • Push: local files → single-column UPDATE (per-file) or full-item UPDATE / INSERT via template-fill
  • Resync: after every push, auto-generated fields (widget_id, date_updated, short_code, revision_timestamp) land back in local meta.json

Git workflow built in

  • main branch = your working tree
  • bd-live branch = snapshot of what BD live looked like before your current push (for conflict detection)
  • Auto pull-rebase on push main to avoid non-fast-forward rejections
  • bd remote-sync --force for clean-slate alignment when repos diverge

Diff-first UI

  • GitHub-style side-by-side diffs via diff2html
  • Two compare modes: git (local vs HEAD) and live (local vs BD column)
  • File-state tags: NEW, DELETED, CHANGED, RENAMED

Drift detection

  • One SELECT id, name FROM <table> per pushed section compares live rows to local dirs
  • Surfaces only_on_live, only_on_local, id_mismatch, duplicates_on_live, duplicates_on_local
  • One-click fixes: pull-live, push-local, delete-live, delete-local, dedupe-live

Safety rails

  • Local directory deletion requires explicit confirm_delete: true on the server side
  • Browser confirm() prompt + red warning banner + red "🗑 Delete on live" button
  • Auto-commits are path-scoped (git add -- <paths>), never git add -A, so unrelated edits aren't swept into a push commit
  • Refuses to push when the repo is mid-merge / mid-rebase (would otherwise silently fail)

Cross-platform Node only

  • Pure Node.js 18+ (ES modules, native fetch, native AbortController)
  • No bash / PowerShell / Python dependencies
  • Works the same on Windows / macOS / Linux

Install

Prerequisites: Node.js 20+ and git. That's it. Verify:

node --version   # v20.x or newer
git  --version

Then install bd-manager globally via npm:

npm install -g bd-manager
bd --help

That's the whole install. No repo to clone, no npm link, no environment setup. The bd command is now available in every terminal.

Why -g? The -g flag puts bd on your system PATH. If you skip it, bd is only available via npx bd from inside the install directory.

Permissions: on macOS / Linux you may need sudo npm install -g bd-manager if your npm global directory isn't writable. The preferred fix is to configure npm to use a per-user prefix so sudo is never needed.

First-time setup (60 seconds)

Three commands from any terminal, any directory. bd-manager remembers everything after the first run.

# 1. Sign in with GitHub
bd login
#   → asks "Worker URL" with a default (press Enter to accept)
#   → opens a browser for the GitHub device-flow code
#   → stores the session token in your OS keychain

# 2. Decide WHERE you want the sites, cd there, and clone them all
mkdir ~/bd && cd ~/bd           # or any path you prefer — e.g. cd ~/projects/bd
bd setup --clone-all
#   → uses the CURRENT directory as the sites root (override: bd setup --clone-all /other/path)
#   → clones bdtools.directoryup.com, pakbusinessworld.com, etc. here
#   → configures each with BD credentials fetched from the worker
#   → saves this location to your user config so `bd web` finds it from anywhere
#   → you never type a site URL or API key by hand

# 3. Open the multi-site dashboard
bd web
#   → http://localhost:4174

That's it. After step 2, every bd command works from any terminal, any directory, any drive — the tool remembers the sites root, the worker URL, and your session in a user-scoped config file (not inside the install dir).

Where bd-manager stores things

| Thing | Location | Why there | |---|---|---| | Your site repos | Whatever directory you ran bd setup --clone-all in (or explicit bd setup --clone-all <path>) | Your choice — stored in config after first run | | Config (sites root, default site, web port, worker URL) | Windows: %APPDATA%\bd-manager\config.jsonmacOS: ~/Library/Application Support/bd-manager/config.jsonLinux: ~/.config/bd-manager/config.json | Platform-standard user config dir — survives npm reinstalls, hidden from normal file browsing | | Session token | OS keychain via keytar (Windows Credential Manager / macOS Keychain / libsecret) | OS-gated; never in a flat file on supported platforms | | Runtime / cache | <config-dir>/runtime/ | Scratch space for long operations |

Nothing important is stored inside the bd-manager install directory, so npm update -g bd-manager or reinstalling the package never wipes your setup.

Subsequent runs

Once setup has run once:

bd web          # open dashboard — session cached, root dir remembered, no re-login
bd whoami       # show who you're signed in as + which sites you can access
bd sites        # list every registered site + last-sync info
bd sync         # force-refresh your session from the worker (picks up new perms immediately)
bd status --all # pending changes across every site
bd push --all   # push every dirty site, halt safely on conflicts
bd logout       # only if switching GitHub accounts

Sessions roll on a 30-day sliding TTL. If yours expires or gets revoked by an admin, any CLI command prompts you to sign in again and retries — no lost work.

Moving your sites to a different folder

Just run bd setup --clone-all /new/path again — or edit sites_root in the config file directly:

{
  "sites_root": "/absolute/path/to/new/location",
  ...
}

Then mv / rsync your existing site dirs over to the new path. bd-manager picks it up on the next command.

How the worker URL works

bd login with no argument suggests https://bd.devorahub.com as the default — the Devorahub-Metaviz deployment. Press Enter to accept, or type a different URL if your org runs its own worker.

Your choice is saved; subsequent bd login / bd sync / etc. reuse it silently.

Override for a fresh start:

bd login https://your-worker.example.com   # explicit URL, replaces stored one
BD_WORKER_URL=https://… bd login           # env var takes effect on first run only

Only your admin does this once — not you

bd-manager connects to a Cloudflare Workers backend that holds every site's encrypted BD credentials, mirrors GitHub org access, and runs the admin UI. Backend setup (GitHub App, deployment, secrets, org wiring) is a one-time task done by an admin from the private Devorahub-Metaviz/bd-manager-worker repo. Regular CLI users never touch it.

Adding a new BD site

Admin adds the repo + BD credentials at https://bd.devorahub.com/admin. Within seconds a webhook updates every logged-in user's session. On your next bd web load (or run bd sync immediately), the new site appears in the sidebar and bd setup --clone-all picks it up.

If you prefer to do everything by hand:

bd add          # interactive: name, site URL, API key, optional git remote
                # (uses local creds instead of the worker; fallback for air-gapped use)

Directory layout

bd-manager lives in its own directory. Site content lives as siblings. Nothing is ever inside bd-manager's repo except the tool itself.

<parent-root>/
├── bd-manager/                     ← this tool, its own git repo
│   ├── bin/bd.js                   ← CLI entry
│   ├── server/                     ← Hono + Preact/htm + SSE dashboard
│   ├── src/                        ← workflow modules, site-aware
│   ├── docs/                       ← BD API reference, DB schemas
│   ├── php/bd-db-bridge.php        ← bridge widget source (auto-deployed to each site)
│   ├── .bd-manager.json            ← tool config
│   └── package.json
│
├── bdtools.directoryup.com/        ← site 1, separate git repo
│   ├── .bd-site.json               ← site metadata (committed)
│   ├── .env.local                  ← BD_SITE_URL + BD_API_KEY (git-ignored)
│   ├── .gitignore
│   ├── widgets/                    ← per-record dirs, code + meta.json per item
│   ├── pages/
│   ├── post-types/
│   ├── menus/, forms/, email-templates/, sidebars/, subscriptions/, seo-templates/
│   ├── settings/website_settings.json, settings/design_settings.json
│   ├── labels/labels.json
│   ├── redirects/, webhooks/, plugins/, categories/, workflows/, widget-placements/
│   └── .git/
│
├── anothersite.example.com/        ← site 2, another separate git repo
└── thirdsite.example.com/          ← site 3, ditto

A directory is recognized as a registered site only if it contains .bd-site.json — an .env.local alone is not enough. This prevents any stray folder with a BD-looking env file from being treated as a site.

CLI reference

Site management

| Command | Purpose | |---|---| | bd sites | List every registered site | | bd add [<name>] | Interactive: create a new site directory, init git, install bridge widget, snapshot, first commit, create bd-live, push both branches. Idempotent — safe to run on an existing site. | | bd update <name> [--rename=…] [--remote=…] [--url=…] [--key=…] | Non-destructive. Each flag touches only what you provide. --rename renames the directory on disk. | | bd remove <name> [--purge] | Drop from registry. --purge also deletes the directory. | | bd default <name> | Set default site (used when <site> is omitted) | | bd setup [<name>] | Re-runnable bootstrap — fills in missing pieces if present, adds if absent | | bd remote <name> <url> | Set / change a site's git remote | | bd remote-sync [<site>…] [--force] | Reconcile local git with origin. Fetch → fast-forward → rebase → (with --force) force-push. Auto-aborts any pending rebase/merge. |

Data flow

| Command | Purpose | |---|---| | bd snapshot [<site>] [--c=N] | Full fresh snapshot (concurrency N, default 30). --all for every site. | | bd pull [<site>] [<section>] [<id\|name>] | Pull. Any omitted arg triggers an interactive picker. | | bd push [<site>] [-m "<msg>"\|"<msg>"] | Push one site. Halts on conflict. If no site given, scans all sites — if exactly one is dirty, pushes it; if several, lists them and asks for --all. "Dirty" = working-tree changes or commits already on main that haven't been mirrored to bd-live yet (e.g. after a manual git commit). Custom commit message via -m / --message or as a bare positional. Deletions prompt for confirmation unless --yes / -y is passed. | | bd push --all [-m "<msg>"] | Push every dirty site sequentially. Halts at the first conflict; other dirty sites stay for after you resolve and re-run. | | bd push [<site>] --dry-run (-n) | Preview the push without mutating anything. Lists each item that would be pushed, each pending deletion, and each branch that would go to origin. Useful before --all. | | bd rollback [<site>] [--count=N] [-m "<msg>"] | Revert the last N commits on main (default 1) and push the revert through the normal flow to bd-live + BD live + origin. Creates new revert commits — safe, no history rewrite. Prompts for confirmation unless --yes. |

Push message forms (all equivalent for providing a custom message):

  • bd push -m "fix: your message"
  • bd push "fix: your message" (bare positional auto-detected as message if it doesn't resolve to a registered site)
  • bd push <site> -m "fix: your message"
  • bd push <site> "fix: your message"
  • git add -A && git commit -m "fix: your message" && bd push (manual commit, bd push picks it up, mirrors to bd-live + live)

Inspection

| Command | Purpose | |---|---| | bd status [<site>\|--all] | Local changes grouped by section/item | | bd warnings [<site>\|--all] | Config / git / bridge health | | bd sections | List all 20 known sections | | bd test [<site>] [--get\|--crud\|--all\|--cleanup] | Live smoke-test via the bridge | | bd test [<site>] --creation [--keep] | Create fresh local items per section, push, verify live + resync | | bd test [<site>] --recreation | After manually deleting items from BD admin, edit one code part locally, push, verify re-creation |

Web

| Command | Purpose | |---|---| | bd web [--port=N] | Launch the multi-site dashboard at http://localhost:4174 |

Run bd help for the live version.

Web dashboard

Navigation is hash-based so every page is bookmarkable and the back button works:

#/                                           Home — site cards with per-site stats
#/all                                        All-sites aggregate + Push all
#/site/<name>                                Site overview — section tiles
#/site/<name>/section/<key>                  Items list + drift panel
#/site/<name>/section/<key>/item/<item>      Item viewer — file tree + code preview
#/site/<name>/changes                        Pending changes as rich cards
#/site/<name>/warnings                       Health panel
#/site/<name>/activity                       Live log

Sidebar — site list with pending-change badges, Add Site button, per-site nav.

Home — rich cards per site: total local items, pending count, top sections, remote status.

Overview — tiles for each section with local item count + pending count. Click a tile to drill in.

Section view — searchable items list. Simple-section rows are expandable to show secondary fields. Per-record rows navigate to the item viewer. "Scan drift" button + drift panel with action buttons.

Changes tab — one card per item with pending files. Click anywhere on a card → 90vw × 90vh diff modal (side-by-side or unified). Per-file chips with NEW / DEL / MOD badges, compare button, push-file button. State banners for deleted / new items.

Compare modal — two modes:

  • git: local working tree vs last commit (for change cards)
  • live: local file content vs BD live column (for item pages)

File headers tag each file as ADDED, DELETED, CHANGED, or RENAMED.

Add / Edit site modal — 4 inputs (directory name, site URL, API key, optional git remote). URL hostname is auto-used as default directory name.

Activity log — live SSE feed with a 300-item ring buffer on the server, so activity persists across page reloads.

How the workflows work

Pull flow (per site)

1. ensure bd-live branch exists (create if missing)
2. checkout bd-live
3. pullSection(s) — one request per section / item via bridge SELECT
4. if uncommitted changes on bd-live: commit "pull from live … — <scope>"
5. if remote: push bd-live to origin
6. checkout main
7. merge bd-live                      ← conflicts? halt, surface them
8. if remote: push main to origin

Push flow (per site)

1. scan pushable changes = working-tree edits  ∪  commits on main not yet on bd-live
   (so a manual `git commit` still triggers the live + remote push)
2. compute reconcile plan — skip items with no local id or id missing on live
   (they'll be INSERTed by pushToLive directly, not reconciled)
3. if working-tree changes exist: git add -A && git commit
   - use -m/--message or bare positional message if provided
   - else auto-generate a detailed message listing <site> ← <sections/items/files>
   - if no working-tree changes but -m was supplied: `git commit --amend -m "<msg>"`
4. pull --rebase origin main          ← avoids non-fast-forward at step 10
5. checkout bd-live
6. pull each reconcile-scoped item from live
7. commit + push bd-live
8. checkout main, merge bd-live       ← conflicts? halt, surface them
9. pushToLive per changed item
    - id matches live → UPDATE
    - id doesn't match but name does → UPDATE (adopt)
    - neither → INSERT with template-fill
    - dir deleted + confirm_delete → DELETE (+ users_meta cleanup)
10. refresh BD cache
11. commit resync on main (new ids, date_updated, revision_timestamp)
12. push main to origin

The reconcile plan is computed from main's state before switching branches, so items that look "new or recreated" skip the bd-live pre-pull (which would otherwise write live's old state and clash with the user's intent).

Drift detection

Triggered automatically after every successful push-item for any per_record section, and on-demand via the "Scan drift" button in the Section view.

SELECT widget_id, widget_name FROM data_widgets   -- one query, no LIMIT

Classifies every row:

| Classification | Meaning | Fix actions available | |---|---|---| | matched | Both sides agree on id + name | — | | id_mismatch | Same name, different id | pull-live (adopt live's id) | | only_on_live | Row on BD, no local dir | pull-live, delete-live | | only_on_local | Dir on disk, no row on BD | push-local, delete-local | | duplicates_on_live | Same name on multiple live rows | dedupe-live (keep lowest id) | | duplicates_on_local | Same name in multiple dirs | manual (rare) |

Cost is ~1 bridge call per section scan. Scaling to 1000s of rows stays a constant cost.

Deletion

Client deletes widgets/foo/ locally → git reports all its files as D.

  • bd push via CLI: pushToLive sees the missing dir, reads the last committed meta via git show HEAD:… (falls back to HEAD^, HEAD~2), runs DELETE FROM <table> + cleans users_meta.
  • bd web or /api/sites/:n/push-item: returns HTTP 409 requires_confirmation: "delete" unless confirm_delete: true is passed. The UI detects the deletion and shows a window.confirm() before re-sending with the flag.

Fallback path — if the git history lookup fails (e.g., item was never committed), the handler queries SELECT id FROM <table> WHERE name = ? using the dir name.

Supported sections

Twenty BD tables are currently mapped. Add a new section = add one entry in src/sections.js — every workflow picks it up.

| Kind | Key | Table | Notes | |---|---|---|---| | simple | labels | website_labels | 759+ rows | | simple | website_settings | website_settings | 322 rows | | simple | design_settings | website_design_settings | 778 rows | | simple | redirects | `301_redirects` | table name needs backticks | | simple | webhooks | bd_webhooks | | | simple | plugins | plugin_records | | | simple | categories | list_professions | bridge-only (API 405s) | | simple | services | list_services | | | simple | category_groups | category_group | | | simple | data_flows | data_flows | | | simple | widget_placements | data_widgets_rel | | | per_record | subscriptions | subscription_types | | | per_record | seo_templates | list_seo_template | | | per_record | sidebars | sidebars | | | per_record | email_templates | email_templates | body.html | | per_record | pages | list_seo | 7 code files per page | | per_record | widgets | data_widgets | html.php, style.css, script.php | | per_record | post_types | data_categories | 8 code files per post type | | menus | menus | menus + menu_items | | | forms | forms | forms + form_fields | bridge-only (API is broken) |

per_record sections store each row as a folder containing code files (split by column mapping) + meta.json for all non-code fields. simple sections store the whole table as a JSON array. menus and forms have special handling for their 2-table structures.

Configuration files

bd-manager/.bd-manager.json (committed)

{
  "sites_root": "..",
  "default_site": "bdtools.directoryup.com",
  "web": { "port": 4174 },
  "concurrency": 30
}

<site>/.bd-site.json (committed to the site's own repo)

{
  "name": "bdtools.directoryup.com",
  "site_url": "https://bdtools.directoryup.com",
  "remote": "[email protected]:org/bdtools.git",
  "branches": { "main": "main", "live": "bd-live" },
  "created": "2026-04-18"
}

<site>/.env.local (git-ignored, never committed)

BD_SITE_URL=https://bdtools.directoryup.com
BD_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The bridge widget

All SQL operations on BD go through a single PHP widget — bd-db-bridge — deployed at /api/widget/get/json/bd-db-bridge on each BD site. Its source is php/bd-db-bridge.php in this repo. bd add and bd setup auto-install it via BD's widget API the first time.

Actions:

| action | Purpose | |---|---| | query | SELECT / SHOW / DESCRIBE — read-only | | execute | INSERT / UPDATE / DELETE — blocks DROP, TRUNCATE, ALTER, RENAME, GRANT, REVOKE | | tables | list all tables | | columns | describe a table |

Authentication validates the request's API key against the bd_api_keys table using plain / md5 / sha256 / bcrypt matching.

HTTP API

The local web server (bd web) exposes these endpoints. Use them from external tools or scripts.

Manager

  • GET /api/health{ ok, version, build, time }
  • GET /api/manager — tool config + sites root
  • GET /api/sections — all known section definitions

Sites

  • GET /api/sites — list of sites + pending-change counts + default
  • GET /api/sites/:name — single site summary + warnings + changes
  • GET /api/sites/:name/overview — rich overview (per-section counts, totals)
  • POST /api/sites — add site (body: name, siteUrl, apiKey, remote)
  • PATCH /api/sites/:name — update metadata (rename, url, key, remote)
  • DELETE /api/sites/:name?purge=1 — remove from registry (optionally delete directory)
  • POST /api/default{ name } — set default site

Data flow

  • POST /api/sites/:name/snapshot — kick off full snapshot
  • POST /api/sites/:name/pull — pull (body: section?, item?)
  • POST /api/sites/:name/push — full push flow for this site
  • POST /api/push-all — sequential multi-site push, halt on first conflict
  • POST /api/sites/:name/push-item — push one item (body: section, item). Returns 409 requires_confirmation: "delete" if item dir is gone; retry with confirm_delete: true.
  • POST /api/sites/:name/push-file — single-column UPDATE (body: section, item, file). Falls back to full push-item if id is missing.
  • POST /api/sites/:name/pull-item — pull one item without the full git dance

Navigation

  • GET /api/sites/:name/section/:section/items — all local items in a section with meta
  • GET /api/sites/:name/section/:section/item/:item — item meta + all file contents

Diff

  • GET /api/sites/:name/diff?path=<rel> — git-style unified diff (local vs HEAD, or vs /dev/null for untracked). Returns state: "new" | "deleted" | "modified".
  • POST /api/sites/:name/diff-live{ section, item, file } → unified diff of local file vs BD live column

Drift

  • GET /api/sites/:name/section/:section/drift — one SELECT per section, classifies all rows
  • POST /api/sites/:name/section/:section/drift/fix{ action, target } — apply one of pull-live, push-local, delete-local, delete-live, dedupe-live

SSE

  • GET /api/stream — server-sent events with replay of the last 300 events (so a page reload picks up recent activity)

Troubleshooting

git pull from the parent root fails — "not a git repository" The parent directory isn't a repo. Each site has its own .git. cd into <site>/ first, or just use bd push <site> which handles this correctly.

Push main rejected — "non-fast-forward" The remote moved ahead of you. bd push will auto pull --rebase on your next run if the histories share an ancestor. If the histories are unrelated (you reused an existing repo with different content), run once:

bd remote-sync <site> --force

bd push produces no output Your site repo is probably mid-rebase or mid-merge from an earlier aborted operation. bd now detects this and refuses to proceed silently, but older data might still be stuck. Run:

cd <site-dir>
git status
git rebase --abort  # or merge --abort / cherry-pick --abort

Browser shows old UI / old button labels The server sends no-cache headers and stamps ?v=<buildStamp> on every asset URL, but one hard refresh (Ctrl+F5) is needed to load the first post-upgrade page. The build stamp in the sidebar (Manager · v0.3.0 · build XYZ) should match GET /api/health → build — that confirms you're on fresh JS.

Can't delete a widget from the web UI The Delete button on deleted-locally cards shows a browser confirm() prompt first. After confirming, the server receives confirm_delete: true and proceeds. Without confirmation, you'll see a toast saying the request was refused (HTTP 409 by design).

Widget dir with trailing space breaks git on Windows BD sometimes stores widget names with trailing whitespace. The slug sanitizer in src/format.js (via sanitize-filename) strips these automatically on pull. If you already have offending dirs on disk, the cleanup script in our repo history (scripts_tmp_rename.mjs) shows the UNC-path rename pattern.

Development

Stack

  • Node.js 18+ (ES modules, native fetch)
  • Hono + @hono/node-server — local dashboard server
  • Preact + htm via esm.sh — zero-build UI
  • diff2html — GitHub-style diff rendering
  • chokidar — file watcher
  • dotenv — env file parsing
  • sqlstring — MySQL-safe escaping
  • sanitize-filename — cross-platform safe dir names
  • prompts — interactive CLI
  • mri — argv parser
  • js-beautify — HTML/CSS/JS formatting on pull
  • diff — unified-diff generation for live-compare

Running from source

npm install
node bin/bd.js help         # or: `bd help` after `npm link`
node bin/bd.js web          # starts the dashboard

Project structure

src/
  bridge.js           bd-db-bridge client (POST /api/widget/get/json/bd-db-bridge)
  api.js              BD REST API v2 client (used for widget create/update)
  http.js             thin fetch wrapper
  git.js              all git commands, cwd-bound to a SiteContext
  config.js           per-site env loader (.env + .env.local)
  manager-config.js   tool-level config (.bd-manager.json)
  site.js             SiteContext class + metadata read/write
  sites-registry.js   discover / resolve / set default site
  sections.js         the 20-section registry
  pull.js             per-section pullers, users_meta merge
  push-live.js        pushToLive + pushFileToLive + deletion paths
  diff.js             scanChanges (git porcelain → bySection → items)
  drift.js            computeDrift + applyDriftFix
  fs-utils.js         saveJson/readJson/splitRecord/mergeRecord
  format.js           slugify / safeDirName / js-beautify
  workflow.js         runSnapshotFlow / runPullFlow / runPushSiteFlow / runMultiSitePush
  site-actions.js     runAddSite / runUpdateSite / runRemoveSite / runSetup
  remote-sync.js      runRemoteSync (branch-by-branch reconcile)
  bridge-installer.js auto-install the bd-db-bridge widget on live
  warnings.js         per-site health checks
  interactive-pull.js 3-step picker (site → section → item)
  pool.js             concurrency limiter
  events.js           event bus + 300-item SSE replay buffer
  log.js              colored CLI output
  prompt.js           wrapping prompts lib
  paths.js            path constants
  test-runner.js      --get + --crud smoke tests
  test-creation.js    --creation test harness
  test-recreation.js  --recreation test harness

server/
  index.js            Hono app + SSE + chokidar watcher
  ui/
    index.html
    styles.css
    main.js           single-file Preact app (Home/Site/Section/Item/Changes/Warnings/Activity)

bin/bd.js             CLI entry
docs/                 BD API reference, DB schemas (read by pull logic)
php/bd-db-bridge.php  bridge widget source

Adding a new section

Open src/sections.js and append to SECTIONS:

my_new_section: {
  kind: "per_record",
  table: "bd_some_table",
  idField: "row_id",
  nameField: "row_name",
  listSql: "SELECT row_id, row_name FROM bd_some_table ORDER BY row_id",
  dir: "my-new-section",
  slugFn: "identity",
  mappings: [
    ["some_code_column", "code.php"],
  ],
  mergeMeta: true,
},

Also add it to DISPLAY_ORDER to control where it shows on the Site overview. Run bd pull <site> my_new_section and it'll create the folder.

Security notes

  • .env.local is in .gitignore by default, both for bd-manager and each site. Never commit your API keys.
  • The bridge widget blocks dangerous SQL (DROP, TRUNCATE, ALTER, RENAME, GRANT, REVOKE) at the server side.
  • All user input flowing into SQL goes through sqlstring (MySQL-compatible escaping, same lib used by mysql2).
  • The local dashboard binds to 127.0.0.1 only — it's a localhost-only tool, not a public service.
  • Dir names are sanitized via sanitize-filename before hitting disk (Windows reserved names, control chars, <>:"/\|?*).
  • push-item and push-file auto-commit only the path-scoped files they just pushed — never git add -A — so unrelated local edits aren't swept into the commit.
  • Deletions require a separate explicit confirmation (confirm_delete: true) on the HTTP path. Without it, the server returns HTTP 409.

License

MIT (or your choice — adjust as needed).