@soleri/cli 1.0.4 → 1.1.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/dist/commands/hooks.js +70 -1
- package/dist/commands/hooks.js.map +1 -1
- package/dist/hook-packs/a11y/hookify.focus-ring-required.local.md +20 -0
- package/dist/hook-packs/a11y/hookify.semantic-html.local.md +17 -0
- package/dist/hook-packs/a11y/hookify.ux-touch-targets.local.md +17 -0
- package/dist/hook-packs/a11y/manifest.json +5 -0
- package/dist/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +17 -0
- package/dist/hook-packs/clean-commits/manifest.json +5 -0
- package/dist/hook-packs/css-discipline/hookify.no-important.local.md +17 -0
- package/dist/hook-packs/css-discipline/hookify.no-inline-styles.local.md +20 -0
- package/dist/hook-packs/css-discipline/manifest.json +5 -0
- package/dist/hook-packs/full/manifest.json +6 -0
- package/dist/hook-packs/installer.d.ts +19 -0
- package/dist/hook-packs/installer.js +103 -0
- package/dist/hook-packs/installer.js.map +1 -0
- package/dist/hook-packs/installer.ts +113 -0
- package/dist/hook-packs/registry.d.ts +21 -0
- package/dist/hook-packs/registry.js +69 -0
- package/dist/hook-packs/registry.js.map +1 -0
- package/dist/hook-packs/registry.ts +84 -0
- package/dist/hook-packs/typescript-safety/hookify.no-any-types.local.md +17 -0
- package/dist/hook-packs/typescript-safety/hookify.no-console-log.local.md +20 -0
- package/dist/hook-packs/typescript-safety/manifest.json +5 -0
- package/package.json +2 -2
- package/src/__tests__/hook-packs.test.ts +195 -0
- package/src/commands/hooks.ts +75 -1
- package/src/hook-packs/a11y/hookify.focus-ring-required.local.md +20 -0
- package/src/hook-packs/a11y/hookify.semantic-html.local.md +17 -0
- package/src/hook-packs/a11y/hookify.ux-touch-targets.local.md +17 -0
- package/src/hook-packs/a11y/manifest.json +5 -0
- package/src/hook-packs/clean-commits/hookify.no-ai-attribution.local.md +17 -0
- package/src/hook-packs/clean-commits/manifest.json +5 -0
- package/src/hook-packs/css-discipline/hookify.no-important.local.md +17 -0
- package/src/hook-packs/css-discipline/hookify.no-inline-styles.local.md +20 -0
- package/src/hook-packs/css-discipline/manifest.json +5 -0
- package/src/hook-packs/full/manifest.json +6 -0
- package/src/hook-packs/installer.ts +113 -0
- package/src/hook-packs/registry.ts +84 -0
- package/src/hook-packs/typescript-safety/hookify.no-any-types.local.md +17 -0
- package/src/hook-packs/typescript-safety/hookify.no-console-log.local.md +20 -0
- package/src/hook-packs/typescript-safety/manifest.json +5 -0
package/dist/commands/hooks.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { SUPPORTED_EDITORS } from '../hooks/templates.js';
|
|
2
2
|
import { installHooks, removeHooks, detectInstalledHooks } from '../hooks/generator.js';
|
|
3
3
|
import { detectAgent } from '../utils/agent-context.js';
|
|
4
|
+
import { listPacks, getPack } from '../hook-packs/registry.js';
|
|
5
|
+
import { installPack, removePack, isPackInstalled } from '../hook-packs/installer.js';
|
|
4
6
|
import * as log from '../utils/logger.js';
|
|
5
7
|
export function registerHooks(program) {
|
|
6
|
-
const hooks = program.command('hooks').description('Manage editor hooks
|
|
8
|
+
const hooks = program.command('hooks').description('Manage editor hooks and hook packs');
|
|
7
9
|
hooks
|
|
8
10
|
.command('add')
|
|
9
11
|
.argument('<editor>', `Editor: ${SUPPORTED_EDITORS.join(', ')}`)
|
|
@@ -69,6 +71,73 @@ export function registerHooks(program) {
|
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
});
|
|
74
|
+
// ── Hook Pack subcommands ──
|
|
75
|
+
hooks
|
|
76
|
+
.command('add-pack')
|
|
77
|
+
.argument('<pack>', 'Hook pack name')
|
|
78
|
+
.description('Install a hook pack globally (~/.claude/)')
|
|
79
|
+
.action((packName) => {
|
|
80
|
+
const pack = getPack(packName);
|
|
81
|
+
if (!pack) {
|
|
82
|
+
const available = listPacks().map((p) => p.name);
|
|
83
|
+
log.fail(`Unknown pack "${packName}". Available: ${available.join(', ')}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const { installed, skipped } = installPack(packName);
|
|
87
|
+
for (const hook of installed) {
|
|
88
|
+
log.pass(`Installed hookify.${hook}.local.md`);
|
|
89
|
+
}
|
|
90
|
+
for (const hook of skipped) {
|
|
91
|
+
log.dim(` hookify.${hook}.local.md — already exists, skipped`);
|
|
92
|
+
}
|
|
93
|
+
if (installed.length > 0) {
|
|
94
|
+
log.info(`Pack "${packName}" installed (${installed.length} hooks)`);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
log.info(`Pack "${packName}" — all hooks already installed`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
hooks
|
|
101
|
+
.command('remove-pack')
|
|
102
|
+
.argument('<pack>', 'Hook pack name')
|
|
103
|
+
.description('Remove a hook pack from ~/.claude/')
|
|
104
|
+
.action((packName) => {
|
|
105
|
+
const pack = getPack(packName);
|
|
106
|
+
if (!pack) {
|
|
107
|
+
const available = listPacks().map((p) => p.name);
|
|
108
|
+
log.fail(`Unknown pack "${packName}". Available: ${available.join(', ')}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const { removed } = removePack(packName);
|
|
112
|
+
if (removed.length === 0) {
|
|
113
|
+
log.info(`No hooks from pack "${packName}" found to remove.`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
for (const hook of removed) {
|
|
117
|
+
log.warn(`Removed hookify.${hook}.local.md`);
|
|
118
|
+
}
|
|
119
|
+
log.info(`Pack "${packName}" removed (${removed.length} hooks)`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
hooks
|
|
123
|
+
.command('list-packs')
|
|
124
|
+
.description('Show available hook packs and their status')
|
|
125
|
+
.action(() => {
|
|
126
|
+
const packs = listPacks();
|
|
127
|
+
log.heading('Hook Packs');
|
|
128
|
+
for (const pack of packs) {
|
|
129
|
+
const status = isPackInstalled(pack.name);
|
|
130
|
+
if (status === true) {
|
|
131
|
+
log.pass(`${pack.name}`, `${pack.description} (${pack.hooks.length} hooks)`);
|
|
132
|
+
}
|
|
133
|
+
else if (status === 'partial') {
|
|
134
|
+
log.warn(`${pack.name}`, `${pack.description} (${pack.hooks.length} hooks) — partial`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
log.dim(` ${pack.name} — ${pack.description} (${pack.hooks.length} hooks)`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
72
141
|
}
|
|
73
142
|
function isValidEditor(editor) {
|
|
74
143
|
return SUPPORTED_EDITORS.includes(editor);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/commands/hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Soleri Hook Pack: a11y
|
|
3
|
+
# Rule: focus-ring-required
|
|
4
|
+
name: focus-ring-required
|
|
5
|
+
enabled: true
|
|
6
|
+
event: file
|
|
7
|
+
action: warn
|
|
8
|
+
conditions:
|
|
9
|
+
- field: file_path
|
|
10
|
+
operator: regex_match
|
|
11
|
+
pattern: src/components/ui/.*\.tsx$
|
|
12
|
+
- field: content
|
|
13
|
+
operator: regex_match
|
|
14
|
+
pattern: (<button|<Button|<a\s+href)
|
|
15
|
+
- field: content
|
|
16
|
+
operator: not_regex_match
|
|
17
|
+
pattern: (focus:ring|focus-visible:ring|focus:outline)
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
⚠️ **A11y:** Add focus ring for keyboard navigation: `focus:ring-2 focus:ring-accent focus:ring-offset-2`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Soleri Hook Pack: a11y
|
|
3
|
+
# Rule: semantic-html
|
|
4
|
+
name: semantic-html
|
|
5
|
+
enabled: true
|
|
6
|
+
event: file
|
|
7
|
+
action: warn
|
|
8
|
+
conditions:
|
|
9
|
+
- field: file_path
|
|
10
|
+
operator: regex_match
|
|
11
|
+
pattern: src/components/.*\.tsx$
|
|
12
|
+
- field: content
|
|
13
|
+
operator: regex_match
|
|
14
|
+
pattern: (<div\s+onClick(?!=)|<span\s+onClick(?!=))
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
⚠️ **A11y:** Use `<button>` instead of `<div onClick>`. Semantic HTML provides keyboard support.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Soleri Hook Pack: a11y
|
|
3
|
+
# Rule: ux-touch-targets
|
|
4
|
+
name: ux-touch-targets
|
|
5
|
+
enabled: true
|
|
6
|
+
event: file
|
|
7
|
+
action: warn
|
|
8
|
+
conditions:
|
|
9
|
+
- field: file_path
|
|
10
|
+
operator: regex_match
|
|
11
|
+
pattern: src/components/.*\.tsx$
|
|
12
|
+
- field: content
|
|
13
|
+
operator: regex_match
|
|
14
|
+
pattern: (<button[^>]*className="[^"]*(?:p-1|p-2|h-6|h-7|h-8|w-6|w-7|w-8)[^"]*"[^>]*>|className="[^"]*(?:p-1|p-2)[^"]*"[^>]*onClick)
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
⚠️ **Touch target too small.** Min 44x44px required. Add `min-h-11 min-w-11` to small interactive elements.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Soleri Hook Pack: clean-commits
|
|
3
|
+
# Rule: no-ai-attribution
|
|
4
|
+
name: no-ai-attribution
|
|
5
|
+
enabled: true
|
|
6
|
+
event: bash
|
|
7
|
+
action: block
|
|
8
|
+
conditions:
|
|
9
|
+
- field: command
|
|
10
|
+
operator: regex_match
|
|
11
|
+
pattern: git\s+commit.*(-m|--message)
|
|
12
|
+
- field: command
|
|
13
|
+
operator: regex_match
|
|
14
|
+
pattern: (🤖|Co-Authored-By|Generated with|AI-generated|by Claude|Claude Code|with Claude|noreply@anthropic\.com|Anthropic|Claude\s+(Opus|Sonnet|Haiku))
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
🚫 **AI attribution blocked.** Use clean conventional commits: `feat:`, `fix:`, `refactor:`. No 🤖, Claude, Co-Authored-By, or Anthropic references.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Soleri Hook Pack: css-discipline
|
|
3
|
+
# Rule: no-important
|
|
4
|
+
name: no-important
|
|
5
|
+
enabled: true
|
|
6
|
+
event: file
|
|
7
|
+
action: block
|
|
8
|
+
conditions:
|
|
9
|
+
- field: file_path
|
|
10
|
+
operator: regex_match
|
|
11
|
+
pattern: \.(tsx?|jsx?|css)$
|
|
12
|
+
- field: content
|
|
13
|
+
operator: regex_match
|
|
14
|
+
pattern: "!important"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
🚫 **!important blocked.** Fix specificity instead. Use Tailwind `!` prefix (`!text-error`) only if needed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Soleri Hook Pack: css-discipline
|
|
3
|
+
# Rule: no-inline-styles
|
|
4
|
+
name: no-inline-styles
|
|
5
|
+
enabled: true
|
|
6
|
+
event: file
|
|
7
|
+
action: warn
|
|
8
|
+
conditions:
|
|
9
|
+
- field: file_path
|
|
10
|
+
operator: regex_match
|
|
11
|
+
pattern: src/components/.*\.tsx$
|
|
12
|
+
- field: file_path
|
|
13
|
+
operator: not_contains
|
|
14
|
+
pattern: .stories.tsx
|
|
15
|
+
- field: content
|
|
16
|
+
operator: regex_match
|
|
17
|
+
pattern: style=\{\{[^}]*(padding|margin|width|height|fontSize|color|background)[^}]*\}\}
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
⚠️ **Inline style detected.** Use Tailwind: `p-4` not `style={{ padding: '16px' }}`. Exception: CSS variables.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "full",
|
|
3
|
+
"description": "Complete quality suite — all 8 hooks",
|
|
4
|
+
"hooks": ["no-any-types", "no-console-log", "no-important", "no-inline-styles", "semantic-html", "focus-ring-required", "ux-touch-targets", "no-ai-attribution"],
|
|
5
|
+
"composedFrom": ["typescript-safety", "a11y", "css-discipline", "clean-commits"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install a hook pack globally to ~/.claude/.
|
|
3
|
+
* Skips files that already exist (idempotent).
|
|
4
|
+
*/
|
|
5
|
+
export declare function installPack(packName: string): {
|
|
6
|
+
installed: string[];
|
|
7
|
+
skipped: string[];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Remove a hook pack's files from ~/.claude/.
|
|
11
|
+
*/
|
|
12
|
+
export declare function removePack(packName: string): {
|
|
13
|
+
removed: string[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Check if a pack is installed.
|
|
17
|
+
* Returns true (all hooks present), false (none present), or 'partial'.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isPackInstalled(packName: string): boolean | 'partial';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook pack installer — copies hookify files to ~/.claude/ for global enforcement.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, copyFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { getPack } from './registry.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolve all hookify file paths for a pack, handling composed packs.
|
|
10
|
+
* Returns a map of hook name → source file path.
|
|
11
|
+
*/
|
|
12
|
+
function resolveHookFiles(packName) {
|
|
13
|
+
const pack = getPack(packName);
|
|
14
|
+
if (!pack)
|
|
15
|
+
return new Map();
|
|
16
|
+
const files = new Map();
|
|
17
|
+
if (pack.manifest.composedFrom) {
|
|
18
|
+
// Composed pack: gather files from constituent packs
|
|
19
|
+
for (const subPackName of pack.manifest.composedFrom) {
|
|
20
|
+
const subFiles = resolveHookFiles(subPackName);
|
|
21
|
+
for (const [hook, path] of subFiles) {
|
|
22
|
+
files.set(hook, path);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Direct pack: look for hookify files in the pack directory
|
|
28
|
+
for (const hook of pack.manifest.hooks) {
|
|
29
|
+
const filePath = join(pack.dir, `hookify.${hook}.local.md`);
|
|
30
|
+
if (existsSync(filePath)) {
|
|
31
|
+
files.set(hook, filePath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return files;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Install a hook pack globally to ~/.claude/.
|
|
39
|
+
* Skips files that already exist (idempotent).
|
|
40
|
+
*/
|
|
41
|
+
export function installPack(packName) {
|
|
42
|
+
const pack = getPack(packName);
|
|
43
|
+
if (!pack) {
|
|
44
|
+
throw new Error(`Unknown hook pack: "${packName}"`);
|
|
45
|
+
}
|
|
46
|
+
const claudeDir = join(homedir(), '.claude');
|
|
47
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
48
|
+
const hookFiles = resolveHookFiles(packName);
|
|
49
|
+
const installed = [];
|
|
50
|
+
const skipped = [];
|
|
51
|
+
for (const [hook, sourcePath] of hookFiles) {
|
|
52
|
+
const destPath = join(claudeDir, `hookify.${hook}.local.md`);
|
|
53
|
+
if (existsSync(destPath)) {
|
|
54
|
+
skipped.push(hook);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
copyFileSync(sourcePath, destPath);
|
|
58
|
+
installed.push(hook);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { installed, skipped };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Remove a hook pack's files from ~/.claude/.
|
|
65
|
+
*/
|
|
66
|
+
export function removePack(packName) {
|
|
67
|
+
const pack = getPack(packName);
|
|
68
|
+
if (!pack) {
|
|
69
|
+
throw new Error(`Unknown hook pack: "${packName}"`);
|
|
70
|
+
}
|
|
71
|
+
const claudeDir = join(homedir(), '.claude');
|
|
72
|
+
const removed = [];
|
|
73
|
+
for (const hook of pack.manifest.hooks) {
|
|
74
|
+
const filePath = join(claudeDir, `hookify.${hook}.local.md`);
|
|
75
|
+
if (existsSync(filePath)) {
|
|
76
|
+
unlinkSync(filePath);
|
|
77
|
+
removed.push(hook);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { removed };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a pack is installed.
|
|
84
|
+
* Returns true (all hooks present), false (none present), or 'partial'.
|
|
85
|
+
*/
|
|
86
|
+
export function isPackInstalled(packName) {
|
|
87
|
+
const pack = getPack(packName);
|
|
88
|
+
if (!pack)
|
|
89
|
+
return false;
|
|
90
|
+
const claudeDir = join(homedir(), '.claude');
|
|
91
|
+
let present = 0;
|
|
92
|
+
for (const hook of pack.manifest.hooks) {
|
|
93
|
+
if (existsSync(join(claudeDir, `hookify.${hook}.local.md`))) {
|
|
94
|
+
present++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (present === 0)
|
|
98
|
+
return false;
|
|
99
|
+
if (present === pack.manifest.hooks.length)
|
|
100
|
+
return true;
|
|
101
|
+
return 'partial';
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/hook-packs/installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook pack installer — copies hookify files to ~/.claude/ for global enforcement.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, copyFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { getPack } from './registry.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve all hookify file paths for a pack, handling composed packs.
|
|
11
|
+
* Returns a map of hook name → source file path.
|
|
12
|
+
*/
|
|
13
|
+
function resolveHookFiles(packName: string): Map<string, string> {
|
|
14
|
+
const pack = getPack(packName);
|
|
15
|
+
if (!pack) return new Map();
|
|
16
|
+
|
|
17
|
+
const files = new Map<string, string>();
|
|
18
|
+
|
|
19
|
+
if (pack.manifest.composedFrom) {
|
|
20
|
+
// Composed pack: gather files from constituent packs
|
|
21
|
+
for (const subPackName of pack.manifest.composedFrom) {
|
|
22
|
+
const subFiles = resolveHookFiles(subPackName);
|
|
23
|
+
for (const [hook, path] of subFiles) {
|
|
24
|
+
files.set(hook, path);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
// Direct pack: look for hookify files in the pack directory
|
|
29
|
+
for (const hook of pack.manifest.hooks) {
|
|
30
|
+
const filePath = join(pack.dir, `hookify.${hook}.local.md`);
|
|
31
|
+
if (existsSync(filePath)) {
|
|
32
|
+
files.set(hook, filePath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return files;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Install a hook pack globally to ~/.claude/.
|
|
42
|
+
* Skips files that already exist (idempotent).
|
|
43
|
+
*/
|
|
44
|
+
export function installPack(packName: string): { installed: string[]; skipped: string[] } {
|
|
45
|
+
const pack = getPack(packName);
|
|
46
|
+
if (!pack) {
|
|
47
|
+
throw new Error(`Unknown hook pack: "${packName}"`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const claudeDir = join(homedir(), '.claude');
|
|
51
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const hookFiles = resolveHookFiles(packName);
|
|
54
|
+
const installed: string[] = [];
|
|
55
|
+
const skipped: string[] = [];
|
|
56
|
+
|
|
57
|
+
for (const [hook, sourcePath] of hookFiles) {
|
|
58
|
+
const destPath = join(claudeDir, `hookify.${hook}.local.md`);
|
|
59
|
+
if (existsSync(destPath)) {
|
|
60
|
+
skipped.push(hook);
|
|
61
|
+
} else {
|
|
62
|
+
copyFileSync(sourcePath, destPath);
|
|
63
|
+
installed.push(hook);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { installed, skipped };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Remove a hook pack's files from ~/.claude/.
|
|
72
|
+
*/
|
|
73
|
+
export function removePack(packName: string): { removed: string[] } {
|
|
74
|
+
const pack = getPack(packName);
|
|
75
|
+
if (!pack) {
|
|
76
|
+
throw new Error(`Unknown hook pack: "${packName}"`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const claudeDir = join(homedir(), '.claude');
|
|
80
|
+
const removed: string[] = [];
|
|
81
|
+
|
|
82
|
+
for (const hook of pack.manifest.hooks) {
|
|
83
|
+
const filePath = join(claudeDir, `hookify.${hook}.local.md`);
|
|
84
|
+
if (existsSync(filePath)) {
|
|
85
|
+
unlinkSync(filePath);
|
|
86
|
+
removed.push(hook);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { removed };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a pack is installed.
|
|
95
|
+
* Returns true (all hooks present), false (none present), or 'partial'.
|
|
96
|
+
*/
|
|
97
|
+
export function isPackInstalled(packName: string): boolean | 'partial' {
|
|
98
|
+
const pack = getPack(packName);
|
|
99
|
+
if (!pack) return false;
|
|
100
|
+
|
|
101
|
+
const claudeDir = join(homedir(), '.claude');
|
|
102
|
+
let present = 0;
|
|
103
|
+
|
|
104
|
+
for (const hook of pack.manifest.hooks) {
|
|
105
|
+
if (existsSync(join(claudeDir, `hookify.${hook}.local.md`))) {
|
|
106
|
+
present++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (present === 0) return false;
|
|
111
|
+
if (present === pack.manifest.hooks.length) return true;
|
|
112
|
+
return 'partial';
|
|
113
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface HookPackManifest {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
hooks: string[];
|
|
5
|
+
composedFrom?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* List all available built-in hook packs.
|
|
9
|
+
*/
|
|
10
|
+
export declare function listPacks(): HookPackManifest[];
|
|
11
|
+
/**
|
|
12
|
+
* Get a specific pack by name.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPack(name: string): {
|
|
15
|
+
manifest: HookPackManifest;
|
|
16
|
+
dir: string;
|
|
17
|
+
} | null;
|
|
18
|
+
/**
|
|
19
|
+
* Get names of packs that are fully installed in ~/.claude/.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getInstalledPacks(): string[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook pack registry — discovers built-in packs and checks installation status.
|
|
3
|
+
*/
|
|
4
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
/** Root directory containing all built-in hook packs. */
|
|
11
|
+
function getPacksRoot() {
|
|
12
|
+
return __dirname;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* List all available built-in hook packs.
|
|
16
|
+
*/
|
|
17
|
+
export function listPacks() {
|
|
18
|
+
const root = getPacksRoot();
|
|
19
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
20
|
+
const packs = [];
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (!entry.isDirectory())
|
|
23
|
+
continue;
|
|
24
|
+
const manifestPath = join(root, entry.name, 'manifest.json');
|
|
25
|
+
if (!existsSync(manifestPath))
|
|
26
|
+
continue;
|
|
27
|
+
try {
|
|
28
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
29
|
+
packs.push(manifest);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Skip malformed manifests
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return packs;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get a specific pack by name.
|
|
39
|
+
*/
|
|
40
|
+
export function getPack(name) {
|
|
41
|
+
const root = getPacksRoot();
|
|
42
|
+
const dir = join(root, name);
|
|
43
|
+
const manifestPath = join(dir, 'manifest.json');
|
|
44
|
+
if (!existsSync(manifestPath))
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
48
|
+
return { manifest, dir };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get names of packs that are fully installed in ~/.claude/.
|
|
56
|
+
*/
|
|
57
|
+
export function getInstalledPacks() {
|
|
58
|
+
const claudeDir = join(homedir(), '.claude');
|
|
59
|
+
const packs = listPacks();
|
|
60
|
+
const installed = [];
|
|
61
|
+
for (const pack of packs) {
|
|
62
|
+
const allPresent = pack.hooks.every((hook) => existsSync(join(claudeDir, `hookify.${hook}.local.md`)));
|
|
63
|
+
if (allPresent) {
|
|
64
|
+
installed.push(pack.name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return installed;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/hook-packs/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AASlC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,yDAAyD;AACzD,SAAS,YAAY;IACnB,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAuB,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS;QAExC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAqB,CAAC;YACrF,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAEhD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAqB,CAAC;QACrF,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3C,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,IAAI,WAAW,CAAC,CAAC,CACxD,CAAC;QACF,IAAI,UAAU,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|