@thecodesaiyan/claude-dev-workflow-hook 1.0.0
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/AGENTS.md +223 -0
- package/README.md +352 -0
- package/bin/cli.js +108 -0
- package/package.json +37 -0
- package/src/hook.js +43 -0
- package/src/installer.js +318 -0
- package/src/protocol.js +230 -0
- package/src/uninstaller.js +149 -0
- package/src/utils.js +19 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { green, yellow, bold, readJsonFile, writeJsonFile } = require('./utils.js');
|
|
6
|
+
const {
|
|
7
|
+
getClaudeHome,
|
|
8
|
+
HOOK_FILENAME,
|
|
9
|
+
SETTINGS_FILENAME,
|
|
10
|
+
CLAUDE_MD_MARKER,
|
|
11
|
+
LEGACY_HOOK_FILENAMES,
|
|
12
|
+
LEGACY_EVENT,
|
|
13
|
+
} = require('./installer.js');
|
|
14
|
+
|
|
15
|
+
// --------------------------------------------------------------------------- //
|
|
16
|
+
// Helpers
|
|
17
|
+
// --------------------------------------------------------------------------- //
|
|
18
|
+
|
|
19
|
+
function matchesAnyHookFilename(command) {
|
|
20
|
+
return LEGACY_HOOK_FILENAMES.some((name) =>
|
|
21
|
+
command.includes('/' + name) || command.includes('\\' + name) || command.endsWith(name)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --------------------------------------------------------------------------- //
|
|
26
|
+
// Uninstall
|
|
27
|
+
// --------------------------------------------------------------------------- //
|
|
28
|
+
|
|
29
|
+
function uninstall(targetDir) {
|
|
30
|
+
console.log(bold('\nUninstalling workflow hook...'));
|
|
31
|
+
|
|
32
|
+
// Remove hook files (both .js and .py variants + protocol.js)
|
|
33
|
+
const hooksDir = path.join(targetDir, 'hooks');
|
|
34
|
+
const hookVariants = ['session-start.js', 'session-start.py', 'protocol.js'];
|
|
35
|
+
let deletedAny = false;
|
|
36
|
+
|
|
37
|
+
for (const filename of hookVariants) {
|
|
38
|
+
const hookPath = path.join(hooksDir, filename);
|
|
39
|
+
if (fs.existsSync(hookPath)) {
|
|
40
|
+
fs.unlinkSync(hookPath);
|
|
41
|
+
deletedAny = true;
|
|
42
|
+
console.log(green(` OK: Removed ${hookPath}`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!deletedAny) {
|
|
47
|
+
console.log(yellow(` SKIP: No hook files found in ${hooksDir}`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Remove from settings.json (check both SessionStart and legacy UserPromptSubmit)
|
|
51
|
+
const settingsPath = path.join(targetDir, SETTINGS_FILENAME);
|
|
52
|
+
if (fs.existsSync(settingsPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const settings = readJsonFile(settingsPath);
|
|
55
|
+
if (settings === null) {
|
|
56
|
+
console.log(yellow(` SKIP: ${settingsPath} is empty`));
|
|
57
|
+
} else {
|
|
58
|
+
let removedAny = false;
|
|
59
|
+
|
|
60
|
+
for (const eventName of ['SessionStart', LEGACY_EVENT]) {
|
|
61
|
+
const hooksList = (settings.hooks || {})[eventName] || [];
|
|
62
|
+
const originalLen = hooksList.length;
|
|
63
|
+
|
|
64
|
+
const filtered = hooksList.filter(
|
|
65
|
+
(entry) => !(entry.hooks || []).some((h) => matchesAnyHookFilename(h.command || ''))
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (filtered.length < originalLen) {
|
|
69
|
+
if (filtered.length > 0) {
|
|
70
|
+
settings.hooks[eventName] = filtered;
|
|
71
|
+
} else {
|
|
72
|
+
delete settings.hooks[eventName];
|
|
73
|
+
}
|
|
74
|
+
removedAny = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (removedAny) {
|
|
79
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
80
|
+
delete settings.hooks;
|
|
81
|
+
}
|
|
82
|
+
writeJsonFile(settingsPath, settings);
|
|
83
|
+
console.log(green(` OK: Removed hook entry from ${settingsPath}`));
|
|
84
|
+
} else {
|
|
85
|
+
console.log(yellow(` SKIP: No hook entry found in ${settingsPath}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
console.log(yellow(` SKIP: ${settingsPath} has invalid JSON — edit manually`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Remove CLAUDE.md binding
|
|
94
|
+
const claudeHome = getClaudeHome();
|
|
95
|
+
const claudeMdPath = path.resolve(targetDir) === path.resolve(claudeHome)
|
|
96
|
+
? path.join(claudeHome, 'CLAUDE.md')
|
|
97
|
+
: path.join(path.dirname(targetDir), 'CLAUDE.md');
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
100
|
+
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
101
|
+
if (content.includes(CLAUDE_MD_MARKER)) {
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
const newLines = [];
|
|
104
|
+
let skip = false;
|
|
105
|
+
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
if (line.includes(CLAUDE_MD_MARKER)) {
|
|
108
|
+
skip = true;
|
|
109
|
+
// Remove blank line before marker
|
|
110
|
+
if (newLines.length > 0 && newLines[newLines.length - 1].trim() === '') {
|
|
111
|
+
newLines.pop();
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (skip && line.includes('<!-- END WORKFLOW PROTOCOL -->')) {
|
|
116
|
+
skip = false;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (!skip) {
|
|
120
|
+
newLines.push(line);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Clean up double blank lines
|
|
125
|
+
let cleaned = newLines.join('\n');
|
|
126
|
+
while (cleaned.includes('\n\n\n')) {
|
|
127
|
+
cleaned = cleaned.replace(/\n\n\n/g, '\n\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(claudeMdPath, cleaned, 'utf-8');
|
|
131
|
+
console.log(green(` OK: Removed authority binding from ${claudeMdPath}`));
|
|
132
|
+
} else {
|
|
133
|
+
console.log(yellow(` SKIP: No authority binding found in ${claudeMdPath}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Clean up empty hooks directory
|
|
138
|
+
if (fs.existsSync(hooksDir)) {
|
|
139
|
+
const remaining = fs.readdirSync(hooksDir);
|
|
140
|
+
if (remaining.length === 0) {
|
|
141
|
+
fs.rmdirSync(hooksDir);
|
|
142
|
+
console.log(green(` OK: Removed empty ${hooksDir}`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(green('\nUninstall complete.'));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = { uninstall };
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
|
|
5
|
+
function green(text) { return `\x1b[32m${text}\x1b[0m`; }
|
|
6
|
+
function yellow(text) { return `\x1b[33m${text}\x1b[0m`; }
|
|
7
|
+
function red(text) { return `\x1b[31m${text}\x1b[0m`; }
|
|
8
|
+
function bold(text) { return `\x1b[1m${text}\x1b[0m`; }
|
|
9
|
+
|
|
10
|
+
function readJsonFile(filePath) {
|
|
11
|
+
if (!fs.existsSync(filePath)) return null;
|
|
12
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeJsonFile(filePath, data) {
|
|
16
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { green, yellow, red, bold, readJsonFile, writeJsonFile };
|