Commands
The package installs two names for the same command: depcurfew and the short dcf, so dcf doctor works like depcurfew doctor.
| Command | What it does |
|---|---|
depcurfew init [directory] [--force] [--json] | Writes depcurfew.json with the default policy, keeping what the repository already sets: a longer release-age delay, a dependency source an .npmrc denies, a delay of security updates in Renovate, the cooldown exceptions of the tools' own lists and the packages their install-script allow-lists approve, each with a note to review; --force overwrites an existing one, --json prints the result as JSON. |
depcurfew explain [directory] [--json] | Describes the detected package managers, versions, bots and workspaces, and what the policy means for each tool: the release-age delay (or why apply writes none, such as a pinned version too old for it), and the settings apply writes for installScripts and sources, or why a tool cannot enforce them. |
depcurfew apply [directory] [--dry-run] [--json] | Writes the policy into the configuration of every target tool; --dry-run prints a unified diff instead, --json the result as JSON. |
depcurfew doctor [directory] [--json | --sarif] [--fail-on error|warning] [--osv] | Finds configuration that silently does not work and drift from the policy, and without depcurfew.json shows what the default policy would change; --sarif prints SARIF 2.1.0 for code scanning, and --fail-on overrides doctor.failOn of the policy (--strict is --fail-on warning). --osv also looks every locked package version up in OSV.dev: see Known malicious versions. |
depcurfew check <package...> [--json] [--dir <directory>] [--registry <url>] [--index <url>] [--osv] | Checks packages against the policy before you install them: see Guarding installs. |
depcurfew check --hook claude-code [--osv] | The same check as a Claude Code hook that blocks install commands breaking the policy. |
depcurfew mcp [--dir <directory>] [--registry <url>] [--index <url>] [--osv] | The same check as tools of a Model Context Protocol server, for AI agents in Claude Desktop, VS Code and other clients: see MCP server. |
depcurfew osv update [--ecosystem npm|pypi|packagist ...] [--accept-fewer] [--json] | Downloads the public OSV.dev data of known malicious packages, which check, the hook, mcp and doctor then use offline, without --osv: see Known malicious versions. |
depcurfew init --user [--force] [--json] | Writes the user policy, which --user uses: see Protecting the whole machine. |
depcurfew apply --user [--dry-run] [--json] [--for <tool@version>] | Writes the user policy into the user-level configuration of the installed package managers. |
depcurfew doctor --user [--json | --sarif] [--fail-on error|warning] [--for <tool@version>] | Checks that configuration against the user policy, and the environment variables that override it. |
Without a directory, explain, apply and doctor work on the nearest directory with depcurfew.json, from the current directory up to the root of the repository (the directory with .git), and say so when that is a parent directory (outside a git repository only the current directory counts, and on Linux and macOS a parent directory counts only when it belongs to you and others cannot write to it, because a shared parent such as /tmp may hold someone else's depcurfew.json; directories inside node_modules are skipped), so doctor run in a subdirectory does not pass for a project without a policy. A directory given on the command line is used as it is, unless the nearest depcurfew.json above it lists it (or a directory it lies in) under projects: then the command works on that root, which checks the directory as one of its projects, as when it runs in that directory, and says so (the GitHub Action does the same for its directory input). check looks for depcurfew.json the same way, starting in --dir.
Exit codes: 0 success (warnings included, except for doctor when warnings fail it), 1 an error (an invalid or unreadable policy, a directory in projects that cannot be used, a file that cannot be edited or written, a doctor finding with level error, or also one with level warning when doctor.failOn in depcurfew.json (with --user the user policy) or --fail-on is warning, which --strict means too; notes never fail), 2 a usage error (such as an unknown option or a directory that does not exist). depcurfew <command> --help describes a command with examples and its exit codes.
Every finding and warning says what to do about it. When a command cannot start, it says why with a code (directory-not-found, not-a-directory, policy-missing, policy-invalid, policy-unreadable, and for init also policy-exists and cannot-write) and what to do; with --json that is a document with errors on stdout, and doctor --sarif prints a run marked as not successful. Text for people is colored on a terminal; NO_COLOR=1 turns colors off and FORCE_COLOR=1 turns them on, for example in a CI log that shows them. Control characters in names and values from the repository or a registry are shown as text (ESC as \x1b, or as \u001b inside a quoted value such as a package name in the problems of depcurfew.json), so a crafted package or file name cannot change the terminal. --json, --sarif and a diff written to a file stay plain, for programs and AI agents.
The --json documents of all commands share these fields: schemaVersion (1), policy (its status in apply and doctor, the path of the depcurfew.json used in check, its values in explain, the file written in init), notices (each with level note, a code, location when it concerns a file, message, and fix when there is something to do; policy-from-parent says that doctor, apply or explain uses the depcurfew.json of a parent directory and works on that directory) and errors, the reasons a command could not start, each with code, level, location.file, message and fix, and issues for policy-invalid (home-missing for a home directory that is not an absolute path, also an empty HOME, names the variable instead of a file: location is {"file": "environment", "variable": "HOME"}, USERPROFILE on Windows, as in doctor --user). A usage error with --json (an unknown option, check without a package, --json with --sarif) prints a document with only schemaVersion and errors, with the code usage and no location, and exits with 2; without --json it is written on stderr like the other errors, as error: <message> (usage) and a fix: line. The warnings of explain have code, level, location (when they concern one file), message, files and fix. Paths are relative to the directory the command works on (a directory that does not exist is named as given). A depcurfew.json that cannot be used is an error with the same code, message and fix in apply, check and explain, which also keeps it in policy; doctor reports it as the finding invalid-policy, so that it reaches code scanning too, and counts it in summary.error. Within a schemaVersion fields and values are only added; a field is removed or changes its meaning only with a new schemaVersion.
explain shows, for each tool in the policy section (and for each directory listed in projects), what installScripts and sources mean there, from the same plan apply makes: the settings apply writes with their files, or the warning apply gives when the tool cannot enforce a part, with its code and what to do. The text has one line per tool and part, nothing to write when apply writes nothing there and has nothing to say, and not managed when the policy has no such section:
installScripts deny, allow esbuild:
<tool>: <setting> and <setting> in <file>, <setting> in <file>; warning: <message> (<code>); fix: <what to do>
sources git deny:
<tool>: nothing written; warning: <tool> has no setting for git dependencies, so "deny" cannot be enforced (source-not-enforceable); fix: <what to do>
In explain --json, policy.installScripts holds default, allow, deny and tools, policy.sources the sources the policy sets and tools, and each entry of projects has installScripts and sources with tools (each null when the policy has no such section). Each entry of tools has tool, settings (setting, file, and table for a TOML table) and warnings, the warnings of apply --json about that part for the tool. A setting that apply writes only to correct a value (allow-all-replaced) is listed only while the file still needs that correction.
How apply edits files:
- It keeps comments, formatting, line endings and key order. In line-based files (INI, TOML) it writes a block between
# >>> depcurfew managed blockand# <<< depcurfew managed blockmarkers and comments out older copies of the settings it manages (they come back when the policy stops managing them). In TOML a marker counts only outside values: a marker line inside a multi-line string or array is text of that value, so a block wrapped in a string is no block, and after each edit the block has to lie outside every value and in its table, orapplyrefuses the file (cannot-edit, with the line and what to do); in YAML it marks the keys it writes with# managed by depcurfew; JSON files keep their comments and indentation. - It reads files the way the tool does. In YAML files of a tool whose loader applies merge keys (
<<: *defaultsinpnpm-workspace.yamland pnpm'sconfig.yaml), a setting a merge key brings in counts, andapplywrites its value into the map itself, which wins over merged keys;.yarnrc.ymlanddependabot.ymlare read without merging. A switch that allows every install script counts as on for every value the tool does not read as off: pnpm runs every build script fordangerouslyAllowAllBuilds: "false"(a string) oryes, npm and pnpm 10 for=1or=yesin.npmrc, and Yarn forenableScripts: 1or"true". TOML table names and keys are read as TOML reads them, so[tool."uv"],[tool . uv]and["install"]are the tables[tool.uv]and[install]. - An install-script allow-list of another type than the tool reads (a list where npm and Yarn take an object of package names, an object where Bun takes a list) stays as it is, with the warning
unexpected-value, since replacing it would drop the packages it names. - It never removes settings it does not manage. Allow-lists are only added to; exception lists follow the policy, except the entries Renovate adds to pnpm's exception list for security updates, which stay with their comment. An exception written by hand that the exceptions of the policy do not name is commented out (in YAML kept as a comment above the list it writes, in JSON removed), with the warning
exclusion-dropped, which names the patterns that keep it. uv also lets the project give one package a date or a delay of its own, which wins over the delay for all packages: one that holds the package back longer thancooldown.minimumstays in the managed block, and a shorter one is commented out withexclusion-dropped. An entry in a tool's own install-script list that contradictsinstallScripts(it lets a package ofinstallScripts.denyrun its scripts, or blocks one ofinstallScripts.allow) stays as it is, with the warningdeny-conflicts-with-file, andapplyrecords no denial for that package. - When it replaces a longer release-age delay someone set by hand with the policy's shorter one, it warns (
cooldown-shortened) and names thecooldown.minimumthat keeps it. A fixed date in place of a delay, which uv, PDM and Deno accept ("2024-01-01"), counts as the whole days since it. A value depcurfew cannot read (Composer's"1 month"or"P30D", which Composer rejects too, Deno's"3 days") stays as it is, withcooldown-not-written. - When it makes a setting it did not write less strict, it warns as well: a dependency source that
.npmrclimits withnoneorroot, pnpm's switch for git and tarball sources and a Yarn list of approved git repositories without**, under a policy that allows those sources (source-loosened), and a Renovate delay of security updates undersecurityUpdates: "bypass"(security-delay-removed). - If any file cannot be edited, nothing is written. If a file cannot be written, the files already written are put back.
- It does not create bot configuration files, project manifests (
package.json,deno.json,pyproject.toml,Pipfile,composer.json,Gemfileorgems.rb) orrequirements.txt; it creates a package manager's configuration file when it is missing. A package manager seen only in a CI setup step, with nothing of it in the repository, is not configured undertargets: "auto". - It warns when a file it writes is ignored by git (
.gitignoreor.git/info/exclude), because the change would then stay on one machine.
Every code init and apply give is listed in Warnings and errors of init and apply.