hardstop 1.4.7 → 1.4.9
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.
- package/.claude/skills/hs/SKILL.md +2 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex/skills/hs/SKILL.md +2 -0
- package/.github/skills/hs/SKILL.md +2 -0
- package/CHANGELOG.md +26 -0
- package/bin/install.js +322 -310
- package/commands/__pycache__/hs_cmd.cpython-313.pyc +0 -0
- package/commands/hs_cmd.py +273 -272
- package/hooks/pre_tool_use.py +7 -3
- package/package.json +1 -1
- package/skills/hs/SKILL.md +10 -6
package/bin/install.js
CHANGED
|
@@ -1,310 +1,322 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
const PLUGIN_NAME = 'hs';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!fs.existsSync(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
fs.
|
|
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
|
-
fs.
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const PLUGIN_NAME = 'hs';
|
|
8
|
+
let CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR || '';
|
|
9
|
+
if (CLAUDE_DIR === '~') {
|
|
10
|
+
CLAUDE_DIR = os.homedir();
|
|
11
|
+
} else if (CLAUDE_DIR.startsWith('~/')) {
|
|
12
|
+
CLAUDE_DIR = path.join(os.homedir(), CLAUDE_DIR.slice(2));
|
|
13
|
+
}
|
|
14
|
+
CLAUDE_DIR = CLAUDE_DIR.replace(/\/+$/, '');
|
|
15
|
+
if (!CLAUDE_DIR) {
|
|
16
|
+
CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
17
|
+
}
|
|
18
|
+
const PLUGINS_DIR = path.join(CLAUDE_DIR, 'plugins');
|
|
19
|
+
const PLUGIN_DIR = path.join(PLUGINS_DIR, PLUGIN_NAME);
|
|
20
|
+
const SKILLS_DIR = path.join(CLAUDE_DIR, 'skills');
|
|
21
|
+
const SKILL_DIR = path.join(SKILLS_DIR, PLUGIN_NAME);
|
|
22
|
+
const SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.json');
|
|
23
|
+
|
|
24
|
+
function detectClaude() {
|
|
25
|
+
// Check if Claude Code is installed
|
|
26
|
+
// Check for settings.json or config.json
|
|
27
|
+
const settingsFile = path.join(CLAUDE_DIR, 'settings.json');
|
|
28
|
+
const configFile = path.join(CLAUDE_DIR, 'config.json');
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(settingsFile) && !fs.existsSync(configFile)) {
|
|
31
|
+
console.error('❌ Claude Code not found. Please install Claude Code first.');
|
|
32
|
+
console.error(' Visit: https://claude.ai/code');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log('✅ Claude Code detected');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createPluginDirectory() {
|
|
39
|
+
if (!fs.existsSync(PLUGINS_DIR)) {
|
|
40
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(PLUGIN_DIR)) {
|
|
44
|
+
console.log('⚠️ Hardstop already installed. Updating...');
|
|
45
|
+
fs.rmSync(PLUGIN_DIR, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fs.mkdirSync(PLUGIN_DIR, { recursive: true });
|
|
49
|
+
console.log('✅ Plugin directory created');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function copyPluginFiles() {
|
|
53
|
+
// Determine source directory
|
|
54
|
+
// When installed via npm, __dirname points to node_modules/hardstop/bin
|
|
55
|
+
// When run from repo, __dirname points to repo/bin
|
|
56
|
+
const sourceDir = path.dirname(__dirname);
|
|
57
|
+
|
|
58
|
+
const filesToCopy = [
|
|
59
|
+
{ name: '.claude-plugin', type: 'dir' },
|
|
60
|
+
{ name: 'hooks', type: 'dir' },
|
|
61
|
+
{ name: 'commands', type: 'dir' },
|
|
62
|
+
{ name: 'patterns', type: 'dir' },
|
|
63
|
+
{ name: 'LICENSE', type: 'file' },
|
|
64
|
+
{ name: 'README.md', type: 'file' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const item of filesToCopy) {
|
|
68
|
+
const source = path.join(sourceDir, item.name);
|
|
69
|
+
const dest = path.join(PLUGIN_DIR, item.name);
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(source)) {
|
|
72
|
+
console.log(`⚠️ Skipping ${item.name} (not found)`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (item.type === 'dir') {
|
|
77
|
+
fs.cpSync(source, dest, { recursive: true });
|
|
78
|
+
} else {
|
|
79
|
+
fs.copyFileSync(source, dest);
|
|
80
|
+
}
|
|
81
|
+
console.log(`✅ Copied ${item.name}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function setExecutablePermissions() {
|
|
86
|
+
if (os.platform() === 'win32') {
|
|
87
|
+
console.log('ℹ️ Windows detected - skipping chmod');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hookFiles = [
|
|
92
|
+
path.join(PLUGIN_DIR, 'hooks', 'pre_tool_use.py'),
|
|
93
|
+
path.join(PLUGIN_DIR, 'hooks', 'pre_read.py'),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
for (const file of hookFiles) {
|
|
97
|
+
if (fs.existsSync(file)) {
|
|
98
|
+
fs.chmodSync(file, '755');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log('✅ Set executable permissions');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createSkill() {
|
|
105
|
+
if (!fs.existsSync(SKILLS_DIR)) {
|
|
106
|
+
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (fs.existsSync(SKILL_DIR)) {
|
|
110
|
+
fs.rmSync(SKILL_DIR, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fs.mkdirSync(SKILL_DIR, { recursive: true });
|
|
114
|
+
|
|
115
|
+
// Copy SKILL.md from source if available, otherwise generate inline
|
|
116
|
+
const sourceDir = path.dirname(__dirname);
|
|
117
|
+
const sourceSkill = path.join(sourceDir, 'skills', PLUGIN_NAME, 'SKILL.md');
|
|
118
|
+
|
|
119
|
+
if (fs.existsSync(sourceSkill)) {
|
|
120
|
+
// Replace hardcoded ~/.claude/plugins/hs/ paths with actual install location
|
|
121
|
+
let content = fs.readFileSync(sourceSkill, 'utf8');
|
|
122
|
+
content = content.replace(/~\/\.claude\/plugins\/hs\//g, PLUGIN_DIR + '/');
|
|
123
|
+
fs.writeFileSync(path.join(SKILL_DIR, 'SKILL.md'), content, 'utf8');
|
|
124
|
+
} else {
|
|
125
|
+
// Fallback: generate a minimal skill file
|
|
126
|
+
const skillContent = `---
|
|
127
|
+
name: hs
|
|
128
|
+
version: 1.0.0
|
|
129
|
+
description: >
|
|
130
|
+
Hardstop - Pre-execution safety layer control. Use this skill when the user wants to
|
|
131
|
+
enable, disable, check status, skip, or view logs for the Hardstop safety system.
|
|
132
|
+
triggers:
|
|
133
|
+
- hs
|
|
134
|
+
- hs on
|
|
135
|
+
- hs off
|
|
136
|
+
- hs status
|
|
137
|
+
- hs skip
|
|
138
|
+
- hs log
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
# Hardstop Control
|
|
142
|
+
|
|
143
|
+
**Purpose:** Control the Hardstop pre-execution safety layer that blocks dangerous shell commands.
|
|
144
|
+
|
|
145
|
+
When the user invokes \`/hs\` (with optional subcommands), run the appropriate Python command:
|
|
146
|
+
|
|
147
|
+
- \`/hs\` or \`/hs status\`: \`python ${PLUGIN_DIR}/commands/hs_cmd.py status\`
|
|
148
|
+
- \`/hs on\`: \`python ${PLUGIN_DIR}/commands/hs_cmd.py on\`
|
|
149
|
+
- \`/hs off\`: \`python ${PLUGIN_DIR}/commands/hs_cmd.py off\`
|
|
150
|
+
- \`/hs skip\`: \`python ${PLUGIN_DIR}/commands/hs_cmd.py skip\`
|
|
151
|
+
- \`/hs log\`: \`python ${PLUGIN_DIR}/commands/hs_cmd.py log\`
|
|
152
|
+
`;
|
|
153
|
+
fs.writeFileSync(path.join(SKILL_DIR, 'SKILL.md'), skillContent, 'utf8');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log('✅ Skill created');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function configureHooks() {
|
|
160
|
+
let settings = {};
|
|
161
|
+
|
|
162
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
163
|
+
try {
|
|
164
|
+
const content = fs.readFileSync(SETTINGS_FILE, 'utf8').trim();
|
|
165
|
+
settings = content ? JSON.parse(content) : {};
|
|
166
|
+
} catch (e) {
|
|
167
|
+
settings = {};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if hooks are already configured
|
|
171
|
+
const raw = fs.readFileSync(SETTINGS_FILE, 'utf8');
|
|
172
|
+
if (raw.includes('pre_tool_use.py') && raw.includes('pre_read.py')) {
|
|
173
|
+
console.log('⚠️ Hooks already configured, skipping');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Backup existing settings
|
|
178
|
+
fs.copyFileSync(SETTINGS_FILE, SETTINGS_FILE + '.backup');
|
|
179
|
+
console.log('ℹ️ Backed up settings.json');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!settings.hooks) {
|
|
183
|
+
settings.hooks = {};
|
|
184
|
+
}
|
|
185
|
+
if (!settings.hooks.PreToolUse) {
|
|
186
|
+
settings.hooks.PreToolUse = [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const bashHook = path.join(PLUGIN_DIR, 'hooks', 'pre_tool_use.py').replace(/\\/g, '/');
|
|
190
|
+
const readHook = path.join(PLUGIN_DIR, 'hooks', 'pre_read.py').replace(/\\/g, '/');
|
|
191
|
+
|
|
192
|
+
settings.hooks.PreToolUse.push({
|
|
193
|
+
matcher: 'Bash',
|
|
194
|
+
hooks: [{
|
|
195
|
+
type: 'command',
|
|
196
|
+
command: `python ${bashHook}`,
|
|
197
|
+
timeout: 30
|
|
198
|
+
}]
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
settings.hooks.PreToolUse.push({
|
|
202
|
+
matcher: 'Read',
|
|
203
|
+
hooks: [{
|
|
204
|
+
type: 'command',
|
|
205
|
+
command: `python ${readHook}`,
|
|
206
|
+
timeout: 30
|
|
207
|
+
}]
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf8');
|
|
211
|
+
console.log('✅ Hooks configured (Bash + Read)');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function verifyInstallation() {
|
|
215
|
+
const requiredFiles = [
|
|
216
|
+
'.claude-plugin/plugin.json',
|
|
217
|
+
'hooks/hooks.json',
|
|
218
|
+
'hooks/pre_tool_use.py',
|
|
219
|
+
'hooks/pre_read.py',
|
|
220
|
+
'patterns/dangerous_commands.yaml',
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
let allPresent = true;
|
|
224
|
+
for (const file of requiredFiles) {
|
|
225
|
+
const fullPath = path.join(PLUGIN_DIR, file);
|
|
226
|
+
if (!fs.existsSync(fullPath)) {
|
|
227
|
+
console.error(`❌ Missing: ${file}`);
|
|
228
|
+
allPresent = false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (allPresent) {
|
|
233
|
+
console.log('✅ Installation verified');
|
|
234
|
+
} else {
|
|
235
|
+
console.error('❌ Installation incomplete');
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function printSuccess() {
|
|
241
|
+
const version = getVersion();
|
|
242
|
+
console.log(`
|
|
243
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
244
|
+
║ 🛡️ Hardstop ${version} installed successfully! ║
|
|
245
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
246
|
+
|
|
247
|
+
Next steps:
|
|
248
|
+
1. Restart Claude Code (if running)
|
|
249
|
+
2. Test with: /hs status
|
|
250
|
+
3. Read docs: ${PLUGIN_DIR}/README.md
|
|
251
|
+
|
|
252
|
+
Hardstop will now intercept dangerous commands before execution.
|
|
253
|
+
Use '/hs help' to see all available commands.
|
|
254
|
+
|
|
255
|
+
Features:
|
|
256
|
+
• 262 patterns mapped to MITRE ATT&CK framework
|
|
257
|
+
• Risk scoring system with session tracking
|
|
258
|
+
• Command chain analysis (&&, ||, ;, |)
|
|
259
|
+
• Read tool protection (credential files)
|
|
260
|
+
• Fail-closed by default
|
|
261
|
+
|
|
262
|
+
Documentation:
|
|
263
|
+
• https://github.com/frmoretto/hardstop
|
|
264
|
+
• ${PLUGIN_DIR}/README.md
|
|
265
|
+
`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getVersion() {
|
|
269
|
+
try {
|
|
270
|
+
const pluginJson = path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json');
|
|
271
|
+
if (fs.existsSync(pluginJson)) {
|
|
272
|
+
const data = JSON.parse(fs.readFileSync(pluginJson, 'utf8'));
|
|
273
|
+
return data.version || 'unknown';
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
// Ignore errors
|
|
277
|
+
}
|
|
278
|
+
return 'v1.4.3';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Main installation flow
|
|
282
|
+
function main() {
|
|
283
|
+
// Check for help flag
|
|
284
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
285
|
+
console.log(`
|
|
286
|
+
Hardstop Installer
|
|
287
|
+
|
|
288
|
+
Usage:
|
|
289
|
+
npx hardstop install Install Hardstop plugin
|
|
290
|
+
npx hardstop --help Show this help
|
|
291
|
+
|
|
292
|
+
Installation:
|
|
293
|
+
Installs Hardstop to: ${CLAUDE_DIR}/plugins/hs
|
|
294
|
+
Requires: Claude Code installed
|
|
295
|
+
|
|
296
|
+
More info:
|
|
297
|
+
https://github.com/frmoretto/hardstop
|
|
298
|
+
`);
|
|
299
|
+
process.exit(0);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.log('\n🚀 Installing Hardstop...\n');
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
detectClaude();
|
|
306
|
+
createPluginDirectory();
|
|
307
|
+
copyPluginFiles();
|
|
308
|
+
setExecutablePermissions();
|
|
309
|
+
createSkill();
|
|
310
|
+
configureHooks();
|
|
311
|
+
verifyInstallation();
|
|
312
|
+
printSuccess();
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('❌ Installation failed:', error.message);
|
|
315
|
+
if (process.env.DEBUG) {
|
|
316
|
+
console.error(error.stack);
|
|
317
|
+
}
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
main();
|
|
Binary file
|