-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathinstall.ps1
More file actions
201 lines (176 loc) · 6.58 KB
/
install.ps1
File metadata and controls
201 lines (176 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Hardstop installer for Windows
# Installs: plugin, skill, and hooks configuration
$ErrorActionPreference = "Stop"
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR.TrimEnd('\', '/') } else { Join-Path $env:USERPROFILE '.claude' }
$PluginDest = Join-Path $ClaudeDir 'plugins\hs'
$SkillDest = Join-Path $ClaudeDir 'skills\hs'
$SettingsFile = Join-Path $ClaudeDir 'settings.json'
$Source = Split-Path -Parent $MyInvocation.MyCommand.Path
Write-Host "=== Hardstop Installer ===" -ForegroundColor Cyan
Write-Host ""
# Step 1: Install plugin
Write-Host "[1/3] Installing plugin to: $PluginDest"
if (-not (Test-Path $PluginDest)) {
New-Item -ItemType Directory -Force -Path $PluginDest | Out-Null
}
Copy-Item -Path "$Source\*" -Destination $PluginDest -Recurse -Force -Exclude '.venv','.git','.pytest_cache','install.ps1','install.sh'
Write-Host " Plugin installed." -ForegroundColor Green
# Step 2: Create skill
Write-Host "[2/3] Creating skill at: $SkillDest"
if (-not (Test-Path $SkillDest)) {
New-Item -ItemType Directory -Force -Path $SkillDest | Out-Null
}
$SkillContent = @"
---
name: hs
version: 1.0.0
description: >
Hardstop - Pre-execution safety layer control. Use this skill when the user wants to
enable, disable, check status, skip, or view logs for the Hardstop safety system.
author: Francesco Marinoni Moretto
license: CC-BY-4.0
triggers:
- hs
- hs on
- hs off
- hs status
- hs skip
- hs log
---
# Hardstop Control
**Purpose:** Control the Hardstop pre-execution safety layer that blocks dangerous shell commands.
When the user invokes ``/hs`` (with optional subcommands), run the appropriate Python command:
- ``/hs`` or ``/hs status``: ``python $PluginDest/commands/hs_cmd.py status``
- ``/hs on``: ``python $PluginDest/commands/hs_cmd.py on``
- ``/hs off``: ``python $PluginDest/commands/hs_cmd.py off``
- ``/hs skip``: ``python $PluginDest/commands/hs_cmd.py skip``
- ``/hs log``: ``python $PluginDest/commands/hs_cmd.py log``
"@
$SkillContent | Out-File -FilePath (Join-Path $SkillDest "SKILL.md") -Encoding utf8
Write-Host " Skill created." -ForegroundColor Green
# Step 3: Add hooks to settings.json
Write-Host "[3/3] Configuring hooks in: $SettingsFile"
# Ensure config directory exists
if (-not (Test-Path $ClaudeDir)) {
New-Item -ItemType Directory -Force -Path $ClaudeDir | Out-Null
}
if (Test-Path $SettingsFile) {
$content = Get-Content $SettingsFile -Raw
if ($content -match "pre_tool_use.py") {
Write-Host " Hooks already configured, skipping." -ForegroundColor Yellow
} else {
# Backup existing settings
Copy-Item $SettingsFile "${SettingsFile}.backup"
Write-Host " Backed up existing settings."
# Use Python for reliable JSON manipulation
$pythonScript = @"
import json
import sys
settings_file = sys.argv[1]
plugin_dest = sys.argv[2]
try:
with open(settings_file, 'r', encoding='utf-8-sig') as f:
content = f.read().strip()
settings = json.loads(content) if content else {}
except:
settings = {}
if 'hooks' not in settings:
settings['hooks'] = {}
if 'PreToolUse' not in settings['hooks']:
settings['hooks']['PreToolUse'] = []
import os
hook_base = os.path.join(plugin_dest, 'hooks')
bash_hook = os.path.join(hook_base, 'pre_tool_use.py').replace('\\', '/')
read_hook = os.path.join(hook_base, 'pre_read.py').replace('\\', '/')
# Add Bash hook
settings['hooks']['PreToolUse'].append({
'matcher': 'Bash',
'hooks': [{
'type': 'command',
'command': f'python {bash_hook}',
'timeout': 30
}]
})
# Add Read hook (v1.3 - secrets protection)
settings['hooks']['PreToolUse'].append({
'matcher': 'Read',
'hooks': [{
'type': 'command',
'command': f'python {read_hook}',
'timeout': 30
}]
})
with open(settings_file, 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=2)
"@
$pythonScript | python - "$SettingsFile" "$PluginDest"
if ($LASTEXITCODE -eq 0) {
Write-Host " Hooks configured (Bash + Read)." -ForegroundColor Green
} else {
Write-Host " WARNING: Could not configure hooks. Add manually." -ForegroundColor Yellow
}
}
} else {
# Create new settings.json with hooks using Python
$pythonScript = @"
import json
import os
import sys
settings_file = sys.argv[1]
plugin_dest = sys.argv[2]
hook_base = os.path.join(plugin_dest, 'hooks')
bash_hook = os.path.join(hook_base, 'pre_tool_use.py').replace('\\', '/')
read_hook = os.path.join(hook_base, 'pre_read.py').replace('\\', '/')
settings = {
'hooks': {
'PreToolUse': [
{
'matcher': 'Bash',
'hooks': [{
'type': 'command',
'command': f'python {bash_hook}',
'timeout': 30
}]
},
{
'matcher': 'Read',
'hooks': [{
'type': 'command',
'command': f'python {read_hook}',
'timeout': 30
}]
}
]
}
}
with open(settings_file, 'w', encoding='utf-8') as f:
json.dump(settings, f, indent=2)
"@
$pythonScript | python - "$SettingsFile" "$PluginDest"
if ($LASTEXITCODE -eq 0) {
Write-Host " Settings created with hooks (Bash + Read)." -ForegroundColor Green
} else {
Write-Host " WARNING: Could not create settings. Add manually." -ForegroundColor Yellow
}
}
Write-Host ""
Write-Host "=== Installation Complete ===" -ForegroundColor Cyan
Write-Host ""
Write-Host "Files installed:" -ForegroundColor Cyan
Write-Host " Plugin: $PluginDest"
Write-Host " Skill: $SkillDest\SKILL.md"
Write-Host " Hooks: $SettingsFile"
Write-Host ""
Write-Host "============================================================" -ForegroundColor Red
Write-Host " IMPORTANT: You MUST restart Claude Code for Hardstop" -ForegroundColor Red
Write-Host " to take effect. Hooks are loaded at session start." -ForegroundColor Red
Write-Host "" -ForegroundColor Red
Write-Host " How to restart:" -ForegroundColor Yellow
Write-Host " - VS Code: Ctrl+Shift+P > 'Developer: Reload Window'" -ForegroundColor Yellow
Write-Host " (or Cmd+Shift+P on Mac)" -ForegroundColor Yellow
Write-Host " - CLI: Exit and run 'claude' again" -ForegroundColor Yellow
Write-Host " - Cowork: Close and reopen Claude Desktop app" -ForegroundColor Yellow
Write-Host "============================================================" -ForegroundColor Red
Write-Host ""
Write-Host "After restart, verify with: /hs status" -ForegroundColor Green
Write-Host ""