Reorganize: Move all skills to skills/ folder

- Created skills/ directory
- Moved 272 skills to skills/ subfolder
- Kept agents/ at root level
- Kept installation scripts and docs at root level

Repository structure:
- skills/           - All 272 skills from skills.sh
- agents/           - Agent definitions
- *.sh, *.ps1       - Installation scripts
- README.md, etc.   - Documentation

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
admin
2026-01-23 18:05:17 +00:00
Unverified
parent 2b4e974878
commit b723e2bd7d
4083 changed files with 1056 additions and 1098063 deletions

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
export declare function runClaudeCodeHook(): Promise<void>;

View File

@@ -0,0 +1 @@
export declare const CUSTOM_RULES_DOC = "# Custom Rules Reference\n\nAgent reference for generating `.safety-net.json` config files.\n\n## Config Locations\n\n| Scope | Path | Priority |\n|-------|------|----------|\n| User | `~/.cc-safety-net/config.json` | Lower |\n| Project | `.safety-net.json` (cwd) | Higher (overrides user) |\n\nDuplicate rule names (case-insensitive) \u2192 project wins.\n\n## Schema\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [...]\n}\n```\n\n- `$schema`: Optional. Enables IDE autocomplete and inline validation.\n- `version`: Required. Must be `1`.\n- `rules`: Optional. Defaults to `[]`.\n\n**Always include `$schema`** when generating config files for IDE support.\n\n## Rule Fields\n\n| Field | Required | Constraints |\n|-------|----------|-------------|\n| `name` | Yes | `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$` \u2014 unique (case-insensitive) |\n| `command` | Yes | `^[a-zA-Z][a-zA-Z0-9_-]*$` \u2014 basename only, not path |\n| `subcommand` | No | Same pattern as command. Omit to match any. |\n| `block_args` | Yes | Non-empty array of non-empty strings |\n| `reason` | Yes | Non-empty string, max 256 chars |\n\n## Guidelines:\n\n- `name`: kebab-case, descriptive (e.g., `block-git-add-all`)\n- `command`: binary name only, lowercase\n- `subcommand`: omit if rule applies to any subcommand\n- `block_args`: include all variants (e.g., both `-g` and `--global`)\n- `reason`: explain why blocked AND suggest alternative\n\n## Matching Behavior\n\n- **Command**: Normalized to basename (`/usr/bin/git` \u2192 `git`)\n- **Subcommand**: First non-option argument after command\n- **Arguments**: Matched literally. Command blocked if **any** `block_args` item present.\n- **Short options**: Expanded (`-Ap` matches `-A`)\n- **Long options**: Exact match (`--all-files` does NOT match `--all`)\n- **Execution order**: Built-in rules first, then custom rules (additive only)\n\n## Examples\n\n### Block `git add -A`\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [\n {\n \"name\": \"block-git-add-all\",\n \"command\": \"git\",\n \"subcommand\": \"add\",\n \"block_args\": [\"-A\", \"--all\", \".\"],\n \"reason\": \"Use 'git add <specific-files>' instead.\"\n }\n ]\n}\n```\n\n### Block global npm install\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [\n {\n \"name\": \"block-npm-global\",\n \"command\": \"npm\",\n \"subcommand\": \"install\",\n \"block_args\": [\"-g\", \"--global\"],\n \"reason\": \"Use npx or local install.\"\n }\n ]\n}\n```\n\n### Block docker system prune\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [\n {\n \"name\": \"block-docker-prune\",\n \"command\": \"docker\",\n \"subcommand\": \"system\",\n \"block_args\": [\"prune\"],\n \"reason\": \"Use targeted cleanup instead.\"\n }\n ]\n}\n```\n\n## Error Handling\n\nInvalid config \u2192 silent fallback to built-in rules only. No custom rules applied.\n";

View File

@@ -0,0 +1 @@
export declare function runGeminiCLIHook(): Promise<void>;

View File

@@ -0,0 +1,2 @@
export declare function printHelp(): void;
export declare function printVersion(): void;

View File

@@ -0,0 +1 @@
export declare function printStatusline(): Promise<void>;

View File

@@ -0,0 +1,12 @@
/**
* Verify user and project scope config files for safety-net.
*/
export interface VerifyConfigOptions {
userConfigPath?: string;
projectConfigPath?: string;
}
/**
* Verify config files and print results.
* @returns Exit code (0 = success, 1 = errors found)
*/
export declare function verifyConfig(options?: VerifyConfigOptions): number;

View File

@@ -0,0 +1,21 @@
import type { AnalyzeOptions, AnalyzeResult } from '../types.ts';
import { findHasDelete } from './analyze/find.ts';
import { extractParallelChildCommand } from './analyze/parallel.ts';
import { hasRecursiveForceFlags } from './analyze/rm-flags.ts';
import { segmentChangesCwd } from './analyze/segment.ts';
import { extractXargsChildCommand, extractXargsChildCommandWithInfo } from './analyze/xargs.ts';
import { loadConfig } from './config.ts';
export declare function analyzeCommand(command: string, options?: AnalyzeOptions): AnalyzeResult | null;
export { loadConfig };
/** @internal Exported for testing */
export { findHasDelete as _findHasDelete };
/** @internal Exported for testing */
export { extractParallelChildCommand as _extractParallelChildCommand };
/** @internal Exported for testing */
export { hasRecursiveForceFlags as _hasRecursiveForceFlags };
/** @internal Exported for testing */
export { segmentChangesCwd as _segmentChangesCwd };
/** @internal Exported for testing */
export { extractXargsChildCommand as _extractXargsChildCommand };
/** @internal Exported for testing */
export { extractXargsChildCommandWithInfo as _extractXargsChildCommandWithInfo };

View File

@@ -0,0 +1,5 @@
import { type AnalyzeOptions, type AnalyzeResult, type Config } from '../../types.ts';
export type InternalOptions = AnalyzeOptions & {
config: Config;
};
export declare function analyzeCommandInternal(command: string, depth: number, options: InternalOptions): AnalyzeResult | null;

View File

@@ -0,0 +1 @@
export declare const DISPLAY_COMMANDS: ReadonlySet<string>;

View File

@@ -0,0 +1 @@
export declare function dangerousInText(text: string): string | null;

View File

@@ -0,0 +1,6 @@
export declare function analyzeFind(tokens: readonly string[]): string | null;
/**
* Check if find command has -delete action (not as argument to another option).
* Handles cases like "find -name -delete" where -delete is a filename pattern.
*/
export declare function findHasDelete(tokens: readonly string[]): boolean;

View File

@@ -0,0 +1,2 @@
export declare function extractInterpreterCodeArg(tokens: readonly string[]): string | null;
export declare function containsDangerousCode(code: string): boolean;

View File

@@ -0,0 +1,9 @@
export interface ParallelAnalyzeContext {
cwd: string | undefined;
originalCwd: string | undefined;
paranoidRm: boolean | undefined;
allowTmpdirVar: boolean;
analyzeNested: (command: string) => string | null;
}
export declare function analyzeParallel(tokens: readonly string[], context: ParallelAnalyzeContext): string | null;
export declare function extractParallelChildCommand(tokens: readonly string[]): string[];

View File

@@ -0,0 +1 @@
export declare function hasRecursiveForceFlags(tokens: readonly string[]): boolean;

View File

@@ -0,0 +1,8 @@
import { type AnalyzeOptions, type Config } from '../../types.ts';
export type InternalOptions = AnalyzeOptions & {
config: Config;
effectiveCwd: string | null | undefined;
analyzeNested: (command: string) => string | null;
};
export declare function analyzeSegment(tokens: string[], depth: number, options: InternalOptions): string | null;
export declare function segmentChangesCwd(segment: readonly string[]): boolean;

View File

@@ -0,0 +1 @@
export declare function extractDashCArg(tokens: readonly string[]): string | null;

View File

@@ -0,0 +1 @@
export declare function isTmpdirOverriddenToNonTemp(envAssignments: Map<string, string>): boolean;

View File

@@ -0,0 +1,14 @@
export interface XargsAnalyzeContext {
cwd: string | undefined;
originalCwd: string | undefined;
paranoidRm: boolean | undefined;
allowTmpdirVar: boolean;
}
export declare function analyzeXargs(tokens: readonly string[], context: XargsAnalyzeContext): string | null;
interface XargsParseResult {
childTokens: string[];
replacementToken: string | null;
}
export declare function extractXargsChildCommandWithInfo(tokens: readonly string[]): XargsParseResult;
export declare function extractXargsChildCommand(tokens: readonly string[]): string[];
export {};

View File

@@ -0,0 +1,17 @@
/**
* Sanitize session ID to prevent path traversal attacks.
* Returns null if the session ID is invalid.
* @internal Exported for testing
*/
export declare function sanitizeSessionIdForFilename(sessionId: string): string | null;
/**
* Write an audit log entry for a denied command.
* Logs are written to ~/.cc-safety-net/logs/<session_id>.jsonl
*/
export declare function writeAuditLog(sessionId: string, command: string, segment: string, reason: string, cwd: string | null, options?: {
homeDir?: string;
}): void;
/**
* Redact secrets from text to avoid leaking sensitive information in logs.
*/
export declare function redactSecrets(text: string): string;

View File

@@ -0,0 +1,12 @@
import { type Config, type ValidationResult } from '../types.ts';
export interface LoadConfigOptions {
/** Override user config directory (for testing) */
userConfigDir?: string;
}
export declare function loadConfig(cwd?: string, options?: LoadConfigOptions): Config;
/** @internal Exported for testing */
export declare function validateConfig(config: unknown): ValidationResult;
export declare function validateConfigFile(path: string): ValidationResult;
export declare function getUserConfigPath(): string;
export declare function getProjectConfigPath(cwd?: string): string;
export type { ValidationResult };

View File

@@ -0,0 +1 @@
export declare const CUSTOM_RULES_DOC = "# Custom Rules Reference\n\nAgent reference for generating `.safety-net.json` config files.\n\n## Config Locations\n\n| Scope | Path | Priority |\n|-------|------|----------|\n| User | `~/.cc-safety-net/config.json` | Lower |\n| Project | `.safety-net.json` (cwd) | Higher (overrides user) |\n\nDuplicate rule names (case-insensitive) \u2192 project wins.\n\n## Schema\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [...]\n}\n```\n\n- `$schema`: Optional. Enables IDE autocomplete and inline validation.\n- `version`: Required. Must be `1`.\n- `rules`: Optional. Defaults to `[]`.\n\n**Always include `$schema`** when generating config files for IDE support.\n\n## Rule Fields\n\n| Field | Required | Constraints |\n|-------|----------|-------------|\n| `name` | Yes | `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$` \u2014 unique (case-insensitive) |\n| `command` | Yes | `^[a-zA-Z][a-zA-Z0-9_-]*$` \u2014 basename only, not path |\n| `subcommand` | No | Same pattern as command. Omit to match any. |\n| `block_args` | Yes | Non-empty array of non-empty strings |\n| `reason` | Yes | Non-empty string, max 256 chars |\n\n## Guidelines:\n\n- `name`: kebab-case, descriptive (e.g., `block-git-add-all`)\n- `command`: binary name only, lowercase\n- `subcommand`: omit if rule applies to any subcommand\n- `block_args`: include all variants (e.g., both `-g` and `--global`)\n- `reason`: explain why blocked AND suggest alternative\n\n## Matching Behavior\n\n- **Command**: Normalized to basename (`/usr/bin/git` \u2192 `git`)\n- **Subcommand**: First non-option argument after command\n- **Arguments**: Matched literally. Command blocked if **any** `block_args` item present.\n- **Short options**: Expanded (`-Ap` matches `-A`)\n- **Long options**: Exact match (`--all-files` does NOT match `--all`)\n- **Execution order**: Built-in rules first, then custom rules (additive only)\n\n## Examples\n\n### Block `git add -A`\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [\n {\n \"name\": \"block-git-add-all\",\n \"command\": \"git\",\n \"subcommand\": \"add\",\n \"block_args\": [\"-A\", \"--all\", \".\"],\n \"reason\": \"Use 'git add <specific-files>' instead.\"\n }\n ]\n}\n```\n\n### Block global npm install\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [\n {\n \"name\": \"block-npm-global\",\n \"command\": \"npm\",\n \"subcommand\": \"install\",\n \"block_args\": [\"-g\", \"--global\"],\n \"reason\": \"Use npx or local install.\"\n }\n ]\n}\n```\n\n### Block docker system prune\n\n```json\n{\n \"$schema\": \"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json\",\n \"version\": 1,\n \"rules\": [\n {\n \"name\": \"block-docker-prune\",\n \"command\": \"docker\",\n \"subcommand\": \"system\",\n \"block_args\": [\"prune\"],\n \"reason\": \"Use targeted cleanup instead.\"\n }\n ]\n}\n```\n\n## Error Handling\n\nInvalid config \u2192 silent fallback to built-in rules only. No custom rules applied.\n";

View File

@@ -0,0 +1 @@
export declare function envTruthy(name: string): boolean;

View File

@@ -0,0 +1,10 @@
type RedactFn = (text: string) => string;
export interface FormatBlockedMessageInput {
reason: string;
command?: string;
segment?: string;
maxLen?: number;
redact?: RedactFn;
}
export declare function formatBlockedMessage(input: FormatBlockedMessageInput): string;
export {};

View File

@@ -0,0 +1,2 @@
import type { CustomRule } from '../types.ts';
export declare function checkCustomRules(tokens: string[], rules: CustomRule[]): string | null;

View File

@@ -0,0 +1,8 @@
export declare function analyzeGit(tokens: readonly string[]): string | null;
declare function extractGitSubcommandAndRest(tokens: readonly string[]): {
subcommand: string | null;
rest: string[];
};
declare function getCheckoutPositionalArgs(tokens: readonly string[]): string[];
/** @internal Exported for testing */
export { extractGitSubcommandAndRest as _extractGitSubcommandAndRest, getCheckoutPositionalArgs as _getCheckoutPositionalArgs, };

View File

@@ -0,0 +1,9 @@
export interface AnalyzeRmOptions {
cwd?: string;
originalCwd?: string;
paranoid?: boolean;
allowTmpdirVar?: boolean;
tmpdirOverridden?: boolean;
}
export declare function analyzeRm(tokens: string[], options?: AnalyzeRmOptions): string | null;
export declare function isHomeDirectory(cwd: string): boolean;

View File

@@ -0,0 +1,15 @@
export declare function splitShellCommands(command: string): string[][];
export interface EnvStrippingResult {
tokens: string[];
envAssignments: Map<string, string>;
}
export declare function stripEnvAssignmentsWithInfo(tokens: string[]): EnvStrippingResult;
export interface WrapperStrippingResult {
tokens: string[];
envAssignments: Map<string, string>;
}
export declare function stripWrappers(tokens: string[]): string[];
export declare function stripWrappersWithInfo(tokens: string[]): WrapperStrippingResult;
export declare function extractShortOpts(tokens: string[]): Set<string>;
export declare function normalizeCommandToken(token: string): string;
export declare function getBasename(token: string): string;

View File

@@ -0,0 +1,12 @@
/**
* Verify user and project scope config files for safety-net.
*/
export interface VerifyConfigOptions {
userConfigPath?: string;
projectConfigPath?: string;
}
/**
* Verify config files and print results.
* @returns Exit code (0 = success, 1 = errors found)
*/
export declare function verifyConfig(options?: VerifyConfigOptions): number;

View File

@@ -0,0 +1,2 @@
import type { BuiltinCommandName, BuiltinCommands } from './types.ts';
export declare function loadBuiltinCommands(disabledCommands?: BuiltinCommandName[]): BuiltinCommands;

View File

@@ -0,0 +1,2 @@
export * from './commands.ts';
export * from './types.ts';

View File

@@ -0,0 +1 @@
export declare const SET_CUSTOM_RULES_TEMPLATE = "You are helping the user configure custom blocking rules for claude-code-safety-net.\n\n## Context\n\n### Schema Documentation\n\n!`npx -y cc-safety-net --custom-rules-doc`\n\n## Your Task\n\nFollow this flow exactly:\n\n### Step 1: Ask for Scope\n\nAsk: **Which scope would you like to configure?**\n- **User** (`~/.cc-safety-net/config.json`) - applies to all your projects\n- **Project** (`.safety-net.json`) - applies only to this project\n\n### Step 2: Show Examples and Ask for Rules\n\nShow examples in natural language:\n- \"Block `git add -A` and `git add .` to prevent blanket staging\"\n- \"Block `npm install -g` to prevent global package installs\"\n- \"Block `docker system prune` to prevent accidental cleanup\"\n\nAsk the user to describe rules in natural language. They can list multiple.\n\n### Step 3: Generate JSON Config\n\nParse user input and generate valid schema JSON using the schema documentation above.\n\n### Step 4: Show Config and Confirm\n\nDisplay the generated JSON and ask:\n- \"Does this look correct?\"\n- \"Would you like to modify anything?\"\n\n### Step 5: Check and Handle Existing Config\n\n1. Check existing User Config with `cat ~/.cc-safety-net/config.json 2>/dev/null || echo \"No user config found\"`\n2. Check existing Project Config with `cat .safety-net.json 2>/dev/null || echo \"No project config found\"`\n\nIf the chosen scope already has a config:\nShow the existing config to the user.\nAsk: **Merge** (add new rules, duplicates use new version) or **Replace**?\n\n### Step 6: Write and Validate\n\nWrite the config to the chosen scope, then validate with `npx -y cc-safety-net --verify-config`.\n\nIf validation errors:\n- Show specific errors\n- Offer to fix with your best suggestion\n- Confirm before proceeding\n\n### Step 7: Confirm Success\n\nTell the user:\n1. Config saved to [path]\n2. **Changes take effect immediately** - no restart needed\n3. Summary of rules added\n\n## Important Notes\n\n- Custom rules can only ADD restrictions, not bypass built-in protections\n- Rule names must be unique (case-insensitive)\n- Invalid config \u2192 entire config ignored, only built-in rules apply";

View File

@@ -0,0 +1 @@
export declare const VERIFY_CUSTOM_RULES_TEMPLATE = "You are helping the user verify the custom rules config file.\n\n## Your Task\n\nRun `npx -y cc-safety-net --verify-config` to check current validation status\n\nIf the config has validation errors:\n1. Show the specific validation errors\n2. Run `npx -y cc-safety-net --custom-rules-doc` to read the schema documentation\n3. Offer to fix them with your best suggestion\n4. Ask for confirmation before proceeding\n5. After fixing, run `npx -y cc-safety-net --verify-config` to verify again";

View File

@@ -0,0 +1,6 @@
export type BuiltinCommandName = 'set-custom-rules' | 'verify-custom-rules';
export interface CommandDefinition {
description?: string;
template: string;
}
export type BuiltinCommands = Record<string, CommandDefinition>;

View File

@@ -0,0 +1,2 @@
import type { Plugin } from '@opencode-ai/plugin';
export declare const SafetyNetPlugin: Plugin;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
/**
* Shared types for the safety-net plugin.
*/
/** Custom rule definition from .safety-net.json */
export interface CustomRule {
/** Unique identifier for the rule */
name: string;
/** Base command to match (e.g., "git", "npm") */
command: string;
/** Optional subcommand to match (e.g., "add", "install") */
subcommand?: string;
/** Arguments that trigger the block */
block_args: string[];
/** Message shown when blocked */
reason: string;
}
/** Configuration loaded from .safety-net.json */
export interface Config {
/** Schema version (must be 1) */
version: number;
/** Custom blocking rules */
rules: CustomRule[];
}
/** Result of config validation */
export interface ValidationResult {
/** List of validation error messages */
errors: string[];
/** Set of rule names found (for duplicate detection) */
ruleNames: Set<string>;
}
/** Result of command analysis */
export interface AnalyzeResult {
/** The reason the command was blocked */
reason: string;
/** The specific segment that triggered the block */
segment: string;
}
/** Claude Code hook input format */
export interface HookInput {
session_id?: string;
transcript_path?: string;
cwd?: string;
permission_mode?: string;
hook_event_name: string;
tool_name: string;
tool_input: {
command: string;
description?: string;
};
tool_use_id?: string;
}
/** Claude Code hook output format */
export interface HookOutput {
hookSpecificOutput: {
hookEventName: string;
permissionDecision: 'allow' | 'deny';
permissionDecisionReason?: string;
};
}
/** Gemini CLI hook input format */
export interface GeminiHookInput {
session_id?: string;
transcript_path?: string;
cwd?: string;
hook_event_name: string;
timestamp?: string;
tool_name?: string;
tool_input?: {
command?: string;
[key: string]: unknown;
};
}
/** Gemini CLI hook output format */
export interface GeminiHookOutput {
decision: 'deny';
reason: string;
systemMessage: string;
continue?: boolean;
stopReason?: string;
suppressOutput?: boolean;
}
/** Options for command analysis */
export interface AnalyzeOptions {
/** Current working directory */
cwd?: string;
/** Effective cwd after cd commands (null = unknown, undefined = use cwd) */
effectiveCwd?: string | null;
/** Loaded configuration */
config?: Config;
/** Fail-closed on unparseable commands */
strict?: boolean;
/** Block non-temp rm -rf even within cwd */
paranoidRm?: boolean;
/** Block interpreter one-liners */
paranoidInterpreters?: boolean;
/** Allow $TMPDIR paths (false when TMPDIR is overridden to non-temp) */
allowTmpdirVar?: boolean;
}
/** Audit log entry */
export interface AuditLogEntry {
ts: string;
command: string;
segment: string;
reason: string;
cwd?: string | null;
}
/** Constants */
export declare const MAX_RECURSION_DEPTH = 10;
export declare const MAX_STRIP_ITERATIONS = 20;
export declare const NAME_PATTERN: RegExp;
export declare const COMMAND_PATTERN: RegExp;
export declare const MAX_REASON_LENGTH = 256;
/** Shell operators that split commands */
export declare const SHELL_OPERATORS: Set<string>;
/** Shell wrappers that need recursive analysis */
export declare const SHELL_WRAPPERS: Set<string>;
/** Interpreters that can execute code */
export declare const INTERPRETERS: Set<string>;
/** Dangerous commands to detect in interpreter code */
export declare const DANGEROUS_PATTERNS: RegExp[];
export declare const PARANOID_INTERPRETERS_SUFFIX = "\n\n(Paranoid mode: interpreter one-liners are blocked.)";