claudepod 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.devcontainer/CHANGELOG.md +179 -50
  2. package/.devcontainer/CLAUDE.md +24 -7
  3. package/.devcontainer/README.md +2 -0
  4. package/.devcontainer/config/main-system-prompt.md +357 -81
  5. package/.devcontainer/config/settings.json +6 -3
  6. package/.devcontainer/devcontainer.json +17 -5
  7. package/.devcontainer/features/agent-browser/README.md +65 -0
  8. package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
  9. package/.devcontainer/features/agent-browser/install.sh +72 -0
  10. package/.devcontainer/features/lsp-servers/devcontainer-feature.json +8 -2
  11. package/.devcontainer/features/lsp-servers/install.sh +25 -1
  12. package/.devcontainer/features/notify-hook/README.md +86 -0
  13. package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
  14. package/.devcontainer/features/notify-hook/install.sh +38 -0
  15. package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +99 -0
  16. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
  17. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
  18. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
  19. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
  20. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
  21. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
  22. package/.devcontainer/plugins/devs-marketplace/plugins/claudepod-lsp/.claude-plugin/plugin.json +7 -0
  23. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
  24. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
  25. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
  26. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
  27. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +16 -0
  28. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
  29. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
  30. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
  31. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
  32. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
  33. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket:create-pr.md +337 -0
  34. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket:new.md +166 -0
  35. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket:review-commit.md +290 -0
  36. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket:work.md +257 -0
  37. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
  38. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
  39. package/.devcontainer/scripts/setup-aliases.sh +41 -13
  40. package/.devcontainer/scripts/setup-plugins.sh +35 -13
  41. package/.devcontainer/scripts/setup.sh +1 -3
  42. package/README.md +37 -0
  43. package/package.json +1 -1
  44. package/.devcontainer/scripts/setup-lsp.sh +0 -20
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Auto-lint files after editing.
4
+
5
+ Reads tool input from stdin, detects file type by extension,
6
+ runs appropriate linter if available.
7
+ Outputs JSON with additionalContext containing lint warnings.
8
+ Non-blocking: exit 0 regardless of lint result.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ # Linter configuration: extension -> (command, args, name, parser)
18
+ PYTHON_EXTENSIONS = {".py", ".pyi"}
19
+
20
+
21
+ def lint_python(file_path: str) -> tuple[bool, str]:
22
+ """Run pyright on a Python file.
23
+
24
+ Returns:
25
+ (success, message)
26
+ """
27
+ pyright_cmd = "pyright"
28
+
29
+ # Check if pyright is available
30
+ try:
31
+ subprocess.run(
32
+ ["which", pyright_cmd],
33
+ capture_output=True,
34
+ check=True
35
+ )
36
+ except subprocess.CalledProcessError:
37
+ return True, "" # Pyright not available
38
+
39
+ try:
40
+ result = subprocess.run(
41
+ [pyright_cmd, "--outputjson", file_path],
42
+ capture_output=True,
43
+ text=True,
44
+ timeout=55
45
+ )
46
+
47
+ # Parse pyright JSON output
48
+ try:
49
+ output = json.loads(result.stdout)
50
+ diagnostics = output.get("generalDiagnostics", [])
51
+
52
+ if not diagnostics:
53
+ return True, "[Auto-linter] Pyright: No issues found"
54
+
55
+ # Format diagnostics
56
+ issues = []
57
+ for diag in diagnostics[:5]: # Limit to first 5 issues
58
+ severity = diag.get("severity", "info")
59
+ message = diag.get("message", "")
60
+ line = diag.get("range", {}).get("start", {}).get("line", 0) + 1
61
+
62
+ if severity == "error":
63
+ icon = "✗"
64
+ elif severity == "warning":
65
+ icon = "!"
66
+ else:
67
+ icon = "•"
68
+
69
+ issues.append(f" {icon} Line {line}: {message}")
70
+
71
+ total = len(diagnostics)
72
+ shown = min(5, total)
73
+ header = f"[Auto-linter] Pyright: {total} issue(s)"
74
+ if total > shown:
75
+ header += f" (showing first {shown})"
76
+
77
+ return True, header + "\n" + "\n".join(issues)
78
+
79
+ except json.JSONDecodeError:
80
+ # Pyright output not JSON, might be an error
81
+ if result.stderr:
82
+ return True, f"[Auto-linter] Pyright error: {result.stderr.strip()[:100]}"
83
+ return True, ""
84
+
85
+ except subprocess.TimeoutExpired:
86
+ return True, "[Auto-linter] Pyright timed out"
87
+ except Exception as e:
88
+ return True, f"[Auto-linter] Error: {e}"
89
+
90
+
91
+ def lint_file(file_path: str) -> tuple[bool, str]:
92
+ """Run appropriate linter for file.
93
+
94
+ Returns:
95
+ (success, message)
96
+ """
97
+ ext = Path(file_path).suffix.lower()
98
+
99
+ if ext in PYTHON_EXTENSIONS:
100
+ return lint_python(file_path)
101
+
102
+ # No linter available for this file type
103
+ return True, ""
104
+
105
+
106
+ def main():
107
+ try:
108
+ input_data = json.load(sys.stdin)
109
+ tool_input = input_data.get("tool_input", {})
110
+ file_path = tool_input.get("file_path", "")
111
+
112
+ if not file_path:
113
+ sys.exit(0)
114
+
115
+ # Check if file exists
116
+ if not os.path.exists(file_path):
117
+ sys.exit(0)
118
+
119
+ _, message = lint_file(file_path)
120
+
121
+ if message:
122
+ # Output context for Claude
123
+ print(json.dumps({
124
+ "additionalContext": message
125
+ }))
126
+
127
+ sys.exit(0)
128
+
129
+ except json.JSONDecodeError:
130
+ sys.exit(0)
131
+ except Exception as e:
132
+ print(f"Hook error: {e}", file=sys.stderr)
133
+ sys.exit(0)
134
+
135
+
136
+ if __name__ == "__main__":
137
+ main()
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "claudepod-lsp",
3
+ "description": "LSP servers for ClaudePod (Python, TypeScript, Go)",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "dangerous-command-blocker",
3
+ "description": "Blocks dangerous bash commands (rm -rf, sudo rm, chmod 777, force push)",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "Block dangerous bash commands before execution",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/block-dangerous.py",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Block dangerous bash commands before execution.
4
+
5
+ Reads tool input from stdin, checks against dangerous patterns.
6
+ Exit code 2 blocks the command with error message.
7
+ Exit code 0 allows the command to proceed.
8
+ """
9
+
10
+ import json
11
+ import re
12
+ import sys
13
+
14
+ DANGEROUS_PATTERNS = [
15
+ # Destructive filesystem deletion
16
+ (r'\brm\s+.*-[^\s]*r[^\s]*f[^\s]*\s+[/~](?:\s|$)',
17
+ "Blocked: rm -rf on root or home directory"),
18
+ (r'\brm\s+.*-[^\s]*f[^\s]*r[^\s]*\s+[/~](?:\s|$)',
19
+ "Blocked: rm -rf on root or home directory"),
20
+ (r'\brm\s+-rf\s+/(?:\s|$)',
21
+ "Blocked: rm -rf /"),
22
+ (r'\brm\s+-rf\s+~(?:\s|$)',
23
+ "Blocked: rm -rf ~"),
24
+
25
+ # Root-level file removal
26
+ (r'\bsudo\s+rm\b',
27
+ "Blocked: sudo rm - use caution with privileged deletion"),
28
+
29
+ # World-writable permissions
30
+ (r'\bchmod\s+777\b',
31
+ "Blocked: chmod 777 creates security vulnerability"),
32
+ (r'\bchmod\s+-R\s+777\b',
33
+ "Blocked: recursive chmod 777 creates security vulnerability"),
34
+
35
+ # Force push to main/master
36
+ (r'\bgit\s+push\s+.*--force.*\s+(origin\s+)?(main|master)\b',
37
+ "Blocked: force push to main/master destroys history"),
38
+ (r'\bgit\s+push\s+.*-f\s+.*\s+(origin\s+)?(main|master)\b',
39
+ "Blocked: force push to main/master destroys history"),
40
+ (r'\bgit\s+push\s+-f\s+(origin\s+)?(main|master)\b',
41
+ "Blocked: force push to main/master destroys history"),
42
+ (r'\bgit\s+push\s+--force\s+(origin\s+)?(main|master)\b',
43
+ "Blocked: force push to main/master destroys history"),
44
+
45
+ # System directory modification
46
+ (r'>\s*/usr/',
47
+ "Blocked: writing to /usr system directory"),
48
+ (r'>\s*/etc/',
49
+ "Blocked: writing to /etc system directory"),
50
+ (r'>\s*/bin/',
51
+ "Blocked: writing to /bin system directory"),
52
+ (r'>\s*/sbin/',
53
+ "Blocked: writing to /sbin system directory"),
54
+
55
+ # Disk formatting
56
+ (r'\bmkfs\.\w+',
57
+ "Blocked: disk formatting command"),
58
+ (r'\bdd\s+.*of=/dev/',
59
+ "Blocked: dd writing to device"),
60
+
61
+ # History manipulation
62
+ (r'\bgit\s+reset\s+--hard\s+origin/(main|master)\b',
63
+ "Blocked: hard reset to remote main/master - destructive operation"),
64
+ ]
65
+
66
+
67
+ def check_command(command: str) -> tuple[bool, str]:
68
+ """Check if command matches any dangerous pattern.
69
+
70
+ Returns:
71
+ (is_dangerous, message)
72
+ """
73
+ for pattern, message in DANGEROUS_PATTERNS:
74
+ if re.search(pattern, command, re.IGNORECASE):
75
+ return True, message
76
+ return False, ""
77
+
78
+
79
+ def main():
80
+ try:
81
+ input_data = json.load(sys.stdin)
82
+ tool_input = input_data.get("tool_input", {})
83
+ command = tool_input.get("command", "")
84
+
85
+ if not command:
86
+ sys.exit(0)
87
+
88
+ is_dangerous, message = check_command(command)
89
+
90
+ if is_dangerous:
91
+ # Output error message and exit 2 to block
92
+ print(json.dumps({
93
+ "error": message
94
+ }))
95
+ sys.exit(2)
96
+
97
+ # Allow command to proceed
98
+ sys.exit(0)
99
+
100
+ except json.JSONDecodeError:
101
+ # If we can't parse input, allow by default
102
+ sys.exit(0)
103
+ except Exception as e:
104
+ # Log error but don't block on hook failure
105
+ print(f"Hook error: {e}", file=sys.stderr)
106
+ sys.exit(0)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "notify-hook",
3
+ "description": "Desktop notifications and audio chime when Claude finishes responding",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "description": "Notification when Claude finishes responding",
3
+ "hooks": {
4
+ "Stop": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "/usr/local/bin/claude-notify",
10
+ "timeout": 5
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "planning-reminder",
3
+ "description": "Injects planning-first workflow reminder on every prompt",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "Inject planning workflow reminder on user prompt submission",
3
+ "hooks": {
4
+ "UserPromptSubmit": [
5
+ {
6
+ "matcher": "*",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "echo '{\"systemMessage\": \"<system-reminder>If the user is asking a question rather than requesting implementation, respond directly and offer to create a plan if relevant. For implementation requests: if no plan exists or the previous plan is complete, use EnterPlanMode to gather requirements and design an approach before writing code. Use AskUserQuestion to clarify ambiguities. Only implement after explicit user approval.</system-reminder>\"}'",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "protected-files-guard",
3
+ "description": "Blocks modifications to .env, lock files, .git/, and credentials",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "Block modifications to protected files",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Edit|Write",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/guard-protected.py",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Block modifications to protected files.
4
+
5
+ Reads tool input from stdin, checks file path against protected patterns.
6
+ Exit code 2 blocks the edit with error message.
7
+ Exit code 0 allows the edit to proceed.
8
+ """
9
+
10
+ import json
11
+ import re
12
+ import sys
13
+
14
+ # Patterns that should be protected from modification
15
+ PROTECTED_PATTERNS = [
16
+ # Environment secrets
17
+ (r'(^|/)\.env$', "Blocked: .env contains secrets - edit manually if needed"),
18
+ (r'(^|/)\.env\.[^/]+$', "Blocked: .env.* files contain secrets - edit manually if needed"),
19
+
20
+ # Git internals
21
+ (r'(^|/)\.git/', "Blocked: .git/ directory is managed by git"),
22
+
23
+ # Lock files (should be modified via package manager)
24
+ (r'(^|/)package-lock\.json$', "Blocked: package-lock.json - use npm install instead"),
25
+ (r'(^|/)yarn\.lock$', "Blocked: yarn.lock - use yarn install instead"),
26
+ (r'(^|/)pnpm-lock\.yaml$', "Blocked: pnpm-lock.yaml - use pnpm install instead"),
27
+ (r'(^|/)Gemfile\.lock$', "Blocked: Gemfile.lock - use bundle install instead"),
28
+ (r'(^|/)poetry\.lock$', "Blocked: poetry.lock - use poetry install instead"),
29
+ (r'(^|/)Cargo\.lock$', "Blocked: Cargo.lock - use cargo build instead"),
30
+ (r'(^|/)composer\.lock$', "Blocked: composer.lock - use composer install instead"),
31
+ (r'(^|/)uv\.lock$', "Blocked: uv.lock - use uv sync instead"),
32
+
33
+ # Certificates and keys
34
+ (r'\.pem$', "Blocked: .pem files contain sensitive cryptographic material"),
35
+ (r'\.key$', "Blocked: .key files contain sensitive cryptographic material"),
36
+ (r'\.crt$', "Blocked: .crt certificate files should not be edited directly"),
37
+ (r'\.p12$', "Blocked: .p12 files contain sensitive cryptographic material"),
38
+ (r'\.pfx$', "Blocked: .pfx files contain sensitive cryptographic material"),
39
+
40
+ # Credential files
41
+ (r'(^|/)credentials\.json$', "Blocked: credentials.json contains secrets"),
42
+ (r'(^|/)secrets\.yaml$', "Blocked: secrets.yaml contains secrets"),
43
+ (r'(^|/)secrets\.yml$', "Blocked: secrets.yml contains secrets"),
44
+ (r'(^|/)secrets\.json$', "Blocked: secrets.json contains secrets"),
45
+ (r'(^|/)\.secrets$', "Blocked: .secrets file contains secrets"),
46
+
47
+ # Auth directories and files
48
+ (r'(^|/)\.ssh/', "Blocked: .ssh/ contains sensitive authentication data"),
49
+ (r'(^|/)\.aws/', "Blocked: .aws/ contains AWS credentials"),
50
+ (r'(^|/)\.netrc$', "Blocked: .netrc contains authentication credentials"),
51
+ (r'(^|/)\.npmrc$', "Blocked: .npmrc may contain auth tokens - edit manually if needed"),
52
+ (r'(^|/)\.pypirc$', "Blocked: .pypirc contains PyPI credentials"),
53
+
54
+ # Other sensitive files
55
+ (r'(^|/)id_rsa', "Blocked: SSH private key file"),
56
+ (r'(^|/)id_ed25519', "Blocked: SSH private key file"),
57
+ (r'(^|/)id_ecdsa', "Blocked: SSH private key file"),
58
+ ]
59
+
60
+
61
+ def check_path(file_path: str) -> tuple[bool, str]:
62
+ """Check if file path matches any protected pattern.
63
+
64
+ Returns:
65
+ (is_protected, message)
66
+ """
67
+ # Normalize path for consistent matching
68
+ normalized = file_path.replace('\\', '/')
69
+
70
+ for pattern, message in PROTECTED_PATTERNS:
71
+ if re.search(pattern, normalized, re.IGNORECASE):
72
+ return True, message
73
+
74
+ return False, ""
75
+
76
+
77
+ def main():
78
+ try:
79
+ input_data = json.load(sys.stdin)
80
+ tool_input = input_data.get("tool_input", {})
81
+ file_path = tool_input.get("file_path", "")
82
+
83
+ if not file_path:
84
+ sys.exit(0)
85
+
86
+ is_protected, message = check_path(file_path)
87
+
88
+ if is_protected:
89
+ # Output error message and exit 2 to block
90
+ print(json.dumps({
91
+ "error": message
92
+ }))
93
+ sys.exit(2)
94
+
95
+ # Allow edit to proceed
96
+ sys.exit(0)
97
+
98
+ except json.JSONDecodeError:
99
+ # If we can't parse input, allow by default
100
+ sys.exit(0)
101
+ except Exception as e:
102
+ # Log error but don't block on hook failure
103
+ print(f"Hook error: {e}", file=sys.stderr)
104
+ sys.exit(0)
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()