@softspark/ai-toolkit 1.2.0 → 1.3.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 +5 -29
- package/CHANGELOG.md +39 -0
- package/README.md +170 -27
- package/app/.claude-plugin/plugin.json +2 -2
- package/app/ARCHITECTURE.md +35 -19
- package/app/agents/ai-engineer.md +47 -5
- package/app/agents/fact-checker.md +1 -1
- package/app/agents/llm-ops-engineer.md +2 -2
- package/app/agents/{mcp-server-architect.md → mcp-specialist.md} +124 -18
- package/app/agents/mcp-testing-engineer.md +2 -2
- package/app/agents/nlp-engineer.md +1 -1
- package/app/agents/orchestrator.md +3 -4
- package/app/agents/project-planner.md +1 -1
- package/app/agents/technical-researcher.md +71 -21
- package/app/hooks/commit-quality.sh +53 -0
- package/app/hooks/governance-capture.sh +77 -0
- package/app/hooks/guard-config.sh +46 -0
- package/app/hooks/mcp-health.sh +53 -0
- package/app/hooks/pre-compact-save.sh +34 -0
- package/app/hooks/session-context.sh +60 -0
- package/app/hooks.json +60 -0
- package/app/mcp-templates/README.md +105 -0
- package/app/mcp-templates/brave-search.json +13 -0
- package/app/mcp-templates/cloudflare.json +14 -0
- package/app/mcp-templates/context7.json +10 -0
- package/app/mcp-templates/custom-template.json +13 -0
- package/app/mcp-templates/datadog.json +15 -0
- package/app/mcp-templates/docker.json +10 -0
- package/app/mcp-templates/fetch.json +10 -0
- package/app/mcp-templates/filesystem.json +10 -0
- package/app/mcp-templates/git.json +10 -0
- package/app/mcp-templates/github.json +13 -0
- package/app/mcp-templates/google-drive.json +15 -0
- package/app/mcp-templates/google-maps.json +13 -0
- package/app/mcp-templates/grafana.json +14 -0
- package/app/mcp-templates/linear.json +13 -0
- package/app/mcp-templates/memory.json +10 -0
- package/app/mcp-templates/notion.json +13 -0
- package/app/mcp-templates/postgres.json +10 -0
- package/app/mcp-templates/puppeteer.json +10 -0
- package/app/mcp-templates/redis.json +13 -0
- package/app/mcp-templates/sentry.json +14 -0
- package/app/mcp-templates/sequential-thinking.json +10 -0
- package/app/mcp-templates/slack.json +14 -0
- package/app/mcp-templates/sqlite.json +10 -0
- package/app/mcp-templates/supabase.json +14 -0
- package/app/mcp-templates/vercel.json +13 -0
- package/app/plugins/research-pack/README.md +1 -1
- package/app/plugins/research-pack/plugin.json +1 -1
- package/app/rules/claude-toolkit-rules.md +1 -1
- package/app/rules/common/coding-style.md +52 -0
- package/app/rules/common/git-workflow.md +45 -0
- package/app/rules/common/performance.md +49 -0
- package/app/rules/common/security.md +59 -0
- package/app/rules/common/testing.md +53 -0
- package/app/rules/cpp/coding-style.md +55 -0
- package/app/rules/cpp/frameworks.md +55 -0
- package/app/rules/cpp/patterns.md +57 -0
- package/app/rules/cpp/security.md +61 -0
- package/app/rules/cpp/testing.md +58 -0
- package/app/rules/csharp/coding-style.md +54 -0
- package/app/rules/csharp/frameworks.md +60 -0
- package/app/rules/csharp/patterns.md +61 -0
- package/app/rules/csharp/security.md +60 -0
- package/app/rules/csharp/testing.md +58 -0
- package/app/rules/dart/coding-style.md +61 -0
- package/app/rules/dart/frameworks.md +59 -0
- package/app/rules/dart/patterns.md +62 -0
- package/app/rules/dart/security.md +60 -0
- package/app/rules/dart/testing.md +60 -0
- package/app/rules/golang/coding-style.md +50 -0
- package/app/rules/golang/frameworks.md +56 -0
- package/app/rules/golang/patterns.md +52 -0
- package/app/rules/golang/security.md +61 -0
- package/app/rules/golang/testing.md +54 -0
- package/app/rules/java/coding-style.md +56 -0
- package/app/rules/java/frameworks.md +54 -0
- package/app/rules/java/patterns.md +59 -0
- package/app/rules/java/security.md +60 -0
- package/app/rules/java/testing.md +55 -0
- package/app/rules/kotlin/coding-style.md +56 -0
- package/app/rules/kotlin/frameworks.md +54 -0
- package/app/rules/kotlin/patterns.md +54 -0
- package/app/rules/kotlin/security.md +62 -0
- package/app/rules/kotlin/testing.md +56 -0
- package/app/rules/php/coding-style.md +53 -0
- package/app/rules/php/frameworks.md +57 -0
- package/app/rules/php/patterns.md +63 -0
- package/app/rules/php/security.md +61 -0
- package/app/rules/php/testing.md +60 -0
- package/app/rules/python/coding-style.md +52 -0
- package/app/rules/python/frameworks.md +53 -0
- package/app/rules/python/patterns.md +53 -0
- package/app/rules/python/security.md +58 -0
- package/app/rules/python/testing.md +52 -0
- package/app/rules/ruby/coding-style.md +56 -0
- package/app/rules/ruby/frameworks.md +56 -0
- package/app/rules/ruby/patterns.md +61 -0
- package/app/rules/ruby/security.md +61 -0
- package/app/rules/ruby/testing.md +63 -0
- package/app/rules/rust/coding-style.md +52 -0
- package/app/rules/rust/frameworks.md +59 -0
- package/app/rules/rust/patterns.md +58 -0
- package/app/rules/rust/security.md +64 -0
- package/app/rules/rust/testing.md +54 -0
- package/app/rules/swift/coding-style.md +55 -0
- package/app/rules/swift/frameworks.md +63 -0
- package/app/rules/swift/patterns.md +63 -0
- package/app/rules/swift/security.md +63 -0
- package/app/rules/swift/testing.md +60 -0
- package/app/rules/typescript/coding-style.md +51 -0
- package/app/rules/typescript/frameworks.md +51 -0
- package/app/rules/typescript/patterns.md +51 -0
- package/app/rules/typescript/security.md +55 -0
- package/app/rules/typescript/testing.md +52 -0
- package/app/skills/app-builder/SKILL.md +1 -1
- package/app/skills/brand-voice/SKILL.md +92 -0
- package/app/skills/council/SKILL.md +133 -0
- package/app/skills/introspect/SKILL.md +125 -0
- package/bin/ai-toolkit.js +103 -2
- package/kb/reference/agents-catalog.md +10 -13
- package/kb/reference/architecture-overview.md +29 -6
- package/kb/reference/commands-catalog.md +4 -4
- package/kb/reference/competitive-features-implementation.md +677 -0
- package/kb/reference/extension-api.md +175 -0
- package/kb/reference/hooks-catalog.md +90 -14
- package/kb/reference/language-rules.md +167 -0
- package/kb/reference/manifest-install.md +165 -0
- package/kb/reference/mcp-templates.md +110 -0
- package/kb/reference/skills-catalog.md +9 -6
- package/llms-full.txt +1511 -59
- package/llms.txt +8 -3
- package/package.json +2 -2
- package/scripts/inject_hook_cli.py +330 -0
- package/scripts/install.py +241 -7
- package/scripts/install_steps/ai_tools.py +63 -1
- package/scripts/install_steps/detect_language.py +50 -0
- package/scripts/install_steps/install_state.py +117 -0
- package/scripts/mcp_manager.py +206 -0
- package/app/agents/mcp-expert.md +0 -228
- package/app/agents/rag-engineer.md +0 -201
- package/app/agents/research-synthesizer.md +0 -138
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ai-toolkit
|
|
2
2
|
|
|
3
|
-
> Professional-grade Claude Code toolkit:
|
|
3
|
+
> Professional-grade Claude Code toolkit: 90 skills, 44 agents, machine-enforced constitution, quality hooks.
|
|
4
4
|
|
|
5
5
|
## Documentation
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- [Best Practices](kb/best-practices/README.md)
|
|
15
15
|
- [How-To Guides](kb/howto/README.md)
|
|
16
16
|
- [SOP: Claude Toolkit Maintenance](kb/procedures/maintenance-sop.md)
|
|
17
|
-
- [Agents Catalog (
|
|
17
|
+
- [Agents Catalog (44 agents)](kb/reference/agents-catalog.md)
|
|
18
18
|
- [Anti-Pattern Registry Format](kb/reference/anti-pattern-registry-format.md)
|
|
19
19
|
- [AI Toolkit Architecture](kb/reference/architecture-overview.md)
|
|
20
20
|
- [Config Benchmark](kb/reference/benchmark-config.md)
|
|
@@ -22,17 +22,22 @@
|
|
|
22
22
|
- [Claude Ecosystem Benchmark Snapshot](kb/reference/claude-ecosystem-benchmark-snapshot.md)
|
|
23
23
|
- [Claude Ecosystem Expansion Foundations](kb/reference/claude-ecosystem-expansion-foundations.md)
|
|
24
24
|
- [Commands Catalog (DEPRECATED)](kb/reference/commands-catalog.md)
|
|
25
|
+
- [Plan: Competitive Features — ai-toolkit](kb/reference/competitive-features-implementation.md)
|
|
25
26
|
- [Distribution Model](kb/reference/distribution-model.md)
|
|
27
|
+
- [Extension API Reference](kb/reference/extension-api.md)
|
|
26
28
|
- [Global Install Model](kb/reference/global-install-model.md)
|
|
27
29
|
- [Hierarchical Override Pattern](kb/reference/hierarchical-override-pattern.md)
|
|
28
30
|
- [Hooks Catalog](kb/reference/hooks-catalog.md)
|
|
29
31
|
- [External Integrations](kb/reference/integrations.md)
|
|
30
32
|
- [Language Plugin Packs](kb/reference/language-packs.md)
|
|
33
|
+
- [Language Rules System](kb/reference/language-rules.md)
|
|
34
|
+
- [Manifest-Driven Install System](kb/reference/manifest-install.md)
|
|
35
|
+
- [MCP Server Templates](kb/reference/mcp-templates.md)
|
|
31
36
|
- [Merge-Friendly Install Model](kb/reference/merge-friendly-install-model.md)
|
|
32
37
|
- [Plugin Pack Conventions](kb/reference/plugin-pack-conventions.md)
|
|
33
38
|
- [Quick Wins Implementation Summary](kb/reference/quick-wins-implementation-summary.md)
|
|
34
39
|
- [Skill Templates](kb/reference/skill-templates.md)
|
|
35
|
-
- [Skills Catalog (
|
|
40
|
+
- [Skills Catalog (90 skills)](kb/reference/skills-catalog.md)
|
|
36
41
|
- [Skills Unification Model](kb/reference/skills-unification.md)
|
|
37
42
|
- [Usage Statistics](kb/reference/stats.md)
|
|
38
43
|
- [Config Sync](kb/reference/sync.md)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Professional-grade AI coding toolkit:
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Professional-grade AI coding toolkit: 90 skills, 44 agents, multi-platform support (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment), machine-enforced safety constitution, persona presets, skill security auditor, expanded lifecycle hooks, 11 plugin packs, and benchmark tooling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Inject external hooks into ~/.claude/settings.json.
|
|
3
|
+
|
|
4
|
+
Allows external tools (MCP servers, plugins, etc.) to register their own
|
|
5
|
+
hooks alongside ai-toolkit's hooks. Each injected file is tagged with a
|
|
6
|
+
``_source`` derived from the filename stem so that re-running is idempotent
|
|
7
|
+
and removal is safe.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
inject_hook_cli.py <hooks-file.json> [target-dir]
|
|
11
|
+
inject_hook_cli.py --remove <hook-source-name> [target-dir]
|
|
12
|
+
|
|
13
|
+
Arguments:
|
|
14
|
+
hooks-file Path to a JSON file with ``{"hooks": {"EventName": [...]}}``
|
|
15
|
+
target-dir Directory containing ``.claude/settings.json`` (default: $HOME)
|
|
16
|
+
|
|
17
|
+
Flags:
|
|
18
|
+
--remove Remove all hook entries tagged with the given source name
|
|
19
|
+
|
|
20
|
+
The source name is derived from the filename stem (e.g.,
|
|
21
|
+
``rag-mcp-hooks.json`` becomes ``"rag-mcp-hooks"``). All entries are tagged
|
|
22
|
+
with ``"_source": "<source-name>"`` for idempotent updates. Re-running
|
|
23
|
+
replaces old entries with the same source name.
|
|
24
|
+
|
|
25
|
+
Entries tagged with ``_source: "ai-toolkit"`` are **never** modified -- those
|
|
26
|
+
are managed exclusively by the toolkit itself.
|
|
27
|
+
|
|
28
|
+
Exit codes:
|
|
29
|
+
0 success
|
|
30
|
+
1 usage / argument error
|
|
31
|
+
2 JSON parse error
|
|
32
|
+
"""
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
# Protected source tag -- this CLI must never touch ai-toolkit's own entries.
|
|
41
|
+
PROTECTED_SOURCE = "ai-toolkit"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# JSON helpers (same style as merge-hooks.py)
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def load_json(path: str) -> dict:
|
|
49
|
+
"""Load and parse a JSON file.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
path: Filesystem path to the JSON file.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Parsed JSON content as a dictionary.
|
|
56
|
+
"""
|
|
57
|
+
with open(path) as f:
|
|
58
|
+
return json.load(f)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def save_json(path: str, data: dict) -> None:
|
|
62
|
+
"""Write a dictionary to a JSON file with 4-space indent and trailing newline.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
path: Filesystem path for the output JSON file.
|
|
66
|
+
data: Dictionary to serialize.
|
|
67
|
+
"""
|
|
68
|
+
with open(path, "w") as f:
|
|
69
|
+
json.dump(data, f, indent=4, ensure_ascii=False)
|
|
70
|
+
f.write("\n")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Core logic
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def _entry_source(entry: dict) -> str | None:
|
|
78
|
+
"""Return the ``_source`` tag of a hook entry, checking nested hooks too.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
entry: A single hook entry dict.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
The source string, or ``None`` if untagged.
|
|
85
|
+
"""
|
|
86
|
+
if "_source" in entry:
|
|
87
|
+
return entry["_source"]
|
|
88
|
+
for hook in entry.get("hooks", []):
|
|
89
|
+
if isinstance(hook, dict) and "_source" in hook:
|
|
90
|
+
return hook["_source"]
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def strip_source(hooks: dict, source: str) -> dict:
|
|
95
|
+
"""Remove all entries whose ``_source`` matches *source*.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
hooks: Existing hooks dict (event-name -> list of entries).
|
|
99
|
+
source: Source tag to strip.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
New hooks dict with matching entries removed. Empty event lists
|
|
103
|
+
are omitted.
|
|
104
|
+
"""
|
|
105
|
+
result: dict = {}
|
|
106
|
+
for event, entries in hooks.items():
|
|
107
|
+
filtered = [e for e in entries if _entry_source(e) != source]
|
|
108
|
+
if filtered:
|
|
109
|
+
result[event] = filtered
|
|
110
|
+
return result
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def tag_entries(hooks: dict, source: str) -> dict:
|
|
114
|
+
"""Tag every entry in *hooks* with ``"_source": source``.
|
|
115
|
+
|
|
116
|
+
Operates on top-level entries only (not nested ``hooks`` arrays) to
|
|
117
|
+
match the convention used in ``app/hooks.json``.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
hooks: Hook entries to tag (event-name -> list of entries).
|
|
121
|
+
source: Source tag to apply.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
New hooks dict with all entries tagged.
|
|
125
|
+
"""
|
|
126
|
+
result: dict = {}
|
|
127
|
+
for event, entries in hooks.items():
|
|
128
|
+
tagged = []
|
|
129
|
+
for entry in entries:
|
|
130
|
+
entry_copy = dict(entry)
|
|
131
|
+
entry_copy["_source"] = source
|
|
132
|
+
tagged.append(entry_copy)
|
|
133
|
+
result[event] = tagged
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def merge_hooks(new_hooks: dict, existing_hooks: dict, source: str) -> dict:
|
|
138
|
+
"""Strip old entries for *source*, then append *new_hooks*.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
new_hooks: Tagged hook entries to inject.
|
|
142
|
+
existing_hooks: Current hooks from the settings file.
|
|
143
|
+
source: Source tag being injected.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Merged hooks dict.
|
|
147
|
+
"""
|
|
148
|
+
merged = strip_source(existing_hooks, source)
|
|
149
|
+
for event, entries in new_hooks.items():
|
|
150
|
+
if event not in merged:
|
|
151
|
+
merged[event] = []
|
|
152
|
+
merged[event].extend(entries)
|
|
153
|
+
return merged
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# CLI actions
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
def inject(hooks_file: str, target_dir: str) -> None:
|
|
161
|
+
"""Inject hooks from *hooks_file* into the target settings.json.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
hooks_file: Path to the external hooks JSON file.
|
|
165
|
+
target_dir: Directory containing ``.claude/settings.json``.
|
|
166
|
+
"""
|
|
167
|
+
# Derive source name from filename stem
|
|
168
|
+
source = Path(hooks_file).stem
|
|
169
|
+
if source == PROTECTED_SOURCE:
|
|
170
|
+
print(
|
|
171
|
+
f"Error: source name '{PROTECTED_SOURCE}' is reserved. "
|
|
172
|
+
"Rename your hooks file.",
|
|
173
|
+
file=sys.stderr,
|
|
174
|
+
)
|
|
175
|
+
sys.exit(1)
|
|
176
|
+
|
|
177
|
+
# Load the hooks file
|
|
178
|
+
try:
|
|
179
|
+
hooks_data = load_json(hooks_file)
|
|
180
|
+
except json.JSONDecodeError as exc:
|
|
181
|
+
print(f"Error: malformed JSON in {hooks_file}: {exc}", file=sys.stderr)
|
|
182
|
+
sys.exit(2)
|
|
183
|
+
except OSError as exc:
|
|
184
|
+
print(f"Error reading hooks file: {exc}", file=sys.stderr)
|
|
185
|
+
sys.exit(1)
|
|
186
|
+
|
|
187
|
+
new_hooks = hooks_data.get("hooks", {})
|
|
188
|
+
if not new_hooks:
|
|
189
|
+
print(f"Warning: no 'hooks' key found in {hooks_file}", file=sys.stderr)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# Tag entries with source
|
|
193
|
+
tagged = tag_entries(new_hooks, source)
|
|
194
|
+
|
|
195
|
+
# Load existing settings
|
|
196
|
+
settings_dir = Path(target_dir) / ".claude"
|
|
197
|
+
settings_path = settings_dir / "settings.json"
|
|
198
|
+
|
|
199
|
+
settings: dict = {}
|
|
200
|
+
if settings_path.is_file():
|
|
201
|
+
try:
|
|
202
|
+
settings = load_json(str(settings_path))
|
|
203
|
+
except json.JSONDecodeError as exc:
|
|
204
|
+
print(
|
|
205
|
+
f"Error: malformed JSON in {settings_path}: {exc}",
|
|
206
|
+
file=sys.stderr,
|
|
207
|
+
)
|
|
208
|
+
sys.exit(2)
|
|
209
|
+
else:
|
|
210
|
+
settings_dir.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
|
|
212
|
+
existing_hooks = settings.get("hooks", {})
|
|
213
|
+
settings["hooks"] = merge_hooks(tagged, existing_hooks, source)
|
|
214
|
+
save_json(str(settings_path), settings)
|
|
215
|
+
print(f"Injected hooks from '{source}' into {settings_path}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def remove(source_name: str, target_dir: str) -> None:
|
|
219
|
+
"""Remove all hook entries tagged with *source_name*.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
source_name: The ``_source`` tag to remove.
|
|
223
|
+
target_dir: Directory containing ``.claude/settings.json``.
|
|
224
|
+
"""
|
|
225
|
+
if source_name == PROTECTED_SOURCE:
|
|
226
|
+
print(
|
|
227
|
+
f"Error: cannot remove '{PROTECTED_SOURCE}' entries. "
|
|
228
|
+
"Use 'ai-toolkit uninstall' instead.",
|
|
229
|
+
file=sys.stderr,
|
|
230
|
+
)
|
|
231
|
+
sys.exit(1)
|
|
232
|
+
|
|
233
|
+
settings_path = Path(target_dir) / ".claude" / "settings.json"
|
|
234
|
+
|
|
235
|
+
if not settings_path.is_file():
|
|
236
|
+
print(f"No settings.json found at {settings_path}")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
settings = load_json(str(settings_path))
|
|
241
|
+
except json.JSONDecodeError as exc:
|
|
242
|
+
print(
|
|
243
|
+
f"Error: malformed JSON in {settings_path}: {exc}",
|
|
244
|
+
file=sys.stderr,
|
|
245
|
+
)
|
|
246
|
+
sys.exit(2)
|
|
247
|
+
|
|
248
|
+
existing_hooks = settings.get("hooks", {})
|
|
249
|
+
cleaned = strip_source(existing_hooks, source_name)
|
|
250
|
+
|
|
251
|
+
if cleaned:
|
|
252
|
+
settings["hooks"] = cleaned
|
|
253
|
+
else:
|
|
254
|
+
settings.pop("hooks", None)
|
|
255
|
+
|
|
256
|
+
save_json(str(settings_path), settings)
|
|
257
|
+
print(f"Removed hooks with source '{source_name}' from {settings_path}")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
# Argument parsing
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
def _parse_args(argv: list[str]) -> dict:
|
|
265
|
+
"""Parse CLI arguments.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Dict with keys: remove_mode, remove_name, source_file, target_dir.
|
|
269
|
+
"""
|
|
270
|
+
result: dict = {
|
|
271
|
+
"remove_mode": False,
|
|
272
|
+
"remove_name": "",
|
|
273
|
+
"source_file": "",
|
|
274
|
+
"target_dir": str(Path.home()),
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
i = 0
|
|
278
|
+
while i < len(argv):
|
|
279
|
+
arg = argv[i]
|
|
280
|
+
if arg == "--remove":
|
|
281
|
+
result["remove_mode"] = True
|
|
282
|
+
i += 1
|
|
283
|
+
if i >= len(argv):
|
|
284
|
+
print("--remove requires a hook source name", file=sys.stderr)
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
result["remove_name"] = argv[i]
|
|
287
|
+
elif arg.startswith("-"):
|
|
288
|
+
print(f"Unknown option: {arg}", file=sys.stderr)
|
|
289
|
+
sys.exit(1)
|
|
290
|
+
elif not result["source_file"] and not result["remove_mode"]:
|
|
291
|
+
result["source_file"] = arg
|
|
292
|
+
else:
|
|
293
|
+
result["target_dir"] = arg
|
|
294
|
+
i += 1
|
|
295
|
+
|
|
296
|
+
return result
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def main() -> None:
|
|
300
|
+
"""Inject or remove external hooks in settings.json."""
|
|
301
|
+
args = _parse_args(sys.argv[1:])
|
|
302
|
+
|
|
303
|
+
# -- remove mode ---------------------------------------------------------
|
|
304
|
+
if args["remove_mode"]:
|
|
305
|
+
remove(args["remove_name"], args["target_dir"])
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
# -- inject mode ---------------------------------------------------------
|
|
309
|
+
source_file = args["source_file"]
|
|
310
|
+
if not source_file:
|
|
311
|
+
print(
|
|
312
|
+
"Usage: inject_hook_cli.py <hooks-file.json> [target-dir]",
|
|
313
|
+
file=sys.stderr,
|
|
314
|
+
)
|
|
315
|
+
print(
|
|
316
|
+
" inject_hook_cli.py --remove <hook-source-name> [target-dir]",
|
|
317
|
+
file=sys.stderr,
|
|
318
|
+
)
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
|
|
321
|
+
source_path = Path(source_file)
|
|
322
|
+
if not source_path.is_file():
|
|
323
|
+
print(f"Hooks file not found: {source_path}", file=sys.stderr)
|
|
324
|
+
sys.exit(1)
|
|
325
|
+
|
|
326
|
+
inject(source_file, args["target_dir"])
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__":
|
|
330
|
+
main()
|