Commands
Reference for every shk CLI command, option, and exit code.
On this page
- Global options
- shk init
- shk status
- shk audit
- shk mcp audit
- shk completions
- shk scan
- shk allowlist suggest
- shk scan --hook-mode
- shk mask
- shk pseudonymize
- shk clipboard
- shk doctor
- shk env encrypt / shk env decrypt
- shk env key
- shk env dotenvx
- shk secrets push
- shk hooks install
- shk hooks install-ai
- shk ci init github
- shk skills
This page describes the implemented shk CLI commands and options.
Global options
| Option | Behavior |
|---|---|
--project-root <DIR> |
Resolve shk.toml and project-relative paths from DIR instead of the current working directory. Works with every command, so shk --project-root /path/to/project mask < prompt.txt uses the project policy even when invoked from elsewhere. |
--no-color |
Disable colored human-readable output. |
shk init
Interactive first-run setup: the starter shk.toml policy, package-manager hardening, the Git pre-commit hook, AI editor hooks, and bundled agent skills.
shk init
shk init --strict
shk init --force
shk init --yes
shk init --yes --no-npm-hardening
shk init --yes --tool claude-code,cursor --log-blocked
shk init --yes --no-git-hook --no-ai-hooks --no-skillsIn a terminal, shk init prompts for the policy profile and asks which steps to run. The policy file is written first because the hook steps require shk.toml. When stdin is not a terminal and no setup flags are passed, shk init falls back to writing only shk.toml.
Options:
| Option | Behavior |
|---|---|
--strict |
Use the strict starter policy profile (medium fail thresholds). |
--force |
Overwrite existing managed files where supported, including an existing shk.toml. |
-y, --yes |
Accept the recommended defaults without prompting. Without --tool, all six AI tools are configured. |
--audit |
Install AI hooks in audit-only mode. Mutually exclusive with --log-blocked. |
--log-blocked |
Install blocking AI hooks that append metadata-only block entries to .shk/audit.log. |
--tool <tool> |
AI tools to configure: claude-code, codex, cursor, copilot, antigravity, windsurf. Repeat the flag or separate values with commas. |
--no-git-hook |
Skip Git pre-commit hook setup. |
--no-ai-hooks |
Skip AI editor hook setup. |
--no-skills |
Skip bundled agent skill setup. |
--no-npm-hardening |
Skip package-manager supply-chain hardening, including in --yes mode. |
--global |
Write AI hooks and skills to user-level config directories. |
--apply-sandbox |
Apply supported AI-tool sandbox hardening while installing hooks. |
When package.json is detected, shk init can apply package-manager supply-chain hardening. For npm projects it writes project .npmrc settings such as ignore-scripts=true and min-release-age=7. For pnpm, Yarn, or Bun projects it writes the corresponding age-gate setting to pnpm-workspace.yaml, .yarnrc.yml, or bunfig.toml. Pass --no-npm-hardening to skip this step, including in --yes mode.
shk policy init writes only the starter shk.toml and skips the other setup steps:
shk policy init
shk policy init --strict
shk policy init --forceshk status
Show a concise project health summary.
shk statusThe status command reports whether shk.toml exists, whether the Git pre-commit hook and managed AI hooks are installed, whether bundled AI skills are installed, and whether a newer shk release is available.
Update checks are limited to shk status and shk doctor version; scan and hook commands do not contact the network for version notices.
shk audit
Preview metadata-only entries from .shk/audit.log.
shk audit
shk audit --json
shk audit --since 7d
shk audit --tool cursor
shk audit --reason action-guard
shk audit --limit 20
shk audit --no-pathsThe audit command summarizes local hook and secret-manager audit entries without printing raw matched values, prompt bodies, or command text. Human output includes a summary, rule/tool/reason/action-category counts, and recent events. JSON output emits the same report structure for automation.
Options:
| Option | Behavior |
|---|---|
PATH |
Project path whose .shk/audit.log should be read. Defaults to .. |
--json |
Print the audit preview as JSON. |
--since <duration> |
Limit entries to a relative duration such as 30m, 24h, 7d, or 1w. |
--tool <tool> |
Limit entries to claude-code, codex, cursor, copilot, antigravity, or windsurf. |
--reason <reason> |
Limit entries to blocked, finding-threshold, or action-guard. |
--limit <n> |
Limit the recent event rows. Defaults to 10. |
--no-paths |
Omit display_path from recent event rows. |
shk audit is read-only. If the log is missing, it exits successfully and prints setup guidance. Invalid JSON lines are skipped and counted as parse warnings.
shk mcp audit
Statically inspect MCP server configuration files without starting servers, resolving commands, expanding variables, or making network requests.
shk mcp audit
shk mcp audit path/to/project
shk mcp audit --json
shk mcp audit --sarif
shk mcp audit --global
shk mcp audit --fail-on mediumThe default scan is limited to project-local files. --global additionally reads user-level files under the current user's home directory. Missing files are silently skipped.
Configuration reads are capped at 1 MiB and 1,000 server entries per file, and must remain inside the selected project or home scope after path resolution. Escaping symbolic links, oversized files, excessive server maps, and non-regular files produce mcp.config_unreadable instead of being read. On Unix, hard-linked configuration files are also rejected.
Project configuration locations:
| Client | File | Server map |
|---|---|---|
| Claude Code | .mcp.json |
mcpServers |
| Cursor | .cursor/mcp.json |
mcpServers |
| VS Code / Copilot | .vscode/mcp.json |
servers |
| Codex | .codex/config.toml |
mcp_servers |
Additional locations with --global:
| Client | File |
|---|---|
| Claude Code | ~/.claude.json |
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Cursor | ~/.cursor/mcp.json |
| Codex | ~/.codex/config.toml |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
Rules:
| Rule | Severity | Detects |
|---|---|---|
mcp.npx_auto_install |
medium | Automatic package installation with npx -y or npx --yes |
mcp.unpinned_package |
medium | npx, uvx, or pipx run packages without an exact version |
mcp.shell_wrapper |
medium | Shell wrappers using -c or /c |
mcp.local_unpinned_executable |
low | Relative or non-system executable paths without integrity verification |
mcp.broad_filesystem_scope |
high / medium | Filesystem servers exposing / or the user's home directory |
mcp.http_no_tls |
high | Non-loopback remote endpoints using http:// |
mcp.secret_in_url |
high | Sensitive query parameter names in a URL |
mcp.unknown_transport |
info | Entries with neither a command nor a URL |
mcp.config_unreadable |
low | Files that cannot be read or parsed |
mcp.env_file_unreadable |
low | An existing --env-file target that escapes the selected scope or cannot be safely read |
Configured argument, process-variable, header, and URL values also pass through the existing secret rule engine. References such as ${VAR}, $VAR, and ${input:token} are not treated as plaintext values. Existing [[allowlist]] entries apply. Reports contain no process-variable values, header values, or raw matches.
Files named by a server's --env-file <path>, --env-file=<path>, or --envfile argument are also read and scanned with the secret and dotenv rules. Those findings keep their normal secret.* / env.* rule ids, with the message rewritten to name the server and client. Relative paths resolve against the config file's directory (the home directory for --global files), ~/ is expanded, ${VAR}-style references are not followed, and a missing file is skipped. See MCP Configuration Audit.
--json returns findings, summary.by_severity, and a redacted servers inventory. --sarif emits SARIF 2.1.0 using the same rule metadata shape as shk scan --sarif. The two output flags are mutually exclusive. Human output hides informational findings unless --verbose is passed.
Exit codes:
| Code | Meaning |
|---|---|
0 |
No finding meets the active threshold (default high). Parse failures below the threshold remain findings and do not abort the audit. |
1 |
At least one finding meets the active threshold. |
2 |
Runtime or argument error, such as an invalid audit path. |
shk completions
Generate shell completion scripts.
shk completions bash > /usr/local/etc/bash_completion.d/shk
shk completions zsh > "${fpath[1]}/_shk"
shk completions fish > ~/.config/fish/completions/shk.fishSupported shells are bash, zsh, fish, powershell, and elvish.
shk scan
Scan a repository or path for secrets, PII, and configured custom rules. Text is also extracted from supported document formats: .docx, .xlsx, .pptx, and text-layer .pdf files.
shk scan
shk scan ./src
shk scan . --json
shk scan . --json --with-value-hash
shk scan . --sarif
shk scan . --sarif --audit
shk scan . --verbose
shk scan . --fail-on medium
shk scan . --include-binary
shk scan . --follow-symlinks
shk scan --staged
shk scan . --changed-since origin/main
shk scan --git-history
shk scan --git-history --preview
shk scan --git-history --ref HEAD~50..HEAD
shk scan --git-history --since 30.days.ago
shk scan . --no-colorOptions:
| Option | Behavior |
|---|---|
PATH |
Path to scan. Defaults to .. |
--json |
Print a JSON report. |
--sarif |
Print SARIF 2.1.0 for GitHub code scanning and compatible tools. Cannot be combined with --json. |
--audit |
Report findings but always exit 0 for findings. Runtime and configuration errors still fail. In hook mode, also append metadata-only audit entries. |
--with-value-hash |
Include deterministic value hashes in JSON/SARIF output. Use only when reports are handled as sensitive artifacts. |
--verbose |
Include informational skip findings in human-readable output. |
--fail-on <severity> |
Override the configured failure threshold. Valid values: info, low, medium, high, critical. |
--include-binary |
Scan binary-looking files instead of reporting scan.binary_skipped info findings. |
--follow-symlinks |
Follow symlinks during traversal. |
--staged |
Scan Git-staged files. Intended for pre-commit usage. |
--changed-since <rev> |
Scan files changed on the current branch since the merge base with <rev>, using git diff <rev>...HEAD. Intended for PR/CI scans. |
--git-history |
Scan committed Git history reachable from refs. Reports paths as <commit>:<path>. |
--preview |
With --git-history, print candidate commit/path/blob counts and sample paths without scanning blob contents. |
--ref <rev> |
With --git-history, scan a Git revision or revision range instead of --all, e.g. main or HEAD~50..HEAD. |
--since <date> |
With --git-history, limit history to commits newer than a Git date expression, e.g. 30.days.ago or 2026-01-01. |
--max-commits <n> |
With --git-history, limit history traversal to the most recent n commits in the selected scope. |
--no-color |
Disable colored human-readable output. This is a global option. |
Traversal notes:
- Hidden files and directories (
.env,.envrc,.npmrc, …) are scanned. The.gitdirectory itself is always skipped. .gitignoreand.git/info/excluderules are honored inside Git repositories. Becauseshk doctorrecommends keeping.envin.gitignore, a gitignored.envin a Git repository is not part of a directory scan — pass it explicitly (shk scan .env) to scan it anyway.
--git-history scans committed blobs from Git history, not the working tree or index. By default it uses git log --all, so local branches, tags, and remote-tracking refs are considered. Deleted secrets can still be detected because the older blob is read from the commit where the file existed. Uncommitted changes and unreachable objects are not scanned.
--changed-since scans the current working-tree content for files listed by Git in the selected branch diff. Deleted files are skipped, and untracked files are not included. Use a stable base such as origin/main in pull request CI.
Use --preview before a broad history scan to see the selected scope, candidate commit/path counts, unique blob count, policy-filtered blob count, and up to 10 sample <commit>:<path> labels. With --json, preview emits the same metadata as machine-readable JSON and exits 0.
Document scan notes:
- Office findings are labelled as
<file>:<internal-entry>, for examplereport.docx:word/document.xmlorworkbook.xlsx:xl/sharedStrings.xml. Use the same label in[[allowlist]].pathwhen suppressing a finding by path. - PDF findings are labelled with the PDF file path itself, for example
report.pdf. - PDF support uses the embedded text layer. Image-only PDFs are not OCRed; they produce
scan.document_text_emptywhen no extractable text is found.
Exit codes:
| Code | Meaning |
|---|---|
0 |
No findings at or above the active threshold, audit mode reported findings, or command completed successfully. |
1 |
Scan or MCP audit findings met or exceeded the active threshold. |
2 |
Blocking AI pre-hook triggered, or a scan/MCP audit runtime or configuration error occurred. |
shk allowlist suggest
Generate [[allowlist]] TOML snippets from a JSON scan report. Suggestions include rule and path metadata, and can include value_hash when the report was produced with --with-value-hash. Raw matched values are never printed.
shk scan . --json --fail-on critical > report.json
shk allowlist suggest --from report.json
shk scan . --json --with-value-hash --fail-on critical > report-with-hashes.json
shk allowlist suggest --from report-with-hashes.json --value-hash --reason "Intentional fixture" --expires 2026-12-31Options:
| Option | Behavior |
|---|---|
--from <file> |
Read a shk scan --json report. Use - to read from stdin. |
--value-hash |
Include value_hash = "sha256-hmac:..." when available, for value-specific suppression. |
--reason <text> |
Fill the reason field in generated entries. |
--expires <YYYY-MM-DD> |
Add an expiration date to generated entries. |
value_hash is deterministic and keyed by public rule IDs, so low-entropy values such as common email addresses or phone numbers may be recoverable by dictionary attack. Do not upload reports containing value hashes to third-party systems unless that exposure is acceptable.
shk scan --hook-mode
Read an AI tool hook JSON payload from stdin, scan the extracted hook body, and print tool-specific hook output.
shk scan . --hook-mode cursor < payload.json
shk scan . --hook-mode claude-code --audit < payload.json
shk scan . --hook-mode cursor --log-blocked < payload.json
shk scan . --hook-mode codex < payload.json
shk scan . --hook-mode codex --post < payload.json
shk scan . --hook-mode copilot < payload.json
shk scan . --hook-mode antigravity < payload.json
shk scan . --hook-mode windsurf < payload.jsonSupported hook mode tools are claude-code, codex, cursor, copilot, antigravity, and windsurf (Windsurf Cascade). Cascade ignores hook stdout, so --hook-mode windsurf signals a block via exit code 2 + a stderr message rather than a stdout decision payload.
Hook mode notes:
--hook-modecannot be combined with--staged,--changed-since, or--git-history.--auditappends metadata-only JSON lines to.shk/audit.log, always exits0, and requires a projectshk.toml.--log-blockedkeeps pre-hook blocking behavior, appends metadata-only blocked entries to.shk/audit.log, and requires a projectshk.toml. Combined with--post, it writes non-blocking post audit entries.--postis non-blocking and always exits0. It reports findings in tool output for review.- Cursor pre-hook scans use the pre-commit threshold by default.
- Codex
UserPromptSubmitpayloads are scanned whenhook_event_nameisUserPromptSubmit; blocks return{"decision":"block","reason":...}. - Claude Code
UserPromptSubmitblocks exit0with{"decision":"block","reason":...,"hookSpecificOutput":{"suppressOriginalPrompt":true}}on stdout: Claude Code only parses that JSON on exit 0, thereason(rule ids, severities, prompt line numbers, and how to fix) is displayed to the user, andsuppressOriginalPromptkeeps the sensitive prompt text out of the block message. The prompt is still blocked and erased. - Project-local Codex hooks installed by
shk hooks install-ai --tool codexscan$(git rev-parse --show-toplevel)instead of.so subdirectory starts still resolve the repo root. - Copilot
preToolUseandpermissionRequestdenials are returned through stdout JSON with exit0, matching Copilot's hook contract.userPromptSubmittedoutput is not processed by Copilot, so prompt scan blocks are advisory warnings. - Antigravity
PreToolUsepayloads ({"toolCall":{"name":...,"args":{...}}}) are parsed with their PascalCase argument names (CommandLine,TargetFile,CodeContent,Url, ...). Blocks return{"decision":"deny","reason":...}; allows return{"decision":"allow"}. Antigravity post hooks always print{}because that is the PostToolUse output schema; post scans can still log runtime error text when present. - Windsurf Cascade payloads use
agent_action_nameplus nestedtool_info(command_line,user_prompt,mcp_tool_arguments,mcp_result,file_path, and edit strings). Blocks use exit2with the message on stderr; stdout is{}because Cascade does not consume a decision payload.
shk mask
Redact sensitive values from stdin or a file.
shk mask < prompt.txt
shk mask prompt.txt
shk mask prompt.txt --output out.txt
shk mask report.docx --output report.redacted.docx
shk mask --json < prompt.txt
shk mask --redaction match < prompt.txt
shk mask --redaction partial < prompt.txt
shk mask --min-severity medium < prompt.txt
shk mask --hook-mode cursor < payload.jsonOptions:
| Option | Behavior |
|---|---|
FILE |
Optional input file. If omitted, stdin is used. |
--json |
Print masked content and findings as JSON. |
--output <path> |
Write masked content to a file. Requires shk.toml. |
--redaction full |
Replace any line containing a finding with [REDACTED_LINE]. |
--redaction match |
Replace only matched values with [REDACTED] (default). |
--redaction partial |
Replace matched values and preserve the configured prefix/suffix. |
--min-severity <severity> |
Override [mask].min_severity for this run. Defaults to medium. |
--hook-mode <tool> |
Read a hook payload from stdin and print tool-specific masked hook output. |
--post |
Post-tool hook mode. Requires --hook-mode <tool>. |
--pseudonymize |
Replace selected PII with deterministic HMAC tokens instead of [REDACTED]. Requires --output unless --dry-run. Supports CSV/TSV, xlsx table mode, and text mode (stdin, .txt, .md, .docx, .pptx, or --mode text). |
--columns <SPEC> |
Table mode: set Name:kind pairs (example: Email:email,Phone:phone,Name:name). Names are matched case-insensitively against the header; an entry that matches no header exits 2. [pseudonymize.columns] entries that do not apply to the file are ignored. |
--yes |
With --pseudonymize, skip confirmation prompts (required in non-TTY environments when a key must be created or columns are inferred). |
--dry-run |
With --pseudonymize, print the plan and do not write files or create a key. |
--no-header |
Table mode: treat the first row as data and address columns as 0, 1, … |
--no-create-key |
With --pseudonymize, fail if this project has no key yet. |
--mode table|text |
Override input classification. .xlsx defaults to table mode; --mode text scans string cells only; numeric values and formulas are left untouched. |
--format csv|tsv |
Force CSV/TSV table parsing (stdin, or files without a .csv/.tsv extension). Not valid for xlsx input or text mode. |
--sheet <NAME|N> |
xlsx table mode: sheet name or 1-based index. Defaults to the first sheet. |
--map <PATH> |
Write or merge an encrypted restore map. Path must end with .shk-map. Requires shk.toml. |
--check-remaining |
After writing output, scan it and exit 1 if pii.* or secret.* detections remain. This is a leftover check, not a sufficiency guarantee. |
Cannot be combined with --pseudonymize: --hook-mode, --min-severity, --redaction.
When no FILE is provided, shk mask reads stdin until EOF. In an interactive
terminal, run it with input redirection (shk mask < prompt.txt) or provide a
file path (shk mask prompt.txt).
mask --output refuses sensitive env files and protected home configuration files. Binary or non-UTF-8 input is passed through unchanged in human-readable output and reported as mask.binary_passthrough in JSON output.
Office document masking supports .docx, .xlsx, and .pptx files and always requires --output so the original document is left unchanged. JSON output reports [DOCUMENT_WRITTEN] as masked_content and includes findings from the rewritten document. PDF masking is not supported; use shk scan to detect text-layer PDF findings and convert or redact PDFs with a dedicated PDF tool.
Office output is transactional: shk finalizes and syncs a sibling temporary archive before replacing --output. ZIP entry count and expanded sizes are bounded to prevent compressed documents from exhausting memory or disk.
--pseudonymize writes <output>.shk-meta.json beside the output. The sidecar records norm version, token width, key fingerprint, column kinds, and counts. It never includes original values or tokens. Tokens are project-local: the same input yields the same token only when the same stored key and salt are used. Rotate or delete the key when a project ends (see shk pseudonymize).
--json with --pseudonymize prints that metadata only (masked_content and findings are omitted).
Table mode replaces whole cells (email, phone, name, custom:<label>). A non-missing value that cannot be parsed as its declared email or phone kind becomes [UNPARSED]; it is counted in metadata and is not recoverable from a restore map. Text mode ignores severity and allowlists: email / phone / name matches become tokens, secret.* and unmapped detections become [REDACTED]. Name labels such as 氏名: or Name: stay in place and only the name is tokenized, so the token matches the table-mode token for the same person. In xlsx table mode, blank rows above the table are skipped, phonetic (rPh) guides are ignored, and cells without an r attribute are positioned after their predecessor. Selected formula cells are rejected rather than converted to constants. xlsx table mode rewrites selected value cells on the selected sheet as inline strings and removes unreferenced shared-string contents. Values still referenced by other cells or sheets remain unchanged. Table mode is not a whole-workbook sanitization pass. XLSX processing rejects archives above 10,000 entries or 256 MiB expanded size, XML entries above 64 MiB, and grids above 1,000,000 cells. --map writes ciphertext only; there is no plaintext map export, and restore maps are limited to 64 MiB. Restore uses the first-seen original when one token maps to several inputs and only replaces complete known tokens. When --map is used, the output extension must preserve the restore format: .csv/.tsv must match the effective delimiter, xlsx must remain .xlsx, Office text output must keep its input extension, and plain-text output must not use a table or Office extension. CSV/TSV restore escapes each field so original delimiters, quotes, and newlines do not alter the table structure. Input, output, map, and metadata paths must be distinct; existing regular-file destinations are atomically replaced. Pseudonymize mutations are serialized per project; recovery material and metadata are committed before the output becomes visible. key import refuses to replace an existing key—delete or rotate the existing key explicitly first. --check-remaining forces secret/PII rules on and bypasses allowlist and inline suppression; it exits 2 instead of passing when the scanner would skip the output (a [scan] exclude / include pattern, or a file above max_file_size_bytes). With --json, metadata is printed before an exit 1 leftover result. Every other failure (usage, I/O, key store, invalid [pseudonymize] settings) exits 2, so exit 1 always means leftover detections. CSV/TSV files are streamed, so table mode does not load the whole file into memory; stdin input is buffered:
shk mask orders.csv --pseudonymize --columns "Email:email,Phone:phone" --output orders.pseudo.csv
shk mask book.xlsx --pseudonymize --sheet 顧客 --columns "Email:email" --output book.pseudo.xlsx
shk mask notes.md --pseudonymize --output notes.pseudo.md --map notes.shk-map
shk mask --pseudonymize --format csv --columns "Email:email" --output stdin.pseudo.csv < rows.csv
shk mask orders.csv --pseudonymize --dry-run
shk mask out.pseudo.csv --pseudonymize --columns "Email:email" --output checked.csv --check-remaining
shk pseudonymize restore --file notes.pseudo.md --map notes.shk-map --output notes.restored.md
shk pseudonymize key show
shk pseudonymize key export --instructionsUTF-8 is required for text and CSV/TSV. Non-UTF-8 input (including Shift_JIS) exits 2 with a conversion hint. Output is still personal data for anyone who holds the key; confirm the receiving AI service's retention and training terms before upload. Keep .shk-map files out of git (default doctor.ignore includes *.shk-map). Do not treat --check-remaining as legal or completeness proof.
Office text mode processes the DOCX main document, XLSX worksheet/shared-string text, and PPTX slide, notes, and comment text. Other package parts such as DOCX headers/footers and PPTX charts or masters are outside the current scope and are also not covered by --check-remaining.
shk pseudonymize
Manage the project pseudonymization key and restore originals from an encrypted .shk-map. Tokens are produced by shk mask --pseudonymize.
shk pseudonymize key show
shk pseudonymize key rotate
shk pseudonymize key rotate --yes
shk pseudonymize key delete --yes
shk pseudonymize key export --instructions
shk pseudonymize key import --stdin
shk pseudonymize restore --file notes.pseudo.md --map notes.shk-map --output notes.restored.md| Subcommand | Behavior |
|---|---|
key show |
Print the key fingerprint and the store backend, never the raw key material. |
key rotate |
Replace the project key. Existing tokens become unlinkable. -y, --yes skips the confirmation prompt. |
key delete |
Delete the project key. -y, --yes skips the confirmation prompt. |
key export --instructions |
Print team handoff instructions without printing raw material. |
key import --stdin |
Import key material from stdin into the configured store. Refuses to replace an existing key; rotate or delete it first. |
restore |
Rewrite tokens in --file back to their originals using --map and write the result to --output. All three options are required, and --output requires shk.toml. |
Keys are stored in the backend selected by [env].secret_store (the OS keyring by default, or 1Password); see Env Secret Store. Restore uses the first-seen original when one token maps to several inputs and only replaces complete known tokens.
shk clipboard
Scan or mask the OS clipboard text. The clipboard is treated like any other untrusted input: clipboard scan never prints raw matched values, and clipboard mask only outputs redacted text.
shk clipboard scan
shk clipboard scan --json
shk clipboard scan --fail-on critical
shk clipboard mask
shk clipboard mask --json
shk clipboard mask --write
shk clipboard mask --write --redaction partialshk clipboard scan options:
| Option | Behavior |
|---|---|
--json |
Print the JSON report instead of human-readable output. |
--verbose |
Show informational skip findings in human-readable output. |
--fail-on <severity> |
Override the [thresholds].scan_fail_on threshold for this run. |
shk clipboard mask options:
| Option | Behavior |
|---|---|
--json |
Print masked content and findings as JSON. |
--write |
Replace the clipboard contents with the masked text instead of printing it. |
--redaction full|match|partial |
Override [mask].redaction for this run. |
--min-severity <severity> |
Override [mask].min_severity for this run. |
Exit codes follow the shared semantics: clipboard scan exits 1 when findings meet the fail threshold, and both subcommands exit 2 when the clipboard is unavailable (for example, no display server on Linux). Non-text clipboard contents (such as images) and an empty clipboard are treated as empty text.
Policy is resolved from shk.toml in the current working directory, the same as shk scan and shk mask.
Note for Linux: after clipboard mask --write, X11/Wayland clipboard contents are owned by the writing process; without a clipboard manager the replaced text may not persist after the command exits. macOS and Windows are unaffected.
shk doctor
Run project diagnostics.
shk doctor
shk doctor --json
shk doctor --strictshk doctor runs the available diagnostics for the current directory. The full check includes the running and PATH-resolved shk executables, Git hooks, managed AI hooks, ignore coverage, plaintext env files, GitHub Actions checkout hardening, and npm/package-manager supply-chain hardening when package.json is present.
When distinct shk executables are present on PATH (for example, separate installer, Homebrew, and npm installations), doctor reports the first executable PATH would select and the shadowed locations. Paths that resolve to the same binary through symlinks are deduplicated, and shadowed executables are never run.
Advisory warnings remain non-blocking by default. shk doctor --strict exits 1 when any advisory warning is present, making the full suite suitable for CI; runtime or configuration errors retain their error exit behavior. JSON output includes ok, strict, warningCount, shkExecutable, and metadata-only envSecretStore fields. Configuration failures are also emitted as valid JSON and exit 2. The full suite performs static 1Password configuration checks only; use shk doctor env when an explicit live op --version / op whoami check is wanted. --strict is only accepted by the full suite; shk doctor --strict <subcommand> exits with an error.
shk doctor ignore
Check ignore coverage across Git and AI-oriented ignore files.
shk doctor ignore
shk doctor ignore ./path
shk doctor ignore ./path --fixThe ignore diagnostic checks .gitignore, .cursorignore, .cursorindexingignore, .codeiumignore, .clineignore, .aiderignore, .continueignore, .tabnineignore, .ignore, and .aiignore when present.
It also reports on Claude Code .claude/settings.json (whether permissions.deny contains the recommended action-guard deny entries and whether the recommended sandbox settings are present) and Codex .codex/config.toml hook/sandbox settings when those files exist.
--fix requires shk.toml and appends missing required patterns to .gitignore.
shk doctor env
Check plaintext .env files at the project root and report the configured env secret store backend.
shk doctor env
shk doctor env --dotenvx
shk doctor env ./path.env.example, .env.sample, dotenvx artifact files (.env.keys, .env.vault), dotenvx-encrypted env files, and shk env encrypt output files are excluded from the plaintext env file warning. If an encrypted env file contains newly added or edited plaintext values, doctor env reports the plaintext key names and recommends re-running shk env encrypt <file> --in-place. With --dotenvx, the diagnostic also reports known dotenvx artifact files such as .env.keys and .env.vault.
When [env].secret_store = "1password", the diagnostic also checks env.project_id, env.onepassword.vault, op resolution (SHK_OP_PATH, known paths, or PATH), CLI version (minimum 2.24.0), and sign-in state (op whoami). When secret_store = "keyring", it reports that the OS keyring is the active backend.
shk doctor workflows
Check GitHub Actions workflows for credential persistence on checkout steps.
shk doctor workflows
shk doctor workflows ./path
shk doctor workflows --fixThe diagnostic scans .github/workflows/*.yml and *.yaml and reports any actions/checkout step that does not set persist-credentials: false. Without it, actions/checkout leaves the workflow's GitHub token in a Git credential file that later steps can read, so a compromised or injected later step can exfiltrate the token.
--fix requires shk.toml and adds persist-credentials: false to flagged checkout steps (creating a with: block when needed, or flipping an explicit true), preserving existing formatting, comments, and the file's line endings (LF or CRLF). It is a project-only hardening aid, not a full GitHub Actions linter.
shk doctor version
Check the latest GitHub release version.
shk doctor version
shk doctor --json versionThis command reports whether an update is available. It does not modify the installed binary.
shk env encrypt / shk env decrypt
Encrypt and decrypt dotenv payloads with shk native encryption. This is separate from the external dotenvx command integration: dotenvx support remains available under shk env dotenvx, while native encryption lets shk operate without an external dotenv encryption tool.
shk env encrypt .env --in-place
shk env run -- npm test
shk env key import
shk env key export --instructions
shk env decrypt .env --output .env.local
shk env encrypt .env --output .env.shk
shk env encrypt .env.production --env production --output .env.production.shk
shk env run -f .env.production --env production -- npm start
shk env decrypt .env.production.shk --env production --output .env.production.localThe encryption key pair is generated per project and environment label, with the public key written to the .env file as DOTENV_PUBLIC_KEY* and the private key stored in the configured env secret store ([env].secret_store in shk.toml) as DOTENV_PRIVATE_KEY*. By default the OS keyring is used; teams can opt in to 1Password. Values are written as KEY="encrypted:...", preserving key names and the dotenv file shape. Use --in-place on encrypt to keep the .env filename while replacing plaintext values with encrypted values. Use --output to write a separate encrypted file instead. Existing output files are refused unless --force is passed. decrypt always requires --output so plaintext is not written to stdout accidentally. Prefer shk env run for day-to-day use: it decrypts values in memory and injects only the resulting application variables into the child process.
Files written by shk env encrypt include a comment-only [SHK_NATIVE_ENV] header before the DOTENV_PUBLIC_KEY* block. This makes native shk output recognizable when reading the file, while preserving the existing encrypted dotenv value shape.
To add or update a variable in an encrypted env file, edit the line as plaintext, then immediately re-run encryption:
# Add or edit lines in .env, for example NEW_API_KEY=...
shk env encrypt .env --in-place
shk doctor envExisting encrypted: values are left encrypted, and only plaintext values are encrypted on the next encrypt run. doctor env warns when an encrypted env file still contains plaintext keys, which helps catch a missed re-encryption step before commit or release.
For existing dotenvx users, import keys once and then switch the runtime command:
shk env dotenvx import-keys .env.keys
shk env run -f .env -- npm testshk env run, decrypt, and encrypt first use native shk keys. If none exist, they can reuse imported dotenvx DOTENV_PRIVATE_KEY* values from the configured env secret store, derive the public key when needed, and attempt to adopt the key into the native store. This keeps existing dotenvx-encrypted files usable while removing the external dotenvx binary from the normal execution path. After a native command reports that the imported key was adopted, the imported dotenvx copy can be removed with shk env dotenvx delete --all if the project no longer needs shk env dotenvx run. If adoption prints a warning, keep the imported dotenvx copy or import the key with shk env key import.
| Option | Meaning |
|---|---|
-o, --output <file> |
Destination file. Required unless encrypt --in-place is used. |
--in-place |
Encrypt only: replace the source file contents with encrypted data. |
--env <name> |
Use DOTENV_PRIVATE_KEY_<NAME> and DOTENV_PUBLIC_KEY_<NAME>. Use default for DOTENV_PRIVATE_KEY / DOTENV_PUBLIC_KEY. Defaults to default. |
--key <DOTENV_PRIVATE_KEY*> |
Use an exact private key variable name instead of deriving one from --env. |
--force |
Overwrite an existing output file. |
--remove-source |
Encrypt only: delete the plaintext source file after successful encryption. |
shk env run accepts -f, --file <file> repeatedly and defaults to .env when no file is provided. It uses the default project key unless --env or --key is supplied. Unlike shk env dotenvx run, it does not invoke an external dotenvx binary and does not pass DOTENV_PRIVATE_KEY* into the child process.
shk env key
Register local decryption keys and show safe team handoff instructions without committing .env.keys.
shk env key import
shk env key import --env production --stdin
shk env key import --key DOTENV_PRIVATE_KEY_STAGING --force
shk env key list
shk env key delete --env staging
shk env key delete --all
shk env key export --env production --instructions
shk env key migrate --to 1password
shk env key migrate --to keyringimport stores one DOTENV_PRIVATE_KEY* value in the native env secret store for the current project. It refuses to replace a key that is already stored unless --force is passed. Without --stdin, it prompts for the key without echoing input. With --stdin, it can read from a password manager CLI:
op read "op://Project/prod/DOTENV_PRIVATE_KEY_PRODUCTION" \
| shk env key import --env production --stdinlist prints only native key names indexed for the current project, never key material. delete removes stored native keys and requires an explicit target: --all, --key <DOTENV_PRIVATE_KEY*>, or --env <name>. Keys created by older versions that are not indexed can still be removed with an exact --key or --env target.
export requires --instructions and intentionally does not print raw key material. It prints the key name, whether a key is already present on this machine, and a recommended local handoff flow: store the key in a team password manager, share vault access with the teammate, and have the recipient run shk env key import.
shk env key migrate
Copy env private keys from the backend configured in shk.toml to another backend and update env.secret_store on success.
Migrating to or using the 1Password backend requires the 1Password CLI (op), version 2.24.0 or later, and an authenticated op session. Run op --version, op whoami, and shk doctor env to verify the prerequisite before migration. Migrating to the keyring does not require op for the destination, but op must still be available to read keys when the current source backend is 1Password.
# Typical keyring → 1Password rollout
# 1. Set project_id and vault in shk.toml while secret_store stays "keyring"
# 2. Run migrate (updates shk.toml to "1password" after copying keys)
shk env key migrate --to 1password
# Verify the destination, then remove source copies explicitly.
# Roll back to the OS keyring when 1Password is configured
shk env key migrate --to keyring| Option | Behavior |
|---|---|
--to <keyring|1password> |
Destination backend. Must differ from the current env.secret_store. |
Migration copies indexed keys plus keys referenced by .env / .env.keys files throughout the project tree (for example via matching DOTENV_PUBLIC_KEY* names). .git, node_modules, and target directories are excluded. Keys that exist only in the source backend with no index entry and no project env reference are not discovered automatically; remove them with an explicit shk env key delete --key … before migrating, or import them into the destination manually.
The command does not open the destination backend before migration starts when the current config already points at a broken backend; it reads keys from env.secret_store and writes to --to. Re-running migrate to the backend already configured in shk.toml is rejected.
Flow on success:
- Copy keys to the destination backend.
- Update
shk.tomlto setenv.secret_storeto--towhen a policy file exists. - Retain source keys for rollback.
After verifying the destination, remove source keys explicitly. Migration does not automatically delete them because the supported backends do not provide a portable conditional-delete operation.
See Configuration for [env] / [env.onepassword] settings and the 1Password threat model notes in the README.
shk env dotenvx
Store dotenvx private keys in the configured env secret store and inject them only when running a command through dotenvx.
shk env dotenvx import-keys .env.keys
shk env dotenvx list
shk env dotenvx run -- npm test
shk env dotenvx run -f .env.production -- npm start
shk env dotenvx run --env production -- npm start
shk env dotenvx run --key DOTENV_PRIVATE_KEY_PRODUCTION -- npm start
shk env dotenvx delete --env production
shk env dotenvx delete --key DOTENV_PRIVATE_KEY_PRODUCTION
shk env dotenvx delete --allThis command group stores dotenvx private keys in the env secret store configured by [env].secret_store in shk.toml. The default backend is the OS keyring (macOS Keychain, Windows Credential Manager, or Linux Secret Service / keyutils depending on platform support). When secret_store = "1password", the same keys are stored as tagged 1Password items in the configured vault.
import-keys reads only DOTENV_PRIVATE_KEY and DOTENV_PRIVATE_KEY_<ENV> entries from a .env.keys-style file. Raw key values are never printed. run reads the stored keys for the current project and invokes dotenvx run -- <command> with those values present only in the child process environment. delete requires an explicit target: --all, --key <DOTENV_PRIVATE_KEY*>, or --env <name>.
There is intentionally no raw-key export under shk env dotenvx because printing or writing raw private keys defeats the purpose of moving .env.keys into the configured secret store. Use shk env key export --instructions for safe handoff guidance that does not print key material.
Run options:
| Option | Behavior |
|---|---|
--dotenvx-bin <bin> |
dotenvx executable to invoke. Defaults to dotenvx. |
-f, --file <file> |
Pass one or more dotenvx env files to dotenvx run. |
--key <DOTENV_PRIVATE_KEY*> |
Inject only the named stored private key. Repeatable. |
--env <name> |
Inject DOTENV_PRIVATE_KEY_<NAME>. Use default for DOTENV_PRIVATE_KEY. Repeatable. |
-- <command> |
Command to run through dotenvx run. Required. |
Delete options:
| Option | Behavior |
|---|---|
--all |
Delete every stored dotenvx private key for the current project. |
--key <DOTENV_PRIVATE_KEY*> |
Delete one exact stored private key. |
--env <name> |
Delete DOTENV_PRIVATE_KEY_<NAME>. Use default for DOTENV_PRIVATE_KEY. |
shk secrets push
Push a dotenv payload into AWS Secrets Manager or GCP Secret Manager without printing raw secret values.
# Store the whole dotenv file as one secret.
shk secrets push --provider aws --target app/prod/dotenv --from .env.production
# Store each dotenv key as a separate secret under a target prefix.
shk secrets push --provider gcp --mode per-key --target-prefix app/prod/ --from .env.keys
# Preview writes and target names without invoking provider CLIs.
shk secrets push --profile prod --dry-runOptions:
| Option | Behavior |
|---|---|
--profile <name> |
Read defaults from [secrets.profiles.<name>] in shk.toml. CLI flags override profile values. |
| `--provider <aws | gcp>` |
--target <name> |
Blob mode target secret name. Cannot be combined with --target-prefix. |
--target-prefix <prefix> |
Per-key mode target prefix. Cannot be combined with --target. |
--from <file> |
Source dotenv file. Required unless configured by profile. |
| `--mode <blob | per-key>` |
--dry-run |
Print planned writes, target names, and metadata without calling AWS or GCP. |
--audit |
Append metadata-only entries to .shk/audit.log. Raw values are not logged. |
--confirm |
Prompt before writing. In non-interactive environments, pass --yes or use --dry-run. |
--yes |
Skip confirmation prompts. |
--create-if-missing |
Create provider secrets when they do not already exist. |
--strict |
Treat dotenv lint warnings as failures. |
--no-scan |
Skip the pre-push PII scan. Use only for an explicit exception. |
--region <region> |
AWS region. Otherwise AWS CLI environment/config is used. |
--project <project> |
GCP project. Otherwise gcloud environment/config is used. |
--location <location> |
GCP location. Defaults to global. |
--expected-env <name> |
Lint hint for values such as NODE_ENV. |
Behavior notes:
shk secrets pushrequires a project root and readsshk.tomlfrom that root.- The source file is scanned for PII before push unless
--no-scanis passed. - Blob mode requires
--target; per-key mode requires--target-prefix. - Per-key mode accepts dotenv-style
KEY=valuelines, rejects duplicate keys, and validates keys as[A-Z_][A-Z0-9_]*. - AWS and GCP writes are performed through the official
awsandgcloudCLIs. Arguments are passed directly, not through a shell.
shk hooks install
Install a Git pre-commit hook that runs shk scan --staged.
shk hooks install
shk hooks install --pre-commitThe command requires a Git repository and a project shk.toml. The hook uses managed markers and can be re-run.
shk hooks install-ai
Install managed AI tool hooks for supported tools.
shk hooks install-ai
shk hooks install-ai --dry-run
shk hooks install-ai --audit
shk hooks install-ai --log-blocked
shk hooks install-ai --tool cursor
shk hooks install-ai --tool claude-code --global
shk hooks install-ai --tool claude-code --apply-deny
shk hooks install-ai --apply-sandbox
shk hooks install-ai --tool cursor --fail-closed
shk hooks install-ai --tool copilot
shk hooks install-ai --tool antigravity
shk hooks install-ai --tool windsurf # Windsurf (Cascade)Options:
| Option | Behavior |
|---|---|
--dry-run |
Print planned changes without writing config files. |
--audit |
Add --audit to installed hook commands. Hooks log metadata and never block. |
--log-blocked |
Add --log-blocked to installed hook commands. Pre hooks still block and append metadata-only block entries to .shk/audit.log; post hooks stay non-blocking and append post audit entries. Mutually exclusive with --audit. |
--global |
Write user-level config files under the user's home directory. |
--tool <tool> |
Limit installation to one of claude-code, codex, cursor, copilot, antigravity, or windsurf. |
--fail-closed |
Cursor hooks only. Sets failClosed on managed entries. |
--apply-deny |
Claude Code: merges recommended permissions.deny entries for sensitive files and dangerous actions. Antigravity: prints recommended permission Deny list entries in the action(target) format (e.g. command(rm -rf), read_file(**/.env)) for manual entry — Antigravity's Allow/Ask/Deny lists are managed in its settings UI, not a documented project file. |
--apply-sandbox |
Applies supported sandbox hardening. Claude Code gets sandbox.enabled, hard-fail, and no unsandboxed escape hatch. Project installs also add a home-read deny with project read re-allow; global installs skip those project-relative read rules. Codex gets sandbox_mode = "workspace-write" and approval_policy = "on-request" when absent or risky. Cursor has no local sandbox setting in hooks.json, so managed hooks are set fail-closed. |
Without --tool, the command targets Claude Code, Codex, Cursor, Copilot, Antigravity, and Windsurf. Non-dry-run installation requires a project shk.toml.
Project installs keep managed hook commands portable so committed config files work on every teammate's machine: Claude Code commands scan "${CLAUDE_PROJECT_DIR:-.}", Cursor commands scan "${CURSOR_PROJECT_DIR:-.}" (both expanded by the editor's hook shell at run time, degrading to . when the variable is absent), and Codex commands scan $(git rev-parse --show-toplevel). Copilot, Antigravity, and Windsurf commands take no path argument; shk scan --hook-mode resolves the project root from the hook process working directory. When an embedded path is missing or points at a different checkout (for example an unexpanded variable or a moved clone), hook mode falls back to cwd-based project-root discovery.
Installed entries:
| Tool | Config file | Managed entries |
|---|---|---|
| Claude Code | .claude/settings.json |
UserPromptSubmit; PreToolUse for `Read |
| Cursor | .cursor/hooks.json |
beforeReadFile, beforeShellExecution, beforeMCPExecution, beforeSubmitPrompt, plus non-blocking post scans on afterShellExecution and afterMCPExecution. Prompt hooks use --fail-on medium. |
| Codex | .codex/config.toml |
PreToolUse, PermissionRequest, UserPromptSubmit, and PostToolUse blocks; also ensures features.hooks = true. Project-local commands scan $(git rev-parse --show-toplevel) so Codex can start from a subdirectory. |
| Copilot | .github/hooks/shk-security.json (global: ~/.copilot/hooks/shk-security.json) |
Command hooks for preToolUse, PermissionRequest, UserPromptSubmit, postToolUse, and postToolUseFailure. Pre-tool and permission denials use Copilot's stdout JSON contract. |
| Antigravity | .agents/hooks.json (global: ~/.gemini/config/hooks.json) |
A managed shk-security entry with blocking PreToolUse and non-blocking PostToolUse hooks matching all Antigravity tools (.*). This covers commands, file operations, searches, scheduled prompts, subagents, permission requests, and future tool names by default. Post hooks run with --post and return {}. |
| Windsurf | .windsurf/hooks.json (global: ~/.codeium/windsurf/hooks.json) |
Cascade hook commands for pre_read_code, pre_write_code, pre_run_command, pre_mcp_tool_use (blocking), pre_user_prompt (--fail-on medium), plus non-blocking post scans on post_run_command and post_mcp_tool_use. Cascade ignores hook stdout, so a block travels via exit code 2 + the stderr message; managed entries are identified by their --hook-mode windsurf command. |
Antigravity also has a unified permission engine (Deny > Ask > Allow, resources formatted as action(target)), but those lists are managed in the Antigravity settings UI and internal per-project config. Use shk hooks install-ai --tool antigravity --apply-deny to print recommended Deny entries to paste there; the shk PreToolUse hook provides equivalent runtime blocking regardless.
Managed entries are tagged with "_shk_managed": true or # shk-managed-start / # shk-managed-end. Copilot and Windsurf instead keep a schema-clean config and identify managed entries by their --hook-mode <tool> command string. Re-running replaces managed entries and leaves non-managed entries in place.
See Uninstall for removing managed hooks, skills, generated workflows, and stored dotenvx keys.
In pre-hook mode, shk also runs an action guard before content scanning. It blocks sensitive file access, environment dump commands, destructive filesystem operations, direct database mutation commands, privilege or system changes, external transfer commands, and package manager operations when they are visible in the hook payload. Tune this with [action_guard] in shk.toml; --audit remains non-blocking, while --log-blocked records only the action category.
Action guard allow patterns apply to individual shell segments by default. A pattern for the first command does not implicitly allow later &&, ||, pipe, or semicolon-separated commands; a compound allow must include the separators and cover the complete trusted command.
shk ci init github
Generate a GitHub Actions workflow that installs shk from the bundled cargo-dist installer and runs shk scan on every pull request and push to main.
shk ci init github
shk ci init github --dry-run
shk ci init github --mode audit
shk ci init github --fail-on critical
shk ci init github --upload-sarif
shk ci init github --shk-version v0.7.0
shk ci init github --output .github/workflows/security.yml --forceOptions:
| Option | Behavior |
|---|---|
| `--mode <blocking | audit>` |
--fail-on <severity> |
Severity threshold for blocking mode. Valid values: info, low, medium, high (default), critical. Ignored under --mode audit (a warning is printed). |
--path <path> |
Path passed to shk scan. Defaults to .. |
--repo <owner/name> |
GitHub repository hosting shk releases. Defaults to Kazuki-tam/security-harness-kit. |
--shk-version <version> |
Release version to install. Defaults to the generating shk release (v + crate version). Also accepts latest or a SemVer-ish tag such as v0.3.0. |
--upload-sarif |
Emit SARIF, upload it with github/codeql-action/upload-sarif@v4, and add security-events: write plus actions: read permissions. The scan exit code is applied after upload. |
--output <path> |
Workflow destination path. Defaults to .github/workflows/shk.yml. |
--dry-run |
Print the workflow YAML to stdout without writing it. |
--force |
Overwrite an existing workflow file. |
Generated workflows include permissions: contents: read and a concurrency block with cancel-in-progress: true so reruns on the same ref supersede in-flight jobs. With --upload-sarif, the generated workflow also grants security-events: write and actions: read; private repositories require GitHub Code Security. The CLI rejects unsafe values for --repo and --shk-version to keep the generated release download commands well-formed.
See GitHub Actions integration for a full guide covering the generated YAML, blocking vs audit rollout, pinning a release, and PR Required Check setup.
shk skills
Manage Claude Code / Codex / Cursor / Copilot / Antigravity / Windsurf skills bundled with shk. Skills are embedded in the binary and deployed to project directories on demand.
shk skills list
shk skills status
shk skills install
shk skills install --tool claude-code
shk skills install --tool codex
shk skills install --tool cursor
shk skills install --tool copilot
shk skills install --tool antigravity
shk skills install --tool windsurf
shk skills install --tool all --global
shk skills install --dry-run
shk skills install --forceshk skills list
Print the built-in skills available for installation.
shk skills status
Show the installation status for all supported tools (project and global paths).
shk skills install
Install the shk skill to the current project's skill directories.
Options:
| Option | Behavior |
|---|---|
--tool <tool> |
Target: claude-code, codex, cursor, copilot, antigravity, windsurf, or all (default: all). |
--global |
Write to user-level directories (~/.claude/skills/, ~/.agents/skills/, ~/.copilot/skills/, ~/.gemini/config/skills/, or ~/.codeium/windsurf/skills/) instead of the project. |
--dry-run |
Print planned paths without writing files. |
--force |
Overwrite an existing skill file. |
Install destinations:
| Tool | Project path | Global path |
|---|---|---|
claude-code |
.claude/skills/shk/SKILL.md |
~/.claude/skills/shk/SKILL.md |
codex / cursor |
.agents/skills/shk/SKILL.md |
~/.agents/skills/shk/SKILL.md |
copilot |
.github/skills/shk/SKILL.md |
~/.copilot/skills/shk/SKILL.md |
antigravity |
.agents/skills/shk/SKILL.md (shared with codex/cursor) |
~/.gemini/config/skills/shk/SKILL.md |
windsurf |
.windsurf/skills/shk/SKILL.md |
~/.codeium/windsurf/skills/shk/SKILL.md |
All destinations use the directory-plus-SKILL.md layout from the open agent skills standard, which is also the layout Claude Code loads skills from. The skill file is embedded in the shk binary at build time and requires no network access.
Versions up to 0.3.17 wrote the Claude Code skill as a flat .claude/skills/shk.md file, which Claude Code does not load. Re-running shk skills install writes the new layout and removes the legacy flat file.