Imported from xristos3490/wp-hooks-graph (
packages/cli/AGENTS.md). Install upstream withnpx skills add xristos3490/wp-hooks-graph --skill cli. Copyright stays with the author.
HooksGraph CLI (packages/cli/)
Node ES-module shim that ships as the @hooksgraph/hooksgraph
npm package. Wraps the PHP parser (packages/parser/) and the static
viewer build (packages/viewer/) into one binary, hooksgraph.
The shim is the single source of truth for parsed-vs-codebase storage routing and
PHP/viewer path resolution. Cli\Runner on the PHP side stays subcommand-agnostic and
just honors HOOKSGRAPH_OUTPUT_DIR.
Layout
package.json @hooksgraph/hooksgraph. type=module. bin: hooksgraph → bin/hooksgraph.js.
`files` whitelists bin/, php/, dist/, README, LICENSE for npm publish.
No runtime deps — Node stdlib only.
bin/hooksgraph.js The entire shim. Shebang-executable (#!/usr/bin/env node).
The shell alias installed by scripts/setup-profile.sh points
directly at this file.
php/ Build output (gitignored). Populated by scripts/build-cli.js
with parser src/, vendor/, hooksgraph.php, server.php.
dist/ Build output (gitignored). Populated by scripts/build-cli.js
with the viewer build (packages/viewer/dist/).
Subcommand surface
| Subcommand | Output | invokedAs (env) |
|---|---|---|
hooksgraph <dirs...> |
parse → ~/.hooksgraph/parsed/ → serve → open browser |
hooksgraph |
hooksgraph parse <dirs...> |
~/.hooksgraph/parsed/ |
hooksgraph parse |
hooksgraph parse-codebase <dirs...> |
~/.hooksgraph/codebases/ |
hooksgraph parse-codebase |
hooksgraph serve [path] |
starts php -S on port 8080+ against viewer dist |
n/a |
| `hooksgraph [--help | -h]` | top-level help |
The no-arg-subcommand shortcut (hooksgraph <dir>) parses with --print-path,
captures stdout to get the output path, then re-invokes serve with it.
Dev-aware path resolution
bin/hooksgraph.js checks for tarball-local php/hooksgraph.php and dist/index.html
to decide whether it's running from a published tarball or a monorepo checkout:
- Tarball: uses
./php/(bundled parser + vendor) and./dist/(bundled viewer). - Monorepo (dev): falls back to
../parser/and../viewer/dist/— sopackages/cli/bin/hooksgraph.jsworks as soon as the viewer has been built once, no priorpnpm build:clirequired.
The probe is for sentinel files (hooksgraph.php, index.html), not just directory
existence — php/ and dist/ may exist as empty placeholders during partial builds.
The Node → PHP contract
Three environment variables flow from this shim to packages/parser/hooksgraph.php:
| Var | Set by | Read by | Purpose |
|---|---|---|---|
HOOKSGRAPH_OUTPUT_DIR |
shim, per subcommand | Cli\Runner |
Storage dir for the JSON. Internal; not user-facing. |
HOOKSGRAPH_INVOKED_AS |
shim | Cli\Help |
Program name shown in help/usage text (hooksgraph parse ...). |
HOOKS_JSON |
shim (serve only) | packages/parser/server.php |
Absolute path to the JSON to serve at /hooks.json. |
HOOKSGRAPH_PARSED_DIR |
user | shim → resolveStorageDir('parsed') |
Override default ~/.hooksgraph/parsed/. |
HOOKSGRAPH_CODEBASES_DIR |
user | shim → resolveStorageDir('codebases') |
Override default ~/.hooksgraph/codebases/. |
Don't move storage routing into PHP. Keeping it in Node means Cli\Runner stays usable
by other PHP callers (the in-monorepo plugin uses the lower-level classes anyway, but
the principle holds) and we only have one place to grep for "where does the JSON go?".
Process model
- PHP is spawned with
stdio: 'inherit'forparse/parse-codebase/serveso the user sees the parser's ANSI progress bar and thephp -Saccess log directly. - The legacy shortcut uses
spawnSync('php', ['--print-path', ...args], { stdio: ['inherit', 'pipe', 'inherit'] })— stdout is piped (captures the resolved output path), stderr is inherited (progress UI).--print-pathmode redirects all parser echo/printf to stderr, so the only thing on stdout is the final path. servefinds a free port starting at 8080 (probes up to +100), forwards SIGINT / SIGTERM to the PHP child, and opens the URL with the first ofopen/xdg-open/wslview/startavailable on PATH.ensurePhp()runs once at startup and exits with code 2 + an install hint ifphpis missing.
Build pipeline
scripts/build-cli.js (at repo root) assembles the publishable package:
pnpm -F @hooksgraph/viewer build→packages/viewer/dist/composer validate --working-dir=packages/parser --no-check-publishcomposer install --working-dir=packages/parser --no-dev --optimize-autoloader- Reset
packages/cli/php/, copy parsersrc/+vendor/+hooksgraph.php+server.phpinto it. - Reset
packages/cli/dist/, copy viewerdist/into it.
Both php/ and dist/ are gitignored — they only exist post-build. The
package.json#files whitelist still includes them so they ship in the tarball.
Code style
- ES modules (
type: "module").importfromnode:namespace for stdlib. - Single-file shim by design — every code path is in
bin/hooksgraph.js. Resist the urge to split intosrc/files; this binary should be diff-able as a unit. - No runtime npm deps. The whole shim is
node:child_process+node:path+node:os+node:fs+node:net+node:url.
Gotchas
- The shell alias installed by
scripts/setup-profile.shpoints atpackages/cli/bin/hooksgraph.jsdirectly (shebang-executable), not at anodewrapper. Removing the shebang or the executable bit breaks every existing install. bin/hooksgraph.jsruns from a published tarball without ever importing from../parser/or../viewer/— when both probes find their sentinel files, the dev branches are inert. Keep it that way: anyimportof a sibling-package file would blow upnpm install -g @hooksgraph/hooksgraph(those packages aren't published).runServe()resolves the JSON path before spawningphp -Sand passes the absolute path viaHOOKS_JSON. Don't pass a relative path — the PHP server's CWD is the viewer dist dir, not yours.mostRecentJson()only looks at*.jsonmtimes in the parsed dir; it ignores subdirectories. Don't introduce nested storage layouts without updating it.findFreePort()probes on127.0.0.1, not0.0.0.0. A port can pass the probe and then fail inphp -Sif another tool grabs it in the race window — rare, but it surfaces asphpexiting non-zero, which we forward as-is.- The fall-through shortcut (no recognised subcommand) treats all args as parse
targets — there's no "unknown subcommand" error. A typo like
hooksgraph parsee ~/wpwill be sent to the parser as a directory list, which will then complain about the bogus path. That's intentional (forgiveness over strictness) but if you add a new subcommand, list it explicitly before the fall-through.
Tests
There is no Node-level test suite for the shim — the parser pipeline it dispatches to
is covered by PHPUnit (pnpm test:parser) and the viewer it serves is covered by
Vitest (pnpm test:js). If you add logic beyond glue (e.g. complex arg parsing,
storage layout migration), add a Vitest suite under packages/cli/tests/ and wire it
into the root vitest.config.js.
See ../../README.md for the user-facing surface and
../parser/AGENTS.md for what the spawned PHP does.