@hasna/hooks
v0.7.10
Published
Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command
Maintainers
Readme
@hasna/hooks
Open source hooks library for AI coding agents - Install safety, quality, and automation hooks with a single command
Install
npm install -g @hasna/hooksCLI Usage
hooks --helphooks installhooks listhooks searchhooks removehooks categorieshooks infohooks doctorhooks run
Compact Output
CLI commands default to compact, agent-friendly output. List and search commands show essential fields, cap terminal rows, and print hints for deeper inspection. Use detail flags when you need more context:
hooks list # compact, capped list
hooks list --all # show all rows
hooks list --verbose # include descriptions
hooks search git --limit 5 # cap result rows
hooks info gitguard # full metadata for one hook
hooks docs gitguard # README preview
hooks docs gitguard --verbose
hooks list --json # stable machine-readable full dataMCP tools follow the same gradual disclosure pattern: list/search/log/profile
tools return compact summaries by default, while explicit flags such as
compact:false, verbose:true, or a detail tool like hooks_info return full
records.
Codewith-native hooks
@hasna/hooks includes unprefixed Codewith-native hook names:
session-start—SessionStartdigest and additional context.prompt-guard—UserPromptSubmitguard for pasted fake policy/freeze/run-this-now content.pre-bash—PreToolUseBash gate for staged secrets scans, scoped destructive-operation blocks, and risky-op comms checks.worktree-guard—PreToolUseguard for managed repos worktree boundaries and file-tool-like payloads touching protected Hasna scopes.stop-sync—Stopturn-end heartbeat/evidence best effort.knowledge-context— deterministic Knowledge context packs forSessionStart,UserPromptSubmit, andSubagentStart.
For Codewith, the installer is renderer-safe by default: it emits a TOML
fragment instead of mutating managed ~/.codewith/config.toml.
hooks install session-start prompt-guard pre-bash worktree-guard stop-sync knowledge-context --target codewithThe scoped destructive-operation guard does not block every cleanup command. It
blocks resolved shell/file-tool targets that threaten / or a system root
(/usr, /etc, /bin, /lib, /var, /boot, /home, /Users, and the
other FHS and macOS equivalents), ~/.hasna, configured workspace roots, Hasna
division/scope roots, or active repo/worktree roots, including recursive rm,
rsync --delete, destructive find, and destructive git clean / git reset
--hard forms.
It also blocks by shape: a destructive target containing a command
substitution or variable expansion immediately followed by / is checked as the
shell would render it if that expansion returned empty, so
rm -rf "$(anything)"/* and rm -rf "$VAR"/* are refused whatever the
expansion is. Wrapped forms (bash -c, su -c, eval, ssh host '…') are
unwrapped first. See hooks/pre-bash/README.md for
the full rules, the deliberate exemptions (${VAR:?}, bare "$(cmd)" with no
trailing separator), and the recommended safe form.
Apply that fragment through configs or the managed config renderer. A
direct write path exists only for explicit local/test use:
hooks install knowledge-context --target codewith --apply-codewith --codewith-config /tmp/codewith-config.tomlCustom and remote hooks
A hook is defined by a manifest — { name, version, description, events, script, args?, timeout_ms? } — where script is a relative path or inline content. Hooks come from three sources: the bundled registry, a user custom directory, or a remote registry.
timeout_ms is an optional positive integer (milliseconds). Omit it for no timeout. A timeout of 0 is never a real value: a manifest or MCP call that passes 0 (or a negative value) is rejected, and the SDK treats a non-positive option as not provided — falling back to the manifest value, or to no timeout when the manifest has none. So timeout_ms: 0 can never mean "kill immediately"; either a positive bound applies or there is no bound at all.
Install custom hooks from a local directory, a git URL, or a manifest URL:
hooks install ./my-hook # directory with manifest.json
hooks install [email protected]:org/hook-repo.git
hooks install https://example.com/hooks/my-hook/manifest.jsonCustom hooks land in ~/.hasna/hooks/hooks/<name>/. A custom hook with the same name as a bundled hook takes precedence (visible in hooks info <name>).
Trust model. Every hook script is pinned by sha256 in ~/.hasna/hooks/hooks.lock and the SQLite hooks table. hooks run verifies the script hash before executing; if the script changed, the run is refused:
hooks trust <name> # re-pin the current script content
hooks update # re-register hooks and refresh pinsRegistry server. hooks serve exposes the local store over HTTP — catalog, artifacts, and the published lock:
hooks serve --port 39428 # publish key resolves from HASNA_HOOKS_API_KEY / HOOKS_API_KEY only
# GET /health, GET /api/v1/catalog, GET /api/v1/hooks/:name/:version,
# PUT /api/v1/hooks (publish, requires the key), GET /api/v1/lockCloudflare registry (opt-in). Presence of an API URL selects the remote registry; absence means local. There is no mode concept.
hooks init --cloudflare --api-url https://registry.example.com --api-key <vault-key-name>
hooks sync # fetch catalog + lock from the API, verify sha256, update the local store
hooks sync --dry-run # print the plan without changing anythinghooks init --cloudflare stores the API URL and a vault key NAME in ~/.hasna/hooks/config.json — never the key value. Serve with the key resolved from the vault:
secrets exec <vault-key-name> --as HASNA_HOOKS_API_KEY -- hooks serveCloudflare provisioning. hooks cf deploy creates the D1 database and R2 bucket via the Cloudflare API, then prints the exact wrangler commands for the worker upload (the worker needs the workerd target, which only wrangler can bundle):
export CF_API_TOKEN=... # resolve from the vault, never paste the value
hooks cf deploy --account-id <id> --dry-run # plan first
hooks cf deploy --account-id <id>The worker (src/cf/worker.ts) implements the same API routes against D1 + R2, with artifacts at hook_artifacts/<name>/<version>.json. See src/cf/wrangler.toml.example.
Storage
Hooks stores data locally by default in ~/.hasna/hooks/ and uses SQLite
directly for hook event history. The package owns its database schema and
migrations; it does not depend on the deprecated shared runtime or its CLI.
The repo includes its own PostgreSQL migration definitions for the optional
hooks storage push|pull|sync commands. Use the hooks log commands to inspect
local hook event data.
hooks storage status --json
HASNA_HOOKS_DATABASE_URL=postgres://... hooks storage push --tables hook_events,feedback --json
hooks storage pull --json
hooks storage sync --jsonConfigure database storage with HASNA_HOOKS_DATABASE_URL or fallback
HOOKS_DATABASE_URL.
Storage backend
Hooks storage has one setting with two values: which data backend, not where anything is deployed.
| HASNA_HOOKS_STORAGE_BACKEND (fallback HOOKS_STORAGE_BACKEND) | meaning |
| --- | --- |
| sqlite | the on-box SQLite file in ~/.hasna/hooks/ (default) |
| postgresql | the PostgreSQL database named by HASNA_HOOKS_DATABASE_URL |
Leave it unset and the backend is inferred exactly as before: postgresql when a
database URL is configured, sqlite otherwise. An unrecognised value is an
error, not a silent fall back to SQLite.
The former deployment-mode variables HASNA_HOOKS_STORAGE_MODE and
HOOKS_STORAGE_MODE, and their local / hybrid / remote / self-hosted /
cloud values, are retired. They are not read; setting one raises an error
naming the replacement variable and the backend to use (local became sqlite,
everything else became postgresql). Deployment location was never a property of
the data layer, so it is no longer expressed as one.
Runtime model
This package is an npm CLI, MCP server, and static dashboard package. Installing and running hooks needs nothing deployed anywhere — the SQLite backend is the default and requires no server.
Data Directory
Data is stored in ~/.hasna/hooks/.
License
Apache-2.0 -- see LICENSE
