@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.
@@ -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 };